From 8f5e92d6e4bd0bebc8e197e08af3860f65590f04 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 13 Jul 2026 21:38:28 +0200 Subject: [PATCH 01/15] Device implementations and touch calibration updates --- Devices/cyd-2432s024r/CMakeLists.txt | 5 +- .../cyd-2432s024r/Source/Configuration.cpp | 21 ---- .../cyd-2432s024r/Source/devices/Display.cpp | 46 --------- .../cyd-2432s024r/Source/devices/Display.h | 28 ------ Devices/cyd-2432s024r/cyd,2432s024r.dts | 32 ++++++- Devices/cyd-2432s024r/device.properties | 2 + Devices/cyd-2432s024r/devicetree.yaml | 2 + .../{Source => source}/module.cpp | 0 Devices/cyd-2432s028r/device.properties | 3 + Devices/unphone/device.properties | 3 + Drivers/XPT2046/Source/Xpt2046Touch.cpp | 22 +---- Drivers/XPT2046/Source/Xpt2046Touch.h | 2 - .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp | 4 - .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.h | 1 - Firmware/Kconfig | 7 ++ .../include/tactility/lvgl_pointer.h | 50 ++++++++++ Modules/lvgl-module/source/lvgl_pointer.c | 95 ++++++++++++++++++- .../app/touchcalibration/TouchCalibration.h | 6 ++ .../settings/TouchCalibrationSettings.h | 20 ++-- Tactility/Source/Tactility.cpp | 4 + .../app/kerneldisplay/KernelDisplay.cpp | 26 ++++- Tactility/Source/app/setup/Setup.cpp | 13 +++ Tactility/Source/app/timezone/TimeZone.cpp | 10 +- .../app/touchcalibration/TouchCalibration.cpp | 91 +++++++++++++++--- Tactility/Source/lvgl/Lvgl.cpp | 20 ++++ .../settings/TouchCalibrationSettings.cpp | 64 +------------ device.py | 87 +++++++++-------- 27 files changed, 400 insertions(+), 264 deletions(-) delete mode 100644 Devices/cyd-2432s024r/Source/Configuration.cpp delete mode 100644 Devices/cyd-2432s024r/Source/devices/Display.cpp delete mode 100644 Devices/cyd-2432s024r/Source/devices/Display.h rename Devices/cyd-2432s024r/{Source => source}/module.cpp (100%) diff --git a/Devices/cyd-2432s024r/CMakeLists.txt b/Devices/cyd-2432s024r/CMakeLists.txt index 1e6393dd4..a832a83a2 100644 --- a/Devices/cyd-2432s024r/CMakeLists.txt +++ b/Devices/cyd-2432s024r/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046 PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-2432s024r/Source/Configuration.cpp b/Devices/cyd-2432s024r/Source/Configuration.cpp deleted file mode 100644 index 91782dd35..000000000 --- a/Devices/cyd-2432s024r/Source/Configuration.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "devices/Display.h" - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT); -} - -static DeviceVector createDevices() { - return { - createDisplay() - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-2432s024r/Source/devices/Display.cpp b/Devices/cyd-2432s024r/Source/devices/Display.cpp deleted file mode 100644 index f8aef5e46..000000000 --- a/Devices/cyd-2432s024r/Source/devices/Display.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Display.h" -#include "Xpt2046Touch.h" -#include -#include - -static std::shared_ptr createTouch(esp_lcd_spi_bus_handle_t spiDevice) { - auto configuration = std::make_unique( - spiDevice, - TOUCH_CS_PIN, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - true, // swapXY - false, // mirrorX - true // mirrorY - ); - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = true, - .mirrorX = true, - .mirrorY = true, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(spi_configuration->spiHostDevice), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = LCD_PIN_RST, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB - }; - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/cyd-2432s024r/Source/devices/Display.h b/Devices/cyd-2432s024r/Source/devices/Display.h deleted file mode 100644 index d8a8ae6c4..000000000 --- a/Devices/cyd-2432s024r/Source/devices/Display.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Display -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_PIN_RST = GPIO_NUM_NC; // tied to ESP32 RST -constexpr auto LCD_PIN_CLK = GPIO_NUM_14; -constexpr auto LCD_PIN_MOSI = GPIO_NUM_13; -constexpr auto LCD_PIN_MISO = GPIO_NUM_12; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; - -// Backlight -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_27; - -// Touch -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-2432s024r/cyd,2432s024r.dts b/Devices/cyd-2432s024r/cyd,2432s024r.dts index a5e3ccd0e..928fb9a14 100644 --- a/Devices/cyd-2432s024r/cyd,2432s024r.dts +++ b/Devices/cyd-2432s024r/cyd,2432s024r.dts @@ -5,9 +5,10 @@ #include #include #include -#include -#include #include +#include +#include +#include / { compatible = "root"; @@ -23,6 +24,15 @@ gpio-count = <40>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -33,11 +43,23 @@ <&gpio0 33 GPIO_FLAG_NONE>; // Touch display@0 { - compatible = "display-placeholder"; + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + swap-xy; + mirror-x; + mirror-y; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <240>; + y-max = <320>; + swap-xy; + mirror-y; }; }; @@ -61,4 +83,4 @@ pin-tx = <&gpio0 1 GPIO_FLAG_NONE>; pin-rx = <&gpio0 3 GPIO_FLAG_NONE>; }; -}; \ No newline at end of file +}; diff --git a/Devices/cyd-2432s024r/device.properties b/Devices/cyd-2432s024r/device.properties index b2e05d040..57b8d394a 100644 --- a/Devices/cyd-2432s024r/device.properties +++ b/Devices/cyd-2432s024r/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.4" diff --git a/Devices/cyd-2432s024r/devicetree.yaml b/Devices/cyd-2432s024r/devicetree.yaml index ab75fd2c2..0f60266f2 100644 --- a/Devices/cyd-2432s024r/devicetree.yaml +++ b/Devices/cyd-2432s024r/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/xpt2046-module dts: cyd,2432s024r.dts diff --git a/Devices/cyd-2432s024r/Source/module.cpp b/Devices/cyd-2432s024r/source/module.cpp similarity index 100% rename from Devices/cyd-2432s024r/Source/module.cpp rename to Devices/cyd-2432s024r/source/module.cpp diff --git a/Devices/cyd-2432s028r/device.properties b/Devices/cyd-2432s028r/device.properties index c0047a338..847dc1fb0 100644 --- a/Devices/cyd-2432s028r/device.properties +++ b/Devices/cyd-2432s028r/device.properties @@ -13,6 +13,9 @@ display.size=2.8" display.shape=rectangle display.dpi=143 +touch.calibrationSupported=true +touch.calibrationRequired=false + cdn.warningMessage=There are 3 hardware variants of this board. This build works on the original variant only ("v1"). lvgl.colorDepth=16 diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties index 3b2f5c7a3..45de3205b 100644 --- a/Devices/unphone/device.properties +++ b/Devices/unphone/device.properties @@ -16,6 +16,9 @@ display.size=3.5" display.shape=rectangle display.dpi=165 +touch.calibrationSupported=true +touch.calibrationRequired=true + cdn.warningMessage=Put the device into bootloader mode by pressing the center nav button and reset for 2-3 seconds, then release reset, then release the nav button.
After flashing is finished, press the reset button to reboot. lvgl.colorDepth=24 diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.cpp b/Drivers/XPT2046/Source/Xpt2046Touch.cpp index 325d7a05b..4c39a63cb 100644 --- a/Drivers/XPT2046/Source/Xpt2046Touch.cpp +++ b/Drivers/XPT2046/Source/Xpt2046Touch.cpp @@ -1,30 +1,10 @@ #include "Xpt2046Touch.h" -#include #include -#include #include #include -static void processCoordinates(esp_lcd_touch_handle_t tp, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) { - (void)strength; - if (tp == nullptr || x == nullptr || y == nullptr || pointCount == nullptr || *pointCount == 0) { - return; - } - - auto* config = static_cast(tp->config.user_data); - if (config == nullptr) { - return; - } - - const auto settings = tt::settings::touch::getActive(); - const auto points = std::min(*pointCount, maxPointCount); - for (uint8_t i = 0; i < points; i++) { - tt::settings::touch::applyCalibration(settings, config->xMax, config->yMax, x[i], y[i]); - } -} - bool Xpt2046Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(configuration->spiPinCs); return esp_lcd_new_panel_io_spi(configuration->spiDevice, &io_config, &outHandle) == ESP_OK; @@ -49,7 +29,7 @@ esp_lcd_touch_config_t Xpt2046Touch::createEspLcdTouchConfig() { .mirror_x = configuration->mirrorX, .mirror_y = configuration->mirrorY, }, - .process_coordinates = processCoordinates, + .process_coordinates = nullptr, .interrupt_callback = nullptr, .user_data = configuration.get(), .driver_data = nullptr diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.h b/Drivers/XPT2046/Source/Xpt2046Touch.h index eb65787f2..975f4a279 100644 --- a/Drivers/XPT2046/Source/Xpt2046Touch.h +++ b/Drivers/XPT2046/Source/Xpt2046Touch.h @@ -56,6 +56,4 @@ class Xpt2046Touch : public EspLcdTouch { std::string getName() const final { return "XPT2046"; } std::string getDescription() const final { return "XPT2046 SPI touch driver"; } - - bool supportsCalibration() const override { return true; } }; diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp index d1c30471b..e8546b958 100644 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp @@ -1,7 +1,6 @@ #include "Xpt2046SoftSpi.h" #include -#include #include @@ -198,9 +197,6 @@ bool Xpt2046SoftSpi::getTouchPoint(Point& point) { uint16_t x = static_cast(std::clamp(mappedX, 0, static_cast(configuration->xMax))); uint16_t y = static_cast(std::clamp(mappedY, 0, static_cast(configuration->yMax))); - const auto calibration = tt::settings::touch::getActive(); - tt::settings::touch::applyCalibration(calibration, configuration->xMax, configuration->yMax, x, y); - point.x = x; point.y = y; return true; diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h index 1bdddcc52..40ab7a0a9 100644 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h @@ -99,7 +99,6 @@ class Xpt2046SoftSpi : public tt::hal::touch::TouchDevice { bool stopLvgl() override; bool supportsTouchDriver() override { return true; } - bool supportsCalibration() const override { return true; } std::shared_ptr getTouchDriver() override; lv_indev_t* getLvglIndev() override { return lvglDevice; } diff --git a/Firmware/Kconfig b/Firmware/Kconfig index 95fce6704..3c575ca17 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -102,4 +102,11 @@ menu "Tactility App" help The minimum time to show the splash screen in milliseconds. When set to 0, startup will continue to desktop as soon as boot operations are finished. + config TT_TOUCH_CALIBRATION_SUPPORTED + bool "Set true when a touch screen calibration app should be included" + default n + config TT_TOUCH_CALIBRATION_REQUIRED + bool "Set true when a touch screen calibration is required before the device is usable" + default n + depends on TT_TOUCH_CALIBRATION_SUPPORTED endmenu diff --git a/Modules/lvgl-module/include/tactility/lvgl_pointer.h b/Modules/lvgl-module/include/tactility/lvgl_pointer.h index 363ae0bc8..b42495941 100644 --- a/Modules/lvgl-module/include/tactility/lvgl_pointer.h +++ b/Modules/lvgl-module/include/tactility/lvgl_pointer.h @@ -10,6 +10,56 @@ extern "C" { #include #include +/** + * @brief Linear per-axis calibration range for raw pointer coordinates. + * + * Values are the raw (pre-calibration) coordinates that should map to the display's + * [0, hor_res-1] / [0, ver_res-1] range. Corrects scale+offset error only; axis + * swap/mirror is handled separately by PointerApi and applied by the driver before + * lvgl_pointer_read_cb() sees the coordinates. + */ +struct LvglPointerCalibration { + int32_t x_min; + int32_t x_max; + int32_t y_min; + int32_t y_max; +}; + +/** + * @brief Sets (or clears, when calibration is NULL) the calibration applied to raw coordinates + * read from the device before they are written into LVGL indev data, on an indev previously + * created with lvgl_pointer_add(). + * + * @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h). + * + * @param[in] indev an indev previously created by lvgl_pointer_add() + * @param[in] calibration the calibration range to apply, or NULL to clear/disable calibration + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_ARGUMENT if indev is NULL, or calibration is non-NULL but invalid + * (x_max <= x_min, y_max <= y_min, or either span smaller than the minimum allowed range) + */ +error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration); + +/** + * @brief Retrieves the calibration currently active on indev, if any. + * @warning Caller must hold the LVGL lock. + * @return true when a calibration is currently set on indev (out_calibration is filled), false otherwise + */ +bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration); + +/** + * @brief Returns the first indev created by lvgl_pointer_add() that hasn't been removed yet. + * + * Unlike iterating LVGL's own indev list, this only ever returns an indev created by + * lvgl_pointer_add() — safe to pass to lvgl_pointer_set_calibration()/lvgl_pointer_get_calibration() + * without risking a foreign indev (e.g. one registered by the deprecated HAL layer) whose driver + * data isn't a struct LvglPointerCtx*. + * + * @warning Caller must hold the LVGL lock. + * @return the indev, or NULL if none is currently registered. + */ +lv_indev_t* lvgl_pointer_get_default(void); + /** * @brief Creates an lv_indev_t bound to the given POINTER_TYPE device and registers a read callback * that polls the device through its PointerApi. diff --git a/Modules/lvgl-module/source/lvgl_pointer.c b/Modules/lvgl-module/source/lvgl_pointer.c index 21894464f..1f64c08a4 100644 --- a/Modules/lvgl-module/source/lvgl_pointer.c +++ b/Modules/lvgl-module/source/lvgl_pointer.c @@ -7,11 +7,52 @@ struct LvglPointerCtx { struct Device* device; + bool calibration_enabled; + struct LvglPointerCalibration calibration; }; // Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll. static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10); +// Tracks the first indev created by lvgl_pointer_add() still alive, for lvgl_pointer_get_default(). +// Only ever set/cleared by lvgl_pointer_add()/lvgl_pointer_remove(), so it can never point at an +// indev created by other code (e.g. the deprecated HAL's own LVGL pointer registration). +static lv_indev_t* default_pointer_indev = NULL; + +// Mirrors Tactility/Source/settings/TouchCalibrationSettings.cpp's isValid(). +static const int32_t LVGL_POINTER_CALIBRATION_MIN_RANGE = 20; + +static bool lvgl_pointer_calibration_is_valid(const struct LvglPointerCalibration* calibration) { + return calibration->x_max > calibration->x_min && + calibration->y_max > calibration->y_min && + (calibration->x_max - calibration->x_min) >= LVGL_POINTER_CALIBRATION_MIN_RANGE && + (calibration->y_max - calibration->y_min) >= LVGL_POINTER_CALIBRATION_MIN_RANGE; +} + +// Linear per-axis rescale of [x_min,x_max]/[y_min,y_max] onto [0,target_x_max]/[0,target_y_max], +// clamped. Mirrors TouchCalibrationSettings.cpp's applyCalibration(). Kept as a standalone +// function (not inlined into the read callback) so the math is isolated and easy to reason about. +static void lvgl_pointer_calibration_apply( + const struct LvglPointerCalibration* calibration, + int32_t target_x_max, + int32_t target_y_max, + uint16_t* x, + uint16_t* y +) { + int64_t mapped_x = ((int64_t)*x - calibration->x_min) * target_x_max / + ((int64_t)calibration->x_max - calibration->x_min); + int64_t mapped_y = ((int64_t)*y - calibration->y_min) * target_y_max / + ((int64_t)calibration->y_max - calibration->y_min); + + if (mapped_x < 0) mapped_x = 0; + if (mapped_x > target_x_max) mapped_x = target_x_max; + if (mapped_y < 0) mapped_y = 0; + if (mapped_y > target_y_max) mapped_y = target_y_max; + + *x = (uint16_t)mapped_x; + *y = (uint16_t)mapped_y; +} + static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); @@ -26,6 +67,16 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { bool touched = pointer_get_touched_points(ctx->device, &x, &y, NULL, &point_count, 1); if (touched && point_count > 0) { + if (ctx->calibration_enabled) { + lv_display_t* display = lv_indev_get_display(indev); + if (display != NULL) { + int32_t target_x_max = lv_display_get_horizontal_resolution(display) - 1; + int32_t target_y_max = lv_display_get_vertical_resolution(display) - 1; + if (target_x_max > 0 && target_y_max > 0) { + lvgl_pointer_calibration_apply(&ctx->calibration, target_x_max, target_y_max, &x, &y); + } + } + } data->point.x = x; data->point.y = y; data->state = LV_INDEV_STATE_PRESSED; @@ -42,7 +93,7 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_ return ERROR_INVALID_ARGUMENT; } - struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)malloc(sizeof(struct LvglPointerCtx)); + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)calloc(1, sizeof(struct LvglPointerCtx)); if (ctx == NULL) { return ERROR_OUT_OF_MEMORY; } @@ -61,16 +112,58 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_ lv_indev_set_display(indev, display); } + if (default_pointer_indev == NULL) { + default_pointer_indev = indev; + } + *out_indev = indev; return ERROR_NONE; } +lv_indev_t* lvgl_pointer_get_default(void) { + return default_pointer_indev; +} + +error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration) { + if (indev == NULL) { + return ERROR_INVALID_ARGUMENT; + } + struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev); + + if (calibration == NULL) { + ctx->calibration_enabled = false; + return ERROR_NONE; + } + if (!lvgl_pointer_calibration_is_valid(calibration)) { + return ERROR_INVALID_ARGUMENT; + } + + ctx->calibration = *calibration; + ctx->calibration_enabled = true; + return ERROR_NONE; +} + +bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration) { + if (indev == NULL || out_calibration == NULL) { + return false; + } + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + if (!ctx->calibration_enabled) { + return false; + } + *out_calibration = ctx->calibration; + return true; +} + void lvgl_pointer_remove(lv_indev_t* indev) { if (indev == NULL) { return; } struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + if (default_pointer_indev == indev) { + default_pointer_indev = NULL; + } lv_indev_delete(indev); free(ctx); } diff --git a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h index 560f2e15e..6a85b6041 100644 --- a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h +++ b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h @@ -1,5 +1,9 @@ #pragma once +#include + +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) + #include namespace tt::app::touchcalibration { @@ -7,3 +11,5 @@ namespace tt::app::touchcalibration { LaunchId start(); } // namespace tt::app::touchcalibration + +#endif diff --git a/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h b/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h index 1d80f7279..410c17099 100644 --- a/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h +++ b/Tactility/Include/Tactility/settings/TouchCalibrationSettings.h @@ -4,6 +4,14 @@ namespace tt::settings::touch { +/** + * @brief Persisted touch calibration coefficients. + * + * Shape mirrors struct LvglPointerCalibration (tactility/lvgl_pointer.h): xMin/xMax/yMin/yMax are + * the raw touch coordinate range that should map onto the display's full resolution. This struct + * only concerns itself with persistence - applying it to a live pointer indev is the caller's + * responsibility (see lvgl_pointer_set_calibration()). + */ struct TouchCalibrationSettings { bool enabled = false; int32_t xMin = 0; @@ -14,20 +22,12 @@ struct TouchCalibrationSettings { TouchCalibrationSettings getDefault(); +bool isValid(const TouchCalibrationSettings& settings); + bool load(TouchCalibrationSettings& settings); TouchCalibrationSettings loadOrGetDefault(); bool save(const TouchCalibrationSettings& settings); -bool isValid(const TouchCalibrationSettings& settings); - -TouchCalibrationSettings getActive(); - -void setRuntimeCalibrationEnabled(bool enabled); - -void invalidateCache(); - -bool applyCalibration(const TouchCalibrationSettings& settings, uint16_t xMax, uint16_t yMax, uint16_t& x, uint16_t& y); - } // namespace tt::settings::touch diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 66af4d9e9..b363ef002 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -132,7 +132,9 @@ namespace app { namespace setup { extern const AppManifest manifest; } namespace systeminfo { extern const AppManifest manifest; } namespace timedatesettings { extern const AppManifest manifest; } +#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED namespace touchcalibration { extern const AppManifest manifest; } +#endif namespace timezone { extern const AppManifest manifest; } namespace usbsettings { extern const AppManifest manifest; } namespace btmanage { extern const AppManifest manifest; } @@ -193,7 +195,9 @@ static void registerInternalApps() { addAppManifest(app::setup::manifest); addAppManifest(app::systeminfo::manifest); addAppManifest(app::timedatesettings::manifest); +#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED addAppManifest(app::touchcalibration::manifest); +#endif addAppManifest(app::timezone::manifest); addAppManifest(app::wifiapsettings::manifest); addAppManifest(app::wificonnect::manifest); diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp index f1d2c0370..4ed1494ae 100644 --- a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -11,10 +11,12 @@ #include #endif #include +#include #include #include #include +#include namespace tt::app::kerneldisplay { @@ -98,6 +100,10 @@ class KernelDisplayApp final : public App { } } + static void onCalibrateTouchClicked(lv_event_t*) { + app::touchcalibration::start(); + } + static void onScreensaverChanged(lv_event_t* event) { auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); @@ -251,8 +257,24 @@ class KernelDisplayApp final : public App { } } - // Note: no touch calibration section here - unlike HalDisplayApp, the kernel PointerApi has - // no calibration support yet. + if (lvgl_pointer_get_default() != nullptr) { + auto* calibrate_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(calibrate_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(calibrate_wrapper, 0, LV_STATE_DEFAULT); + + auto* calibrate_label = lv_label_create(calibrate_wrapper); + lv_label_set_text(calibrate_label, "Touch calibration"); + lv_obj_align(calibrate_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* calibrate_button = lv_button_create(calibrate_wrapper); + lv_obj_align(calibrate_button, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, this); + + auto* calibrate_button_label = lv_label_create(calibrate_button); + lv_label_set_text(calibrate_button_label, "Calibrate"); + lv_obj_center(calibrate_button_label); + } } void onHide(AppContext& app) override { diff --git a/Tactility/Source/app/setup/Setup.cpp b/Tactility/Source/app/setup/Setup.cpp index d9bfff498..4336023ae 100644 --- a/Tactility/Source/app/setup/Setup.cpp +++ b/Tactility/Source/app/setup/Setup.cpp @@ -17,6 +17,12 @@ #include #include +#include + +#if defined(CONFIG_TT_TOUCH_CALIBRATION_REQUIRED) +#include +#endif + namespace tt::app::setup { extern const AppManifest manifest; @@ -139,6 +145,13 @@ class SetupApp final : public App { void onCreate(AppContext& app) override { steps = { +#if defined(CONFIG_TT_TOUCH_CALIBRATION_REQUIRED) + { + .title = "Touch Calibration", + .description = "Let's calibrate the touch screen.", + .run = [] { touchcalibration::start(); } + }, +#endif { .title = "Time Zone Setup", .description = "Let's set the time zone.", diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index 11ab9992c..3d913db11 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -166,16 +166,16 @@ class TimeZoneApp final : public App { } void updateList() { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + if (lvgl::lock(200 / portTICK_PERIOD_MS)) { std::string filter = string::lowercase(std::string(lv_textarea_get_text(filterTextareaWidget))); - readTimeZones(filter); lvgl::unlock(); + readTimeZones(filter); } else { - LOG_E(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); + LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "TimeZone LVGL"); return; } - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + if (lvgl::lock(200 / portTICK_PERIOD_MS)) { if (mutex.lock(100 / portTICK_PERIOD_MS)) { lv_obj_clean(listWidget); @@ -189,6 +189,8 @@ class TimeZoneApp final : public App { } lvgl::unlock(); + } else { + LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "TimeZone LVGL"); } } diff --git a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp index 4af6013ab..01380492d 100644 --- a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp +++ b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp @@ -1,10 +1,13 @@ -#include - #include +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) + +#include #include #include +#include +#include #include #include @@ -30,6 +33,7 @@ class TouchCalibrationApp final : public App { Sample samples[4] = {}; uint8_t sampleCount = 0; + bool calibrationApplied = false; lv_obj_t* root = nullptr; lv_obj_t* target = nullptr; @@ -73,12 +77,33 @@ class TouchCalibrationApp final : public App { } void finishCalibration() { - constexpr int32_t MIN_RANGE = 20; + const int32_t xLow = (static_cast(samples[0].x) + static_cast(samples[3].x)) / 2; + const int32_t xHigh = (static_cast(samples[1].x) + static_cast(samples[2].x)) / 2; + const int32_t yLow = (static_cast(samples[0].y) + static_cast(samples[1].y)) / 2; + const int32_t yHigh = (static_cast(samples[2].y) + static_cast(samples[3].y)) / 2; + + // Targets sit TARGET_MARGIN in from each edge (see getTargetPoint()), not at the screen + // edges themselves - xLow/xHigh/yLow/yHigh are raw samples at those inset positions, not + // at 0/width or 0/height. Extrapolate them out to the true edges so the saved range (which + // lvgl_pointer.h maps onto the full [0, resolution) display range) lines up correctly + // across the whole screen instead of being off by a margin's worth of scale and offset. + const auto width = lv_obj_get_content_width(root); + const auto height = lv_obj_get_content_height(root); + const int32_t xSpan = static_cast(width) - 2 * TARGET_MARGIN; + const int32_t ySpan = static_cast(height) - 2 * TARGET_MARGIN; - const int32_t xMin = (static_cast(samples[0].x) + static_cast(samples[3].x)) / 2; - const int32_t xMax = (static_cast(samples[1].x) + static_cast(samples[2].x)) / 2; - const int32_t yMin = (static_cast(samples[0].y) + static_cast(samples[1].y)) / 2; - const int32_t yMax = (static_cast(samples[2].y) + static_cast(samples[3].y)) / 2; + if (xSpan <= 0 || ySpan <= 0) { + lv_label_set_text(titleLabel, "Calibration Failed"); + lv_label_set_text(hintLabel, "Screen too small. Tap to close."); + lv_obj_add_flag(target, LV_OBJ_FLAG_HIDDEN); + setResult(Result::Error); + return; + } + + const int32_t xMin = xLow - (xHigh - xLow) * TARGET_MARGIN / xSpan; + const int32_t xMax = xHigh + (xHigh - xLow) * TARGET_MARGIN / xSpan; + const int32_t yMin = yLow - (yHigh - yLow) * TARGET_MARGIN / ySpan; + const int32_t yMax = yHigh + (yHigh - yLow) * TARGET_MARGIN / ySpan; settings::touch::TouchCalibrationSettings settings = settings::touch::getDefault(); settings.enabled = true; @@ -87,7 +112,7 @@ class TouchCalibrationApp final : public App { settings.yMin = yMin; settings.yMax = yMax; - if ((xMax - xMin) < MIN_RANGE || (yMax - yMin) < MIN_RANGE || !settings::touch::isValid(settings)) { + if (!settings::touch::isValid(settings)) { lv_label_set_text(titleLabel, "Calibration Failed"); lv_label_set_text(hintLabel, "Range invalid. Tap to close."); lv_obj_add_flag(target, LV_OBJ_FLAG_HIDDEN); @@ -103,6 +128,20 @@ class TouchCalibrationApp final : public App { return; } + LvglPointerCalibration calibration = { + .x_min = xMin, + .x_max = xMax, + .y_min = yMin, + .y_max = yMax, + }; + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr) { + lvgl_pointer_set_calibration(indev, &calibration); + } + lvgl_unlock(); + calibrationApplied = true; + LOG_I(TAG, "Saved calibration x=[%d, %d] y=[%d, %d]", xMin, xMax, yMin, yMax); lv_label_set_text(titleLabel, "Calibration Complete"); lv_label_set_text(hintLabel, "Touch anywhere to continue."); @@ -141,14 +180,36 @@ class TouchCalibrationApp final : public App { void onCreate(AppContext& app) override { (void)app; - settings::touch::setRuntimeCalibrationEnabled(false); - settings::touch::invalidateCache(); + // Clear any active calibration so the taps sampled below are raw, uncalibrated coordinates. + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr) { + lvgl_pointer_set_calibration(indev, nullptr); + } + lvgl_unlock(); } void onDestroy(AppContext& app) override { (void)app; - settings::touch::setRuntimeCalibrationEnabled(true); - settings::touch::invalidateCache(); + // finishCalibration() already applied a new calibration on success. On cancel/failure, + // restore whatever calibration was on disk before onCreate() cleared it above. + if (calibrationApplied) { + return; + } + + settings::touch::TouchCalibrationSettings settings; + lvgl_lock(); + auto* indev = lvgl_pointer_get_default(); + if (indev != nullptr && settings::touch::load(settings) && settings.enabled && settings::touch::isValid(settings)) { + LvglPointerCalibration calibration = { + .x_min = settings.xMin, + .x_max = settings.xMax, + .y_min = settings.yMin, + .y_max = settings.yMax, + }; + lvgl_pointer_set_calibration(indev, &calibration); + } + lvgl_unlock(); } void onShow(AppContext& app, lv_obj_t* parent) override { @@ -196,9 +257,11 @@ class TouchCalibrationApp final : public App { extern const AppManifest manifest = { .appId = "TouchCalibration", .appName = "Touch Calibration", - .appCategory = Category::System, - .appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden, + .appCategory = Category::Settings, + .appFlags = AppManifest::Flags::HideStatusBar, .createApp = create }; } // namespace tt::app::touchcalibration + +#endif // defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index f1ee46ba7..601114be9 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -71,6 +73,24 @@ void attachDevices() { } } + // Apply touch calibration (kernel POINTER_TYPE model only - see tactility/lvgl_pointer.h) + LOG_I(TAG, "Apply touch calibration"); + auto touch_calibration_settings = settings::touch::loadOrGetDefault(); + if (touch_calibration_settings.enabled && settings::touch::isValid(touch_calibration_settings)) { + auto* pointer_indev = lvgl_pointer_get_default(); + if (pointer_indev != nullptr) { + struct LvglPointerCalibration calibration = { + .x_min = touch_calibration_settings.xMin, + .x_max = touch_calibration_settings.xMax, + .y_min = touch_calibration_settings.yMin, + .y_max = touch_calibration_settings.yMax, + }; + if (lvgl_pointer_set_calibration(pointer_indev, &calibration) != ERROR_NONE) { + LOG_E(TAG, "Failed to apply saved touch calibration"); + } + } + } + // Start keyboards LOG_I(TAG, "Start keyboards"); auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); diff --git a/Tactility/Source/settings/TouchCalibrationSettings.cpp b/Tactility/Source/settings/TouchCalibrationSettings.cpp index dab64f86b..d83f742c4 100644 --- a/Tactility/Source/settings/TouchCalibrationSettings.cpp +++ b/Tactility/Source/settings/TouchCalibrationSettings.cpp @@ -2,10 +2,8 @@ #include #include -#include #include -#include #include #include #include @@ -24,11 +22,6 @@ constexpr auto* SETTINGS_KEY_X_MAX = "xMax"; constexpr auto* SETTINGS_KEY_Y_MIN = "yMin"; constexpr auto* SETTINGS_KEY_Y_MAX = "yMax"; -static bool runtimeCalibrationEnabled = true; -static bool cacheInitialized = false; -static TouchCalibrationSettings cachedSettings; -static tt::Mutex cacheMutex; - static bool toBool(const std::string& value) { return value == "1" || value == "true" || value == "True"; } @@ -127,62 +120,7 @@ bool save(const TouchCalibrationSettings& settings) { return false; } - if (!file::savePropertiesFile(settings_path, map)) { - return false; - } - - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - cachedSettings = settings; - cacheInitialized = true; - return true; -} - -TouchCalibrationSettings getActive() { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - if (!cacheInitialized) { - cachedSettings = loadOrGetDefault(); - cacheInitialized = true; - } - if (!runtimeCalibrationEnabled) { - auto disabled = cachedSettings; - disabled.enabled = false; - return disabled; - } - return cachedSettings; -} - -void setRuntimeCalibrationEnabled(bool enabled) { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - runtimeCalibrationEnabled = enabled; -} - -void invalidateCache() { - auto lock = cacheMutex.asScopedLock(); - lock.lock(); - cacheInitialized = false; -} - -bool applyCalibration(const TouchCalibrationSettings& settings, uint16_t xMax, uint16_t yMax, uint16_t& x, uint16_t& y) { - if (!settings.enabled || !isValid(settings)) { - return false; - } - - const int32_t in_x = static_cast(x); - const int32_t in_y = static_cast(y); - - const int64_t mapped_x = (static_cast(in_x) - static_cast(settings.xMin)) * - static_cast(xMax) / - (static_cast(settings.xMax) - static_cast(settings.xMin)); - const int64_t mapped_y = (static_cast(in_y) - static_cast(settings.yMin)) * - static_cast(yMax) / - (static_cast(settings.yMax) - static_cast(settings.yMin)); - - x = static_cast(std::clamp(mapped_x, 0, static_cast(xMax))); - y = static_cast(std::clamp(mapped_y, 0, static_cast(yMax))); - return true; + return file::savePropertiesFile(settings_path, map); } } // namespace tt::settings::touch diff --git a/device.py b/device.py index 570add48a..c3aa1f232 100644 --- a/device.py +++ b/device.py @@ -62,20 +62,19 @@ def has_group(properties: dict, group: str): prefix = f"{group}." return any(key.startswith(prefix) for key in properties) -def get_property_or_exit(properties: dict, group: str, key: str): - full_key = f"{group}.{key}" - if full_key not in properties: - exit_with_error(f"Device properties does not contain key: {full_key}") - return properties[full_key] +def get_property_or_exit(properties: dict, key: str): + if key not in properties: + exit_with_error(f"Device properties does not contain key: {key}") + return properties[key] -def get_property_or_default(properties: dict, group: str, key: str, default): - return properties.get(f"{group}.{key}", default) +def get_property_or_default(properties: dict, key: str, default): + return properties.get(key, default) -def get_property_or_none(properties: dict, group: str, key: str): - return get_property_or_default(properties, group, key, None) +def get_property_or_none(properties: dict, key: str): + return get_property_or_default(properties, key, None) -def get_boolean_property_or_false(properties: dict, group: str, key: str): - return properties.get(f"{group}.{key}") == "true" +def get_boolean_property_or_false(properties: dict, key: str): + return properties.get(key) == "true" def safe_int(value: str, error_message: str): try: @@ -95,13 +94,13 @@ def write_defaults(output_file): output_file.write(default_properties) def get_user_data_location(device_properties: dict): - user_data_location = get_property_or_exit(device_properties, "storage", "userDataLocation") + user_data_location = get_property_or_exit(device_properties, "storage.userDataLocation") if user_data_location not in ("SD", "Internal"): exit_with_error(f"storage.userDataLocation must be 'SD' or 'Internal', but was: '{user_data_location}'") return user_data_location def write_partition_table(output_file, device_properties: dict, is_dev: bool): - flash_size = get_property_or_exit(device_properties, "hardware", "flashSize") + flash_size = get_property_or_exit(device_properties, "hardware.flashSize") if not flash_size.endswith("MB"): exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)") flash_size_number = flash_size[:-2] @@ -122,8 +121,8 @@ def write_partition_table(output_file, device_properties: dict, is_dev: bool): def write_tactility_variables(output_file, device_properties: dict, device_id: str): # Board and vendor - board_vendor = get_property_or_exit(device_properties, "general", "vendor").replace("\"", "\\\"") - board_name = get_property_or_exit(device_properties, "general", "name").replace("\"", "\\\"") + board_vendor = get_property_or_exit(device_properties, "general.vendor").replace("\"", "\\\"") + board_name = get_property_or_exit(device_properties, "general.name").replace("\"", "\\\"") if board_name == board_vendor or board_vendor == "": output_file.write(f"CONFIG_TT_DEVICE_NAME=\"{board_name}\"\n") else: @@ -134,10 +133,10 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s if device_id == "lilygo-tdeck": output_file.write("CONFIG_TT_TDECK_WORKAROUND=y\n") # Launcher app id - launcher_app_id = get_property_or_exit(device_properties, "apps", "launcherAppId").replace("\"", "\\\"") + launcher_app_id = get_property_or_exit(device_properties, "apps.launcherAppId").replace("\"", "\\\"") output_file.write(f"CONFIG_TT_LAUNCHER_APP_ID=\"{launcher_app_id}\"\n") # Auto start app id - auto_start_app_id = get_property_or_none(device_properties, "apps", "autoStartAppId") + auto_start_app_id = get_property_or_none(device_properties, "apps.autoStartAppId") if auto_start_app_id is not None: safe_auto_start_app_id = auto_start_app_id.replace("\"", "\\\"") output_file.write(f"CONFIG_TT_AUTO_START_APP_ID=\"{safe_auto_start_app_id}\"\n") @@ -148,7 +147,7 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s output_file.write("CONFIG_TT_USER_DATA_LOCATION_INTERNAL=y\n") def write_core_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() output_file.write("# Target\n") output_file.write(f"CONFIG_IDF_TARGET=\"{idf_target}\"\n") output_file.write("# CPU\n") @@ -171,28 +170,28 @@ def write_core_variables(output_file, device_properties: dict): output_file.write("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y\n") output_file.write("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=y\n") # Usage of tt::hal can be disabled to simplify dependency wiring (device depends on TactilityKernel instead of Tactility) - use_deprecated_hal = get_property_or_none(device_properties, "dependencies", "useDeprecatedHal") + use_deprecated_hal = get_property_or_none(device_properties, "dependencies.useDeprecatedHal") if use_deprecated_hal is None or use_deprecated_hal.lower() == "true": output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=y\n") else: output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=n\n") def write_flash_variables(output_file, device_properties: dict): - flash_size = get_property_or_exit(device_properties, "hardware", "flashSize") + flash_size = get_property_or_exit(device_properties, "hardware.flashSize") if not flash_size.endswith("MB"): exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)") output_file.write("# Flash\n") flash_size_number = flash_size[:-2] output_file.write(f"CONFIG_ESPTOOLPY_FLASHSIZE_{flash_size_number}MB=y\n") - flash_mode = get_property_or_default(device_properties, "hardware", "flashMode", 'QIO') + flash_mode = get_property_or_default(device_properties, "hardware.flashMode", 'QIO') output_file.write(f"CONFIG_FLASHMODE_{flash_mode}=y\n") - esptool_flash_freq = get_property_or_none(device_properties, "hardware", "esptoolFlashFreq") + esptool_flash_freq = get_property_or_none(device_properties, "hardware.esptoolFlashFreq") if esptool_flash_freq is not None: output_file.write(f"CONFIG_ESPTOOLPY_FLASHFREQ_{esptool_flash_freq}=y\n") def write_spiram_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() - has_spiram = get_property_or_exit(device_properties, "hardware", "spiRam") + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() + has_spiram = get_property_or_exit(device_properties, "hardware.spiRam") if has_spiram != "true": return output_file.write("# SPIRAM\n") @@ -201,7 +200,7 @@ def write_spiram_variables(output_file, device_properties: dict): # Enable output_file.write("CONFIG_SPIRAM=y\n") output_file.write(f"CONFIG_{idf_target.upper()}_SPIRAM_SUPPORT=y\n") - mode = get_property_or_exit(device_properties, "hardware", "spiRamMode") + mode = get_property_or_exit(device_properties, "hardware.spiRamMode") if mode == "OPI": mode = "OCT" # Mode @@ -209,7 +208,7 @@ def write_spiram_variables(output_file, device_properties: dict): output_file.write(f"CONFIG_SPIRAM_MODE_{mode}=y\n") else: output_file.write("CONFIG_SPIRAM_TYPE_AUTO=y\n") - speed = get_property_or_exit(device_properties, "hardware", "spiRamSpeed") + speed = get_property_or_exit(device_properties, "hardware.spiRamSpeed") # Speed output_file.write(f"CONFIG_SPIRAM_SPEED_{speed}=y\n") output_file.write(f"CONFIG_SPIRAM_SPEED={speed}\n") @@ -223,7 +222,7 @@ def write_spiram_variables(output_file, device_properties: dict): output_file.write("CONFIG_SPIRAM_XIP_FROM_PSRAM=y\n") def write_performance_improvements(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() if idf_target == "esp32s3": output_file.write("# Performance improvement: Fixes glitches in the RGB display driver when rendering new screens/apps\n") output_file.write("CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y\n") @@ -246,16 +245,16 @@ def write_lvgl_variables(output_file, device_properties: dict): write_lvgl_variable_placeholders(output_file) return # LVGL DPI overrides the real DPI settings - dpi_text = get_property_or_none(device_properties, "lvgl", "dpi") + dpi_text = get_property_or_none(device_properties, "lvgl.dpi") if dpi_text is None: - dpi_text = get_property_or_exit(device_properties, "display", "dpi") + dpi_text = get_property_or_exit(device_properties, "display.dpi") dpi = safe_int(dpi_text, f"DPI must be an integer, but was: '{dpi_text}'") output_file.write(f"CONFIG_LV_DPI_DEF={dpi}\n") - color_depth = get_property_or_exit(device_properties, "lvgl", "colorDepth") + color_depth = get_property_or_exit(device_properties, "lvgl.colorDepth") output_file.write(f"CONFIG_LV_COLOR_DEPTH={color_depth}\n") output_file.write(f"CONFIG_LV_COLOR_DEPTH_{color_depth}=y\n") output_file.write("CONFIG_LV_DISP_DEF_REFR_PERIOD=10\n") - theme = get_property_or_default(device_properties, "lvgl", "theme", "DefaultDark") + theme = get_property_or_default(device_properties, "lvgl.theme", "DefaultDark") if theme == "DefaultDark": output_file.write("CONFIG_LV_THEME_DEFAULT_DARK=y\n") elif theme == "DefaultLight": @@ -264,7 +263,7 @@ def write_lvgl_variables(output_file, device_properties: dict): output_file.write("CONFIG_LV_USE_THEME_MONO=y\n") else: exit_with_error(f"Unknown theme: {theme}") - font_height_text = get_property_or_default(device_properties, "lvgl", "fontSize", "14") + font_height_text = get_property_or_default(device_properties, "lvgl.fontSize", "14") font_height = safe_int(font_height_text, f"Font height must be an integer, but was: '{font_height_text}'") if font_height <= 12: output_file.write("CONFIG_LV_FONT_MONTSERRAT_8=y\n") @@ -333,14 +332,23 @@ def write_lvgl_variables(output_file, device_properties: dict): output_file.write("CONFIG_TT_LVGL_LAUNCHER_ICON_SIZE=72\n") output_file.write("CONFIG_TT_LVGL_SHARED_ICON_SIZE=32\n") +def write_touch_calibration_variables(output_file, device_properties: dict): + calibration_supported = get_property_or_none(device_properties, "touch.calibrationSupported") + if calibration_supported is not None and calibration_supported.lower() == "true": + output_file.write("# Touch calibration\n") + output_file.write("CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED=y\n") + calibration_required = get_property_or_none(device_properties, "touch.calibrationRequired") + if calibration_required is not None and calibration_required.lower() == "true": + output_file.write("CONFIG_TT_TOUCH_CALIBRATION_REQUIRED=y\n") + def write_usb_variables(output_file, device_properties: dict): - has_tiny_usb = get_boolean_property_or_false(device_properties, "hardware", "tinyUsb") + has_tiny_usb = get_boolean_property_or_false(device_properties, "hardware.tinyUsb") if has_tiny_usb: output_file.write("# TinyUSB\n") output_file.write("CONFIG_TINYUSB_MSC_ENABLED=y\n") output_file.write("CONFIG_TINYUSB_MSC_MOUNT_PATH=\"/sdcard\"\n") - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() if idf_target == "esp32p4": # P4 has two USB-DWC controllers (HS/UTMI and FS/FSLS). esp_tinyusb defaults to # RHPORT_HS (UTMI), which is the same controller claimed by usbhost0's @@ -349,8 +357,8 @@ def write_usb_variables(output_file, device_properties: dict): output_file.write("CONFIG_TINYUSB_RHPORT_FS=y\n") def write_bluetooth_variables(output_file, device_properties: dict): - idf_target = get_property_or_exit(device_properties, "hardware", "target").lower() - has_bluetooth = get_boolean_property_or_false(device_properties, "hardware", "bluetooth") + idf_target = get_property_or_exit(device_properties, "hardware.target").lower() + has_bluetooth = get_boolean_property_or_false(device_properties, "hardware.bluetooth") if has_bluetooth: output_file.write("# Bluetooth (NimBLE)\n") output_file.write("CONFIG_BT_ENABLED=y\n") @@ -365,7 +373,7 @@ def write_bluetooth_variables(output_file, device_properties: dict): # and does not suffer from the same fragmentation — enabling reliable re-init. # Also frees significant internal RAM on memory-constrained targets (e.g. S3). # Dependency: CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC (set by write_spiram_variables). - has_spiram = get_boolean_property_or_false(device_properties, "hardware", "spiRam") + has_spiram = get_boolean_property_or_false(device_properties, "hardware.spiRam") if has_spiram: output_file.write("CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y\n") # Expand NimBLE's GAP device name buffer to match BLE_DEVICE_NAME_MAX. @@ -382,7 +390,7 @@ def write_bluetooth_variables(output_file, device_properties: dict): output_file.write("CONFIG_BT_NIMBLE_NVS_PERSIST=y\n") def write_usbhost_variables(output_file, device_properties: dict): - has_usbhost = get_boolean_property_or_false(device_properties, "hardware", "usbHostEnabled") + has_usbhost = get_boolean_property_or_false(device_properties, "hardware.usbHostEnabled") if has_usbhost: output_file.write("# USB Host\n") output_file.write("CONFIG_FATFS_VOLUME_COUNT=6\n") @@ -416,6 +424,7 @@ def write_properties(output_file, device_properties: dict, device_id: str, is_de write_usbhost_variables(output_file, device_properties) write_custom_sdkconfig(output_file, device_properties) write_lvgl_variables(output_file, device_properties) + write_touch_calibration_variables(output_file, device_properties) def get_current_sdkconfig_target(sdkconfig_path: str): if not os.path.isfile(sdkconfig_path): @@ -448,7 +457,7 @@ def main(device_id: str, is_dev: bool): output_file_path = "sdkconfig" # Clean build dirs if target changes device_properties = read_device_properties(device_id) - new_target = get_property_or_exit(device_properties, "hardware", "target").lower() + new_target = get_property_or_exit(device_properties, "hardware.target").lower() sdkconfig_target = get_current_sdkconfig_target(output_file_path) clean_build_dirs_on_platform_change(sdkconfig_target, new_target) if os.path.isfile(output_file_path): From f31103882dd043a7a46df6496f9f6c7c1c697543 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 13 Jul 2026 23:18:37 +0200 Subject: [PATCH 02/15] Device and driver migrations --- Devices/cyd-2432s028r/CMakeLists.txt | 5 +- .../cyd-2432s028r/Source/Configuration.cpp | 32 -- .../cyd-2432s028r/Source/devices/Display.cpp | 49 --- .../cyd-2432s028r/Source/devices/Display.h | 27 -- Devices/cyd-2432s028r/Source/module.cpp | 23 -- Devices/cyd-2432s028r/cyd,2432s028r.dts | 43 ++- Devices/cyd-2432s028r/device.properties | 2 + Devices/cyd-2432s028r/devicetree.yaml | 2 + Devices/cyd-2432s028r/source/module.cpp | 34 ++ Devices/cyd-2432s028rv3/CMakeLists.txt | 5 +- .../cyd-2432s028rv3/Source/Configuration.cpp | 32 -- .../Source/devices/Display.cpp | 50 --- .../cyd-2432s028rv3/Source/devices/Display.h | 27 -- Devices/cyd-2432s028rv3/Source/module.cpp | 23 -- Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts | 43 ++- Devices/cyd-2432s028rv3/device.properties | 5 + Devices/cyd-2432s028rv3/devicetree.yaml | 2 + Devices/cyd-2432s028rv3/source/module.cpp | 33 ++ Devices/cyd-e32r28t/CMakeLists.txt | 5 +- Devices/cyd-e32r28t/Source/Configuration.cpp | 19 - .../cyd-e32r28t/Source/devices/Display.cpp | 51 --- Devices/cyd-e32r28t/Source/devices/Display.h | 27 -- Devices/cyd-e32r28t/cyd,e32r28t.dts | 45 ++- Devices/cyd-e32r28t/device.properties | 5 + Devices/cyd-e32r28t/devicetree.yaml | 2 + .../cyd-e32r28t/{Source => source}/module.cpp | 0 Devices/cyd-e32r32p/device.properties | 3 + .../device.properties | 3 + .../device.properties | 3 + Devices/lilygo-thmi/CMakeLists.txt | 5 +- Devices/lilygo-thmi/Source/Configuration.cpp | 22 -- Devices/lilygo-thmi/Source/Init.cpp | 48 --- .../lilygo-thmi/Source/devices/Display.cpp | 45 --- Devices/lilygo-thmi/Source/devices/Display.h | 35 -- Devices/lilygo-thmi/Source/devices/Power.cpp | 90 ----- Devices/lilygo-thmi/Source/devices/Power.h | 33 -- Devices/lilygo-thmi/device.properties | 5 + Devices/lilygo-thmi/devicetree.yaml | 3 + Devices/lilygo-thmi/lilygo,thmi.dts | 102 ++++- .../lilygo-thmi/{Source => source}/module.cpp | 0 Drivers/st7789-i8080-module/CMakeLists.txt | 11 + .../st7789-i8080-module/LICENSE-Apache-2.0.md | 195 ++++++++++ Drivers/st7789-i8080-module/README.md | 5 + .../bindings/sitronix,st7789-i8080.yaml | 63 ++++ Drivers/st7789-i8080-module/devicetree.yaml | 3 + .../include/bindings/st7789_i8080.h | 7 + .../include/drivers/st7789_i8080.h | 34 ++ .../include/st7789_i8080_module.h | 14 + Drivers/st7789-i8080-module/source/module.cpp | 32 ++ .../source/st7789_i8080.cpp | 349 ++++++++++++++++++ Drivers/xpt2046-softspi-module/CMakeLists.txt | 11 + .../LICENSE-Apache-2.0.md | 195 ++++++++++ Drivers/xpt2046-softspi-module/README.md | 9 + .../bindings/xptek,xpt2046-softspi.yaml | 44 +++ .../xpt2046-softspi-module/devicetree.yaml | 3 + .../include/bindings/xpt2046_softspi.h | 7 + .../include/drivers/xpt2046_softspi.h | 27 ++ .../include/xpt2046_softspi_module.h | 14 + .../xpt2046-softspi-module/source/module.cpp | 32 ++ .../source/xpt2046_softspi.cpp | 293 +++++++++++++++ Platforms/platform-esp32/CMakeLists.txt | 2 +- .../bindings/espressif,esp32-i8080.yaml | 64 ++++ .../include/tactility/bindings/esp32_i8080.h | 15 + .../include/tactility/drivers/esp32_i8080.h | 55 +++ .../source/drivers/esp32_i8080.cpp | 185 ++++++++++ Platforms/platform-esp32/source/module.cpp | 9 + .../app/kerneldisplay/KernelDisplay.cpp | 8 + .../bindings/i8080-controller.yaml | 1 + .../bindings/tactility,gpio-hog.yaml | 19 + .../include/tactility/bindings/gpio_hog.h | 7 + .../include/tactility/drivers/gpio_hog.h | 19 + .../tactility/drivers/i8080_controller.h | 19 + TactilityKernel/source/drivers/gpio_hog.cpp | 55 +++ .../source/drivers/i8080_controller.cpp | 11 + TactilityKernel/source/kernel_init.cpp | 2 + 75 files changed, 2113 insertions(+), 694 deletions(-) delete mode 100644 Devices/cyd-2432s028r/Source/Configuration.cpp delete mode 100644 Devices/cyd-2432s028r/Source/devices/Display.cpp delete mode 100644 Devices/cyd-2432s028r/Source/devices/Display.h delete mode 100644 Devices/cyd-2432s028r/Source/module.cpp create mode 100644 Devices/cyd-2432s028r/source/module.cpp delete mode 100644 Devices/cyd-2432s028rv3/Source/Configuration.cpp delete mode 100644 Devices/cyd-2432s028rv3/Source/devices/Display.cpp delete mode 100644 Devices/cyd-2432s028rv3/Source/devices/Display.h delete mode 100644 Devices/cyd-2432s028rv3/Source/module.cpp create mode 100644 Devices/cyd-2432s028rv3/source/module.cpp delete mode 100644 Devices/cyd-e32r28t/Source/Configuration.cpp delete mode 100644 Devices/cyd-e32r28t/Source/devices/Display.cpp delete mode 100644 Devices/cyd-e32r28t/Source/devices/Display.h rename Devices/cyd-e32r28t/{Source => source}/module.cpp (100%) delete mode 100644 Devices/lilygo-thmi/Source/Configuration.cpp delete mode 100644 Devices/lilygo-thmi/Source/Init.cpp delete mode 100644 Devices/lilygo-thmi/Source/devices/Display.cpp delete mode 100644 Devices/lilygo-thmi/Source/devices/Display.h delete mode 100644 Devices/lilygo-thmi/Source/devices/Power.cpp delete mode 100644 Devices/lilygo-thmi/Source/devices/Power.h rename Devices/lilygo-thmi/{Source => source}/module.cpp (100%) create mode 100644 Drivers/st7789-i8080-module/CMakeLists.txt create mode 100644 Drivers/st7789-i8080-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/st7789-i8080-module/README.md create mode 100644 Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml create mode 100644 Drivers/st7789-i8080-module/devicetree.yaml create mode 100644 Drivers/st7789-i8080-module/include/bindings/st7789_i8080.h create mode 100644 Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h create mode 100644 Drivers/st7789-i8080-module/include/st7789_i8080_module.h create mode 100644 Drivers/st7789-i8080-module/source/module.cpp create mode 100644 Drivers/st7789-i8080-module/source/st7789_i8080.cpp create mode 100644 Drivers/xpt2046-softspi-module/CMakeLists.txt create mode 100644 Drivers/xpt2046-softspi-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/xpt2046-softspi-module/README.md create mode 100644 Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml create mode 100644 Drivers/xpt2046-softspi-module/devicetree.yaml create mode 100644 Drivers/xpt2046-softspi-module/include/bindings/xpt2046_softspi.h create mode 100644 Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h create mode 100644 Drivers/xpt2046-softspi-module/include/xpt2046_softspi_module.h create mode 100644 Drivers/xpt2046-softspi-module/source/module.cpp create mode 100644 Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp create mode 100644 Platforms/platform-esp32/bindings/espressif,esp32-i8080.yaml create mode 100644 Platforms/platform-esp32/include/tactility/bindings/esp32_i8080.h create mode 100644 Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h create mode 100644 Platforms/platform-esp32/source/drivers/esp32_i8080.cpp create mode 100644 TactilityKernel/bindings/i8080-controller.yaml create mode 100644 TactilityKernel/bindings/tactility,gpio-hog.yaml create mode 100644 TactilityKernel/include/tactility/bindings/gpio_hog.h create mode 100644 TactilityKernel/include/tactility/drivers/gpio_hog.h create mode 100644 TactilityKernel/include/tactility/drivers/i8080_controller.h create mode 100644 TactilityKernel/source/drivers/gpio_hog.cpp create mode 100644 TactilityKernel/source/drivers/i8080_controller.cpp diff --git a/Devices/cyd-2432s028r/CMakeLists.txt b/Devices/cyd-2432s028r/CMakeLists.txt index d691e4554..a832a83a2 100644 --- a/Devices/cyd-2432s028r/CMakeLists.txt +++ b/Devices/cyd-2432s028r/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-2432s028r/Source/Configuration.cpp b/Devices/cyd-2432s028r/Source/Configuration.cpp deleted file mode 100644 index b979797e1..000000000 --- a/Devices/cyd-2432s028r/Source/Configuration.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "devices/Display.h" -#include - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - // Set the RGB LED Pins to output and turn them off - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue - - // 0 on, 1 off - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue - - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT); -} - -static DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-2432s028r/Source/devices/Display.cpp b/Devices/cyd-2432s028r/Source/devices/Display.cpp deleted file mode 100644 index 1664b7c4c..000000000 --- a/Devices/cyd-2432s028r/Source/devices/Display.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "Display.h" -#include "Xpt2046SoftSpi.h" -#include -#include - -static std::shared_ptr createTouch() { - auto configuration = std::make_unique( - TOUCH_MOSI_PIN, - TOUCH_MISO_PIN, - TOUCH_SCK_PIN, - TOUCH_CS_PIN, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - false, // swapXY - true, // mirrorX - false // mirrorY - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = true, - .mirrorY = false, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR - }; - - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/cyd-2432s028r/Source/devices/Display.h b/Devices/cyd-2432s028r/Source/devices/Display.h deleted file mode 100644 index d3428a78c..000000000 --- a/Devices/cyd-2432s028r/Source/devices/Display.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Display -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; - -// Display backlight (PWM) -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_21; - -// Touch (Software SPI) -constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39; -constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32; -constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25; -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-2432s028r/Source/module.cpp b/Devices/cyd-2432s028r/Source/module.cpp deleted file mode 100644 index 82789f73d..000000000 --- a/Devices/cyd-2432s028r/Source/module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -extern "C" { - -static error_t start() { - // Empty for now - return ERROR_NONE; -} - -static error_t stop() { - // Empty for now - return ERROR_NONE; -} - -struct Module cyd_2432s028r_module = { - .name = "cyd-2432s028r", - .start = start, - .stop = stop, - .symbols = nullptr, - .internal = nullptr -}; - -} diff --git a/Devices/cyd-2432s028r/cyd,2432s028r.dts b/Devices/cyd-2432s028r/cyd,2432s028r.dts index 141c9750e..4a4378ea2 100644 --- a/Devices/cyd-2432s028r/cyd,2432s028r.dts +++ b/Devices/cyd-2432s028r/cyd,2432s028r.dts @@ -7,8 +7,9 @@ #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -32,21 +33,43 @@ pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + + touch { + compatible = "xptek,xpt2046-softspi"; + pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 39 GPIO_FLAG_NONE>; + pin-sck = <&gpio0 25 GPIO_FLAG_NONE>; + pin-cs = <&gpio0 33 GPIO_FLAG_NONE>; + x-max = <240>; + y-max = <320>; + mirror-x; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; - cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display - <&gpio0 33 GPIO_FLAG_NONE>; // Touch + cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-miso = <&gpio0 12 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; - - display@0 { - compatible = "display-placeholder"; - }; - touch@1 { - compatible = "pointer-placeholder"; + display@0 { + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + mirror-x; + bgr-order; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; diff --git a/Devices/cyd-2432s028r/device.properties b/Devices/cyd-2432s028r/device.properties index 847dc1fb0..05598a702 100644 --- a/Devices/cyd-2432s028r/device.properties +++ b/Devices/cyd-2432s028r/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" diff --git a/Devices/cyd-2432s028r/devicetree.yaml b/Devices/cyd-2432s028r/devicetree.yaml index 1bf600561..8d45bc194 100644 --- a/Devices/cyd-2432s028r/devicetree.yaml +++ b/Devices/cyd-2432s028r/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/xpt2046-softspi-module dts: cyd,2432s028r.dts diff --git a/Devices/cyd-2432s028r/source/module.cpp b/Devices/cyd-2432s028r/source/module.cpp new file mode 100644 index 000000000..83bd0960d --- /dev/null +++ b/Devices/cyd-2432s028r/source/module.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include + +extern "C" { + +static error_t start() { + // Set the RGB LED pins to output and turn them off (0 on, 1 off) + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red + gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green + gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue + + gpio_set_level(GPIO_NUM_4, 1); // Red + gpio_set_level(GPIO_NUM_16, 1); // Green + gpio_set_level(GPIO_NUM_17, 1); // Blue + + return ERROR_NONE; +} + +static error_t stop() { + // Empty for now + return ERROR_NONE; +} + +struct Module cyd_2432s028r_module = { + .name = "cyd-2432s028r", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/cyd-2432s028rv3/CMakeLists.txt b/Devices/cyd-2432s028rv3/CMakeLists.txt index d9b561f2f..a832a83a2 100644 --- a/Devices/cyd-2432s028rv3/CMakeLists.txt +++ b/Devices/cyd-2432s028rv3/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ST7789 XPT2046SoftSPI PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-2432s028rv3/Source/Configuration.cpp b/Devices/cyd-2432s028rv3/Source/Configuration.cpp deleted file mode 100644 index b979797e1..000000000 --- a/Devices/cyd-2432s028rv3/Source/Configuration.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "devices/Display.h" -#include - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - // Set the RGB LED Pins to output and turn them off - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue - - // 0 on, 1 off - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue - - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT); -} - -static DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-2432s028rv3/Source/devices/Display.cpp b/Devices/cyd-2432s028rv3/Source/devices/Display.cpp deleted file mode 100644 index 8948885ce..000000000 --- a/Devices/cyd-2432s028rv3/Source/devices/Display.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "Display.h" -#include "Xpt2046SoftSpi.h" -#include -#include - -constexpr auto* TAG = "CYD"; - -static std::shared_ptr createTouch() { - auto configuration = std::make_unique( - TOUCH_MOSI_PIN, - TOUCH_MISO_PIN, - TOUCH_SCK_PIN, - TOUCH_CS_PIN, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - false, // swapXY - true, // mirrorX - false // mirrorY - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - St7789Display::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = false, - .mirrorY = false, - .invertColor = false, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .lvglSwapBytes = false - }; - - auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 62'500'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration); -} diff --git a/Devices/cyd-2432s028rv3/Source/devices/Display.h b/Devices/cyd-2432s028rv3/Source/devices/Display.h deleted file mode 100644 index 67a097996..000000000 --- a/Devices/cyd-2432s028rv3/Source/devices/Display.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include - -// Display -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_21; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; - -// Touch (Software SPI) -constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39; -constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32; -constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25; -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-2432s028rv3/Source/module.cpp b/Devices/cyd-2432s028rv3/Source/module.cpp deleted file mode 100644 index e0eda01c7..000000000 --- a/Devices/cyd-2432s028rv3/Source/module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -extern "C" { - -static error_t start() { - // Empty for now - return ERROR_NONE; -} - -static error_t stop() { - // Empty for now - return ERROR_NONE; -} - -struct Module cyd_2432s028rv3_module = { - .name = "cyd-2432s028rv3", - .start = start, - .stop = stop, - .symbols = nullptr, - .internal = nullptr -}; - -} diff --git a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts index 99ea163fe..48c105288 100644 --- a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts +++ b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts @@ -7,8 +7,9 @@ #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -32,21 +33,41 @@ pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + + touch { + compatible = "xptek,xpt2046-softspi"; + pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 39 GPIO_FLAG_NONE>; + pin-sck = <&gpio0 25 GPIO_FLAG_NONE>; + pin-cs = <&gpio0 33 GPIO_FLAG_NONE>; + x-max = <240>; + y-max = <320>; + mirror-x; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; - cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display - <&gpio0 33 GPIO_FLAG_NONE>; // Touch + cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-miso = <&gpio0 12 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; - - display@0 { - compatible = "display-placeholder"; - }; - touch@1 { - compatible = "pointer-placeholder"; + display@0 { + compatible = "sitronix,st7789"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + pixel-clock-hz = <62500000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; @@ -57,7 +78,7 @@ pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>; pin-miso = <&gpio0 19 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>; - + sdcard@0 { compatible = "espressif,esp32-sdspi"; frequency-khz = <20000>; diff --git a/Devices/cyd-2432s028rv3/device.properties b/Devices/cyd-2432s028rv3/device.properties index ae3e926f9..683b2f35f 100644 --- a/Devices/cyd-2432s028rv3/device.properties +++ b/Devices/cyd-2432s028rv3/device.properties @@ -7,12 +7,17 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" display.shape=rectangle display.dpi=143 +touch.calibrationSupported=true +touch.calibrationRequired=false + cdn.warningMessage=There are 3 hardware variants of this board. This build only supports board version 3. lvgl.colorDepth=16 diff --git a/Devices/cyd-2432s028rv3/devicetree.yaml b/Devices/cyd-2432s028rv3/devicetree.yaml index 239312972..2a9ecdd3a 100644 --- a/Devices/cyd-2432s028rv3/devicetree.yaml +++ b/Devices/cyd-2432s028rv3/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/st7789-module + - Drivers/xpt2046-softspi-module dts: cyd,2432s028rv3.dts diff --git a/Devices/cyd-2432s028rv3/source/module.cpp b/Devices/cyd-2432s028rv3/source/module.cpp new file mode 100644 index 000000000..3f7e4f1cc --- /dev/null +++ b/Devices/cyd-2432s028rv3/source/module.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include + +extern "C" { + +static error_t start() { + // Set the RGB LED pins to output and turn them off (0 on, 1 off) + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red + gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green + gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue + + gpio_set_level(GPIO_NUM_4, 1); // Red + gpio_set_level(GPIO_NUM_16, 1); // Green + gpio_set_level(GPIO_NUM_17, 1); // Blue + + return ERROR_NONE; +} + +static error_t stop() { + return ERROR_NONE; +} + +struct Module cyd_2432s028rv3_module = { + .name = "cyd-2432s028rv3", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/cyd-e32r28t/CMakeLists.txt b/Devices/cyd-e32r28t/CMakeLists.txt index d691e4554..a832a83a2 100644 --- a/Devices/cyd-e32r28t/CMakeLists.txt +++ b/Devices/cyd-e32r28t/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-e32r28t/Source/Configuration.cpp b/Devices/cyd-e32r28t/Source/Configuration.cpp deleted file mode 100644 index 620a05fe5..000000000 --- a/Devices/cyd-e32r28t/Source/Configuration.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "devices/Display.h" - -#include -#include - -static bool initBoot() { - return driver::pwmbacklight::init(LCD_BACKLIGHT_PIN); -} - -static tt::hal::DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const tt::hal::Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-e32r28t/Source/devices/Display.cpp b/Devices/cyd-e32r28t/Source/devices/Display.cpp deleted file mode 100644 index 9ba75bde2..000000000 --- a/Devices/cyd-e32r28t/Source/devices/Display.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "Display.h" - -#include -#include -#include -#include - -static std::shared_ptr createTouch() { - auto config = std::make_unique( - TOUCH_MOSI_PIN, - TOUCH_MISO_PIN, - TOUCH_SCK_PIN, - TOUCH_CS_PIN, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - false, - true, - false - ); - - return std::make_shared(std::move(config)); -} - -std::shared_ptr createDisplay() { - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = true, - .mirrorY = false, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR - }; - - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/cyd-e32r28t/Source/devices/Display.h b/Devices/cyd-e32r28t/Source/devices/Display.h deleted file mode 100644 index fbe79f5a3..000000000 --- a/Devices/cyd-e32r28t/Source/devices/Display.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Display -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = (LCD_VERTICAL_RESOLUTION / 10); -constexpr auto LCD_BUFFER_SIZE = (LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT); - -// Touch (Software SPI) -constexpr auto TOUCH_MISO_PIN = GPIO_NUM_39; -constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_32; -constexpr auto TOUCH_SCK_PIN = GPIO_NUM_25; -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - -// Backlight -constexpr auto LCD_BACKLIGHT_PIN = GPIO_NUM_21; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-e32r28t/cyd,e32r28t.dts b/Devices/cyd-e32r28t/cyd,e32r28t.dts index 236b5637f..2ae405d13 100644 --- a/Devices/cyd-e32r28t/cyd,e32r28t.dts +++ b/Devices/cyd-e32r28t/cyd,e32r28t.dts @@ -5,8 +5,9 @@ #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -22,21 +23,43 @@ gpio-count = <40>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + + touch { + compatible = "xptek,xpt2046-softspi"; + pin-mosi = <&gpio0 32 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 39 GPIO_FLAG_NONE>; + pin-sck = <&gpio0 25 GPIO_FLAG_NONE>; + pin-cs = <&gpio0 33 GPIO_FLAG_NONE>; + x-max = <240>; + y-max = <320>; + mirror-x; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; - cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>, // Display - <&gpio0 33 GPIO_FLAG_NONE>; // Touch + cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-miso = <&gpio0 12 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; - - display@0 { - compatible = "display-placeholder"; - }; - touch@1 { - compatible = "pointer-placeholder"; + display@0 { + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + mirror-x; + bgr-order; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; @@ -47,7 +70,7 @@ pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>; pin-miso = <&gpio0 19 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>; - + sdcard@0 { compatible = "espressif,esp32-sdspi"; frequency-khz = <20000>; diff --git a/Devices/cyd-e32r28t/device.properties b/Devices/cyd-e32r28t/device.properties index 0adcd93d7..a60fd82db 100644 --- a/Devices/cyd-e32r28t/device.properties +++ b/Devices/cyd-e32r28t/device.properties @@ -7,10 +7,15 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" display.shape=rectangle display.dpi=143 +touch.calibrationSupported=true +touch.calibrationRequired=false + lvgl.colorDepth=16 diff --git a/Devices/cyd-e32r28t/devicetree.yaml b/Devices/cyd-e32r28t/devicetree.yaml index a658b391d..a77f50c5f 100644 --- a/Devices/cyd-e32r28t/devicetree.yaml +++ b/Devices/cyd-e32r28t/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/xpt2046-softspi-module dts: cyd,e32r28t.dts diff --git a/Devices/cyd-e32r28t/Source/module.cpp b/Devices/cyd-e32r28t/source/module.cpp similarity index 100% rename from Devices/cyd-e32r28t/Source/module.cpp rename to Devices/cyd-e32r28t/source/module.cpp diff --git a/Devices/cyd-e32r32p/device.properties b/Devices/cyd-e32r32p/device.properties index 3c10c3e89..27f2ad0a6 100644 --- a/Devices/cyd-e32r32p/device.properties +++ b/Devices/cyd-e32r32p/device.properties @@ -13,4 +13,7 @@ display.size=2.8" display.shape=rectangle display.dpi=125 +touch.calibrationSupported=true +touch.calibrationRequired=false + lvgl.colorDepth=16 diff --git a/Devices/elecrow-crowpanel-basic-28/device.properties b/Devices/elecrow-crowpanel-basic-28/device.properties index 61b715566..acc1405a4 100644 --- a/Devices/elecrow-crowpanel-basic-28/device.properties +++ b/Devices/elecrow-crowpanel-basic-28/device.properties @@ -13,4 +13,7 @@ display.size=2.8" display.shape=rectangle display.dpi=143 +touch.calibrationSupported=true +touch.calibrationRequired=false + lvgl.colorDepth=16 diff --git a/Devices/elecrow-crowpanel-basic-35/device.properties b/Devices/elecrow-crowpanel-basic-35/device.properties index 814042df4..0e1800e26 100644 --- a/Devices/elecrow-crowpanel-basic-35/device.properties +++ b/Devices/elecrow-crowpanel-basic-35/device.properties @@ -15,4 +15,7 @@ display.size=3.5" display.shape=rectangle display.dpi=165 +touch.calibrationSupported=true +touch.calibrationRequired=false + lvgl.colorDepth=16 diff --git a/Devices/lilygo-thmi/CMakeLists.txt b/Devices/lilygo-thmi/CMakeLists.txt index 5f1b69dcf..a832a83a2 100644 --- a/Devices/lilygo-thmi/CMakeLists.txt +++ b/Devices/lilygo-thmi/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility ButtonControl XPT2046SoftSPI PwmBacklight EstimatedPower ST7789-i8080 driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/lilygo-thmi/Source/Configuration.cpp b/Devices/lilygo-thmi/Source/Configuration.cpp deleted file mode 100644 index 68aa2d31c..000000000 --- a/Devices/lilygo-thmi/Source/Configuration.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "devices/Power.h" -#include "devices/Display.h" - -#include -#include - -bool initBoot(); - -using namespace tt::hal; - -static std::vector> createDevices() { - return { - createDisplay(), - std::make_shared(), - ButtonControl::createOneButtonControl(0) - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/lilygo-thmi/Source/Init.cpp b/Devices/lilygo-thmi/Source/Init.cpp deleted file mode 100644 index e74fd553c..000000000 --- a/Devices/lilygo-thmi/Source/Init.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "devices/Power.h" -#include "devices/Display.h" - -#include "PwmBacklight.h" -#include -#include -#include - -#define TAG "thmi" - -static bool powerOn() { - gpio_config_t power_signal_config = { - .pin_bit_mask = (1ULL << THMI_POWERON_GPIO) | (1ULL << THMI_POWEREN_GPIO), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - - if (gpio_config(&power_signal_config) != ESP_OK) { - return false; - } - - if (gpio_set_level(THMI_POWERON_GPIO, 1) != ESP_OK) { - return false; - } - - if (gpio_set_level(THMI_POWEREN_GPIO, 1) != ESP_OK) { - return false; - } - - return true; -} - -bool initBoot() { - LOG_I(TAG, "Powering on the board..."); - if (!powerOn()) { - LOG_E(TAG, "Failed to power on the board."); - return false; - } - - if (!driver::pwmbacklight::init(DISPLAY_BL, 30000)) { - LOG_E(TAG, "Failed to initialize backlight."); - return false; - } - - return true; -} diff --git a/Devices/lilygo-thmi/Source/devices/Display.cpp b/Devices/lilygo-thmi/Source/devices/Display.cpp deleted file mode 100644 index 73da119d4..000000000 --- a/Devices/lilygo-thmi/Source/devices/Display.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include - -#include "Display.h" -#include "PwmBacklight.h" -#include "St7789i8080Display.h" - -static bool touchSpiInitialized = false; - -static std::shared_ptr createTouch() { - auto config = std::make_unique( - TOUCH_MOSI_PIN, - TOUCH_MISO_PIN, - TOUCH_SCK_PIN, - TOUCH_CS_PIN, - DISPLAY_HORIZONTAL_RESOLUTION, - DISPLAY_VERTICAL_RESOLUTION - ); - - return std::make_shared(std::move(config)); -} - -std::shared_ptr createDisplay() { - // Create configuration - auto config = St7789i8080Display::Configuration( - DISPLAY_CS, // CS - DISPLAY_DC, // DC - DISPLAY_WR, // WR - DISPLAY_RD, // RD - { DISPLAY_I80_D0, DISPLAY_I80_D1, DISPLAY_I80_D2, DISPLAY_I80_D3, - DISPLAY_I80_D4, DISPLAY_I80_D5, DISPLAY_I80_D6, DISPLAY_I80_D7 }, // D0..D7 - DISPLAY_RST, // RST - DISPLAY_BL // BL - ); - - // Set resolution explicitly - config.horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION; - config.verticalResolution = DISPLAY_VERTICAL_RESOLUTION; - config.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; - config.touch = createTouch(); - config.invertColor = false; - - auto display = std::make_shared(config); - return display; -} diff --git a/Devices/lilygo-thmi/Source/devices/Display.h b/Devices/lilygo-thmi/Source/devices/Display.h deleted file mode 100644 index ec375a702..000000000 --- a/Devices/lilygo-thmi/Source/devices/Display.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include -#include - -#include "driver/spi_common.h" - -class St7789i8080Display; - -constexpr auto DISPLAY_CS = GPIO_NUM_6; -constexpr auto DISPLAY_DC = GPIO_NUM_7; -constexpr auto DISPLAY_WR = GPIO_NUM_8; -constexpr auto DISPLAY_RD = GPIO_NUM_NC; -constexpr auto DISPLAY_RST = GPIO_NUM_NC; -constexpr auto DISPLAY_BL = GPIO_NUM_38; -constexpr auto DISPLAY_I80_D0 = GPIO_NUM_48; -constexpr auto DISPLAY_I80_D1 = GPIO_NUM_47; -constexpr auto DISPLAY_I80_D2 = GPIO_NUM_39; -constexpr auto DISPLAY_I80_D3 = GPIO_NUM_40; -constexpr auto DISPLAY_I80_D4 = GPIO_NUM_41; -constexpr auto DISPLAY_I80_D5 = GPIO_NUM_42; -constexpr auto DISPLAY_I80_D6 = GPIO_NUM_45; -constexpr auto DISPLAY_I80_D7 = GPIO_NUM_46; -constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 240; -constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320; - -// Touch (XPT2046, resistive) -constexpr auto TOUCH_SPI_HOST = SPI2_HOST; -constexpr auto TOUCH_MISO_PIN = GPIO_NUM_4; -constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_3; -constexpr auto TOUCH_SCK_PIN = GPIO_NUM_1; -constexpr auto TOUCH_CS_PIN = GPIO_NUM_2; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_9; - -// Factory function for registration -std::shared_ptr createDisplay(); diff --git a/Devices/lilygo-thmi/Source/devices/Power.cpp b/Devices/lilygo-thmi/Source/devices/Power.cpp deleted file mode 100644 index 674429990..000000000 --- a/Devices/lilygo-thmi/Source/devices/Power.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Power.h" - -#include -#include - -constexpr auto* TAG = "Power"; - -bool Power::adcInitCalibration() { - bool calibrated = false; - - esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT); - if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) { - LOG_W(TAG, "Calibration scheme not supported, skip software calibration"); - } else if (efuse_read_result == ESP_ERR_INVALID_VERSION) { - LOG_W(TAG, "eFuse not burnt, skip software calibration"); - } else if (efuse_read_result == ESP_OK) { - calibrated = true; - LOG_I(TAG, "Calibration success"); - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics); - } else { - LOG_W(TAG, "eFuse read failed, skipping calibration"); - } - - return calibrated; -} - -uint32_t Power::adcReadValue() const { - int adc_raw = adc1_get_raw(ADC1_CHANNEL_4); - LOG_D(TAG, "Raw data: %d", adc_raw); - - uint32_t voltage; - - if (calibrated) { - voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics); - LOG_D(TAG, "Calibrated data: %d mV", (int)voltage); - } else { - voltage = (adc_raw * 3300) / 4095; // fallback - LOG_D(TAG, "Estimated data: %d mV", (int)voltage); - } - - return voltage; -} - -bool Power::ensureInitialized() { - if (!initialized) { - - if (adc1_config_width(ADC_WIDTH_BIT_12) != ESP_OK) { - LOG_E(TAG, "ADC1 config width failed"); - return false; - } - - if (adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11) != ESP_OK) { - LOG_E(TAG, "ADC1 config attenuation failed"); - return false; - } - - calibrated = adcInitCalibration(); - - initialized = true; - } - return true; -} - -bool Power::supportsMetric(MetricType type) const { - switch (type) { - using enum MetricType; - case BatteryVoltage: - case ChargeLevel: - return true; - default: - return false; - } -} - -bool Power::getMetric(MetricType type, MetricData& data) { - if (!ensureInitialized()) { - return false; - } - - switch (type) { - case MetricType::BatteryVoltage: - data.valueAsUint32 = adcReadValue() * 2; - return true; - case MetricType::ChargeLevel: - data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2); - return true; - default: - return false; - } -} diff --git a/Devices/lilygo-thmi/Source/devices/Power.h b/Devices/lilygo-thmi/Source/devices/Power.h deleted file mode 100644 index c831083c4..000000000 --- a/Devices/lilygo-thmi/Source/devices/Power.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -constexpr auto THMI_POWEREN_GPIO = GPIO_NUM_10; -constexpr auto THMI_POWERON_GPIO = GPIO_NUM_14; - -using tt::hal::power::PowerDevice; - -class Power final : public PowerDevice { - - ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); - esp_adc_cal_characteristics_t adcCharacteristics; - bool initialized = false; - bool calibrated = false; - - bool adcInitCalibration(); - uint32_t adcReadValue() const; - - bool ensureInitialized(); - -public: - - std::string getName() const override { return "T-HMI Power"; } - std::string getDescription() const override { return "Power measurement via ADC"; } - - bool supportsMetric(MetricType type) const override; - bool getMetric(MetricType type, MetricData& data) override; -}; diff --git a/Devices/lilygo-thmi/device.properties b/Devices/lilygo-thmi/device.properties index d58efc9f3..6c348116c 100644 --- a/Devices/lilygo-thmi/device.properties +++ b/Devices/lilygo-thmi/device.properties @@ -12,10 +12,15 @@ hardware.tinyUsb=true hardware.esptoolFlashFreq=120M hardware.bluetooth=true +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" display.shape=rectangle display.dpi=125 +touch.calibrationSupported=true +touch.calibrationRequired=false + lvgl.colorDepth=16 diff --git a/Devices/lilygo-thmi/devicetree.yaml b/Devices/lilygo-thmi/devicetree.yaml index 68d5c934f..20bd971f1 100644 --- a/Devices/lilygo-thmi/devicetree.yaml +++ b/Devices/lilygo-thmi/devicetree.yaml @@ -1,3 +1,6 @@ dependencies: - Platforms/platform-esp32 + - Drivers/xpt2046-softspi-module + - Drivers/st7789-i8080-module + - Drivers/button-control-module dts: lilygo,thmi.dts diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index 3495a425b..6ef4ac943 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -1,14 +1,19 @@ /dts-v1/; #include +#include +#include #include #include #include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include / { compatible = "root"; @@ -28,23 +33,88 @@ compatible = "espressif,esp32-gpio"; gpio-count = <49>; }; - - spi0 { - compatible = "espressif,esp32-spi"; - host = ; - cs-gpios = <&gpio0 6 GPIO_FLAG_NONE>, // Display - <&gpio0 2 GPIO_FLAG_NONE>; // Touch - pin-mosi = <&gpio0 3 GPIO_FLAG_NONE>; - pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; - pin-sclk = <&gpio0 1 GPIO_FLAG_NONE>; + + // Board power-enable pins. Must be asserted before the i8080 bus/display below start, since + // devicetree devices are constructed and started earlier (kernel_init()) than the deprecated + // HAL's initBoot() used to run. gpio-hog nodes run in declaration order, so they must stay + // before the i8080 bus node. + power_on { + compatible = "tactility,gpio-hog"; + pin = <&gpio0 14 GPIO_FLAG_NONE>; + }; + + power_en { + compatible = "tactility,gpio-hog"; + pin = <&gpio0 10 GPIO_FLAG_NONE>; + }; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + channels = ; + }; + + // Battery voltage sits behind a 2:1 divider before reaching the ADC (see the deprecated HAL's + // old Power.cpp: adcReadValue() * 2). 3300mV matches its uncalibrated fallback reference + // voltage. Charge-percent curve is battery-sense's own fixed 3200-4200mV estimate (see + // TactilityKernel/source/drivers/battery_sense.cpp) rather than the old driver's 3300-4200mV + // ChargeFromVoltage curve - a shared kernel driver, not a per-board tunable. + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>; + frequency-hz = <30000>; + }; + + i8080_0 { + compatible = "espressif,esp32-i8080"; + pin-dc = <&gpio0 7 GPIO_FLAG_NONE>; + pin-wr = <&gpio0 8 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 48 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 47 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 39 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 40 GPIO_FLAG_NONE>; + pin-d4 = <&gpio0 41 GPIO_FLAG_NONE>; + pin-d5 = <&gpio0 42 GPIO_FLAG_NONE>; + pin-d6 = <&gpio0 45 GPIO_FLAG_NONE>; + pin-d7 = <&gpio0 46 GPIO_FLAG_NONE>; + // horizontal-resolution * vertical-resolution / 10 (partial buffer) * 2 bytes/pixel + max-transfer-bytes = <15360>; + cs-gpios = <&gpio0 6 GPIO_FLAG_NONE>; display@0 { - compatible = "display-placeholder"; + compatible = "sitronix,st7789-i8080"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + pixel-clock-hz = <16000000>; + backlight = <&display_backlight>; }; + }; - touch@1 { - compatible = "pointer-placeholder"; - }; + touch { + compatible = "xptek,xpt2046-softspi"; + pin-mosi = <&gpio0 3 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; + pin-sck = <&gpio0 1 GPIO_FLAG_NONE>; + pin-cs = <&gpio0 2 GPIO_FLAG_NONE>; + x-max = <240>; + y-max = <320>; + }; + + // One-button mode: short press = select next, long press = press/enter (matches the + // deprecated HAL's old ButtonControl::createOneButtonControl(0)). + buttons { + compatible = "tactility,button-control"; + pin-primary = <&gpio0 0 GPIO_FLAG_NONE>; }; sdmmc0 { diff --git a/Devices/lilygo-thmi/Source/module.cpp b/Devices/lilygo-thmi/source/module.cpp similarity index 100% rename from Devices/lilygo-thmi/Source/module.cpp rename to Devices/lilygo-thmi/source/module.cpp diff --git a/Drivers/st7789-i8080-module/CMakeLists.txt b/Drivers/st7789-i8080-module/CMakeLists.txt new file mode 100644 index 000000000..7188a11c9 --- /dev/null +++ b/Drivers/st7789-i8080-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(st7789-i8080-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd driver +) diff --git a/Drivers/st7789-i8080-module/LICENSE-Apache-2.0.md b/Drivers/st7789-i8080-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/st7789-i8080-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/st7789-i8080-module/README.md b/Drivers/st7789-i8080-module/README.md new file mode 100644 index 000000000..7ac152365 --- /dev/null +++ b/Drivers/st7789-i8080-module/README.md @@ -0,0 +1,5 @@ +# ST7789 i8080 Display Driver + +A kernel driver for the `ST7789` display panel over an i8080 (parallel) bus, built on ESP-IDF's `esp_lcd` component. Child of an i8080 bus controller device (e.g. `espressif,esp32-i8080` in `Platforms/platform-esp32`) - use `st7789-module` instead for SPI-attached ST7789 panels. + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml new file mode 100644 index 000000000..dbf57f2c5 --- /dev/null +++ b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml @@ -0,0 +1,63 @@ +description: Sitronix ST7789 display panel over an i8080 (parallel) bus + +compatible: "sitronix,st7789-i8080" + +bus: i8080 + +properties: + horizontal-resolution: + type: int + required: true + description: Horizontal resolution in pixels + vertical-resolution: + type: int + required: true + description: Vertical resolution in pixels + gap-x: + type: int + default: 0 + description: X offset applied to all draw operations + gap-y: + type: int + default: 0 + description: Y offset applied to all draw operations + swap-xy: + type: boolean + default: false + description: Swap the X and Y axes + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + invert-color: + type: boolean + default: false + description: Invert the panel's color output + bgr-order: + type: boolean + default: false + description: Use BGR element order instead of RGB + pixel-clock-hz: + type: int + default: 16000000 + description: i80 bus write-clock frequency in Hz + transaction-queue-depth: + type: int + default: 10 + description: Size of the internal transaction queue + pin-reset: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Reset GPIO pin + reset-active-high: + type: boolean + default: false + description: Whether the reset pin is active high + backlight: + type: phandle + default: NULL + description: Optional reference to this display's backlight device diff --git a/Drivers/st7789-i8080-module/devicetree.yaml b/Drivers/st7789-i8080-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/st7789-i8080-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/st7789-i8080-module/include/bindings/st7789_i8080.h b/Drivers/st7789-i8080-module/include/bindings/st7789_i8080.h new file mode 100644 index 000000000..9af712655 --- /dev/null +++ b/Drivers/st7789-i8080-module/include/bindings/st7789_i8080.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st7789_i8080, struct St7789I8080Config) diff --git a/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h b/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h new file mode 100644 index 000000000..1a59b9c23 --- /dev/null +++ b/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct St7789I8080Config { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; + int32_t gap_x; + int32_t gap_y; + bool swap_xy; + bool mirror_x; + bool mirror_y; + bool invert_color; + bool bgr_order; + uint32_t pixel_clock_hz; + uint8_t transaction_queue_depth; + struct GpioPinSpec pin_reset; + bool reset_active_high; + // Optional reference to this display's backlight device, NULL if none. + struct Device* backlight; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/st7789-i8080-module/include/st7789_i8080_module.h b/Drivers/st7789-i8080-module/include/st7789_i8080_module.h new file mode 100644 index 000000000..ce61024be --- /dev/null +++ b/Drivers/st7789-i8080-module/include/st7789_i8080_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module st7789_i8080_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/st7789-i8080-module/source/module.cpp b/Drivers/st7789-i8080-module/source/module.cpp new file mode 100644 index 000000000..0a1567d8e --- /dev/null +++ b/Drivers/st7789-i8080-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver st7789_i8080_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&st7789_i8080_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&st7789_i8080_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module st7789_i8080_module = { + .name = "st7789_i8080", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/st7789-i8080-module/source/st7789_i8080.cpp b/Drivers/st7789-i8080-module/source/st7789_i8080.cpp new file mode 100644 index 000000000..6e93bcc39 --- /dev/null +++ b/Drivers/st7789-i8080-module/source/st7789_i8080.cpp @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#define TAG "ST7789I8080" +#define GET_CONFIG(device) (static_cast((device)->config)) + +namespace { + +// Panel bring-up commands beyond esp_lcd_panel_init()'s generic ST7789 sequence: gate/porch/power +// control and gamma curves. Kept from the deprecated HAL's St7789i8080Display driver verbatim - +// likely panel-specific tuning for the exact glass used on LilyGO T-HMI boards, not guaranteed +// redundant with esp_lcd's built-in defaults, so preserved rather than dropped. +struct LcdInitCmd { + uint8_t cmd; + uint8_t data[14]; + uint8_t len; +}; + +constexpr LcdInitCmd ST7789_INIT_CMDS[] = { + {0x36, {0x08}, 1}, + {0x3A, {0x05}, 1}, + {0x20, {0}, 0}, + {0xB2, {0x0B, 0x0B, 0x00, 0x33, 0x33}, 5}, + {0xB7, {0x75}, 1}, + {0xBB, {0x28}, 1}, + {0xC0, {0x2C}, 1}, + {0xC2, {0x01}, 1}, + {0xC3, {0x1F}, 1}, + {0xC6, {0x13}, 1}, + {0xD0, {0xA7}, 1}, + {0xD0, {0xA4, 0xA1}, 2}, + {0xD6, {0xA1}, 1}, + {0xE0, {0xF0, 0x05, 0x0A, 0x06, 0x06, 0x03, 0x2B, 0x32, 0x43, 0x36, 0x11, 0x10, 0x2B, 0x32}, 14}, + {0xE1, {0xF0, 0x08, 0x0C, 0x0B, 0x09, 0x24, 0x2B, 0x22, 0x43, 0x38, 0x15, 0x16, 0x2F, 0x37}, 14}, +}; + +} // namespace + +struct St7789I8080Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; + // See Ili9341Internal's identical field in ili9341-module for why this exists: draw_bitmap() + // must block until the transfer physically completes (DisplayApi's synchronous contract). + SemaphoreHandle_t draw_done_semaphore; +}; + +static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) { + auto* internal = static_cast(user_ctx); + BaseType_t high_task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken); + return high_task_woken == pdTRUE; +} + +static int pin_or_unused(const struct GpioPinSpec& pin) { + return pin.gpio_controller == nullptr ? -1 : static_cast(pin.pin); +} + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I8080_CONTROLLER_TYPE); + + esp_lcd_i80_bus_handle_t bus_handle; + if (esp32_i8080_get_bus_handle(device, &bus_handle) != ERROR_NONE) { + LOG_E(TAG, "Failed to resolve i80 bus handle"); + return ERROR_RESOURCE; + } + + struct GpioPinSpec cs_pin; + if (esp32_i8080_get_cs_pin(device, &cs_pin) != ERROR_NONE) { + LOG_E(TAG, "Failed to resolve CS pin"); + return ERROR_RESOURCE; + } + + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(St7789I8080Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->draw_done_semaphore = xSemaphoreCreateBinary(); + if (internal->draw_done_semaphore == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = pin_or_unused(cs_pin), + .pclk_hz = config->pixel_clock_hz, + .trans_queue_depth = config->transaction_queue_depth, + .on_color_trans_done = on_color_trans_done, + .user_ctx = internal, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_levels = { + .dc_idle_level = 0, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + .flags = { + .cs_active_high = 0, + .reverse_color_bits = 0, + .swap_color_bytes = 1, + .pclk_active_neg = 0, + .pclk_idle_low = 0, + }, + }; + + esp_err_t ret = esp_lcd_new_panel_io_i80(bus_handle, &io_config, &internal->io_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret)); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = pin_or_unused(config->pin_reset), + .rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = 16, + .flags = { .reset_active_high = config->reset_active_high }, + .vendor_config = nullptr, + }; + + ret = esp_lcd_new_panel_st7789(internal->io_handle, &panel_config, &internal->panel_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret)); + esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + // Bring-up sequence, order matches the deprecated HAL's St7789i8080Display (proven correct on + // real T-HMI hardware). Every failure path below must clean up fully, same reasoning as + // ili9341-module's start(). + bool ok = + esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && + esp_lcd_panel_init(internal->panel_handle) == ESP_OK; + + if (ok) { + for (const auto& init_cmd : ST7789_INIT_CMDS) { + if (esp_lcd_panel_io_tx_param(internal->io_handle, init_cmd.cmd, init_cmd.data, init_cmd.len) != ESP_OK) { + ok = false; + break; + } + } + } + + if (ok) { + int gap_x = config->swap_xy ? config->gap_y : config->gap_x; + int gap_y = config->swap_xy ? config->gap_x : config->gap_y; + ok = (gap_x == 0 && gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, gap_x, gap_y) == ESP_OK; + } + ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK); + ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); + ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; + + if (!ok) { + LOG_E(TAG, "Failed to bring up panel"); + esp_lcd_panel_del(internal->panel_handle); + esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + error_t result = ERROR_NONE; + + if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel"); + result = ERROR_RESOURCE; + } + if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel IO"); + result = ERROR_RESOURCE; + } + + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return result; +} + +// endregion + +// region DisplayApi + +static error_t st7789_i8080_reset(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_init(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + + xSemaphoreTake(internal->draw_done_semaphore, 0); + + if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { + return ERROR_RESOURCE; + } + + xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); + return ERROR_NONE; +} + +static error_t st7789_i8080_mirror(Device* device, bool x_axis, bool y_axis) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_swap_xy(Device* device, bool swap_axes) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static bool st7789_i8080_get_swap_xy(Device* device) { + return GET_CONFIG(device)->swap_xy; +} + +static bool st7789_i8080_get_mirror_x(Device* device) { + return GET_CONFIG(device)->mirror_x; +} + +static bool st7789_i8080_get_mirror_y(Device* device) { + return GET_CONFIG(device)->mirror_y; +} + +static error_t st7789_i8080_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_invert_color(Device* device, bool invert_color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_disp_on_off(Device* device, bool on_off) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_i8080_disp_sleep(Device* device, bool sleep) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +// swap_color_bytes=1 in the panel IO config means the i80 peripheral itself byte-swaps each +// RGB565 pixel over the parallel bus, matching how ili9341-module's SPI variant needs +// RGB565_SWAPPED - same reasoning, different transport. +static enum DisplayColorFormat st7789_i8080_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED; +} + +static uint16_t st7789_i8080_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t st7789_i8080_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void st7789_i8080_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t st7789_i8080_get_frame_buffer_count(Device*) { + return 0; +} + +static error_t st7789_i8080_get_backlight(Device* device, Device** backlight) { + auto* configured_backlight = GET_CONFIG(device)->backlight; + if (configured_backlight == nullptr) { + return ERROR_NOT_SUPPORTED; + } + *backlight = configured_backlight; + return ERROR_NONE; +} + +// endregion + +static const DisplayApi st7789_i8080_display_api = { + .reset = st7789_i8080_reset, + .init = st7789_i8080_init, + .draw_bitmap = st7789_i8080_draw_bitmap, + .mirror = st7789_i8080_mirror, + .swap_xy = st7789_i8080_swap_xy, + .get_swap_xy = st7789_i8080_get_swap_xy, + .get_mirror_x = st7789_i8080_get_mirror_x, + .get_mirror_y = st7789_i8080_get_mirror_y, + .set_gap = st7789_i8080_set_gap, + .invert_color = st7789_i8080_invert_color, + .disp_on_off = st7789_i8080_disp_on_off, + .disp_sleep = st7789_i8080_disp_sleep, + .get_color_format = st7789_i8080_get_color_format, + .get_resolution_x = st7789_i8080_get_resolution_x, + .get_resolution_y = st7789_i8080_get_resolution_y, + .get_frame_buffer = st7789_i8080_get_frame_buffer, + .get_frame_buffer_count = st7789_i8080_get_frame_buffer_count, + .get_backlight = st7789_i8080_get_backlight, +}; + +Driver st7789_i8080_driver = { + .name = "st7789_i8080", + .compatible = (const char*[]) { "sitronix,st7789-i8080", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &st7789_i8080_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &st7789_i8080_module, + .internal = nullptr +}; diff --git a/Drivers/xpt2046-softspi-module/CMakeLists.txt b/Drivers/xpt2046-softspi-module/CMakeLists.txt new file mode 100644 index 000000000..17e748154 --- /dev/null +++ b/Drivers/xpt2046-softspi-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(xpt2046-softspi-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 driver +) diff --git a/Drivers/xpt2046-softspi-module/LICENSE-Apache-2.0.md b/Drivers/xpt2046-softspi-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/xpt2046-softspi-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/xpt2046-softspi-module/README.md b/Drivers/xpt2046-softspi-module/README.md new file mode 100644 index 000000000..d869dd49f --- /dev/null +++ b/Drivers/xpt2046-softspi-module/README.md @@ -0,0 +1,9 @@ +# XPT2046 Software SPI Touch Driver + +A kernel driver for the `XPT2046` resistive touch controller, bit-banged over plain GPIO pins instead of a hardware SPI peripheral. Use this instead of `xpt2046-module` when no SPI host is free for touch (e.g. both `SPI2_HOST` and `SPI3_HOST` are already claimed by the display and an SD card). + +No reset or interrupt pin support (matches the controller's typical wiring: polled, no dedicated IRQ line wired on most panels using it). + +See https://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml b/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml new file mode 100644 index 000000000..3548cd48c --- /dev/null +++ b/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml @@ -0,0 +1,44 @@ +description: > + XPT2046 resistive touch controller driven over bit-banged (software) SPI on plain GPIO + pins, for boards where no hardware SPI peripheral is available for touch (e.g. both + SPI2_HOST and SPI3_HOST are already claimed by the display and SD card). + +compatible: "xptek,xpt2046-softspi" + +properties: + pin-mosi: + type: phandles + required: true + description: MOSI (controller-out, peripheral-in) GPIO pin + pin-miso: + type: phandles + required: true + description: MISO (controller-in, peripheral-out) GPIO pin + pin-sck: + type: phandles + required: true + description: Clock GPIO pin + pin-cs: + type: phandles + required: true + description: Chip-select GPIO pin + x-max: + type: int + required: true + description: Maximum X coordinate reported by the controller (typically the panel's horizontal resolution) + y-max: + type: int + required: true + description: Maximum Y coordinate reported by the controller (typically the panel's vertical resolution) + swap-xy: + type: boolean + default: false + description: Swap the X and Y axes + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis diff --git a/Drivers/xpt2046-softspi-module/devicetree.yaml b/Drivers/xpt2046-softspi-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/xpt2046-softspi-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/xpt2046-softspi-module/include/bindings/xpt2046_softspi.h b/Drivers/xpt2046-softspi-module/include/bindings/xpt2046_softspi.h new file mode 100644 index 000000000..e7c67758b --- /dev/null +++ b/Drivers/xpt2046-softspi-module/include/bindings/xpt2046_softspi.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(xpt2046_softspi, struct Xpt2046SoftSpiConfig) diff --git a/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h b/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h new file mode 100644 index 000000000..f293edf0c --- /dev/null +++ b/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +struct Xpt2046SoftSpiConfig { + struct GpioPinSpec pin_mosi; + struct GpioPinSpec pin_miso; + struct GpioPinSpec pin_sck; + struct GpioPinSpec pin_cs; + uint16_t x_max; + uint16_t y_max; + bool swap_xy; + bool mirror_x; + bool mirror_y; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/xpt2046-softspi-module/include/xpt2046_softspi_module.h b/Drivers/xpt2046-softspi-module/include/xpt2046_softspi_module.h new file mode 100644 index 000000000..7ab9a0775 --- /dev/null +++ b/Drivers/xpt2046-softspi-module/include/xpt2046_softspi_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module xpt2046_softspi_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/xpt2046-softspi-module/source/module.cpp b/Drivers/xpt2046-softspi-module/source/module.cpp new file mode 100644 index 000000000..e79883056 --- /dev/null +++ b/Drivers/xpt2046-softspi-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver xpt2046_softspi_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&xpt2046_softspi_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&xpt2046_softspi_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module xpt2046_softspi_module = { + .name = "xpt2046_softspi", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp b/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp new file mode 100644 index 000000000..1418173e1 --- /dev/null +++ b/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "XPT2046SoftSPI" +#define GET_CONFIG(device) (static_cast((device)->config)) + +namespace { + +constexpr uint8_t CMD_READ_X = 0xD0; +constexpr uint8_t CMD_READ_Y = 0x90; + +constexpr int RAW_MIN_DEFAULT = 100; +constexpr int RAW_MAX_DEFAULT = 1900; +constexpr int RAW_VALID_MIN = 100; +constexpr int RAW_VALID_MAX = 3900; +constexpr int SAMPLE_COUNT = 8; + +} // namespace + +struct Xpt2046SoftSpiInternal { + GpioDescriptor* mosi; + GpioDescriptor* miso; + GpioDescriptor* sck; + GpioDescriptor* cs; + bool swap_xy; + bool mirror_x; + bool mirror_y; + bool touched; + uint16_t x; + uint16_t y; +}; + +// region Driver lifecycle + +static void release_descriptors(Xpt2046SoftSpiInternal* internal) { + if (internal->mosi != nullptr) gpio_descriptor_release(internal->mosi); + if (internal->miso != nullptr) gpio_descriptor_release(internal->miso); + if (internal->sck != nullptr) gpio_descriptor_release(internal->sck); + if (internal->cs != nullptr) gpio_descriptor_release(internal->cs); +} + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(Xpt2046SoftSpiInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + *internal = {}; + + internal->mosi = gpio_descriptor_acquire(config->pin_mosi.gpio_controller, config->pin_mosi.pin, GPIO_OWNER_GPIO); + internal->miso = gpio_descriptor_acquire(config->pin_miso.gpio_controller, config->pin_miso.pin, GPIO_OWNER_GPIO); + internal->sck = gpio_descriptor_acquire(config->pin_sck.gpio_controller, config->pin_sck.pin, GPIO_OWNER_GPIO); + internal->cs = gpio_descriptor_acquire(config->pin_cs.gpio_controller, config->pin_cs.pin, GPIO_OWNER_GPIO); + + if (internal->mosi == nullptr || internal->miso == nullptr || internal->sck == nullptr || internal->cs == nullptr) { + LOG_E(TAG, "Failed to acquire GPIO descriptors"); + release_descriptors(internal); + free(internal); + return ERROR_RESOURCE; + } + + bool ok = + gpio_descriptor_set_flags(internal->mosi, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE && + gpio_descriptor_set_flags(internal->sck, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE && + gpio_descriptor_set_flags(internal->cs, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE && + gpio_descriptor_set_flags(internal->miso, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP) == ERROR_NONE; + + // Idle state: CS high (deselected), SCK/MOSI low. + ok = ok && + gpio_descriptor_set_level(internal->cs, true) == ERROR_NONE && + gpio_descriptor_set_level(internal->sck, false) == ERROR_NONE && + gpio_descriptor_set_level(internal->mosi, false) == ERROR_NONE; + + if (!ok) { + LOG_E(TAG, "Failed to configure GPIO pins"); + release_descriptors(internal); + free(internal); + return ERROR_RESOURCE; + } + + internal->swap_xy = config->swap_xy; + internal->mirror_x = config->mirror_x; + internal->mirror_y = config->mirror_y; + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + release_descriptors(internal); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region Bit-banged SPI + +// XPT2046 protocol: 8-bit command out, 12-bit conversion result back (MSB first), clocked +// manually since no hardware SPI peripheral is available for this pin set (see the binding's +// description). 1us clock half-period is well within the XPT2046's timing budget (it's rated +// for a multi-MHz SPI clock; this bit-bang loop runs orders of magnitude slower). +static int read_spi_command(Xpt2046SoftSpiInternal* internal, uint8_t command) { + int result = 0; + + gpio_descriptor_set_level(internal->cs, false); + delay_micros(1); + + for (int i = 7; i >= 0; i--) { + gpio_descriptor_set_level(internal->mosi, (command & (1 << i)) != 0); + gpio_descriptor_set_level(internal->sck, true); + delay_micros(1); + gpio_descriptor_set_level(internal->sck, false); + delay_micros(1); + } + + for (int i = 11; i >= 0; i--) { + gpio_descriptor_set_level(internal->sck, true); + delay_micros(1); + bool level = false; + gpio_descriptor_get_level(internal->miso, &level); + if (level) { + result |= (1 << i); + } + gpio_descriptor_set_level(internal->sck, false); + delay_micros(1); + } + + gpio_descriptor_set_level(internal->cs, true); + + return result; +} + +// endregion + +// region PointerApi + +static error_t xpt2046_softspi_enter_sleep(Device*) { + // No software-controlled power-down for this bit-banged variant; nothing to do. + return ERROR_NONE; +} + +static error_t xpt2046_softspi_exit_sleep(Device*) { + return ERROR_NONE; +} + +// Takes and averages several raw samples (rejecting out-of-range noise) then maps them onto +// [0, x_max]/[0, y_max], applying swap/mirror. Mirrors the deprecated HAL's Xpt2046SoftSpi driver. +static error_t xpt2046_softspi_read_data(Device* device, TickType_t timeout) { + (void)timeout; + auto* internal = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + + int total_x = 0; + int total_y = 0; + int valid_samples = 0; + + for (int i = 0; i < SAMPLE_COUNT; i++) { + const int raw_x = read_spi_command(internal, CMD_READ_X); + const int raw_y = read_spi_command(internal, CMD_READ_Y); + + if (raw_x > RAW_VALID_MIN && raw_x < RAW_VALID_MAX && raw_y > RAW_VALID_MIN && raw_y < RAW_VALID_MAX) { + total_x += raw_x; + total_y += raw_y; + valid_samples++; + } + + delay_millis(1); + } + + if (valid_samples < 3) { + internal->touched = false; + return ERROR_NONE; + } + + const int raw_x = total_x / valid_samples; + const int raw_y = total_y / valid_samples; + + int mapped_x = (raw_x - RAW_MIN_DEFAULT) * static_cast(config->x_max) / (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT); + int mapped_y = (raw_y - RAW_MIN_DEFAULT) * static_cast(config->y_max) / (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT); + + if (internal->swap_xy) { + const int swapped = mapped_x; + mapped_x = mapped_y; + mapped_y = swapped; + } + if (internal->mirror_x) { + mapped_x = static_cast(config->x_max) - mapped_x; + } + if (internal->mirror_y) { + mapped_y = static_cast(config->y_max) - mapped_y; + } + + if (mapped_x < 0) mapped_x = 0; + if (mapped_x > static_cast(config->x_max)) mapped_x = static_cast(config->x_max); + if (mapped_y < 0) mapped_y = 0; + if (mapped_y > static_cast(config->y_max)) mapped_y = static_cast(config->y_max); + + internal->x = static_cast(mapped_x); + internal->y = static_cast(mapped_y); + internal->touched = true; + return ERROR_NONE; +} + +static bool xpt2046_softspi_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { + (void)strength; + auto* internal = static_cast(device_get_driver_data(device)); + + if (!internal->touched || max_point_count < 1) { + *point_count = 0; + return false; + } + + *x = internal->x; + *y = internal->y; + *point_count = 1; + return true; +} + +static error_t xpt2046_softspi_set_swap_xy(Device* device, bool swap) { + auto* internal = static_cast(device_get_driver_data(device)); + internal->swap_xy = swap; + return ERROR_NONE; +} + +static error_t xpt2046_softspi_get_swap_xy(Device* device, bool* swap) { + auto* internal = static_cast(device_get_driver_data(device)); + *swap = internal->swap_xy; + return ERROR_NONE; +} + +static error_t xpt2046_softspi_set_mirror_x(Device* device, bool mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + internal->mirror_x = mirror; + return ERROR_NONE; +} + +static error_t xpt2046_softspi_get_mirror_x(Device* device, bool* mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + *mirror = internal->mirror_x; + return ERROR_NONE; +} + +static error_t xpt2046_softspi_set_mirror_y(Device* device, bool mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + internal->mirror_y = mirror; + return ERROR_NONE; +} + +static error_t xpt2046_softspi_get_mirror_y(Device* device, bool* mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + *mirror = internal->mirror_y; + return ERROR_NONE; +} + +// endregion + +static const PointerApi xpt2046_softspi_pointer_api = { + .enter_sleep = xpt2046_softspi_enter_sleep, + .exit_sleep = xpt2046_softspi_exit_sleep, + .read_data = xpt2046_softspi_read_data, + .get_touched_points = xpt2046_softspi_get_touched_points, + .set_swap_xy = xpt2046_softspi_set_swap_xy, + .get_swap_xy = xpt2046_softspi_get_swap_xy, + .set_mirror_x = xpt2046_softspi_set_mirror_x, + .get_mirror_x = xpt2046_softspi_get_mirror_x, + .set_mirror_y = xpt2046_softspi_set_mirror_y, + .get_mirror_y = xpt2046_softspi_get_mirror_y, +}; + +Driver xpt2046_softspi_driver = { + .name = "xpt2046_softspi", + .compatible = (const char*[]) { "xptek,xpt2046-softspi", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &xpt2046_softspi_pointer_api, + .device_type = &POINTER_TYPE, + .owner = &xpt2046_softspi_module, + .internal = nullptr +}; diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index 6d419cb1c..eab9bf82a 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -6,7 +6,7 @@ idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "include/" PRIV_INCLUDE_DIRS "private/" - REQUIRES TactilityKernel driver esp_adc esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event + REQUIRES TactilityKernel driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event ) idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc) diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-i8080.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-i8080.yaml new file mode 100644 index 000000000..6e028495a --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-i8080.yaml @@ -0,0 +1,64 @@ +description: ESP32 i8080 (8080-series parallel) LCD bus controller + +include: ["i8080-controller.yaml"] + +compatible: "espressif,esp32-i8080" + +properties: + pin-dc: + type: phandles + required: true + description: Data/Command GPIO pin + pin-wr: + type: phandles + required: true + description: Write-strobe GPIO pin + pin-rd: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Read-strobe GPIO pin. Optional - driven high once and left alone if provided, since this bus only ever writes. + pin-d0: + type: phandles + required: true + description: Data bus bit 0 GPIO pin + pin-d1: + type: phandles + required: true + description: Data bus bit 1 GPIO pin + pin-d2: + type: phandles + required: true + description: Data bus bit 2 GPIO pin + pin-d3: + type: phandles + required: true + description: Data bus bit 3 GPIO pin + pin-d4: + type: phandles + required: true + description: Data bus bit 4 GPIO pin + pin-d5: + type: phandles + required: true + description: Data bus bit 5 GPIO pin + pin-d6: + type: phandles + required: true + description: Data bus bit 6 GPIO pin + pin-d7: + type: phandles + required: true + description: Data bus bit 7 GPIO pin + max-transfer-bytes: + type: int + required: true + description: | + Maximum size in bytes of a single transfer over this bus. Sized for the attached + panel's buffer (e.g. horizontal-resolution * vertical-resolution / N * bytes-per-pixel + for an N-th sized partial buffer) - there is no bus-level default since it's entirely + dependent on what's attached. + cs-gpios: + type: phandle-array + element-type: "struct GpioPinSpec" + default: "{ }" + description: Null-terminated array of chip select GPIO pin specs for peripherals on this bus diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_i8080.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_i8080.h new file mode 100644 index 000000000..6dd14c321 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_i8080.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_i8080, struct Esp32I8080Config) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h new file mode 100644 index 000000000..b2bdf62e0 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32I8080Config { + struct GpioPinSpec pin_dc; + struct GpioPinSpec pin_wr; + struct GpioPinSpec pin_rd; + struct GpioPinSpec pin_d0; + struct GpioPinSpec pin_d1; + struct GpioPinSpec pin_d2; + struct GpioPinSpec pin_d3; + struct GpioPinSpec pin_d4; + struct GpioPinSpec pin_d5; + struct GpioPinSpec pin_d6; + struct GpioPinSpec pin_d7; + int max_transfer_bytes; + /** Array of chip select GPIO pin specs */ + struct GpioPinSpec* cs_gpios; + /** The item count of cs_gpios */ + uint8_t cs_gpios_count; +}; + +/** + * @brief Get the CS pin spec for a child device on this i8080 bus. + * Uses the child device's address as index into the parent's cs_gpios array. + * @param[in] child_device a child device of an i8080 controller + * @param[out] out_pin the GPIO pin spec for the CS pin + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_STATE if the parent is not an i8080 controller + * @retval ERROR_OUT_OF_RANGE if the device address exceeds the cs_gpios array + */ +error_t esp32_i8080_get_cs_pin(struct Device* child_device, struct GpioPinSpec* out_pin); + +/** + * @brief Get the i80 bus handle for a child device's i8080 controller parent. + * Only valid after the controller device has started. + * @param[in] child_device a child device of an i8080 controller + * @param[out] out_handle the bus handle + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_STATE if the parent is not an i8080 controller, or hasn't started + */ +error_t esp32_i8080_get_bus_handle(struct Device* child_device, esp_lcd_i80_bus_handle_t* out_handle); + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp b/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp new file mode 100644 index 000000000..121e35051 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +#include "tactility/drivers/gpio_descriptor.h" +#include +#include + +#include + +#include +#include + +#define TAG "esp32_i8080" + +#define GET_CONFIG(device) ((const struct Esp32I8080Config*)device->config) +#define GET_DATA(device) ((struct Esp32I8080Internal*)device_get_driver_data(device)) + +extern "C" { + +struct Esp32I8080Internal { + esp_lcd_i80_bus_handle_t bus_handle = nullptr; + + GpioDescriptor* dc_descriptor = nullptr; + GpioDescriptor* wr_descriptor = nullptr; + GpioDescriptor* rd_descriptor = nullptr; + GpioDescriptor* d0_descriptor = nullptr; + GpioDescriptor* d1_descriptor = nullptr; + GpioDescriptor* d2_descriptor = nullptr; + GpioDescriptor* d3_descriptor = nullptr; + GpioDescriptor* d4_descriptor = nullptr; + GpioDescriptor* d5_descriptor = nullptr; + GpioDescriptor* d6_descriptor = nullptr; + GpioDescriptor* d7_descriptor = nullptr; + + std::vector cs_descriptors; + + void cleanup_pins() { + release_pin(&dc_descriptor); + release_pin(&wr_descriptor); + release_pin(&rd_descriptor); + release_pin(&d0_descriptor); + release_pin(&d1_descriptor); + release_pin(&d2_descriptor); + release_pin(&d3_descriptor); + release_pin(&d4_descriptor); + release_pin(&d5_descriptor); + release_pin(&d6_descriptor); + release_pin(&d7_descriptor); + for (auto*& desc : cs_descriptors) { + release_pin(&desc); + } + cs_descriptors.clear(); + } +}; + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* data = new (std::nothrow) Esp32I8080Internal(); + if (data == nullptr) return ERROR_OUT_OF_MEMORY; + + device_set_driver_data(device, data); + + const auto* config = GET_CONFIG(device); + + bool pins_ok = + acquire_pin_or_set_null(config->pin_dc, &data->dc_descriptor) && + acquire_pin_or_set_null(config->pin_wr, &data->wr_descriptor) && + acquire_pin_or_set_null(config->pin_rd, &data->rd_descriptor) && + acquire_pin_or_set_null(config->pin_d0, &data->d0_descriptor) && + acquire_pin_or_set_null(config->pin_d1, &data->d1_descriptor) && + acquire_pin_or_set_null(config->pin_d2, &data->d2_descriptor) && + acquire_pin_or_set_null(config->pin_d3, &data->d3_descriptor) && + acquire_pin_or_set_null(config->pin_d4, &data->d4_descriptor) && + acquire_pin_or_set_null(config->pin_d5, &data->d5_descriptor) && + acquire_pin_or_set_null(config->pin_d6, &data->d6_descriptor) && + acquire_pin_or_set_null(config->pin_d7, &data->d7_descriptor); + + if (!pins_ok) { + LOG_E(TAG, "Failed to acquire required i8080 pins"); + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + delete data; + return ERROR_RESOURCE; + } + + // RD is never toggled by the i80 LCD peripheral (write-only bus); drive it high once so the + // panel doesn't see spurious read strobes. + if (data->rd_descriptor != nullptr) { + gpio_descriptor_set_flags(data->rd_descriptor, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_level(data->rd_descriptor, true); + } + + esp_lcd_i80_bus_config_t bus_cfg = { + .dc_gpio_num = get_native_pin(data->dc_descriptor), + .wr_gpio_num = get_native_pin(data->wr_descriptor), + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + get_native_pin(data->d0_descriptor), + get_native_pin(data->d1_descriptor), + get_native_pin(data->d2_descriptor), + get_native_pin(data->d3_descriptor), + get_native_pin(data->d4_descriptor), + get_native_pin(data->d5_descriptor), + get_native_pin(data->d6_descriptor), + get_native_pin(data->d7_descriptor), + }, + .bus_width = 8, + .max_transfer_bytes = static_cast(config->max_transfer_bytes), + .psram_trans_align = 64, + .sram_trans_align = 4 + }; + + esp_err_t ret = esp_lcd_new_i80_bus(&bus_cfg, &data->bus_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create I80 bus: %s", esp_err_to_name(ret)); + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + delete data; + return ERROR_RESOURCE; + } + + // Acquire and deselect all CS pins (drive high) + for (uint8_t i = 0; i < config->cs_gpios_count; i++) { + const GpioPinSpec* cs = &config->cs_gpios[i]; + if (cs->gpio_controller == nullptr) continue; + GpioDescriptor* desc = gpio_descriptor_acquire(cs->gpio_controller, cs->pin, GPIO_OWNER_GPIO); + if (desc != nullptr) { + gpio_descriptor_set_flags(desc, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_level(desc, true); + data->cs_descriptors.push_back(desc); + } + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + LOG_I(TAG, "stop %s", device->name); + auto* data = GET_DATA(device); + + if (data->bus_handle != nullptr) { + esp_lcd_del_i80_bus(data->bus_handle); + } + + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + delete data; + return ERROR_NONE; +} + +error_t esp32_i8080_get_cs_pin(Device* child_device, GpioPinSpec* out_pin) { + auto* parent = device_get_parent(child_device); + if (parent == nullptr || device_get_type(parent) != &I8080_CONTROLLER_TYPE) return ERROR_INVALID_STATE; + const auto* config = GET_CONFIG(parent); + int32_t index = child_device->address; + if (index < 0 || index >= config->cs_gpios_count) return ERROR_OUT_OF_RANGE; + *out_pin = config->cs_gpios[index]; + return ERROR_NONE; +} + +error_t esp32_i8080_get_bus_handle(Device* child_device, esp_lcd_i80_bus_handle_t* out_handle) { + auto* parent = device_get_parent(child_device); + if (parent == nullptr || device_get_type(parent) != &I8080_CONTROLLER_TYPE) return ERROR_INVALID_STATE; + auto* data = GET_DATA(parent); + if (data == nullptr || data->bus_handle == nullptr) return ERROR_INVALID_STATE; + *out_handle = data->bus_handle; + return ERROR_NONE; +} + +extern struct Module platform_esp32_module; + +Driver esp32_i8080_driver = { + .name = "esp32_i8080", + .compatible = (const char*[]) { "espressif,esp32-i8080", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = &I8080_CONTROLLER_TYPE, + .owner = &platform_esp32_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index f4c3545ec..89131f156 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -16,6 +16,9 @@ extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2c_master_driver; extern Driver esp32_i2s_driver; +#if SOC_LCD_I80_SUPPORTED +extern Driver esp32_i8080_driver; +#endif extern Driver esp32_ledc_backlight_driver; #if SOC_SDMMC_HOST_SUPPORTED extern Driver esp32_sdmmc_driver; @@ -49,6 +52,9 @@ static error_t start() { check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_master_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_LCD_I80_SUPPORTED + check(driver_construct_add(&esp32_i8080_driver) == ERROR_NONE); +#endif check(driver_construct_add(&esp32_ledc_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); @@ -100,6 +106,9 @@ static error_t stop() { check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_master_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_LCD_I80_SUPPORTED + check(driver_remove_destruct(&esp32_i8080_driver) == ERROR_NONE); +#endif check(driver_remove_destruct(&esp32_ledc_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp index 4ed1494ae..7efec6a21 100644 --- a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -18,6 +18,10 @@ #include #include +#ifdef ESP_PLATFORM +#include +#endif + namespace tt::app::kerneldisplay { constexpr auto* TAG = "KernelDisplay"; @@ -100,9 +104,11 @@ class KernelDisplayApp final : public App { } } +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) static void onCalibrateTouchClicked(lv_event_t*) { app::touchcalibration::start(); } +#endif static void onScreensaverChanged(lv_event_t* event) { auto* app = static_cast(lv_event_get_user_data(event)); @@ -257,6 +263,7 @@ class KernelDisplayApp final : public App { } } +#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) if (lvgl_pointer_get_default() != nullptr) { auto* calibrate_wrapper = lv_obj_create(main_wrapper); lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); @@ -275,6 +282,7 @@ class KernelDisplayApp final : public App { lv_label_set_text(calibrate_button_label, "Calibrate"); lv_obj_center(calibrate_button_label); } +#endif } void onHide(AppContext& app) override { diff --git a/TactilityKernel/bindings/i8080-controller.yaml b/TactilityKernel/bindings/i8080-controller.yaml new file mode 100644 index 000000000..0fec84ab3 --- /dev/null +++ b/TactilityKernel/bindings/i8080-controller.yaml @@ -0,0 +1 @@ +description: i8080 (8080-series parallel) LCD bus controller diff --git a/TactilityKernel/bindings/tactility,gpio-hog.yaml b/TactilityKernel/bindings/tactility,gpio-hog.yaml new file mode 100644 index 000000000..19e0ec76a --- /dev/null +++ b/TactilityKernel/bindings/tactility,gpio-hog.yaml @@ -0,0 +1,19 @@ +description: > + Claims a GPIO pin and drives it to a fixed level for as long as the module is running, with + no further runtime behavior. Useful for board-level power-enable pins that must be asserted + before other devicetree devices try to use hardware gated by that pin - all devicetree + devices are constructed and started during kernel_init(), which runs before the deprecated + HAL's initBoot() hook. Declare a gpio-hog node before the dependent device(s) in the .dts + source so it runs first. + +compatible: "tactility,gpio-hog" + +properties: + pin: + type: phandles + required: true + description: The GPIO pin to hog + output-high: + type: boolean + default: true + description: Level to drive the pin to (true = high, false = low) diff --git a/TactilityKernel/include/tactility/bindings/gpio_hog.h b/TactilityKernel/include/tactility/bindings/gpio_hog.h new file mode 100644 index 000000000..1ae77ac8c --- /dev/null +++ b/TactilityKernel/include/tactility/bindings/gpio_hog.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(gpio_hog, struct GpioHogConfig) diff --git a/TactilityKernel/include/tactility/drivers/gpio_hog.h b/TactilityKernel/include/tactility/drivers/gpio_hog.h new file mode 100644 index 000000000..8ece049c5 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/gpio_hog.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +struct GpioHogConfig { + struct GpioPinSpec pin; + bool output_high; +}; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/i8080_controller.h b/TactilityKernel/include/tactility/drivers/i8080_controller.h new file mode 100644 index 000000000..2dc2a06af --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/i8080_controller.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Generic device type for i8080 (8080-series parallel) LCD bus controllers. + * + * Unlike SPI_CONTROLLER_TYPE, there is no locking API: an i80 bus only ever has one + * consumer (a single display panel) in every known board, so no arbitration is needed. + */ +extern const struct DeviceType I8080_CONTROLLER_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/drivers/gpio_hog.cpp b/TactilityKernel/source/drivers/gpio_hog.cpp new file mode 100644 index 000000000..37fbbde38 --- /dev/null +++ b/TactilityKernel/source/drivers/gpio_hog.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "GpioHog" +#define GET_CONFIG(device) (static_cast((device)->config)) + +namespace { +const struct DeviceType GPIO_HOG_TYPE { .name = "gpio_hog" }; +} + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, GPIO_OWNER_HOG); + if (descriptor == nullptr) { + LOG_E(TAG, "Failed to acquire GPIO descriptor"); + return ERROR_RESOURCE; + } + + if (gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE || + gpio_descriptor_set_level(descriptor, config->output_high) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure hogged pin"); + gpio_descriptor_release(descriptor); + return ERROR_RESOURCE; + } + + device_set_driver_data(device, descriptor); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* descriptor = static_cast(device_get_driver_data(device)); + gpio_descriptor_release(descriptor); + return ERROR_NONE; +} + +extern Module root_module; + +Driver gpio_hog_driver = { + .name = "gpio_hog", + .compatible = (const char*[]) { "tactility,gpio-hog", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = &GPIO_HOG_TYPE, + .owner = &root_module, + .internal = nullptr +}; diff --git a/TactilityKernel/source/drivers/i8080_controller.cpp b/TactilityKernel/source/drivers/i8080_controller.cpp new file mode 100644 index 000000000..a0bcba47e --- /dev/null +++ b/TactilityKernel/source/drivers/i8080_controller.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +const DeviceType I8080_CONTROLLER_TYPE { + .name = "i8080-controller" +}; + +} diff --git a/TactilityKernel/source/kernel_init.cpp b/TactilityKernel/source/kernel_init.cpp index f6fc5148e..d02cad9bf 100644 --- a/TactilityKernel/source/kernel_init.cpp +++ b/TactilityKernel/source/kernel_init.cpp @@ -24,6 +24,8 @@ static error_t start() { if (driver_construct_add(&battery_sense_driver) != ERROR_NONE) return ERROR_RESOURCE; extern Driver battery_sense_power_supply_driver; if (driver_construct_add(&battery_sense_power_supply_driver) != ERROR_NONE) return ERROR_RESOURCE; + extern Driver gpio_hog_driver; + if (driver_construct_add(&gpio_hog_driver) != ERROR_NONE) return ERROR_RESOURCE; return ERROR_NONE; } From d2889b2d3d0f27668c586faf0d9b5a2b1c10f4a7 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 13 Jul 2026 23:42:37 +0200 Subject: [PATCH 03/15] Support for gamma curves --- Drivers/ili9341-module/bindings/ilitek,ili9341.yaml | 4 ++++ Drivers/ili9341-module/include/drivers/ili9341.h | 2 ++ Drivers/ili9341-module/source/ili9341.cpp | 7 +++++++ .../bindings/sitronix,st7789-i8080.yaml | 4 ++++ Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h | 2 ++ Drivers/st7789-i8080-module/source/st7789_i8080.cpp | 7 +++++++ Drivers/st7789-module/bindings/sitronix,st7789.yaml | 4 ++++ Drivers/st7789-module/include/drivers/st7789.h | 2 ++ Drivers/st7789-module/source/st7789.cpp | 7 +++++++ 9 files changed, 39 insertions(+) diff --git a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml index 1588890d6..0d805b82b 100644 --- a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml +++ b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml @@ -53,6 +53,10 @@ properties: type: int default: 10 description: Size of the internal SPI transaction queue + gamma-curve: + type: int + default: 1 + description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-dc: type: phandles required: true diff --git a/Drivers/ili9341-module/include/drivers/ili9341.h b/Drivers/ili9341-module/include/drivers/ili9341.h index 8c1b64a39..d7b105b5e 100644 --- a/Drivers/ili9341-module/include/drivers/ili9341.h +++ b/Drivers/ili9341-module/include/drivers/ili9341.h @@ -24,6 +24,8 @@ struct Ili9341Config { uint32_t bits_per_pixel; uint32_t pixel_clock_hz; uint8_t transaction_queue_depth; + // Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up. + uint8_t gamma_curve; struct GpioPinSpec pin_dc; struct GpioPinSpec pin_reset; bool reset_active_high; diff --git a/Drivers/ili9341-module/source/ili9341.cpp b/Drivers/ili9341-module/source/ili9341.cpp index 84d2cbeac..8971480dd 100644 --- a/Drivers/ili9341-module/source/ili9341.cpp +++ b/Drivers/ili9341-module/source/ili9341.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,11 @@ #define GET_CONFIG(device) (static_cast((device)->config)) +// Maps gamma-curve devicetree index [0,3] to the MIPI DCS GAMSET (0x26) parameter value. Mirrors +// the deprecated HAL's EspLcdSpiDisplay::setGammaCurve() (Drivers/EspLcdCompat) - note the +// non-linear mapping, not index+1. +static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; + struct Ili9341Internal { esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_handle_t panel_handle; @@ -140,6 +146,7 @@ static error_t start(Device* device) { ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK); ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; if (!ok) { diff --git a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml index dbf57f2c5..fcc0e4585 100644 --- a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml +++ b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml @@ -49,6 +49,10 @@ properties: type: int default: 10 description: Size of the internal transaction queue + gamma-curve: + type: int + default: 1 + description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-reset: type: phandles default: GPIO_PIN_SPEC_NONE diff --git a/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h b/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h index 1a59b9c23..c71b281ff 100644 --- a/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h +++ b/Drivers/st7789-i8080-module/include/drivers/st7789_i8080.h @@ -23,6 +23,8 @@ struct St7789I8080Config { bool bgr_order; uint32_t pixel_clock_hz; uint8_t transaction_queue_depth; + // Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up. + uint8_t gamma_curve; struct GpioPinSpec pin_reset; bool reset_active_high; // Optional reference to this display's backlight device, NULL if none. diff --git a/Drivers/st7789-i8080-module/source/st7789_i8080.cpp b/Drivers/st7789-i8080-module/source/st7789_i8080.cpp index 6e93bcc39..6d0d12ef4 100644 --- a/Drivers/st7789-i8080-module/source/st7789_i8080.cpp +++ b/Drivers/st7789-i8080-module/source/st7789_i8080.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,11 @@ #define TAG "ST7789I8080" #define GET_CONFIG(device) (static_cast((device)->config)) +// Maps gamma-curve devicetree index [0,3] to the MIPI DCS GAMSET (0x26) parameter value. Mirrors +// the deprecated HAL's EspLcdSpiDisplay::setGammaCurve() (Drivers/EspLcdCompat) - note the +// non-linear mapping, not index+1. +static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; + namespace { // Panel bring-up commands beyond esp_lcd_panel_init()'s generic ST7789 sequence: gate/porch/power @@ -179,6 +185,7 @@ static error_t start(Device* device) { ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK); ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; if (!ok) { diff --git a/Drivers/st7789-module/bindings/sitronix,st7789.yaml b/Drivers/st7789-module/bindings/sitronix,st7789.yaml index ce89db639..a3f0d43ab 100644 --- a/Drivers/st7789-module/bindings/sitronix,st7789.yaml +++ b/Drivers/st7789-module/bindings/sitronix,st7789.yaml @@ -53,6 +53,10 @@ properties: type: int default: 10 description: Size of the internal SPI transaction queue + gamma-curve: + type: int + default: 1 + description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-dc: type: phandles required: true diff --git a/Drivers/st7789-module/include/drivers/st7789.h b/Drivers/st7789-module/include/drivers/st7789.h index 87f53edf5..6358acacc 100644 --- a/Drivers/st7789-module/include/drivers/st7789.h +++ b/Drivers/st7789-module/include/drivers/st7789.h @@ -24,6 +24,8 @@ struct St7789Config { uint32_t bits_per_pixel; uint32_t pixel_clock_hz; uint8_t transaction_queue_depth; + // Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up. + uint8_t gamma_curve; struct GpioPinSpec pin_dc; struct GpioPinSpec pin_reset; bool reset_active_high; diff --git a/Drivers/st7789-module/source/st7789.cpp b/Drivers/st7789-module/source/st7789.cpp index 20a5bd8d4..c76742a7a 100644 --- a/Drivers/st7789-module/source/st7789.cpp +++ b/Drivers/st7789-module/source/st7789.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,11 @@ #define GET_CONFIG(device) (static_cast((device)->config)) +// Maps gamma-curve devicetree index [0,3] to the MIPI DCS GAMSET (0x26) parameter value. Mirrors +// the deprecated HAL's EspLcdSpiDisplay::setGammaCurve() (Drivers/EspLcdCompat) - note the +// non-linear mapping, not index+1. +static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; + struct St7789Internal { esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_handle_t panel_handle; @@ -140,6 +146,7 @@ static error_t start(Device* device) { ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK); ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; if (!ok) { From 49af1d42f87df50614ba98be6f3ee56ca4535528 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 13 Jul 2026 23:58:14 +0200 Subject: [PATCH 04/15] Fixes and improvements --- .../platform-esp32/include/tactility/drivers/esp32_i8080.h | 5 +++++ Platforms/platform-esp32/source/drivers/esp32_i8080.cpp | 7 +++++++ .../Tactility/app/touchcalibration/TouchCalibration.h | 2 ++ TactilityKernel/source/drivers/gpio_hog.cpp | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h index b2bdf62e0..997b6be9a 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_i8080.h @@ -1,6 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include +#if SOC_LCD_I80_SUPPORTED + #include #include @@ -53,3 +56,5 @@ error_t esp32_i8080_get_bus_handle(struct Device* child_device, esp_lcd_i80_bus_ #ifdef __cplusplus } #endif + +#endif // SOC_LCD_I80_SUPPORTED diff --git a/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp b/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp index 121e35051..9af6f3123 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_i8080.cpp @@ -1,4 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#if SOC_LCD_I80_SUPPORTED + #include #include #include @@ -130,6 +133,8 @@ static error_t start(Device* device) { gpio_descriptor_set_flags(desc, GPIO_FLAG_DIRECTION_OUTPUT); gpio_descriptor_set_level(desc, true); data->cs_descriptors.push_back(desc); + } else { + LOG_E(TAG, "Failed to acquire CS pin %u", i); } } @@ -183,3 +188,5 @@ Driver esp32_i8080_driver = { }; } // extern "C" + +#endif // SOC_LCD_I80_SUPPORTED diff --git a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h index 6a85b6041..5c8dce48f 100644 --- a/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h +++ b/Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h @@ -1,6 +1,8 @@ #pragma once +#ifdef ESP_PLATFORM #include +#endif #if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) diff --git a/TactilityKernel/source/drivers/gpio_hog.cpp b/TactilityKernel/source/drivers/gpio_hog.cpp index 37fbbde38..28b15ca01 100644 --- a/TactilityKernel/source/drivers/gpio_hog.cpp +++ b/TactilityKernel/source/drivers/gpio_hog.cpp @@ -12,7 +12,7 @@ #define GET_CONFIG(device) (static_cast((device)->config)) namespace { -const struct DeviceType GPIO_HOG_TYPE { .name = "gpio_hog" }; +const DeviceType GPIO_HOG_TYPE { .name = "gpio_hog" }; } static error_t start(Device* device) { From 1069032148f87dca8a5fce427271d324441c9dfe Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:02:40 +0200 Subject: [PATCH 05/15] Improve gpio-hog driver --- Devices/lilygo-thmi/lilygo,thmi.dts | 2 ++ .../bindings/tactility,gpio-hog.yaml | 10 ++++---- .../include/tactility/drivers/gpio_hog.h | 10 +++++--- TactilityKernel/source/drivers/gpio_hog.cpp | 23 ++++++++++++++++--- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index 6ef4ac943..f17b24b8a 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -41,11 +41,13 @@ power_on { compatible = "tactility,gpio-hog"; pin = <&gpio0 14 GPIO_FLAG_NONE>; + mode = ; }; power_en { compatible = "tactility,gpio-hog"; pin = <&gpio0 10 GPIO_FLAG_NONE>; + mode = ; }; adc0 { diff --git a/TactilityKernel/bindings/tactility,gpio-hog.yaml b/TactilityKernel/bindings/tactility,gpio-hog.yaml index 19e0ec76a..1cd39f002 100644 --- a/TactilityKernel/bindings/tactility,gpio-hog.yaml +++ b/TactilityKernel/bindings/tactility,gpio-hog.yaml @@ -13,7 +13,9 @@ properties: type: phandles required: true description: The GPIO pin to hog - output-high: - type: boolean - default: true - description: Level to drive the pin to (true = high, false = low) + mode: + type: int + default: 0 + description: | + Pin mode, see enum GpioHogMode in tactility/drivers/gpio_hog.h: + 0 = GPIO_HOG_MODE_OUTPUT_HIGH, 1 = GPIO_HOG_MODE_OUTPUT_LOW, 2 = GPIO_HOG_MODE_INPUT. diff --git a/TactilityKernel/include/tactility/drivers/gpio_hog.h b/TactilityKernel/include/tactility/drivers/gpio_hog.h index 8ece049c5..6bb159aad 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_hog.h +++ b/TactilityKernel/include/tactility/drivers/gpio_hog.h @@ -5,13 +5,17 @@ extern "C" { #endif -#include - #include +enum GpioHogMode { + GPIO_HOG_MODE_OUTPUT_HIGH, + GPIO_HOG_MODE_OUTPUT_LOW, + GPIO_HOG_MODE_INPUT, +}; + struct GpioHogConfig { struct GpioPinSpec pin; - bool output_high; + enum GpioHogMode mode; }; #ifdef __cplusplus diff --git a/TactilityKernel/source/drivers/gpio_hog.cpp b/TactilityKernel/source/drivers/gpio_hog.cpp index 28b15ca01..d3f08cab0 100644 --- a/TactilityKernel/source/drivers/gpio_hog.cpp +++ b/TactilityKernel/source/drivers/gpio_hog.cpp @@ -24,9 +24,26 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } - if (gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE || - gpio_descriptor_set_level(descriptor, config->output_high) != ERROR_NONE) { - LOG_E(TAG, "Failed to configure hogged pin"); + bool ok; + switch (config->mode) { + case GPIO_HOG_MODE_OUTPUT_HIGH: + ok = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE && + gpio_descriptor_set_level(descriptor, true) == ERROR_NONE; + break; + case GPIO_HOG_MODE_OUTPUT_LOW: + ok = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE && + gpio_descriptor_set_level(descriptor, false) == ERROR_NONE; + break; + case GPIO_HOG_MODE_INPUT: + ok = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_INPUT) == ERROR_NONE; + break; + default: + ok = false; + break; + } + + if (!ok) { + LOG_E(TAG, "Failed to configure hogged pin %u", config->pin.pin); gpio_descriptor_release(descriptor); return ERROR_RESOURCE; } From b02663b4bf8bc2f2a9ef451a3809b268172ce610 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:03:50 +0200 Subject: [PATCH 06/15] Remove unused driver --- Drivers/XPT2046SoftSPI/CMakeLists.txt | 5 - Drivers/XPT2046SoftSPI/README.md | 4 - .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp | 237 ------------------ .../XPT2046SoftSPI/Source/Xpt2046SoftSpi.h | 108 -------- 4 files changed, 354 deletions(-) delete mode 100644 Drivers/XPT2046SoftSPI/CMakeLists.txt delete mode 100644 Drivers/XPT2046SoftSPI/README.md delete mode 100644 Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp delete mode 100644 Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h diff --git a/Drivers/XPT2046SoftSPI/CMakeLists.txt b/Drivers/XPT2046SoftSPI/CMakeLists.txt deleted file mode 100644 index 8c472f930..000000000 --- a/Drivers/XPT2046SoftSPI/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -idf_component_register( - SRC_DIRS "Source" - INCLUDE_DIRS "Source" - REQUIRES Tactility driver esp_lvgl_port -) \ No newline at end of file diff --git a/Drivers/XPT2046SoftSPI/README.md b/Drivers/XPT2046SoftSPI/README.md deleted file mode 100644 index ec7ff8278..000000000 --- a/Drivers/XPT2046SoftSPI/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# XPT2046_SoftSPI Driver - -XPT2046_SoftSPI is a driver for the XPT2046 resistive touchscreen controller that uses SoftSPI. -Inspiration from: https://github.com/ddxfish/XPT2046_Bitbang_Arduino_Library/ diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp deleted file mode 100644 index e8546b958..000000000 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp +++ /dev/null @@ -1,237 +0,0 @@ -#include "Xpt2046SoftSpi.h" - -#include - -#include - -#include -#include -#include -#include -#include - -constexpr auto* TAG = "Xpt2046SoftSpi"; - -constexpr auto CMD_READ_Y = 0x90; -constexpr auto CMD_READ_X = 0xD0; - -constexpr int RAW_MIN_DEFAULT = 100; -constexpr int RAW_MAX_DEFAULT = 1900; -constexpr int RAW_VALID_MIN = 100; -constexpr int RAW_VALID_MAX = 3900; - -Xpt2046SoftSpi::Xpt2046SoftSpi(std::unique_ptr inConfiguration) - : configuration(std::move(inConfiguration)) { - assert(configuration != nullptr); -} - -bool Xpt2046SoftSpi::start() { - LOG_I(TAG, "Starting Xpt2046SoftSpi touch driver"); - - // Configure GPIO pins - gpio_config_t io_conf = {}; - - // Configure MOSI, CLK, CS as outputs - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << configuration->mosiPin) | - (1ULL << configuration->clkPin) | - (1ULL << configuration->csPin); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - - if (gpio_config(&io_conf) != ESP_OK) { - LOG_E(TAG, "Failed to configure output pins"); - return false; - } - - // Configure MISO as input - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << configuration->misoPin); - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - - if (gpio_config(&io_conf) != ESP_OK) { - LOG_E(TAG, "Failed to configure input pin"); - return false; - } - - // Initialize pin states - gpio_set_level(configuration->csPin, 1); // CS high - gpio_set_level(configuration->clkPin, 0); // CLK low - gpio_set_level(configuration->mosiPin, 0); // MOSI low - - LOG_I( - TAG, - "GPIO configured: MOSI=%d, MISO=%d, CLK=%d, CS=%d", - static_cast(configuration->mosiPin), - static_cast(configuration->misoPin), - static_cast(configuration->clkPin), - static_cast(configuration->csPin) - ); - - return true; -} - -bool Xpt2046SoftSpi::stop() { - LOG_I(TAG, "Stopping Xpt2046SoftSpi touch driver"); - - // Stop LVLG if needed - if (lvglDevice != nullptr) { - stopLvgl(); - } - - return true; -} - -bool Xpt2046SoftSpi::startLvgl(lv_display_t* display) { - (void)display; - if (lvglDevice != nullptr) { - LOG_E(TAG, "LVGL was already started"); - return false; - } - - lvglDevice = lv_indev_create(); - if (lvglDevice == nullptr) { - LOG_E(TAG, "Failed to create LVGL input device"); - return false; - } - - lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_POINTER); - lv_indev_set_read_cb(lvglDevice, touchReadCallback); - lv_indev_set_user_data(lvglDevice, this); - - LOG_I(TAG, "Xpt2046SoftSpi touch driver started successfully"); - return true; -} - -bool Xpt2046SoftSpi::stopLvgl() { - if (lvglDevice != nullptr) { - lv_indev_delete(lvglDevice); - lvglDevice = nullptr; - } - return true; -} - -int Xpt2046SoftSpi::readSPI(uint8_t command) { - int result = 0; - - // Pull CS low for this transaction - gpio_set_level(configuration->csPin, 0); - ets_delay_us(1); - - // Send 8-bit command - for (int i = 7; i >= 0; i--) { - gpio_set_level(configuration->mosiPin, (command & (1 << i)) ? 1 : 0); - gpio_set_level(configuration->clkPin, 1); - ets_delay_us(1); - gpio_set_level(configuration->clkPin, 0); - ets_delay_us(1); - } - - for (int i = 11; i >= 0; i--) { - gpio_set_level(configuration->clkPin, 1); - ets_delay_us(1); - if (gpio_get_level(configuration->misoPin)) { - result |= (1 << i); - } - gpio_set_level(configuration->clkPin, 0); - ets_delay_us(1); - } - - // Pull CS high for this transaction - gpio_set_level(configuration->csPin, 1); - - return result; -} - -bool Xpt2046SoftSpi::readRawPoint(uint16_t& x, uint16_t& y) { - constexpr int sampleCount = 8; - int totalX = 0; - int totalY = 0; - int validSamples = 0; - - for (int i = 0; i < sampleCount; i++) { - const int rawX = readSPI(CMD_READ_X); - const int rawY = readSPI(CMD_READ_Y); - - if (rawX > RAW_VALID_MIN && rawX < RAW_VALID_MAX && rawY > RAW_VALID_MIN && rawY < RAW_VALID_MAX) { - totalX += rawX; - totalY += rawY; - validSamples++; - } - - vTaskDelay(pdMS_TO_TICKS(1)); - } - - if (validSamples < 3) { - return false; - } - - x = static_cast(totalX / validSamples); - y = static_cast(totalY / validSamples); - return true; -} - -bool Xpt2046SoftSpi::getTouchPoint(Point& point) { - uint16_t rawX = 0; - uint16_t rawY = 0; - if (!readRawPoint(rawX, rawY)) { - return false; - } - - int mappedX = (static_cast(rawX) - RAW_MIN_DEFAULT) * static_cast(configuration->xMax) / - (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT); - int mappedY = (static_cast(rawY) - RAW_MIN_DEFAULT) * static_cast(configuration->yMax) / - (RAW_MAX_DEFAULT - RAW_MIN_DEFAULT); - - if (configuration->swapXy) { - std::swap(mappedX, mappedY); - } - if (configuration->mirrorX) { - mappedX = static_cast(configuration->xMax) - mappedX; - } - if (configuration->mirrorY) { - mappedY = static_cast(configuration->yMax) - mappedY; - } - - uint16_t x = static_cast(std::clamp(mappedX, 0, static_cast(configuration->xMax))); - uint16_t y = static_cast(std::clamp(mappedY, 0, static_cast(configuration->yMax))); - - point.x = x; - point.y = y; - return true; -} - -bool Xpt2046SoftSpi::isTouched() { - uint16_t x = 0; - uint16_t y = 0; - return readRawPoint(x, y); -} - -void Xpt2046SoftSpi::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) { - auto* touch = static_cast(lv_indev_get_user_data(indev)); - if (touch == nullptr) { - data->state = LV_INDEV_STATE_RELEASED; - return; - } - - Point point; - if (touch->getTouchPoint(point)) { - data->point.x = point.x; - data->point.y = point.y; - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->state = LV_INDEV_STATE_RELEASED; - } -} - -// Return driver instance if any -std::shared_ptr Xpt2046SoftSpi::getTouchDriver() { - assert(lvglDevice == nullptr); // Still attached to LVGL context. Call stopLvgl() first. - - if (touchDriver == nullptr) { - touchDriver = std::make_shared(this); - } - - return touchDriver; -} diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h deleted file mode 100644 index 40ab7a0a9..000000000 --- a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "Tactility/hal/touch/TouchDevice.h" -#include "Tactility/hal/touch/TouchDriver.h" -#include "lvgl.h" -#include -#include -#include - -#ifndef TFT_WIDTH -#define TFT_WIDTH 240 -#endif - -#ifndef TFT_HEIGHT -#define TFT_HEIGHT 320 -#endif - -struct Point { - int x; - int y; -}; - -class Xpt2046SoftSpi : public tt::hal::touch::TouchDevice { -public: - class Configuration { - public: - Configuration( - gpio_num_t mosiPin, - gpio_num_t misoPin, - gpio_num_t clkPin, - gpio_num_t csPin, - uint16_t xMax = TFT_WIDTH, - uint16_t yMax = TFT_HEIGHT, - bool swapXy = false, - bool mirrorX = false, - bool mirrorY = false - ) : mosiPin(mosiPin), - misoPin(misoPin), - clkPin(clkPin), - csPin(csPin), - xMax(xMax), - yMax(yMax), - swapXy(swapXy), - mirrorX(mirrorX), - mirrorY(mirrorY) - {} - - gpio_num_t mosiPin; - gpio_num_t misoPin; - gpio_num_t clkPin; - gpio_num_t csPin; - uint16_t xMax; - uint16_t yMax; - bool swapXy; - bool mirrorX; - bool mirrorY; - }; - -private: - - class Xpt2046SoftSpiDriver final : public tt::hal::touch::TouchDriver { - Xpt2046SoftSpi* device; - public: - Xpt2046SoftSpiDriver(Xpt2046SoftSpi* device) : device(device) {} - bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) override { - Point point; - if (device->isTouched() && device->getTouchPoint(point)) { - *x = point.x; - *y = point.y; - *pointCount = 1; - return true; - } else { - *pointCount = 0; - return false; - } - } - }; - - std::unique_ptr configuration; - lv_indev_t* lvglDevice = nullptr; - std::shared_ptr touchDriver; - - int readSPI(uint8_t command); - bool readRawPoint(uint16_t& x, uint16_t& y); - static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data); - -public: - explicit Xpt2046SoftSpi(std::unique_ptr inConfiguration); - - // TouchDevice interface - std::string getName() const final { return "Xpt2046SoftSpi"; } - std::string getDescription() const final { return "Xpt2046 Soft SPI touch driver"; } - - bool start() override; - bool stop() override; - - bool supportsLvgl() const override { return true; } - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - - bool supportsTouchDriver() override { return true; } - std::shared_ptr getTouchDriver() override; - lv_indev_t* getLvglIndev() override { return lvglDevice; } - - // XPT2046-specific methods - bool getTouchPoint(Point& point); - bool isTouched(); -}; From f1e96d2ef5d181482167a584fc550de236ba1a06 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:07:06 +0200 Subject: [PATCH 07/15] Fix for build --- Devices/cyd-e32r32p/Source/Configuration.cpp | 23 -------- .../cyd-e32r32p/Source/devices/Display.cpp | 52 ------------------- Devices/cyd-e32r32p/Source/devices/Display.h | 26 ---------- Devices/cyd-e32r32p/Source/devices/Power.cpp | 12 ----- Devices/cyd-e32r32p/Source/devices/Power.h | 6 --- .../cyd-e32r32p/{Source => source}/module.cpp | 0 Tactility/Source/app/setup/Setup.cpp | 4 +- 7 files changed, 3 insertions(+), 120 deletions(-) delete mode 100644 Devices/cyd-e32r32p/Source/Configuration.cpp delete mode 100644 Devices/cyd-e32r32p/Source/devices/Display.cpp delete mode 100644 Devices/cyd-e32r32p/Source/devices/Display.h delete mode 100644 Devices/cyd-e32r32p/Source/devices/Power.cpp delete mode 100644 Devices/cyd-e32r32p/Source/devices/Power.h rename Devices/cyd-e32r32p/{Source => source}/module.cpp (100%) diff --git a/Devices/cyd-e32r32p/Source/Configuration.cpp b/Devices/cyd-e32r32p/Source/Configuration.cpp deleted file mode 100644 index a7a18f6a3..000000000 --- a/Devices/cyd-e32r32p/Source/Configuration.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "devices/Display.h" -#include "devices/Power.h" - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(DISPLAY_BACKLIGHT_PIN); -} - -static tt::hal::DeviceVector createDevices() { - return { - createPower(), - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; \ No newline at end of file diff --git a/Devices/cyd-e32r32p/Source/devices/Display.cpp b/Devices/cyd-e32r32p/Source/devices/Display.cpp deleted file mode 100644 index 2371943ad..000000000 --- a/Devices/cyd-e32r32p/Source/devices/Display.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "Display.h" - -#include -#include -#include -#include - -// Create the XPT2046 touch device (hardware/esp_lcd driver) -static std::shared_ptr createTouch() { - auto config = std::make_unique( - DISPLAY_SPI_HOST, // spi device / bus (SPI2_HOST) - TOUCH_CS_PIN, // touch CS (IO33) - (uint16_t)DISPLAY_HORIZONTAL_RESOLUTION, // x max - (uint16_t)DISPLAY_VERTICAL_RESOLUTION, // y max - false, // swapXy - true, // mirrorX - true // mirrorY - ); - - return std::make_shared(std::move(config)); -} - -std::shared_ptr createDisplay() { - // Create the ST7789 panel configuration - St7789Display::Configuration panel_configuration = { - .horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION, - .verticalResolution = DISPLAY_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = false, - .mirrorY = false, - .invertColor = false, - .bufferSize = DISPLAY_DRAW_BUFFER_SIZE, // 0 -> default 1/10 screen - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .lvglSwapBytes = false, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR // BGR for this display - }; - - // Create the SPI configuration (from EspLcdSpiDisplay base class) - auto spi_configuration = std::make_shared(EspLcdSpiDisplay::SpiConfiguration { - .spiHostDevice = DISPLAY_SPI_HOST, - .csPin = DISPLAY_PIN_CS, - .dcPin = DISPLAY_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration); -} \ No newline at end of file diff --git a/Devices/cyd-e32r32p/Source/devices/Display.h b/Devices/cyd-e32r32p/Source/devices/Display.h deleted file mode 100644 index 0106b0913..000000000 --- a/Devices/cyd-e32r32p/Source/devices/Display.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "driver/gpio.h" -#include "driver/spi_common.h" -#include - -// Display (ST7789P3 on this board) -constexpr auto DISPLAY_SPI_HOST = SPI2_HOST; -constexpr auto DISPLAY_PIN_CS = GPIO_NUM_15; -constexpr auto DISPLAY_PIN_DC = GPIO_NUM_2; -constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 240; -constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320; -constexpr auto DISPLAY_DRAW_BUFFER_HEIGHT = (DISPLAY_VERTICAL_RESOLUTION / 10); -constexpr auto DISPLAY_DRAW_BUFFER_SIZE = (DISPLAY_HORIZONTAL_RESOLUTION * DISPLAY_DRAW_BUFFER_HEIGHT); -constexpr auto DISPLAY_BACKLIGHT_PIN = GPIO_NUM_27; - -// Touch (XPT2046, resistive, shared SPI with display) -constexpr auto TOUCH_MISO_PIN = GPIO_NUM_12; -constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_13; -constexpr auto TOUCH_SCK_PIN = GPIO_NUM_14; -constexpr auto TOUCH_CS_PIN = GPIO_NUM_33; -constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_36; - - -std::shared_ptr createDisplay(); \ No newline at end of file diff --git a/Devices/cyd-e32r32p/Source/devices/Power.cpp b/Devices/cyd-e32r32p/Source/devices/Power.cpp deleted file mode 100644 index febacad19..000000000 --- a/Devices/cyd-e32r32p/Source/devices/Power.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "Power.h" - -#include -#include - -std::shared_ptr createPower() { - ChargeFromAdcVoltage::Configuration configuration; - // 2.0 ratio, but +.11 added as display voltage sag compensation. - configuration.adcMultiplier = 2.11; - - return std::make_shared(configuration); -} \ No newline at end of file diff --git a/Devices/cyd-e32r32p/Source/devices/Power.h b/Devices/cyd-e32r32p/Source/devices/Power.h deleted file mode 100644 index 7598ded6f..000000000 --- a/Devices/cyd-e32r32p/Source/devices/Power.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -std::shared_ptr createPower(); diff --git a/Devices/cyd-e32r32p/Source/module.cpp b/Devices/cyd-e32r32p/source/module.cpp similarity index 100% rename from Devices/cyd-e32r32p/Source/module.cpp rename to Devices/cyd-e32r32p/source/module.cpp diff --git a/Tactility/Source/app/setup/Setup.cpp b/Tactility/Source/app/setup/Setup.cpp index 4336023ae..40aac59ea 100644 --- a/Tactility/Source/app/setup/Setup.cpp +++ b/Tactility/Source/app/setup/Setup.cpp @@ -17,9 +17,11 @@ #include #include +#ifdef ESP_PLATFORM #include +#endif -#if defined(CONFIG_TT_TOUCH_CALIBRATION_REQUIRED) +#ifdef CONFIG_TT_TOUCH_CALIBRATION_REQUIRED #include #endif From daded517aaf80dcbc74492f0c031399b99f89d69 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:09:22 +0200 Subject: [PATCH 08/15] Migrate cyd-e32r32p to kernel drivers --- Devices/cyd-e32r32p/CMakeLists.txt | 5 ++- Devices/cyd-e32r32p/cyd,e32r32p.dts | 50 +++++++++++++++++++++++---- Devices/cyd-e32r32p/device.properties | 2 ++ Devices/cyd-e32r32p/devicetree.yaml | 2 ++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Devices/cyd-e32r32p/CMakeLists.txt b/Devices/cyd-e32r32p/CMakeLists.txt index ce394afbf..a832a83a2 100644 --- a/Devices/cyd-e32r32p/CMakeLists.txt +++ b/Devices/cyd-e32r32p/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ST7789 XPT2046 PwmBacklight EstimatedPower driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-e32r32p/cyd,e32r32p.dts b/Devices/cyd-e32r32p/cyd,e32r32p.dts index b47d2735c..384fb36ea 100644 --- a/Devices/cyd-e32r32p/cyd,e32r32p.dts +++ b/Devices/cyd-e32r32p/cyd,e32r32p.dts @@ -1,13 +1,16 @@ /dts-v1/; #include +#include +#include #include #include #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -31,6 +34,31 @@ pin-scl = <&gpio0 25 GPIO_FLAG_NONE>; }; + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + channels = ; + }; + + // Matches the deprecated HAL's old ChargeFromAdcVoltage config: adcMultiplier=2.11, + // adcRefVoltage=3.3 (default), voltageMin/Max=3.2/4.2 (default, same as battery-sense's own + // fixed curve - see TactilityKernel/source/drivers/battery_sense.cpp). + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2110>; + }; + + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <40000>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -39,13 +67,23 @@ pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-miso = <&gpio0 12 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; - + display@0 { - compatible = "display-placeholder"; + compatible = "sitronix,st7789"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + bgr-order; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <240>; + y-max = <320>; + mirror-x; + mirror-y; }; }; @@ -56,7 +94,7 @@ pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>; pin-miso = <&gpio0 19 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>; - + sdcard@0 { compatible = "espressif,esp32-sdspi"; frequency-khz = <20000>; diff --git a/Devices/cyd-e32r32p/device.properties b/Devices/cyd-e32r32p/device.properties index 27f2ad0a6..1a60af40c 100644 --- a/Devices/cyd-e32r32p/device.properties +++ b/Devices/cyd-e32r32p/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" diff --git a/Devices/cyd-e32r32p/devicetree.yaml b/Devices/cyd-e32r32p/devicetree.yaml index 34099aabc..5817b2267 100644 --- a/Devices/cyd-e32r32p/devicetree.yaml +++ b/Devices/cyd-e32r32p/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/st7789-module + - Drivers/xpt2046-module dts: cyd,e32r32p.dts From ceed6f1d27f70e8cf6ca23f1e3c887db2e4e2719 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:27:20 +0200 Subject: [PATCH 09/15] Migrate crowpanel-basic-28 --- .../elecrow-crowpanel-basic-28/CMakeLists.txt | 5 +- .../Source/Configuration.cpp | 21 -------- .../Source/devices/Display.cpp | 49 ------------------- .../Source/devices/Display.h | 17 ------- .../device.properties | 2 + .../devicetree.yaml | 2 + .../elecrow,crowpanel-basic-28.dts | 32 +++++++++--- .../{Source => source}/module.cpp | 0 8 files changed, 32 insertions(+), 96 deletions(-) delete mode 100644 Devices/elecrow-crowpanel-basic-28/Source/Configuration.cpp delete mode 100644 Devices/elecrow-crowpanel-basic-28/Source/devices/Display.cpp delete mode 100644 Devices/elecrow-crowpanel-basic-28/Source/devices/Display.h rename Devices/elecrow-crowpanel-basic-28/{Source => source}/module.cpp (100%) diff --git a/Devices/elecrow-crowpanel-basic-28/CMakeLists.txt b/Devices/elecrow-crowpanel-basic-28/CMakeLists.txt index e9d973e08..a832a83a2 100644 --- a/Devices/elecrow-crowpanel-basic-28/CMakeLists.txt +++ b/Devices/elecrow-crowpanel-basic-28/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046 PwmBacklight driver + REQUIRES TactilityKernel driver ) diff --git a/Devices/elecrow-crowpanel-basic-28/Source/Configuration.cpp b/Devices/elecrow-crowpanel-basic-28/Source/Configuration.cpp deleted file mode 100644 index f413f9d3e..000000000 --- a/Devices/elecrow-crowpanel-basic-28/Source/Configuration.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "PwmBacklight.h" -#include "devices/Display.h" - -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(GPIO_NUM_27); -} - -static DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.cpp b/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.cpp deleted file mode 100644 index 1ba08b7dc..000000000 --- a/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "Display.h" - -#include -#include - -#include - -std::shared_ptr createTouch() { - auto configuration = std::make_unique( - LCD_SPI_HOST, - TOUCH_PIN_CS, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION, - false, - true, - false - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = true, - .mirrorY = false, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR - }; - - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.h b/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.h deleted file mode 100644 index 6dfd79a91..000000000 --- a/Devices/elecrow-crowpanel-basic-28/Source/devices/Display.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include - -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto TOUCH_PIN_CS = GPIO_NUM_33; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; // RS -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; -constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8; - -std::shared_ptr createDisplay(); diff --git a/Devices/elecrow-crowpanel-basic-28/device.properties b/Devices/elecrow-crowpanel-basic-28/device.properties index acc1405a4..770406ae2 100644 --- a/Devices/elecrow-crowpanel-basic-28/device.properties +++ b/Devices/elecrow-crowpanel-basic-28/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.8" diff --git a/Devices/elecrow-crowpanel-basic-28/devicetree.yaml b/Devices/elecrow-crowpanel-basic-28/devicetree.yaml index 61fa12e3b..ec0480924 100644 --- a/Devices/elecrow-crowpanel-basic-28/devicetree.yaml +++ b/Devices/elecrow-crowpanel-basic-28/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/xpt2046-module dts: elecrow,crowpanel-basic-28.dts diff --git a/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts b/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts index e3a53374b..58945c75a 100644 --- a/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts +++ b/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts @@ -7,8 +7,9 @@ #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -32,6 +33,15 @@ pin-scl = <&gpio0 21 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <40000>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -41,13 +51,23 @@ pin-miso = <&gpio0 12 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; max-transfer-size = <65536>; - + display@0 { - compatible = "display-placeholder"; + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + mirror-x; + bgr-order; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <240>; + y-max = <320>; + mirror-x; }; }; @@ -58,7 +78,7 @@ pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>; pin-miso = <&gpio0 19 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>; - + sdcard@0 { compatible = "espressif,esp32-sdspi"; frequency-khz = <20000>; diff --git a/Devices/elecrow-crowpanel-basic-28/Source/module.cpp b/Devices/elecrow-crowpanel-basic-28/source/module.cpp similarity index 100% rename from Devices/elecrow-crowpanel-basic-28/Source/module.cpp rename to Devices/elecrow-crowpanel-basic-28/source/module.cpp From 44f3eec365f3677449770c87791c28212eaf52fd Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 00:33:54 +0200 Subject: [PATCH 10/15] Fix for ADC channel --- Devices/cyd-e32r32p/cyd,e32r32p.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Devices/cyd-e32r32p/cyd,e32r32p.dts b/Devices/cyd-e32r32p/cyd,e32r32p.dts index 384fb36ea..bdbc379d7 100644 --- a/Devices/cyd-e32r32p/cyd,e32r32p.dts +++ b/Devices/cyd-e32r32p/cyd,e32r32p.dts @@ -37,7 +37,7 @@ adc0 { compatible = "espressif,esp32-adc-oneshot"; unit-id = ; - channels = ; + channels = ; }; // Matches the deprecated HAL's old ChargeFromAdcVoltage config: adcMultiplier=2.11, From 4d41d8497aaa4a5999a4f769adcb616a96f735b5 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 01:07:08 +0200 Subject: [PATCH 11/15] unphone display and touch migration --- Devices/unphone/CMakeLists.txt | 2 +- Devices/unphone/Source/Configuration.cpp | 12 +- Devices/unphone/Source/InitBoot.cpp | 10 +- .../unphone/Source/devices/Hx8357Display.cpp | 119 ----- .../unphone/Source/devices/Hx8357Display.h | 66 --- Devices/unphone/Source/devices/Touch.cpp | 12 - Devices/unphone/Source/devices/Touch.h | 8 - Devices/unphone/Source/hx8357/README.md | 4 - Devices/unphone/Source/hx8357/disp_spi.c | 311 ------------ Devices/unphone/Source/hx8357/disp_spi.h | 81 --- Devices/unphone/Source/hx8357/hx8357.c | 292 ----------- Devices/unphone/Source/hx8357/hx8357.h | 133 ----- Devices/unphone/Source/lvgl_spi_conf.h | 7 - Devices/unphone/devicetree.yaml | 2 + Devices/unphone/unphone.dts | 18 +- Drivers/XPT2046/CMakeLists.txt | 5 - Drivers/XPT2046/README.md | 3 - Drivers/XPT2046/Source/Xpt2046Touch.cpp | 37 -- Drivers/XPT2046/Source/Xpt2046Touch.h | 59 --- Drivers/hx8357-module/CMakeLists.txt | 11 + Drivers/hx8357-module/LICENSE-Apache-2.0.md | 195 ++++++++ Drivers/hx8357-module/README.md | 7 + .../hx8357-module/bindings/himax,hx8357.yaml | 64 +++ Drivers/hx8357-module/devicetree.yaml | 3 + .../hx8357-module/include/bindings/hx8357.h | 7 + .../hx8357-module/include/drivers/hx8357.h | 33 ++ Drivers/hx8357-module/include/hx8357_module.h | 14 + Drivers/hx8357-module/source/hx8357.cpp | 462 ++++++++++++++++++ Drivers/hx8357-module/source/module.cpp | 32 ++ .../bindings/ilitek,ili9341.yaml | 2 +- .../bindings/ilitek,ili9488.yaml | 2 +- .../bindings/sitronix,st7789-i8080.yaml | 2 +- .../bindings/sitronix,st7789.yaml | 2 +- 33 files changed, 856 insertions(+), 1161 deletions(-) delete mode 100644 Devices/unphone/Source/devices/Hx8357Display.cpp delete mode 100644 Devices/unphone/Source/devices/Hx8357Display.h delete mode 100644 Devices/unphone/Source/devices/Touch.cpp delete mode 100644 Devices/unphone/Source/devices/Touch.h delete mode 100644 Devices/unphone/Source/hx8357/README.md delete mode 100644 Devices/unphone/Source/hx8357/disp_spi.c delete mode 100644 Devices/unphone/Source/hx8357/disp_spi.h delete mode 100644 Devices/unphone/Source/hx8357/hx8357.c delete mode 100644 Devices/unphone/Source/hx8357/hx8357.h delete mode 100644 Devices/unphone/Source/lvgl_spi_conf.h delete mode 100644 Drivers/XPT2046/CMakeLists.txt delete mode 100644 Drivers/XPT2046/README.md delete mode 100644 Drivers/XPT2046/Source/Xpt2046Touch.cpp delete mode 100644 Drivers/XPT2046/Source/Xpt2046Touch.h create mode 100644 Drivers/hx8357-module/CMakeLists.txt create mode 100644 Drivers/hx8357-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/hx8357-module/README.md create mode 100644 Drivers/hx8357-module/bindings/himax,hx8357.yaml create mode 100644 Drivers/hx8357-module/devicetree.yaml create mode 100644 Drivers/hx8357-module/include/bindings/hx8357.h create mode 100644 Drivers/hx8357-module/include/drivers/hx8357.h create mode 100644 Drivers/hx8357-module/include/hx8357_module.h create mode 100644 Drivers/hx8357-module/source/hx8357.cpp create mode 100644 Drivers/hx8357-module/source/module.cpp diff --git a/Devices/unphone/CMakeLists.txt b/Devices/unphone/CMakeLists.txt index 061470ddc..0a4fc7e73 100644 --- a/Devices/unphone/CMakeLists.txt +++ b/Devices/unphone/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_io_expander esp_io_expander_tca95xx_16bit BQ24295 XPT2046 + REQUIRES Tactility esp_lvgl_port esp_io_expander esp_io_expander_tca95xx_16bit BQ24295 ) diff --git a/Devices/unphone/Source/Configuration.cpp b/Devices/unphone/Source/Configuration.cpp index b32363ff9..75331ec46 100644 --- a/Devices/unphone/Source/Configuration.cpp +++ b/Devices/unphone/Source/Configuration.cpp @@ -1,17 +1,7 @@ -#include "UnPhoneFeatures.h" -#include "devices/Hx8357Display.h" - #include bool initBoot(); -static tt::hal::DeviceVector createDevices() { - return { - createDisplay(), - }; -} - extern const tt::hal::Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices + .initBoot = initBoot }; diff --git a/Devices/unphone/Source/InitBoot.cpp b/Devices/unphone/Source/InitBoot.cpp index 188803ab4..3be9bb5cd 100644 --- a/Devices/unphone/Source/InitBoot.cpp +++ b/Devices/unphone/Source/InitBoot.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -161,7 +160,10 @@ static bool unPhonePowerOn() { bootStats.printInfo(); bootStats.notifyBootStart(); - bq24295 = std::make_shared(device_find_by_name("i2c_internal")); + ::Device* i2c_internal = nullptr; + check(device_get_by_name("i2c_internal", &i2c_internal) == ERROR_NONE); + bq24295 = std::make_shared(i2c_internal); + device_put(i2c_internal); unPhoneFeatures = std::make_shared(bq24295); @@ -172,7 +174,9 @@ static bool unPhonePowerOn() { unPhoneFeatures->printInfo(); - unPhoneFeatures->setBacklightPower(false); + // Kernel devicetree devices (incl. the hx8357 display) already started by kernel_init() + // before initBoot() runs, so it's safe to turn the backlight on here now. + unPhoneFeatures->setBacklightPower(true); unPhoneFeatures->setVibePower(false); unPhoneFeatures->setIrPower(false); unPhoneFeatures->setExpanderPower(false); diff --git a/Devices/unphone/Source/devices/Hx8357Display.cpp b/Devices/unphone/Source/devices/Hx8357Display.cpp deleted file mode 100644 index db462e6a9..000000000 --- a/Devices/unphone/Source/devices/Hx8357Display.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "Hx8357Display.h" -#include "Touch.h" - -#include - -#include -#include -#include - -constexpr auto* TAG = "Hx8357Display"; - -constexpr auto BUFFER_SIZE = (UNPHONE_LCD_HORIZONTAL_RESOLUTION * UNPHONE_LCD_DRAW_BUFFER_HEIGHT * LV_COLOR_DEPTH / 8); - -extern std::shared_ptr unPhoneFeatures; - -bool Hx8357Display::start() { - LOG_I(TAG, "start"); - - disp_spi_add_device(SPI2_HOST); - - hx8357_reset(GPIO_NUM_46); - hx8357_init(UNPHONE_LCD_PIN_DC); - uint8_t madctl = (1U << MADCTL_BIT_INDEX_COLUMN_ADDRESS_ORDER); - hx8357_set_madctl(madctl); - - return true; -} - -bool Hx8357Display::stop() { - LOG_I(TAG, "stop"); - disp_spi_remove_device(); - return true; -} - -bool Hx8357Display::startLvgl() { - LOG_I(TAG, "startLvgl"); - - if (lvglDisplay != nullptr) { - LOG_W(TAG, "LVGL was already started"); - return false; - } - - lvglDisplay = lv_display_create(UNPHONE_LCD_HORIZONTAL_RESOLUTION, UNPHONE_LCD_VERTICAL_RESOLUTION); - lv_display_set_physical_resolution(lvglDisplay, UNPHONE_LCD_HORIZONTAL_RESOLUTION, UNPHONE_LCD_VERTICAL_RESOLUTION); - lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_NATIVE); - - // TODO malloc to use SPIRAM - buffer = static_cast(heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_DMA)); - assert(buffer != nullptr); - - lv_display_set_buffers( - lvglDisplay, - buffer, - nullptr, - BUFFER_SIZE, - LV_DISPLAY_RENDER_MODE_PARTIAL - ); - - lv_display_set_flush_cb(lvglDisplay, hx8357_flush); - - if (lvglDisplay == nullptr) { - LOG_I(TAG, "Failed"); - return false; - } - - unPhoneFeatures->setBacklightPower(true); - - auto touch_device = getTouchDevice(); - if (touch_device != nullptr) { - touch_device->startLvgl(lvglDisplay); - } - - return true; -} - -bool Hx8357Display::stopLvgl() { - LOG_I(TAG, "stopLvgl"); - - if (lvglDisplay == nullptr) { - LOG_W(TAG, "LVGL was already stopped"); - return false; - } - - // Just in case - disp_wait_for_pending_transactions(); - - auto touch_device = getTouchDevice(); - if (touch_device != nullptr && touch_device->getLvglIndev() != nullptr) { - LOG_I(TAG, "Stopping touch device"); - touch_device->stopLvgl(); - } - - lv_display_delete(lvglDisplay); - lvglDisplay = nullptr; - - heap_caps_free(buffer); - buffer = nullptr; - - return true; -} - -std::shared_ptr Hx8357Display::getTouchDevice() { - if (touchDevice == nullptr) { - touchDevice = std::reinterpret_pointer_cast(createTouch()); - LOG_I(TAG, "Created touch device"); - } - - return touchDevice; -} - -std::shared_ptr createDisplay() { - return std::make_shared(); -} - -bool Hx8357Display::Hx8357Driver::drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) { - lv_area_t area = { xStart, yStart, xEnd, yEnd }; - hx8357_flush(nullptr, &area, (uint8_t*)pixelData); - return true; -} diff --git a/Devices/unphone/Source/devices/Hx8357Display.h b/Devices/unphone/Source/devices/Hx8357Display.h deleted file mode 100644 index 24b0076f6..000000000 --- a/Devices/unphone/Source/devices/Hx8357Display.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -#include - -#define UNPHONE_LCD_SPI_HOST SPI2_HOST -#define UNPHONE_LCD_PIN_CS GPIO_NUM_48 -#define UNPHONE_LCD_PIN_DC GPIO_NUM_47 -#define UNPHONE_LCD_PIN_RESET GPIO_NUM_46 -#define UNPHONE_LCD_SPI_FREQUENCY 27000000 -#define UNPHONE_LCD_HORIZONTAL_RESOLUTION 320 -#define UNPHONE_LCD_VERTICAL_RESOLUTION 480 -#define UNPHONE_LCD_DRAW_BUFFER_HEIGHT (UNPHONE_LCD_VERTICAL_RESOLUTION / 15) - -class Hx8357Display : public tt::hal::display::DisplayDevice { - - uint8_t* buffer = nullptr; - lv_display_t* lvglDisplay = nullptr; - std::shared_ptr touchDevice; - std::shared_ptr nativeDisplay; - - class Hx8357Driver : public tt::hal::display::DisplayDriver { - public: - tt::hal::display::ColorFormat getColorFormat() const override { return tt::hal::display::ColorFormat::RGB888; } - uint16_t getPixelWidth() const override { return UNPHONE_LCD_HORIZONTAL_RESOLUTION; } - uint16_t getPixelHeight() const override { return UNPHONE_LCD_VERTICAL_RESOLUTION; } - bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) override; - }; - -public: - - std::string getName() const final { return "HX8357"; } - std::string getDescription() const final { return "SPI display"; } - - bool start() override; - - bool stop() override; - - bool supportsLvgl() const override { return true; } - - bool startLvgl() override; - - bool stopLvgl() override; - - std::shared_ptr getTouchDevice() override; - - lv_display_t* getLvglDisplay() const override { return lvglDisplay; } - - // TODO: Set to true after fixing UnPhoneDisplayDriver - bool supportsDisplayDriver() const override { return false; } - - std::shared_ptr getDisplayDriver() override { - if (nativeDisplay == nullptr) { - nativeDisplay = std::make_shared(); - } - assert(nativeDisplay != nullptr); - return nativeDisplay; - } -}; - -std::shared_ptr createDisplay(); diff --git a/Devices/unphone/Source/devices/Touch.cpp b/Devices/unphone/Source/devices/Touch.cpp deleted file mode 100644 index 8c9a19b4d..000000000 --- a/Devices/unphone/Source/devices/Touch.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "Touch.h" - -std::shared_ptr createTouch() { - auto configuration = std::make_unique( - SPI2_HOST, - GPIO_NUM_38, - 320, - 480 - ); - - return std::make_shared(std::move(configuration)); -} diff --git a/Devices/unphone/Source/devices/Touch.h b/Devices/unphone/Source/devices/Touch.h deleted file mode 100644 index a6d20c0f3..000000000 --- a/Devices/unphone/Source/devices/Touch.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include - -extern std::shared_ptr touchInstance; - -std::shared_ptr createTouch(); diff --git a/Devices/unphone/Source/hx8357/README.md b/Devices/unphone/Source/hx8357/README.md deleted file mode 100644 index c580f6ced..000000000 --- a/Devices/unphone/Source/hx8357/README.md +++ /dev/null @@ -1,4 +0,0 @@ -The files in this folder are from https://github.com/lvgl/lvgl_esp32_drivers -The original license is an MIT license: https://github.com/lvgl/lvgl_esp32_drivers/blob/master/LICENSE - -You may use the files in this folder under the original license, or under GPL v3 from the main Tactility project. \ No newline at end of file diff --git a/Devices/unphone/Source/hx8357/disp_spi.c b/Devices/unphone/Source/hx8357/disp_spi.c deleted file mode 100644 index a81e4f60c..000000000 --- a/Devices/unphone/Source/hx8357/disp_spi.c +++ /dev/null @@ -1,311 +0,0 @@ -#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration - -/** - * @file disp_spi.c - * - */ - -/********************* - * INCLUDES - *********************/ -#include "esp_system.h" -#include "driver/gpio.h" -#include "driver/spi_master.h" -#include "esp_log.h" - -#define TAG "disp_spi" - -#include - -#include -#include -#include - -#include - -#include "disp_spi.h" -//#include "disp_driver.h" - -//#include "../lvgl_helpers.h" -#include "../lvgl_spi_conf.h" - -/****************************************************************************** - * Notes about DMA spi_transaction_ext_t structure pooling - * - * An xQueue is used to hold a pool of reusable SPI spi_transaction_ext_t - * structures that get used for all DMA SPI transactions. While an xQueue may - * seem like overkill it is an already built-in RTOS feature that comes at - * little cost. xQueues are also ISR safe if it ever becomes necessary to - * access the pool in the ISR callback. - * - * When a DMA request is sent, a transaction structure is removed from the - * pool, filled out, and passed off to the esp32 SPI driver. Later, when - * servicing pending SPI transaction results, the transaction structure is - * recycled back into the pool for later reuse. This matches the DMA SPI - * transaction life cycle requirements of the esp32 SPI driver. - * - * When polling or synchronously sending SPI requests, and as required by the - * esp32 SPI driver, all pending DMA transactions are first serviced. Then the - * polling SPI request takes place. - * - * When sending an asynchronous DMA SPI request, if the pool is empty, some - * small percentage of pending transactions are first serviced before sending - * any new DMA SPI transactions. Not too many and not too few as this balance - * controls DMA transaction latency. - * - * It is therefore not the design that all pending transactions must be - * serviced and placed back into the pool with DMA SPI requests - that - * will happen eventually. The pool just needs to contain enough to float some - * number of in-flight SPI requests to speed up the overall DMA SPI data rate - * and reduce transaction latency. If however a display driver uses some - * polling SPI requests or calls disp_wait_for_pending_transactions() directly, - * the pool will reach the full state more often and speed up DMA queuing. - * - *****************************************************************************/ - -/********************* - * DEFINES - *********************/ -#define SPI_TRANSACTION_POOL_SIZE 50 /* maximum number of DMA transactions simultaneously in-flight */ - -/* DMA Transactions to reserve before queueing additional DMA transactions. A 1/10th seems to be a good balance. Too many (or all) and it will increase latency. */ -#define SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE 10 -#if SPI_TRANSACTION_POOL_SIZE >= SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE -#define SPI_TRANSACTION_POOL_RESERVE (SPI_TRANSACTION_POOL_SIZE / SPI_TRANSACTION_POOL_RESERVE_PERCENTAGE) -#else -#define SPI_TRANSACTION_POOL_RESERVE 1 /* defines minimum size */ -#endif - -/********************** - * TYPEDEFS - **********************/ - -/********************** - * STATIC PROTOTYPES - **********************/ -static void spi_ready(spi_transaction_t*trans); - -/********************** - * STATIC VARIABLES - **********************/ -static spi_host_device_t spi_host; -static spi_device_handle_t spi; -static QueueHandle_t TransactionPool = NULL; -static transaction_cb_t chained_post_cb; - -/********************** - * MACROS - **********************/ - -/********************** - * GLOBAL FUNCTIONS - **********************/ -void disp_spi_add_device_config(spi_host_device_t host, spi_device_interface_config_t *devcfg) -{ - spi_host=host; - chained_post_cb=devcfg->post_cb; - devcfg->post_cb=spi_ready; - esp_err_t ret=spi_bus_add_device(host, devcfg, &spi); - assert(ret==ESP_OK); -} - -void disp_spi_add_device(spi_host_device_t host) -{ - disp_spi_add_device_with_speed(host, SPI_TFT_CLOCK_SPEED_HZ); -} - -void disp_spi_add_device_with_speed(spi_host_device_t host, int clock_speed_hz) -{ - ESP_LOGI(TAG, "Adding SPI device"); - ESP_LOGI(TAG, "Clock speed: %dHz, mode: %d, CS pin: %d", - clock_speed_hz, SPI_TFT_SPI_MODE, DISP_SPI_CS); - - spi_device_interface_config_t devcfg={ - .clock_speed_hz = clock_speed_hz, - .mode = SPI_TFT_SPI_MODE, - .spics_io_num=DISP_SPI_CS, // CS pin - .input_delay_ns=DISP_SPI_INPUT_DELAY_NS, - .queue_size=SPI_TRANSACTION_POOL_SIZE, - .pre_cb=NULL, - .post_cb=NULL, -#if defined(DISP_SPI_HALF_DUPLEX) - .flags = SPI_DEVICE_NO_DUMMY | SPI_DEVICE_HALFDUPLEX, /* dummy bits should be explicitly handled via DISP_SPI_VARIABLE_DUMMY as needed */ -#else - #if defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X) - .flags = 0, - #elif defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875) - .flags = SPI_DEVICE_NO_DUMMY, - #endif -#endif - }; - - disp_spi_add_device_config(host, &devcfg); - - /* create the transaction pool and fill it with ptrs to spi_transaction_ext_t to reuse */ - if(TransactionPool == NULL) { - TransactionPool = xQueueCreate(SPI_TRANSACTION_POOL_SIZE, sizeof(spi_transaction_ext_t*)); - assert(TransactionPool != NULL); - for (size_t i = 0; i < SPI_TRANSACTION_POOL_SIZE; i++) - { - spi_transaction_ext_t* pTransaction = (spi_transaction_ext_t*)heap_caps_malloc(sizeof(spi_transaction_ext_t), MALLOC_CAP_DMA); - assert(pTransaction != NULL); - memset(pTransaction, 0, sizeof(spi_transaction_ext_t)); - xQueueSend(TransactionPool, &pTransaction, portMAX_DELAY); - } - } -} - -void disp_spi_change_device_speed(int clock_speed_hz) -{ - if (clock_speed_hz <= 0) { - clock_speed_hz = SPI_TFT_CLOCK_SPEED_HZ; - } - ESP_LOGI(TAG, "Changing SPI device clock speed: %d", clock_speed_hz); - disp_spi_remove_device(); - disp_spi_add_device_with_speed(spi_host, clock_speed_hz); -} - -void disp_spi_remove_device() -{ - /* Wait for previous pending transaction results */ - disp_wait_for_pending_transactions(); - - esp_err_t ret=spi_bus_remove_device(spi); - assert(ret==ESP_OK); -} - -void disp_spi_transaction(const uint8_t *data, size_t length, - int flags, uint8_t *out, - uint64_t addr, uint8_t dummy_bits) -{ - if (0 == length) { - return; - } - - spi_transaction_ext_t t = {0}; - - /* transaction length is in bits */ - t.base.length = length * 8; - - if (length <= 4 && data != NULL) { - t.base.flags = SPI_TRANS_USE_TXDATA; - memcpy(t.base.tx_data, data, length); - } else { - t.base.tx_buffer = data; - } - - if (flags & DISP_SPI_RECEIVE) { - assert(out != NULL && (flags & (DISP_SPI_SEND_POLLING | DISP_SPI_SEND_SYNCHRONOUS))); - t.base.rx_buffer = out; - -#if defined(DISP_SPI_HALF_DUPLEX) - t.base.rxlength = t.base.length; - t.base.length = 0; /* no MOSI phase in half-duplex reads */ -#else - t.base.rxlength = 0; /* in full-duplex mode, zero means same as tx length */ -#endif - } - - if (flags & DISP_SPI_ADDRESS_8) { - t.address_bits = 8; - } else if (flags & DISP_SPI_ADDRESS_16) { - t.address_bits = 16; - } else if (flags & DISP_SPI_ADDRESS_24) { - t.address_bits = 24; - } else if (flags & DISP_SPI_ADDRESS_32) { - t.address_bits = 32; - } - if (t.address_bits) { - t.base.addr = addr; - t.base.flags |= SPI_TRANS_VARIABLE_ADDR; - } - -#if defined(DISP_SPI_HALF_DUPLEX) - if (flags & DISP_SPI_MODE_DIO) { - t.base.flags |= SPI_TRANS_MODE_DIO; - } else if (flags & DISP_SPI_MODE_QIO) { - t.base.flags |= SPI_TRANS_MODE_QIO; - } - - if (flags & DISP_SPI_MODE_DIOQIO_ADDR) { - t.base.flags |= SPI_TRANS_MODE_DIOQIO_ADDR; - } - - if ((flags & DISP_SPI_VARIABLE_DUMMY) && dummy_bits) { - t.dummy_bits = dummy_bits; - t.base.flags |= SPI_TRANS_VARIABLE_DUMMY; - } -#endif - - /* Save flags for pre/post transaction processing */ - t.base.user = (void *) flags; - - /* Poll/Complete/Queue transaction */ - if (flags & DISP_SPI_SEND_POLLING) { - disp_wait_for_pending_transactions(); /* before polling, all previous pending transactions need to be serviced */ - spi_device_polling_transmit(spi, (spi_transaction_t *) &t); - } else if (flags & DISP_SPI_SEND_SYNCHRONOUS) { - disp_wait_for_pending_transactions(); /* before synchronous queueing, all previous pending transactions need to be serviced */ - spi_device_transmit(spi, (spi_transaction_t *) &t); - } else { - - /* if necessary, ensure we can queue new transactions by servicing some previous transactions */ - if(uxQueueMessagesWaiting(TransactionPool) == 0) { - spi_transaction_t *presult; - while(uxQueueMessagesWaiting(TransactionPool) < SPI_TRANSACTION_POOL_RESERVE) { - if (spi_device_get_trans_result(spi, &presult, 1) == ESP_OK) { - xQueueSend(TransactionPool, &presult, portMAX_DELAY); /* back to the pool to be reused */ - } - } - } - - spi_transaction_ext_t *pTransaction = NULL; - xQueueReceive(TransactionPool, &pTransaction, portMAX_DELAY); - memcpy(pTransaction, &t, sizeof(t)); - if (spi_device_queue_trans(spi, (spi_transaction_t *) pTransaction, portMAX_DELAY) != ESP_OK) { - xQueueSend(TransactionPool, &pTransaction, portMAX_DELAY); /* send failed transaction back to the pool to be reused */ - } - } -} - - -void disp_wait_for_pending_transactions(void) -{ - spi_transaction_t *presult; - - while(uxQueueMessagesWaiting(TransactionPool) < SPI_TRANSACTION_POOL_SIZE) { /* service until the transaction reuse pool is full again */ - if (spi_device_get_trans_result(spi, &presult, 1) == ESP_OK) { - xQueueSend(TransactionPool, &presult, portMAX_DELAY); - } - } -} - -void disp_spi_acquire(void) -{ - esp_err_t ret = spi_device_acquire_bus(spi, portMAX_DELAY); - assert(ret == ESP_OK); -} - -void disp_spi_release(void) -{ - spi_device_release_bus(spi); -} - -/********************** - * STATIC FUNCTIONS - **********************/ - -static void IRAM_ATTR spi_ready(spi_transaction_t *trans) -{ - disp_spi_send_flag_t flags = (disp_spi_send_flag_t) trans->user; - - if (flags & DISP_SPI_SIGNAL_FLUSH) { - lv_disp_t* disp = lv_refr_get_disp_refreshing(); - lv_disp_flush_ready(disp); - } - - if (chained_post_cb) { - chained_post_cb(trans); - } -} - diff --git a/Devices/unphone/Source/hx8357/disp_spi.h b/Devices/unphone/Source/hx8357/disp_spi.h deleted file mode 100644 index 453946cc1..000000000 --- a/Devices/unphone/Source/hx8357/disp_spi.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @file disp_spi.h - * - */ - -#ifndef DISP_SPI_H -#define DISP_SPI_H - -#ifdef __cplusplus -extern "C" { -#endif - -/********************* - * INCLUDES - *********************/ -#include -#include -#include - -/********************* - * DEFINES - *********************/ - -/********************** - * TYPEDEFS - **********************/ -typedef enum _disp_spi_send_flag_t { - DISP_SPI_SEND_QUEUED = 0x00000000, - DISP_SPI_SEND_POLLING = 0x00000001, - DISP_SPI_SEND_SYNCHRONOUS = 0x00000002, - DISP_SPI_SIGNAL_FLUSH = 0x00000004, - DISP_SPI_RECEIVE = 0x00000008, - DISP_SPI_CMD_8 = 0x00000010, /* Reserved */ - DISP_SPI_CMD_16 = 0x00000020, /* Reserved */ - DISP_SPI_ADDRESS_8 = 0x00000040, - DISP_SPI_ADDRESS_16 = 0x00000080, - DISP_SPI_ADDRESS_24 = 0x00000100, - DISP_SPI_ADDRESS_32 = 0x00000200, - DISP_SPI_MODE_DIO = 0x00000400, - DISP_SPI_MODE_QIO = 0x00000800, - DISP_SPI_MODE_DIOQIO_ADDR = 0x00001000, - DISP_SPI_VARIABLE_DUMMY = 0x00002000, -} disp_spi_send_flag_t; - - -/********************** - * GLOBAL PROTOTYPES - **********************/ -void disp_spi_add_device(spi_host_device_t host); -void disp_spi_add_device_config(spi_host_device_t host, spi_device_interface_config_t *devcfg); -void disp_spi_add_device_with_speed(spi_host_device_t host, int clock_speed_hz); -void disp_spi_change_device_speed(int clock_speed_hz); -void disp_spi_remove_device(); - -/* Important! - All buffers should also be 32-bit aligned and DMA capable to prevent extra allocations and copying. - When DMA reading (even in polling mode) the ESP32 always read in 4-byte chunks even if less is requested. - Extra space will be zero filled. Always ensure the out buffer is large enough to hold at least 4 bytes! -*/ -void disp_spi_transaction(const uint8_t *data, size_t length, - int flags, uint8_t *out, uint64_t addr, uint8_t dummy_bits); - -void disp_wait_for_pending_transactions(void); -void disp_spi_acquire(void); -void disp_spi_release(void); - -static inline void disp_spi_send_data(uint8_t *data, size_t length) { - disp_spi_transaction(data, length, DISP_SPI_SEND_POLLING, NULL, 0, 0); -} - -static inline void disp_spi_send_colors(uint8_t *data, size_t length) { - disp_spi_transaction(data, length, - DISP_SPI_SEND_QUEUED | DISP_SPI_SIGNAL_FLUSH, - NULL, 0, 0); -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /*DISP_SPI_H*/ diff --git a/Devices/unphone/Source/hx8357/hx8357.c b/Devices/unphone/Source/hx8357/hx8357.c deleted file mode 100644 index 51375ef34..000000000 --- a/Devices/unphone/Source/hx8357/hx8357.c +++ /dev/null @@ -1,292 +0,0 @@ -/** - * @file HX8357.c - * - * Roughly based on the Adafruit_HX8357_Library - * - * This library should work with: - * Adafruit 3.5" TFT 320x480 + Touchscreen Breakout - * http://www.adafruit.com/products/2050 - * - * Adafruit TFT FeatherWing - 3.5" 480x320 Touchscreen for Feathers - * https://www.adafruit.com/product/3651 - * - */ - -/********************* - * INCLUDES - *********************/ -#include "hx8357.h" -#include "disp_spi.h" -#include "driver/gpio.h" -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -/********************* - * DEFINES - *********************/ - -#define TAG "HX8357" - -/********************** - * TYPEDEFS - **********************/ - -static gpio_num_t dcPin = GPIO_NUM_NC; - -/*The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct. */ -typedef struct { - uint8_t cmd; - uint8_t data[16]; - uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds. -} lcd_init_cmd_t; - -/********************** - * STATIC PROTOTYPES - **********************/ -static void hx8357_send_cmd(uint8_t cmd); -static void hx8357_send_data(void * data, uint16_t length); -static void hx8357_send_color(void * data, uint16_t length); - - -/********************** - * INITIALIZATION ARRAYS - **********************/ -// Taken from the Adafruit driver -static const uint8_t - initb[] = { - HX8357B_SETPOWER, 3, - 0x44, 0x41, 0x06, - HX8357B_SETVCOM, 2, - 0x40, 0x10, - HX8357B_SETPWRNORMAL, 2, - 0x05, 0x12, - HX8357B_SET_PANEL_DRIVING, 5, - 0x14, 0x3b, 0x00, 0x02, 0x11, - HX8357B_SETDISPLAYFRAME, 1, - 0x0c, // 6.8mhz - HX8357B_SETPANELRELATED, 1, - 0x01, // BGR - 0xEA, 3, // seq_undefined1, 3 args - 0x03, 0x00, 0x00, - 0xEB, 4, // undef2, 4 args - 0x40, 0x54, 0x26, 0xdb, - HX8357B_SETGAMMA, 12, - 0x00, 0x15, 0x00, 0x22, 0x00, 0x08, 0x77, 0x26, 0x66, 0x22, 0x04, 0x00, - HX8357_MADCTL, 1, - 0xC0, - HX8357_COLMOD, 1, - 0x55, - HX8357_PASET, 4, - 0x00, 0x00, 0x01, 0xDF, - HX8357_CASET, 4, - 0x00, 0x00, 0x01, 0x3F, - HX8357B_SETDISPMODE, 1, - 0x00, // CPU (DBI) and internal oscillation ?? - HX8357_SLPOUT, 0x80 + 120/5, // Exit sleep, then delay 120 ms - HX8357_DISPON, 0x80 + 10/5, // Main screen turn on, delay 10 ms - 0 // END OF COMMAND LIST - }, initd[] = { - HX8357_SWRESET, 0x80 + 100/5, // Soft reset, then delay 10 ms - HX8357D_SETC, 3, - 0xFF, 0x83, 0x57, - 0xFF, 0x80 + 500/5, // No command, just delay 300 ms - HX8357_SETRGB, 4, - 0x80, 0x00, 0x06, 0x06, // 0x80 enables SDO pin (0x00 disables) - HX8357D_SETCOM, 1, - 0x25, // -1.52V - HX8357_SETOSC, 1, - 0x68, // Normal mode 70Hz, Idle mode 55 Hz - HX8357_SETPANEL, 1, - 0x05, // BGR, Gate direction swapped - HX8357_SETPWR1, 6, - 0x00, // Not deep standby - 0x15, // BT - 0x1C, // VSPR - 0x1C, // VSNR - 0x83, // AP - 0xAA, // FS - HX8357D_SETSTBA, 6, - 0x50, // OPON normal - 0x50, // OPON idle - 0x01, // STBA - 0x3C, // STBA - 0x1E, // STBA - 0x08, // GEN - HX8357D_SETCYC, 7, - 0x02, // NW 0x02 - 0x40, // RTN - 0x00, // DIV - 0x2A, // DUM - 0x2A, // DUM - 0x0D, // GDON - 0x78, // GDOFF - HX8357D_SETGAMMA, 34, - 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, - 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, - 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A, - 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01, - HX8357_COLMOD, 1, - 0x57, // 0x55 = 16 bit, 0x57 = 24bit - HX8357_MADCTL, 1, - 0xC0, - HX8357_TEON, 1, - 0x00, // TW off - HX8357_TEARLINE, 2, - 0x00, 0x02, - HX8357_SLPOUT, 0x80 + 150/5, // Exit Sleep, then delay 150 ms - HX8357_DISPON, 0x80 + 50/5, // Main screen turn on, delay 50 ms - 0, // END OF COMMAND LIST - }; - -/********************** - * STATIC VARIABLES - **********************/ - -/********************** - * MACROS - **********************/ - -/********************** - * GLOBAL FUNCTIONS - **********************/ -static uint8_t displayType = HX8357D; - -void hx8357_reset(gpio_num_t resetPin) { - if (resetPin != GPIO_NUM_NC) { - esp_rom_gpio_pad_select_gpio(resetPin); - gpio_set_direction(resetPin, GPIO_MODE_OUTPUT); - - //Reset the display - gpio_set_level(resetPin, 0); - vTaskDelay(10 / portTICK_PERIOD_MS); - gpio_set_level(resetPin, 1); - vTaskDelay(120 / portTICK_PERIOD_MS); - } -} - -void hx8357_init(gpio_num_t newDcPin) { - ESP_LOGI(TAG, "Initialization."); - - dcPin = newDcPin; - - //Initialize non-SPI GPIOs - esp_rom_gpio_pad_select_gpio(dcPin); - gpio_set_direction(dcPin, GPIO_MODE_OUTPUT); - - //Send all the commands - const uint8_t *addr = (displayType == HX8357B) ? initb : initd; - uint8_t cmd, x, numArgs; - while((cmd = *addr++) > 0) { // '0' command ends list - x = *addr++; - numArgs = x & 0x7F; - if (cmd != 0xFF) { // '255' is ignored - if (x & 0x80) { // If high bit set, numArgs is a delay time - hx8357_send_cmd(cmd); - } else { - hx8357_send_cmd(cmd); - hx8357_send_data((void *) addr, numArgs); - addr += numArgs; - } - } - if (x & 0x80) { // If high bit set... - vTaskDelay(numArgs * 5 / portTICK_PERIOD_MS); // numArgs is actually a delay time (5ms units) - } - } - -#if HX8357_INVERT_COLORS - hx8357_send_cmd(HX8357_INVON); -#else - hx8357_send_cmd(HX8357_INVOFF); -#endif -} - -//(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); -void hx8357_flush(lv_disp_t* drv, const lv_area_t * area, uint8_t * color_map) -{ - uint32_t size = lv_area_get_width(area) * lv_area_get_height(area); - - /* Column addresses */ - uint8_t xb[] = { - (uint8_t) (area->x1 >> 8) & 0xFF, - (uint8_t) (area->x1) & 0xFF, - (uint8_t) (area->x2 >> 8) & 0xFF, - (uint8_t) (area->x2) & 0xFF, - }; - - /* Page addresses */ - uint8_t yb[] = { - (uint8_t) (area->y1 >> 8) & 0xFF, - (uint8_t) (area->y1) & 0xFF, - (uint8_t) (area->y2 >> 8) & 0xFF, - (uint8_t) (area->y2) & 0xFF, - }; - - /*Column addresses*/ - hx8357_send_cmd(HX8357_CASET); - hx8357_send_data(xb, 4); - - /*Page addresses*/ - hx8357_send_cmd(HX8357_PASET); - hx8357_send_data(yb, 4); - - /*Memory write*/ - hx8357_send_cmd(HX8357_RAMWR); - hx8357_send_color((void*)color_map, size * (LV_COLOR_DEPTH / 8)); -} - -void hx8357_set_madctl(uint8_t value) { - hx8357_send_cmd(HX8357_MADCTL); - hx8357_send_data(&value, 1); -} -/********************** - * STATIC FUNCTIONS - **********************/ - - -static void hx8357_send_cmd(uint8_t cmd) -{ - disp_wait_for_pending_transactions(); - gpio_set_level(dcPin, 0); /*Command mode*/ - disp_spi_send_data(&cmd, 1); -} - - -static void hx8357_send_data(void * data, uint16_t length) -{ - disp_wait_for_pending_transactions(); - gpio_set_level(dcPin, 1); /*Data mode*/ - disp_spi_send_data(data, length); -} - - -static void hx8357_send_color(void * data, uint16_t length) -{ - disp_wait_for_pending_transactions(); - gpio_set_level(dcPin, 1); /*Data mode*/ - disp_spi_send_colors(data, length); -} - -uint8_t hx8357d_get_gamma_curve_count() { - return 4; -} - -void hx8357d_set_gamme_curve(uint8_t index) { - uint8_t curve = 1; - switch (index) { - case 0: - curve = 0x01; - break; - case 1: - curve = 0x02; - break; - case 2: - curve = 0x04; - break; - case 3: - curve = 0x08; - break; - } - hx8357_send_cmd(HX8357D_SETGAMMA_BY_ID); - hx8357_send_data(&curve, 1); -} diff --git a/Devices/unphone/Source/hx8357/hx8357.h b/Devices/unphone/Source/hx8357/hx8357.h deleted file mode 100644 index 1a4d11edf..000000000 --- a/Devices/unphone/Source/hx8357/hx8357.h +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @file hx8357.h - * - * Roughly based on the Adafruit_HX8357_Library - * - * This library should work with: - * Adafruit 3.5" TFT 320x480 + Touchscreen Breakout - * http://www.adafruit.com/products/2050 - * - * Adafruit TFT FeatherWing - 3.5" 480x320 Touchscreen for Feathers - * https://www.adafruit.com/product/3651 - * - * Datasheet: - * https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf - */ - -#ifndef HX8357_H -#define HX8357_H - -#ifdef __cplusplus -extern "C" { -#endif - -/********************* - * INCLUDES - *********************/ -#include -#include - -#include -#include - -#define HX8357D 0xD ///< Our internal const for D type -#define HX8357B 0xB ///< Our internal const for B type - -#define HX8357_TFTWIDTH 320 ///< 320 pixels wide -#define HX8357_TFTHEIGHT 480 ///< 480 pixels tall - -#define HX8357_NOP 0x00 ///< No op -#define HX8357_SWRESET 0x01 ///< software reset -#define HX8357_RDDID 0x04 ///< Read ID -#define HX8357_RDDST 0x09 ///< (unknown) - -#define HX8357_RDPOWMODE 0x0A ///< Read power mode Read power mode -#define HX8357_RDMADCTL 0x0B ///< Read MADCTL -#define HX8357_RDCOLMOD 0x0C ///< Column entry mode -#define HX8357_RDDIM 0x0D ///< Read display image mode -#define HX8357_RDDSDR 0x0F ///< Read dosplay signal mode - -#define HX8357_SLPIN 0x10 ///< Enter sleep mode -#define HX8357_SLPOUT 0x11 ///< Exit sleep mode -#define HX8357B_PTLON 0x12 ///< Partial mode on -#define HX8357B_NORON 0x13 ///< Normal mode - -#define HX8357_INVOFF 0x20 ///< Turn off invert -#define HX8357_INVON 0x21 ///< Turn on invert -#define HX8357_DISPOFF 0x28 ///< Display on -#define HX8357_DISPON 0x29 ///< Display off - -#define HX8357_CASET 0x2A ///< Column addr set -#define HX8357_PASET 0x2B ///< Page addr set -#define HX8357_RAMWR 0x2C ///< Write VRAM -#define HX8357_RAMRD 0x2E ///< Read VRAm - -#define HX8357B_PTLAR 0x30 ///< (unknown) -#define HX8357_TEON 0x35 ///< Tear enable on -#define HX8357_TEARLINE 0x44 ///< (unknown) -#define HX8357_MADCTL 0x36 ///< Memory access control -#define HX8357_COLMOD 0x3A ///< Color mode - -#define HX8357_SETOSC 0xB0 ///< Set oscillator -#define HX8357_SETPWR1 0xB1 ///< Set power control -#define HX8357B_SETDISPLAY 0xB2 ///< Set display mode -#define HX8357_SETRGB 0xB3 ///< Set RGB interface -#define HX8357D_SETCOM 0xB6 ///< Set VCOM voltage - -#define HX8357B_SETDISPMODE 0xB4 ///< Set display mode -#define HX8357D_SETCYC 0xB4 ///< Set display cycle reg -#define HX8357B_SETOTP 0xB7 ///< Set OTP memory -#define HX8357D_SETC 0xB9 ///< Enable extension command - -#define HX8357B_SET_PANEL_DRIVING 0xC0 ///< Set panel drive mode -#define HX8357D_SETSTBA 0xC0 ///< Set source option -#define HX8357B_SETDGC 0xC1 ///< Set DGC settings -#define HX8357B_SETID 0xC3 ///< Set ID -#define HX8357B_SETDDB 0xC4 ///< Set DDB -#define HX8357B_SETDISPLAYFRAME 0xC5 ///< Set display frame -#define HX8357B_GAMMASET 0xC8 ///< Set Gamma correction -#define HX8357B_SETCABC 0xC9 ///< Set CABC -#define HX8357_SETPANEL 0xCC ///< Set Panel - -#define HX8357B_SETPOWER 0xD0 ///< Set power control -#define HX8357B_SETVCOM 0xD1 ///< Set VCOM -#define HX8357B_SETPWRNORMAL 0xD2 ///< Set power normal - -#define HX8357B_RDID1 0xDA ///< Read ID #1 -#define HX8357B_RDID2 0xDB ///< Read ID #2 -#define HX8357B_RDID3 0xDC ///< Read ID #3 -#define HX8357B_RDID4 0xDD ///< Read ID #4 - -#define HX8357D_SETGAMMA 0xE0 ///< Set Gamma curve data -#define HX8357D_SETGAMMA_BY_ID 0x26 ///< Set Gamma curve by curve identifier (0x01, 0x02, 0x04, 0x08) -#define HX8357B_SETGAMMA 0xC8 ///< Set Gamma -#define HX8357B_SETPANELRELATED 0xE9 ///< Set panel related - -// MADCTL -// See datasheet page 123: https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf -#define MADCTL_BIT_INDEX_COMMON_OUTPUTS_RAM 0 // N/A - set to 0 -#define MADCTL_BIT_INDEX_SEGMENT_OUTPUTS_RAM 1 // N/A - set to 0 -#define MADCTL_BIT_INDEX_DATA_LATCH_ORDER 2 // 0 = left-to-right refresh, 1 = right-to-left -#define MADCTL_BIT_INDEX_RGB_BGR_ORDER 3 // 0 = RGB, 1 = BGR -#define MADCTL_BIT_INDEX_LINE_ADDRESS_ORDER 4 // 0 = top-to-bottom refresh, 1 = bottom-to-top -#define MADCTL_BIT_INDEX_PAGE_COLUMN_ORDER 5 // 0 = normal, 1 = reverse -#define MADCTL_BIT_INDEX_COLUMN_ADDRESS_ORDER 6 // 0 = left-to-right, 1 = right-to-left -#define MADCTL_BIT_INDEX_PAGE_ADDRESS_ORDER 7 // 0 = top-to-bottom, 1 = bottom-to-top - -void hx8357_reset(gpio_num_t resetPin); -void hx8357_init(gpio_num_t dcPin); -void hx8357_set_madctl(uint8_t value); -void hx8357_flush(lv_disp_t* drv, const lv_area_t* area, uint8_t* color_map); - -uint8_t hx8357d_get_gamma_curve_count(); -/** - * Note: this doesn't work, even though the manual says it should - * Page 141: https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf - */ -void hx8357d_set_gamme_curve(uint8_t index); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /*HX8357_H*/ diff --git a/Devices/unphone/Source/lvgl_spi_conf.h b/Devices/unphone/Source/lvgl_spi_conf.h deleted file mode 100644 index bde9214c3..000000000 --- a/Devices/unphone/Source/lvgl_spi_conf.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#define SPI_TFT_CLOCK_SPEED_HZ (26*1000*1000) -#define SPI_TFT_SPI_MODE (0) -#define DISP_SPI_CS GPIO_NUM_48 -#define DISP_SPI_INPUT_DELAY_NS 0 - diff --git a/Devices/unphone/devicetree.yaml b/Devices/unphone/devicetree.yaml index c2902c36a..1185f608a 100644 --- a/Devices/unphone/devicetree.yaml +++ b/Devices/unphone/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/hx8357-module + - Drivers/xpt2046-module dts: unphone.dts diff --git a/Devices/unphone/unphone.dts b/Devices/unphone/unphone.dts index da2c1f737..892d68885 100644 --- a/Devices/unphone/unphone.dts +++ b/Devices/unphone/unphone.dts @@ -7,8 +7,8 @@ #include #include #include -#include -#include +#include +#include / { compatible = "root"; @@ -37,7 +37,7 @@ pin-scl = <&gpio0 4 GPIO_FLAG_NONE>; }; - sdcard_spi: spi0 { + spi0 { compatible = "espressif,esp32-spi"; host = ; cs-gpios = <&gpio0 48 GPIO_FLAG_NONE>, // Display @@ -49,11 +49,19 @@ max-transfer-size = <65536>; display@0 { - compatible = "display-placeholder"; + compatible = "himax,hx8357"; + horizontal-resolution = <320>; + vertical-resolution = <480>; + mirror-x; + pixel-clock-hz = <26000000>; + pin-dc = <&gpio0 47 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 46 GPIO_FLAG_NONE>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <320>; + y-max = <480>; }; sdcard@2 { diff --git a/Drivers/XPT2046/CMakeLists.txt b/Drivers/XPT2046/CMakeLists.txt deleted file mode 100644 index 3d442aa9a..000000000 --- a/Drivers/XPT2046/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -idf_component_register( - SRC_DIRS "Source" - INCLUDE_DIRS "Source" - REQUIRES Tactility EspLcdCompat esp_lcd_touch_xpt2046 -) diff --git a/Drivers/XPT2046/README.md b/Drivers/XPT2046/README.md deleted file mode 100644 index c822faa17..000000000 --- a/Drivers/XPT2046/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# XPT2046 - -A basic XPT2046 touch driver. diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.cpp b/Drivers/XPT2046/Source/Xpt2046Touch.cpp deleted file mode 100644 index 4c39a63cb..000000000 --- a/Drivers/XPT2046/Source/Xpt2046Touch.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Xpt2046Touch.h" - -#include - -#include -#include - -bool Xpt2046Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { - const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(configuration->spiPinCs); - return esp_lcd_new_panel_io_spi(configuration->spiDevice, &io_config, &outHandle) == ESP_OK; -} - -bool Xpt2046Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& panelHandle) { - return esp_lcd_touch_new_spi_xpt2046(ioHandle, &config, &panelHandle) == ESP_OK; -} - -esp_lcd_touch_config_t Xpt2046Touch::createEspLcdTouchConfig() { - return { - .x_max = configuration->xMax, - .y_max = configuration->yMax, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = configuration->swapXy, - .mirror_x = configuration->mirrorX, - .mirror_y = configuration->mirrorY, - }, - .process_coordinates = nullptr, - .interrupt_callback = nullptr, - .user_data = configuration.get(), - .driver_data = nullptr - }; -} diff --git a/Drivers/XPT2046/Source/Xpt2046Touch.h b/Drivers/XPT2046/Source/Xpt2046Touch.h deleted file mode 100644 index 975f4a279..000000000 --- a/Drivers/XPT2046/Source/Xpt2046Touch.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -#include - -class Xpt2046Touch : public EspLcdTouch { - -public: - - class Configuration { - public: - - Configuration( - esp_lcd_spi_bus_handle_t spiDevice, - gpio_num_t spiPinCs, - uint16_t xMax, - uint16_t yMax, - bool swapXy = false, - bool mirrorX = false, - bool mirrorY = false - ) : spiDevice(spiDevice), - spiPinCs(spiPinCs), - xMax(xMax), - yMax(yMax), - swapXy(swapXy), - mirrorX(mirrorX), - mirrorY(mirrorY) - {} - - esp_lcd_spi_bus_handle_t spiDevice; - gpio_num_t spiPinCs; - uint16_t xMax; - uint16_t yMax; - bool swapXy; - bool mirrorX; - bool mirrorY; - }; - -private: - - std::unique_ptr configuration; - - bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override; - - bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override; - - esp_lcd_touch_config_t createEspLcdTouchConfig() override; - -public: - - explicit Xpt2046Touch(std::unique_ptr inConfiguration) : configuration(std::move(inConfiguration)) { - assert(configuration != nullptr); - } - - std::string getName() const final { return "XPT2046"; } - - std::string getDescription() const final { return "XPT2046 SPI touch driver"; } -}; diff --git a/Drivers/hx8357-module/CMakeLists.txt b/Drivers/hx8357-module/CMakeLists.txt new file mode 100644 index 000000000..d81a6638d --- /dev/null +++ b/Drivers/hx8357-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(hx8357-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 driver +) diff --git a/Drivers/hx8357-module/LICENSE-Apache-2.0.md b/Drivers/hx8357-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/hx8357-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/hx8357-module/README.md b/Drivers/hx8357-module/README.md new file mode 100644 index 000000000..b0fdc5586 --- /dev/null +++ b/Drivers/hx8357-module/README.md @@ -0,0 +1,7 @@ +# HX8357 Display Driver + +A kernel driver for the `HX8357-D` display panel (24bpp/RGB888). No ESP-IDF `esp_lcd` component exists for this controller, so this driver speaks its raw SPI command protocol directly rather than wrapping `esp_lcd_panel_io`/`esp_lcd_panel`. + +See https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/hx8357-module/bindings/himax,hx8357.yaml b/Drivers/hx8357-module/bindings/himax,hx8357.yaml new file mode 100644 index 000000000..779297667 --- /dev/null +++ b/Drivers/hx8357-module/bindings/himax,hx8357.yaml @@ -0,0 +1,64 @@ +description: > + Himax HX8357-D display panel. 24bpp/RGB888 only. No ESP-IDF esp_lcd component exists for this + controller, so this driver speaks the panel's raw SPI command protocol directly (bit-banged + DC line, blocking spi_device_transmit()) rather than wrapping esp_lcd_panel_io/esp_lcd_panel. + No bgr-order property: unlike the RGB565 panels (ili9341-module, st7789-module), there is no + DISPLAY_COLOR_FORMAT_BGR888 in the kernel model, so a BGR-wired panel isn't representable here. + +compatible: "himax,hx8357" + +bus: spi + +properties: + horizontal-resolution: + type: int + required: true + description: Horizontal resolution in pixels + vertical-resolution: + type: int + required: true + description: Vertical resolution in pixels + gap-x: + type: int + default: 0 + description: X offset applied to all draw operations + gap-y: + type: int + default: 0 + description: Y offset applied to all draw operations + swap-xy: + type: boolean + default: false + description: Swap the X and Y axes + mirror-x: + type: boolean + default: true + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + invert-color: + type: boolean + default: false + description: Invert the panel's color output + pixel-clock-hz: + type: int + default: 26000000 + description: SPI pixel clock frequency in Hz + pin-dc: + type: phandles + required: true + description: Data/Command GPIO pin + pin-reset: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Reset GPIO pin + reset-active-high: + type: boolean + default: false + description: Whether the reset pin is active high + backlight: + type: phandle + default: "NULL" + description: Optional reference to this display's backlight device diff --git a/Drivers/hx8357-module/devicetree.yaml b/Drivers/hx8357-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/hx8357-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/hx8357-module/include/bindings/hx8357.h b/Drivers/hx8357-module/include/bindings/hx8357.h new file mode 100644 index 000000000..24171f08b --- /dev/null +++ b/Drivers/hx8357-module/include/bindings/hx8357.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(hx8357, struct Hx8357Config) diff --git a/Drivers/hx8357-module/include/drivers/hx8357.h b/Drivers/hx8357-module/include/drivers/hx8357.h new file mode 100644 index 000000000..ec2f8a132 --- /dev/null +++ b/Drivers/hx8357-module/include/drivers/hx8357.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct Hx8357Config { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; + int32_t gap_x; + int32_t gap_y; + bool swap_xy; + bool mirror_x; + bool mirror_y; + bool invert_color; + uint32_t pixel_clock_hz; + struct GpioPinSpec pin_dc; + struct GpioPinSpec pin_reset; + bool reset_active_high; + // Optional reference to this display's backlight device, NULL if none. + struct Device* backlight; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/hx8357-module/include/hx8357_module.h b/Drivers/hx8357-module/include/hx8357_module.h new file mode 100644 index 000000000..5239f8aa4 --- /dev/null +++ b/Drivers/hx8357-module/include/hx8357_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module hx8357_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/hx8357-module/source/hx8357.cpp b/Drivers/hx8357-module/source/hx8357.cpp new file mode 100644 index 000000000..8d7adc267 --- /dev/null +++ b/Drivers/hx8357-module/source/hx8357.cpp @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#define TAG "HX8357" +#define GET_CONFIG(device) (static_cast((device)->config)) + +namespace { + +// HX8357-D command set (subset actually used). See +// https://cdn-shop.adafruit.com/datasheets/HX8357-D_DS_April2012.pdf +constexpr uint8_t HX8357_SWRESET = 0x01; +constexpr uint8_t HX8357_SLPOUT = 0x11; +constexpr uint8_t HX8357_INVOFF = 0x20; +constexpr uint8_t HX8357_INVON = 0x21; +constexpr uint8_t HX8357_DISPON = 0x29; +constexpr uint8_t HX8357_CASET = 0x2A; +constexpr uint8_t HX8357_PASET = 0x2B; +constexpr uint8_t HX8357_RAMWR = 0x2C; +constexpr uint8_t HX8357_MADCTL = 0x36; +constexpr uint8_t HX8357_COLMOD = 0x3A; +constexpr uint8_t HX8357_TEON = 0x35; +constexpr uint8_t HX8357_TEARLINE = 0x44; +constexpr uint8_t HX8357_SETOSC = 0xB0; +constexpr uint8_t HX8357_SETPWR1 = 0xB1; +constexpr uint8_t HX8357_SETRGB = 0xB3; +constexpr uint8_t HX8357D_SETCOM = 0xB6; +constexpr uint8_t HX8357D_SETCYC = 0xB4; +constexpr uint8_t HX8357D_SETC = 0xB9; +constexpr uint8_t HX8357D_SETSTBA = 0xC0; +constexpr uint8_t HX8357_SETPANEL = 0xCC; +constexpr uint8_t HX8357D_SETGAMMA = 0xE0; + +// MADCTL bit indices, see the datasheet page 123. +constexpr uint8_t MADCTL_BIT_PAGE_COLUMN_ORDER = 5; // swap-xy +constexpr uint8_t MADCTL_BIT_COLUMN_ADDRESS_ORDER = 6; // mirror-x +constexpr uint8_t MADCTL_BIT_PAGE_ADDRESS_ORDER = 7; // mirror-y + +// Bring-up command list, ported verbatim (byte-for-byte) from the deprecated HAL's hx8357.c +// initd[] table (Devices/unphone/Source/hx8357/hx8357.c) - the HX8357B variant (initb[]) is +// dead code there (displayType is hardcoded to HX8357D) and was not ported. Format matches the +// original: cmd, then a length byte (bit7 set means "this is actually a delay in 5ms units, no +// data follows"), then that many data bytes. A single 0 cmd byte ends the list. +constexpr uint8_t INIT_CMDS[] = { + HX8357_SWRESET, 0x80 + 100 / 5, // Soft reset, then delay 100 ms + HX8357D_SETC, 3, + 0xFF, 0x83, 0x57, + 0xFF, 0x80 + 500 / 5, // No command, just delay 500 ms + HX8357_SETRGB, 4, + 0x80, 0x00, 0x06, 0x06, // 0x80 enables SDO pin (0x00 disables) + HX8357D_SETCOM, 1, + 0x25, // -1.52V + HX8357_SETOSC, 1, + 0x68, // Normal mode 70Hz, Idle mode 55 Hz + HX8357_SETPANEL, 1, + 0x05, // BGR, Gate direction swapped + HX8357_SETPWR1, 6, + 0x00, // Not deep standby + 0x15, // BT + 0x1C, // VSPR + 0x1C, // VSNR + 0x83, // AP + 0xAA, // FS + HX8357D_SETSTBA, 6, + 0x50, // OPON normal + 0x50, // OPON idle + 0x01, // STBA + 0x3C, // STBA + 0x1E, // STBA + 0x08, // GEN + HX8357D_SETCYC, 7, + 0x02, // NW 0x02 + 0x40, // RTN + 0x00, // DIV + 0x2A, // DUM + 0x2A, // DUM + 0x0D, // GDON + 0x78, // GDOFF + HX8357D_SETGAMMA, 34, + 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, + 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, + 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A, + 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01, + HX8357_COLMOD, 1, + 0x57, // 24bpp + HX8357_MADCTL, 1, + 0xC0, + HX8357_TEON, 1, + 0x00, // TW off + HX8357_TEARLINE, 2, + 0x00, 0x02, + HX8357_SLPOUT, 0x80 + 150 / 5, // Exit sleep, then delay 150 ms + HX8357_DISPON, 0x80 + 50 / 5, // Main screen turn on, delay 50 ms + 0, // END OF COMMAND LIST +}; + +} // namespace + +struct Hx8357Internal { + spi_device_handle_t spi_handle; + gpio_num_t dc_pin; + size_t max_transfer_size; +}; + +static int pin_or_unused(const struct GpioPinSpec& pin) { + return pin.gpio_controller == nullptr ? -1 : static_cast(pin.pin); +} + +// region Bring-up protocol + +static void spi_send(spi_device_handle_t spi, const uint8_t* data, size_t length) { + if (length == 0) { + return; + } + spi_transaction_t transaction = {}; + transaction.length = length * 8; + if (length <= 4) { + transaction.flags = SPI_TRANS_USE_TXDATA; + memcpy(transaction.tx_data, data, length); + } else { + transaction.tx_buffer = data; + } + // Blocking: physically completes before returning, unlike esp_lcd_panel_draw_bitmap() - + // no semaphore/ISR-callback dance needed to honor DisplayApi's synchronous draw_bitmap contract. + spi_device_transmit(spi, &transaction); +} + +static void send_cmd(const Hx8357Internal* internal, uint8_t cmd) { + gpio_set_level(internal->dc_pin, 0); + spi_send(internal->spi_handle, &cmd, 1); +} + +// Chunked to stay under the SPI bus's max single-transaction transfer size (e.g. RAMWR pixel +// bursts can be hundreds of KB). CS toggles between chunks, which the panel tolerates: it only +// resets its RAMWR auto-increment pointer on a new command byte, not on CS deselect. +static void send_data(const Hx8357Internal* internal, const uint8_t* data, size_t length) { + gpio_set_level(internal->dc_pin, 1); + while (length > 0) { + size_t chunk = length < internal->max_transfer_size ? length : internal->max_transfer_size; + spi_send(internal->spi_handle, data, chunk); + data += chunk; + length -= chunk; + } +} + +static void run_init_cmds(const Hx8357Internal* internal) { + const uint8_t* addr = INIT_CMDS; + uint8_t cmd; + while ((cmd = *addr++) > 0) { // 0 command ends the list + uint8_t x = *addr++; + uint8_t num_args = x & 0x7F; + if (cmd != 0xFF) { // 0xFF is a no-op placeholder (only used to carry a delay) + send_cmd(internal, cmd); + if (!(x & 0x80)) { + send_data(internal, addr, num_args); + addr += num_args; + } + } + if (x & 0x80) { // high bit set: num_args is actually a delay, in 5ms units + vTaskDelay(pdMS_TO_TICKS(num_args * 5)); + } + } +} + +static void send_madctl(const Hx8357Internal* internal, const Hx8357Config* config) { + uint8_t madctl = 0; + if (config->swap_xy) madctl |= (1 << MADCTL_BIT_PAGE_COLUMN_ORDER); + if (config->mirror_x) madctl |= (1 << MADCTL_BIT_COLUMN_ADDRESS_ORDER); + if (config->mirror_y) madctl |= (1 << MADCTL_BIT_PAGE_ADDRESS_ORDER); + send_cmd(internal, HX8357_MADCTL); + send_data(internal, &madctl, 1); +} + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &SPI_CONTROLLER_TYPE); + + const auto* spi_config = static_cast(parent->config); + const auto* config = GET_CONFIG(device); + + struct GpioPinSpec cs_pin; + if (esp32_spi_get_cs_pin(device, &cs_pin) != ERROR_NONE) { + LOG_E(TAG, "Failed to resolve CS pin"); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(Hx8357Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->dc_pin = static_cast(pin_or_unused(config->pin_dc)); + // Clamped below the bus's configured max_transfer_size: that value only bounds the DMA + // buffer/descriptor allocation, not the SPI peripheral's own per-transaction bit-length + // register (e.g. 18 bits = 32768 bytes on ESP32-S3, 24 bits elsewhere) - so a large + // max-transfer-size in the devicetree (sized for e.g. an SD card) can still overflow a + // single spi_device_transmit() here. 4096 is safely under that hardware limit on every + // ESP32 variant. + constexpr size_t MAX_SPI_CHUNK_SIZE = 4096; + internal->max_transfer_size = (spi_config->max_transfer_size > 0 && static_cast(spi_config->max_transfer_size) < MAX_SPI_CHUNK_SIZE) + ? static_cast(spi_config->max_transfer_size) + : MAX_SPI_CHUNK_SIZE; + gpio_config_t dc_config = { + .pin_bit_mask = 1ULL << internal->dc_pin, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + if (gpio_config(&dc_config) != ESP_OK) { + LOG_E(TAG, "Failed to configure DC pin"); + free(internal); + return ERROR_RESOURCE; + } + + spi_device_interface_config_t device_config = { + .mode = 0, + .clock_speed_hz = static_cast(config->pixel_clock_hz), + .spics_io_num = pin_or_unused(cs_pin), + .flags = 0, + .queue_size = 1, + }; + + if (spi_bus_add_device((spi_host_device_t)spi_config->host, &device_config, &internal->spi_handle) != ESP_OK) { + LOG_E(TAG, "Failed to add SPI device"); + free(internal); + return ERROR_RESOURCE; + } + + // Hardware reset, in addition to the SWRESET the bring-up command list sends - matches the + // deprecated HAL's Hx8357Display::start(), which did both. + int reset_pin = pin_or_unused(config->pin_reset); + if (reset_pin != -1) { + gpio_config_t reset_config = { + .pin_bit_mask = 1ULL << reset_pin, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + if (gpio_config(&reset_config) != ESP_OK) { + LOG_E(TAG, "Failed to configure reset pin"); + spi_bus_remove_device(internal->spi_handle); + free(internal); + return ERROR_RESOURCE; + } + gpio_set_level(static_cast(reset_pin), config->reset_active_high ? 1 : 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(static_cast(reset_pin), config->reset_active_high ? 0 : 1); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + run_init_cmds(internal); + send_madctl(internal, config); + send_cmd(internal, config->invert_color ? HX8357_INVON : HX8357_INVOFF); + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + if (spi_bus_remove_device(internal->spi_handle) != ESP_OK) { + LOG_E(TAG, "Failed to remove SPI device"); + free(internal); + return ERROR_RESOURCE; + } + + free(internal); + return ERROR_NONE; +} + +// endregion + +// region DisplayApi + +static error_t hx8357_reset(Device*) { + // No standalone reset beyond what start_device() already does; matches the deprecated HAL + // (its reset() DisplayDevice override was never wired to anything beyond initial bring-up). + return ERROR_NONE; +} + +static error_t hx8357_init(Device*) { + return ERROR_NONE; +} + +static error_t hx8357_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + + // x_end/y_end are exclusive (see lvgl_display.c); CASET/PASET want an inclusive last pixel. + const int32_t x1 = x_start + config->gap_x; + const int32_t x2 = x_end + config->gap_x - 1; + const int32_t y1 = y_start + config->gap_y; + const int32_t y2 = y_end + config->gap_y - 1; + + const uint8_t xb[4] = { + static_cast((x1 >> 8) & 0xFF), static_cast(x1 & 0xFF), + static_cast((x2 >> 8) & 0xFF), static_cast(x2 & 0xFF), + }; + const uint8_t yb[4] = { + static_cast((y1 >> 8) & 0xFF), static_cast(y1 & 0xFF), + static_cast((y2 >> 8) & 0xFF), static_cast(y2 & 0xFF), + }; + + send_cmd(internal, HX8357_CASET); + send_data(internal, xb, 4); + send_cmd(internal, HX8357_PASET); + send_data(internal, yb, 4); + send_cmd(internal, HX8357_RAMWR); + + const size_t pixel_count = static_cast(x_end - x_start) * static_cast(y_end - y_start); + send_data(internal, static_cast(color_data), pixel_count * 3); // RGB888 = 3 bytes/pixel + + return ERROR_NONE; +} + +static error_t hx8357_mirror(Device* device, bool x_axis, bool y_axis) { + auto* internal = static_cast(device_get_driver_data(device)); + auto* config = GET_CONFIG(device); + // Reads back through the same config the panel was started with; swap_xy/gap are unaffected. + Hx8357Config effective = *config; + effective.mirror_x = x_axis; + effective.mirror_y = y_axis; + send_madctl(internal, &effective); + return ERROR_NONE; +} + +static error_t hx8357_swap_xy(Device* device, bool swap_axes) { + auto* internal = static_cast(device_get_driver_data(device)); + auto* config = GET_CONFIG(device); + Hx8357Config effective = *config; + effective.swap_xy = swap_axes; + send_madctl(internal, &effective); + return ERROR_NONE; +} + +// Reads the devicetree-configured baseline, not live hardware state - same convention as +// ili9341-module/st7789-module: mirror()/swap_xy() calls after start_device() don't change +// what "rotation 0" means here. +static bool hx8357_get_swap_xy(Device* device) { + return GET_CONFIG(device)->swap_xy; +} + +static bool hx8357_get_mirror_x(Device* device) { + return GET_CONFIG(device)->mirror_x; +} + +static bool hx8357_get_mirror_y(Device* device) { + return GET_CONFIG(device)->mirror_y; +} + +static error_t hx8357_set_gap(Device*, int32_t, int32_t) { + // Not supported by this controller's fixed CASET/PASET-per-draw protocol beyond the static + // gap-x/gap-y devicetree config already folded into draw_bitmap(); matches the deprecated + // HAL, which never exposed a runtime gap either. + return ERROR_NOT_SUPPORTED; +} + +static error_t hx8357_invert_color(Device* device, bool invert_color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + send_cmd(internal, invert_color_data ? HX8357_INVON : HX8357_INVOFF); + return ERROR_NONE; +} + +static error_t hx8357_disp_on_off(Device* device, bool on_off) { + auto* internal = static_cast(device_get_driver_data(device)); + send_cmd(internal, on_off ? HX8357_DISPON : 0x28 /* HX8357_DISPOFF */); + return ERROR_NONE; +} + +static error_t hx8357_disp_sleep(Device* device, bool sleep) { + auto* internal = static_cast(device_get_driver_data(device)); + send_cmd(internal, sleep ? 0x10 /* HX8357_SLPIN */ : HX8357_SLPOUT); + return ERROR_NONE; +} + +static enum DisplayColorFormat hx8357_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_RGB888; +} + +static uint16_t hx8357_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t hx8357_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void hx8357_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t hx8357_get_frame_buffer_count(Device*) { + return 0; +} + +static error_t hx8357_get_backlight(Device* device, Device** backlight) { + auto* configured_backlight = GET_CONFIG(device)->backlight; + if (configured_backlight == nullptr) { + return ERROR_NOT_SUPPORTED; + } + *backlight = configured_backlight; + return ERROR_NONE; +} + +// endregion + +static const DisplayApi hx8357_display_api = { + .reset = hx8357_reset, + .init = hx8357_init, + .draw_bitmap = hx8357_draw_bitmap, + .mirror = hx8357_mirror, + .swap_xy = hx8357_swap_xy, + .get_swap_xy = hx8357_get_swap_xy, + .get_mirror_x = hx8357_get_mirror_x, + .get_mirror_y = hx8357_get_mirror_y, + .set_gap = hx8357_set_gap, + .invert_color = hx8357_invert_color, + .disp_on_off = hx8357_disp_on_off, + .disp_sleep = hx8357_disp_sleep, + .get_color_format = hx8357_get_color_format, + .get_resolution_x = hx8357_get_resolution_x, + .get_resolution_y = hx8357_get_resolution_y, + .get_frame_buffer = hx8357_get_frame_buffer, + .get_frame_buffer_count = hx8357_get_frame_buffer_count, + .get_backlight = hx8357_get_backlight, +}; + +Driver hx8357_driver = { + .name = "hx8357", + .compatible = (const char*[]) { "himax,hx8357", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &hx8357_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &hx8357_module, + .internal = nullptr +}; diff --git a/Drivers/hx8357-module/source/module.cpp b/Drivers/hx8357-module/source/module.cpp new file mode 100644 index 000000000..538243a77 --- /dev/null +++ b/Drivers/hx8357-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver hx8357_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&hx8357_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&hx8357_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module hx8357_module = { + .name = "hx8357", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml index 0d805b82b..38bc1ced1 100644 --- a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml +++ b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml @@ -71,5 +71,5 @@ properties: description: Whether the reset pin is active high backlight: type: phandle - default: NULL + default: "NULL" description: Optional reference to this display's backlight device diff --git a/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml b/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml index 797390bfd..cba33f327 100644 --- a/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml +++ b/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml @@ -71,5 +71,5 @@ properties: description: Whether the reset pin is active high backlight: type: phandle - default: NULL + default: "NULL" description: Optional reference to this display's backlight device diff --git a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml index fcc0e4585..15305bac7 100644 --- a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml +++ b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml @@ -63,5 +63,5 @@ properties: description: Whether the reset pin is active high backlight: type: phandle - default: NULL + default: "NULL" description: Optional reference to this display's backlight device diff --git a/Drivers/st7789-module/bindings/sitronix,st7789.yaml b/Drivers/st7789-module/bindings/sitronix,st7789.yaml index a3f0d43ab..39c5a33db 100644 --- a/Drivers/st7789-module/bindings/sitronix,st7789.yaml +++ b/Drivers/st7789-module/bindings/sitronix,st7789.yaml @@ -71,5 +71,5 @@ properties: description: Whether the reset pin is active high backlight: type: phandle - default: NULL + default: "NULL" description: Optional reference to this display's backlight device From 7fef42426fd9f36512478355687dce911a582863 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 01:16:55 +0200 Subject: [PATCH 12/15] Fix for display orientation --- Drivers/hx8357-module/source/hx8357.cpp | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Drivers/hx8357-module/source/hx8357.cpp b/Drivers/hx8357-module/source/hx8357.cpp index 8d7adc267..2b3afbd22 100644 --- a/Drivers/hx8357-module/source/hx8357.cpp +++ b/Drivers/hx8357-module/source/hx8357.cpp @@ -118,6 +118,14 @@ struct Hx8357Internal { spi_device_handle_t spi_handle; gpio_num_t dc_pin; size_t max_transfer_size; + // Live MADCTL orientation bits, seeded from config at start() and updated in place by + // mirror()/swap_xy() - NOT reconstructed from the static config each call. swap_xy() and + // mirror() are invoked back-to-back by lvgl_display_apply_rotation() on every rotation + // change, so rebuilding from config would silently discard whichever bit the other call + // just set. + bool swap_xy; + bool mirror_x; + bool mirror_y; }; static int pin_or_unused(const struct GpioPinSpec& pin) { @@ -180,11 +188,11 @@ static void run_init_cmds(const Hx8357Internal* internal) { } } -static void send_madctl(const Hx8357Internal* internal, const Hx8357Config* config) { +static void send_madctl(const Hx8357Internal* internal) { uint8_t madctl = 0; - if (config->swap_xy) madctl |= (1 << MADCTL_BIT_PAGE_COLUMN_ORDER); - if (config->mirror_x) madctl |= (1 << MADCTL_BIT_COLUMN_ADDRESS_ORDER); - if (config->mirror_y) madctl |= (1 << MADCTL_BIT_PAGE_ADDRESS_ORDER); + if (internal->swap_xy) madctl |= (1 << MADCTL_BIT_PAGE_COLUMN_ORDER); + if (internal->mirror_x) madctl |= (1 << MADCTL_BIT_COLUMN_ADDRESS_ORDER); + if (internal->mirror_y) madctl |= (1 << MADCTL_BIT_PAGE_ADDRESS_ORDER); send_cmd(internal, HX8357_MADCTL); send_data(internal, &madctl, 1); } @@ -272,8 +280,12 @@ static error_t start(Device* device) { vTaskDelay(pdMS_TO_TICKS(120)); } + internal->swap_xy = config->swap_xy; + internal->mirror_x = config->mirror_x; + internal->mirror_y = config->mirror_y; + run_init_cmds(internal); - send_madctl(internal, config); + send_madctl(internal); send_cmd(internal, config->invert_color ? HX8357_INVON : HX8357_INVOFF); device_set_driver_data(device, internal); @@ -340,21 +352,16 @@ static error_t hx8357_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta static error_t hx8357_mirror(Device* device, bool x_axis, bool y_axis) { auto* internal = static_cast(device_get_driver_data(device)); - auto* config = GET_CONFIG(device); - // Reads back through the same config the panel was started with; swap_xy/gap are unaffected. - Hx8357Config effective = *config; - effective.mirror_x = x_axis; - effective.mirror_y = y_axis; - send_madctl(internal, &effective); + internal->mirror_x = x_axis; + internal->mirror_y = y_axis; + send_madctl(internal); return ERROR_NONE; } static error_t hx8357_swap_xy(Device* device, bool swap_axes) { auto* internal = static_cast(device_get_driver_data(device)); - auto* config = GET_CONFIG(device); - Hx8357Config effective = *config; - effective.swap_xy = swap_axes; - send_madctl(internal, &effective); + internal->swap_xy = swap_axes; + send_madctl(internal); return ERROR_NONE; } From 9f92c6daaf9fce09ce19f73bd243ca69066be185 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 01:30:44 +0200 Subject: [PATCH 13/15] Add support for min/max property range in dts --- .../source/binding_parser.py | 2 + .../DevicetreeCompiler/source/generator.py | 21 +++ .../DevicetreeCompiler/source/models.py | 2 + .../tests/test_integration.py | 140 +++++++++++++++++- .../bindings/ilitek,ili9341.yaml | 2 + .../bindings/sitronix,st7789-i8080.yaml | 2 + .../bindings/sitronix,st7789.yaml | 2 + 7 files changed, 170 insertions(+), 1 deletion(-) diff --git a/Buildscripts/DevicetreeCompiler/source/binding_parser.py b/Buildscripts/DevicetreeCompiler/source/binding_parser.py index 538d3c06d..e50fbd42b 100644 --- a/Buildscripts/DevicetreeCompiler/source/binding_parser.py +++ b/Buildscripts/DevicetreeCompiler/source/binding_parser.py @@ -46,6 +46,8 @@ def parse_binding(file_path: str, binding_dirs: list[str]) -> Binding: description=details.get('description', '').strip(), default=details.get('default', None), element_type=details.get('element-type', None), + min=details.get('min', None), + max=details.get('max', None), ) properties_dict[name] = prop filename = os.path.basename(file_path) diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index 9d3a12e99..3bd641a46 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -118,6 +118,25 @@ def resolve_phandle_array_entries(device_property, devices): entries.append(str(item)) return entries +def validate_property_range(device: Device, binding_property, value) -> None: + """Enforces a binding's optional min/max on int-typed properties. Silently skips + values that aren't parseable as a plain integer literal (e.g. a passed-through + symbolic #define) - those can't be range-checked at compile time.""" + if binding_property.min is None and binding_property.max is None: + return + try: + numeric_value = int(value, 0) if isinstance(value, str) else int(value) + except (TypeError, ValueError): + return + if binding_property.min is not None and numeric_value < binding_property.min: + raise DevicetreeException( + f"Device '{device.node_name}' property '{binding_property.name}' value {numeric_value} is below minimum {binding_property.min}" + ) + if binding_property.max is not None and numeric_value > binding_property.max: + raise DevicetreeException( + f"Device '{device.node_name}' property '{binding_property.name}' value {numeric_value} is above maximum {binding_property.max}" + ) + def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], devices: list[Device]) -> list: compatible_property = find_device_property(device, "compatible") if compatible_property is None: @@ -168,6 +187,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de if device_property is None: if binding_property.default is not None: + validate_property_range(device, binding_property, binding_property.default) temp_prop = DeviceProperty( name=binding_property.name, type=binding_property.type, @@ -184,6 +204,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de else: raise DevicetreeException(f"Device {device.node_name} doesn't have property '{binding_property.name}' and no default value is set") else: + validate_property_range(device, binding_property, device_property.value) result.append(property_to_string(device_property, devices)) return result, phandle_arrays diff --git a/Buildscripts/DevicetreeCompiler/source/models.py b/Buildscripts/DevicetreeCompiler/source/models.py index e1b8aeabd..1b9c5543c 100644 --- a/Buildscripts/DevicetreeCompiler/source/models.py +++ b/Buildscripts/DevicetreeCompiler/source/models.py @@ -40,6 +40,8 @@ class BindingProperty: description: str default: object = None element_type: str = None + min: object = None + max: object = None @dataclass class Binding: diff --git a/Buildscripts/DevicetreeCompiler/tests/test_integration.py b/Buildscripts/DevicetreeCompiler/tests/test_integration.py index 1e9308da3..d1c6f96f1 100644 --- a/Buildscripts/DevicetreeCompiler/tests/test_integration.py +++ b/Buildscripts/DevicetreeCompiler/tests/test_integration.py @@ -76,6 +76,139 @@ def test_compile_invalid_dts(): print("PASSED") return True +def write_minmax_config(tmp_dir, device_property_line, binding_min=0, binding_max=3, binding_default=1): + config_dir = os.path.join(tmp_dir, "minmax_data") + bindings_dir = os.path.join(config_dir, "bindings") + os.makedirs(bindings_dir) + + with open(os.path.join(config_dir, "devicetree.yaml"), "w") as f: + f.write("dts: test.dts\nbindings: bindings") + + with open(os.path.join(config_dir, "test.dts"), "w") as f: + f.write(f"""/dts-v1/; + +/ {{ + compatible = "test,root"; + model = "Test Model"; + + test-device@0 {{ + compatible = "test,minmax-device"; + {device_property_line} + }}; +}}; +""") + + with open(os.path.join(bindings_dir, "test,root.yaml"), "w") as f: + f.write("description: Test root binding\ncompatible: \"test,root\"\nproperties:\n model:\n type: string\n") + + with open(os.path.join(bindings_dir, "test,minmax-device.yaml"), "w") as f: + f.write(f"""description: Test min/max binding +compatible: "test,minmax-device" +properties: + ranged-prop: + type: int + default: {binding_default} + min: {binding_min} + max: {binding_max} +""") + + return config_dir + +def test_minmax_within_range_succeeds(): + print("Running test_minmax_within_range_succeeds...") + with tempfile.TemporaryDirectory() as tmp_dir: + config_dir = write_minmax_config(tmp_dir, "ranged-prop = <2>;") + output_dir = os.path.join(tmp_dir, "output") + os.makedirs(output_dir) + + result = run_compiler(config_dir, output_dir) + + if result.returncode != 0: + print(f"FAILED: Compilation should have succeeded: {result.stderr} {result.stdout}") + return False + + print("PASSED") + return True + +def test_minmax_below_minimum_fails(): + print("Running test_minmax_below_minimum_fails...") + with tempfile.TemporaryDirectory() as tmp_dir: + config_dir = write_minmax_config(tmp_dir, "ranged-prop = <-1>;") + output_dir = os.path.join(tmp_dir, "output") + os.makedirs(output_dir) + + result = run_compiler(config_dir, output_dir) + + if result.returncode == 0: + print("FAILED: Compilation should have failed for a below-minimum value") + return False + + if "below minimum" not in result.stdout: + print(f"FAILED: Expected 'below minimum' error message, got: {result.stdout}") + return False + + print("PASSED") + return True + +def test_minmax_above_maximum_fails(): + print("Running test_minmax_above_maximum_fails...") + with tempfile.TemporaryDirectory() as tmp_dir: + config_dir = write_minmax_config(tmp_dir, "ranged-prop = <7>;") + output_dir = os.path.join(tmp_dir, "output") + os.makedirs(output_dir) + + result = run_compiler(config_dir, output_dir) + + if result.returncode == 0: + print("FAILED: Compilation should have failed for an above-maximum value") + return False + + if "above maximum" not in result.stdout: + print(f"FAILED: Expected 'above maximum' error message, got: {result.stdout}") + return False + + print("PASSED") + return True + +def test_minmax_out_of_range_default_fails(): + print("Running test_minmax_out_of_range_default_fails...") + with tempfile.TemporaryDirectory() as tmp_dir: + # Property omitted from the .dts entirely, so the (invalid) binding default is used. + config_dir = write_minmax_config(tmp_dir, "", binding_default=9) + output_dir = os.path.join(tmp_dir, "output") + os.makedirs(output_dir) + + result = run_compiler(config_dir, output_dir) + + if result.returncode == 0: + print("FAILED: Compilation should have failed for an out-of-range default value") + return False + + if "above maximum" not in result.stdout: + print(f"FAILED: Expected 'above maximum' error message, got: {result.stdout}") + return False + + print("PASSED") + return True + +def test_minmax_symbolic_value_skips_validation(): + print("Running test_minmax_symbolic_value_skips_validation...") + with tempfile.TemporaryDirectory() as tmp_dir: + # A passed-through symbolic constant can't be range-checked at compile time and must + # not be rejected just because a min/max is declared. + config_dir = write_minmax_config(tmp_dir, "ranged-prop = ;") + output_dir = os.path.join(tmp_dir, "output") + os.makedirs(output_dir) + + result = run_compiler(config_dir, output_dir) + + if result.returncode != 0: + print(f"FAILED: Compilation should have succeeded for a symbolic value: {result.stderr} {result.stdout}") + return False + + print("PASSED") + return True + def test_compile_missing_config(): print("Running test_compile_missing_config...") with tempfile.TemporaryDirectory() as output_dir: @@ -96,7 +229,12 @@ def test_compile_missing_config(): tests = [ test_compile_success, test_compile_invalid_dts, - test_compile_missing_config + test_compile_missing_config, + test_minmax_within_range_succeeds, + test_minmax_below_minimum_fails, + test_minmax_above_maximum_fails, + test_minmax_out_of_range_default_fails, + test_minmax_symbolic_value_skips_validation ] failed = 0 diff --git a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml index 38bc1ced1..68f6c271d 100644 --- a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml +++ b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml @@ -56,6 +56,8 @@ properties: gamma-curve: type: int default: 1 + min: 0 + max: 3 description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-dc: type: phandles diff --git a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml index 15305bac7..8eee8489e 100644 --- a/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml +++ b/Drivers/st7789-i8080-module/bindings/sitronix,st7789-i8080.yaml @@ -52,6 +52,8 @@ properties: gamma-curve: type: int default: 1 + min: 0 + max: 3 description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-reset: type: phandles diff --git a/Drivers/st7789-module/bindings/sitronix,st7789.yaml b/Drivers/st7789-module/bindings/sitronix,st7789.yaml index 39c5a33db..3602b5108 100644 --- a/Drivers/st7789-module/bindings/sitronix,st7789.yaml +++ b/Drivers/st7789-module/bindings/sitronix,st7789.yaml @@ -56,6 +56,8 @@ properties: gamma-curve: type: int default: 1 + min: 0 + max: 3 description: Gamma curve preset index [0,3], sent via the MIPI DCS GAMSET (0x26) command at bring-up pin-dc: type: phandles From e542f007dc18252a4aed5425ad1f9cd98894876d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 08:50:57 +0200 Subject: [PATCH 14/15] Support for new audio systems --- Devices/lilygo-tdeck/device.properties | 2 ++ Devices/lilygo-tdeck/devicetree.yaml | 3 ++ Devices/lilygo-tdeck/lilygo,tdeck.dts | 48 ++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Devices/lilygo-tdeck/device.properties b/Devices/lilygo-tdeck/device.properties index 8284f6c0b..7f4b9e457 100644 --- a/Devices/lilygo-tdeck/device.properties +++ b/Devices/lilygo-tdeck/device.properties @@ -21,3 +21,5 @@ display.dpi=143 cdn.infoMessage=To put the device into bootloader mode:
1. Press the trackball and then the reset button at the same time,
2. Let go of the reset button, then the trackball.

When this website reports that flashing is finished, you likely have to press the reset button. lvgl.colorDepth=16 + +sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y \ No newline at end of file diff --git a/Devices/lilygo-tdeck/devicetree.yaml b/Devices/lilygo-tdeck/devicetree.yaml index 6624359c5..59fbd4fc9 100644 --- a/Devices/lilygo-tdeck/devicetree.yaml +++ b/Devices/lilygo-tdeck/devicetree.yaml @@ -3,4 +3,7 @@ dependencies: - Drivers/st7789-module - Drivers/gt911-module - Drivers/lilygo-module + - Drivers/es7210-module + - Drivers/dummy-i2s-amp-module + - Drivers/audio-stream-module dts: lilygo,tdeck.dts diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 91935b517..265c3c6d3 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -64,6 +66,34 @@ pin-click = <&gpio0 0 GPIO_FLAG_NONE>; }; + // i2s0 and i2s1 are declared before i2c0 so i2s1 has started before the es7210 codec (below) references it. + // Speaker I2S (MAX98357A amplifier) + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 7 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 5 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>; + }; + + // Microphone I2S (ES7210), separate port from the speaker + i2s1 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 47 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 21 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 48 GPIO_FLAG_NONE>; + }; + + // MAX98357A class-D speaker amplifier. No I2C. Per schematic, SD_MODE/GAIN_SLOT is + // tied via fixed resistors (R21/R22), not driven by an MCU GPIO -- the amp is always + // enabled when powered, so no enable-gpio is wired here. + speaker0 { + compatible = "maxim,max98357a"; + i2s = <&i2s0>; + }; + i2c_internal: i2c0 { compatible = "espressif,esp32-i2c"; port = ; @@ -90,14 +120,18 @@ compatible = "lilygo,tdeck-keyboard-backlight"; reg = <0x55>; }; - }; - i2s0 { - compatible = "espressif,esp32-i2s"; - port = ; - pin-bclk = <&gpio0 7 GPIO_FLAG_NONE>; - pin-ws = <&gpio0 5 GPIO_FLAG_NONE>; - pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>; + // ES7210 microphone ADC. Per schematic all 4 MSM381A3729H9CP mic capsules are + // populated (MIC1-4), and AD0/AD1 are unconnected (default low -> address 0x40). + // Stock LilyGO firmware drives MIC1/MIC2 at 0dB and MIC3/MIC4 at 37.5dB gain -- + // the es7210 driver here applies a single gain to all active mics, so per-pair + // gain differentiation is not yet replicated. + es7210 { + compatible = "everest,es7210"; + reg = <0x40>; + i2s = <&i2s1>; + mic-mask = <15>; + }; }; display_backlight { From 4088b2e84443b736e0f250a1f892c910163e9c37 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Jul 2026 19:46:36 +0200 Subject: [PATCH 15/15] Updates and fixes --- .../cyd-2432s032c/Source/Configuration.cpp | 4 +- Devices/lilygo-thmi/lilygo,thmi.dts | 8 +- Devices/unphone/CMakeLists.txt | 6 +- Devices/unphone/Source/Configuration.cpp | 7 - Devices/unphone/Source/InitBoot.cpp | 201 ------------ Devices/unphone/Source/UnPhoneFeatures.cpp | 306 ------------------ Devices/unphone/Source/UnPhoneFeatures.h | 55 ---- Devices/unphone/Source/module.cpp | 23 -- .../unphone/bindings/unphone,nav-buttons.yaml | 20 ++ .../bindings/unphone,power-switch.yaml | 11 + Devices/unphone/device.properties | 2 + Devices/unphone/devicetree.yaml | 3 + Devices/unphone/source/BatteryManager.cpp | 72 +++++ Devices/unphone/source/BatteryManager.h | 41 +++ .../source/bindings/unphone_nav_buttons.h | 18 ++ .../source/bindings/unphone_power_switch.h | 17 + .../source/drivers/unphone_nav_buttons.cpp | 209 ++++++++++++ .../source/drivers/unphone_nav_buttons.h | 18 ++ .../source/drivers/unphone_power_switch.cpp | 90 ++++++ .../source/drivers/unphone_power_switch.h | 28 ++ Devices/unphone/source/init_boot.cpp | 108 +++++++ Devices/unphone/source/module.cpp | 66 ++++ Devices/unphone/unphone.dts | 71 ++++ Drivers/BQ24295/CMakeLists.txt | 5 - Drivers/BQ24295/README.md | 5 - Drivers/BQ24295/Source/Bq24295.cpp | 105 ------ Drivers/BQ24295/Source/Bq24295.h | 38 --- Drivers/bq24295-module/CMakeLists.txt | 11 + Drivers/bq24295-module/LICENSE-Apache-2.0.md | 195 +++++++++++ Drivers/bq24295-module/README.md | 7 + .../bq24295-module/bindings/ti,bq24295.yaml | 5 + Drivers/bq24295-module/devicetree.yaml | 3 + .../bq24295-module/include/bindings/bq24295.h | 15 + .../bq24295-module/include/bq24295_module.h | 14 + .../bq24295-module/include/drivers/bq24295.h | 42 +++ Drivers/bq24295-module/source/bq24295.cpp | 270 ++++++++++++++++ Drivers/bq24295-module/source/module.cpp | 35 ++ Drivers/hx8357-module/source/hx8357.cpp | 7 +- Drivers/hx8357-module/source/module.cpp | 4 +- Drivers/tca95xx-16bit-module/CMakeLists.txt | 11 + .../LICENSE-Apache-2.0.md | 195 +++++++++++ Drivers/tca95xx-16bit-module/README.md | 11 + .../bindings/ti,tca9535.yaml | 5 + .../bindings/ti,tca9539.yaml | 5 + Drivers/tca95xx-16bit-module/devicetree.yaml | 3 + .../include/bindings/tca95xx.h | 21 ++ .../include/drivers/tca95xx.h | 17 + .../include/tca95xx_module.h | 16 + .../tca95xx-16bit-module/source/module.cpp | 32 ++ .../tca95xx-16bit-module/source/tca95xx.cpp | 194 +++++++++++ .../bindings/xptek,xpt2046.yaml | 8 + .../xpt2046-module/include/drivers/xpt2046.h | 4 + Drivers/xpt2046-module/source/module.cpp | 3 + Drivers/xpt2046-module/source/xpt2046.cpp | 152 +++++++++ .../bindings/xptek,xpt2046-softspi.yaml | 8 + .../include/drivers/xpt2046_softspi.h | 4 + .../xpt2046-softspi-module/source/module.cpp | 3 + .../source/xpt2046_softspi.cpp | 151 +++++++++ Firmware/idf_component.yml | 2 - Modules/lvgl-module/source/lvgl_pointer.c | 68 ++-- .../espressif,esp32-gpio-backlight.yaml | 16 + .../tactility/bindings/esp32_gpio_backlight.h | 15 + .../tactility/drivers/esp32_gpio_backlight.h | 18 ++ .../source/drivers/esp32_gpio_backlight.cpp | 119 +++++++ Platforms/platform-esp32/source/module.cpp | 3 + Tactility/Source/app/display/Display.cpp | 34 -- .../app/kerneldisplay/KernelDisplay.cpp | 29 -- Tactility/Source/app/power/Power.cpp | 283 ++++++++-------- .../Source/service/statusbar/Statusbar.cpp | 1 - .../include/tactility/device_listener.h | 4 +- TactilityKernel/source/device_listener.cpp | 4 +- TactilityKernel/source/kernel_symbols.c | 89 +++++ 72 files changed, 2675 insertions(+), 998 deletions(-) delete mode 100644 Devices/unphone/Source/Configuration.cpp delete mode 100644 Devices/unphone/Source/InitBoot.cpp delete mode 100644 Devices/unphone/Source/UnPhoneFeatures.cpp delete mode 100644 Devices/unphone/Source/UnPhoneFeatures.h delete mode 100644 Devices/unphone/Source/module.cpp create mode 100644 Devices/unphone/bindings/unphone,nav-buttons.yaml create mode 100644 Devices/unphone/bindings/unphone,power-switch.yaml create mode 100644 Devices/unphone/source/BatteryManager.cpp create mode 100644 Devices/unphone/source/BatteryManager.h create mode 100644 Devices/unphone/source/bindings/unphone_nav_buttons.h create mode 100644 Devices/unphone/source/bindings/unphone_power_switch.h create mode 100644 Devices/unphone/source/drivers/unphone_nav_buttons.cpp create mode 100644 Devices/unphone/source/drivers/unphone_nav_buttons.h create mode 100644 Devices/unphone/source/drivers/unphone_power_switch.cpp create mode 100644 Devices/unphone/source/drivers/unphone_power_switch.h create mode 100644 Devices/unphone/source/init_boot.cpp create mode 100644 Devices/unphone/source/module.cpp delete mode 100644 Drivers/BQ24295/CMakeLists.txt delete mode 100644 Drivers/BQ24295/README.md delete mode 100644 Drivers/BQ24295/Source/Bq24295.cpp delete mode 100644 Drivers/BQ24295/Source/Bq24295.h create mode 100644 Drivers/bq24295-module/CMakeLists.txt create mode 100644 Drivers/bq24295-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/bq24295-module/README.md create mode 100644 Drivers/bq24295-module/bindings/ti,bq24295.yaml create mode 100644 Drivers/bq24295-module/devicetree.yaml create mode 100644 Drivers/bq24295-module/include/bindings/bq24295.h create mode 100644 Drivers/bq24295-module/include/bq24295_module.h create mode 100644 Drivers/bq24295-module/include/drivers/bq24295.h create mode 100644 Drivers/bq24295-module/source/bq24295.cpp create mode 100644 Drivers/bq24295-module/source/module.cpp create mode 100644 Drivers/tca95xx-16bit-module/CMakeLists.txt create mode 100644 Drivers/tca95xx-16bit-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/tca95xx-16bit-module/README.md create mode 100644 Drivers/tca95xx-16bit-module/bindings/ti,tca9535.yaml create mode 100644 Drivers/tca95xx-16bit-module/bindings/ti,tca9539.yaml create mode 100644 Drivers/tca95xx-16bit-module/devicetree.yaml create mode 100644 Drivers/tca95xx-16bit-module/include/bindings/tca95xx.h create mode 100644 Drivers/tca95xx-16bit-module/include/drivers/tca95xx.h create mode 100644 Drivers/tca95xx-16bit-module/include/tca95xx_module.h create mode 100644 Drivers/tca95xx-16bit-module/source/module.cpp create mode 100644 Drivers/tca95xx-16bit-module/source/tca95xx.cpp create mode 100644 Platforms/platform-esp32/bindings/espressif,esp32-gpio-backlight.yaml create mode 100644 Platforms/platform-esp32/include/tactility/bindings/esp32_gpio_backlight.h create mode 100644 Platforms/platform-esp32/include/tactility/drivers/esp32_gpio_backlight.h create mode 100644 Platforms/platform-esp32/source/drivers/esp32_gpio_backlight.cpp diff --git a/Devices/cyd-2432s032c/Source/Configuration.cpp b/Devices/cyd-2432s032c/Source/Configuration.cpp index 326040665..9e5abd20c 100644 --- a/Devices/cyd-2432s032c/Source/Configuration.cpp +++ b/Devices/cyd-2432s032c/Source/Configuration.cpp @@ -7,7 +7,7 @@ #include -static bool initBoot() { +static bool init_boot() { if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT)) { return false; } @@ -42,6 +42,6 @@ static tt::hal::DeviceVector createDevices() { } extern const tt::hal::Configuration hardwareConfiguration = { - .initBoot = initBoot, + .initBoot = init_boot, .createDevices = createDevices }; diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index f17b24b8a..33f023cec 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -99,6 +99,7 @@ vertical-resolution = <320>; pixel-clock-hz = <16000000>; backlight = <&display_backlight>; + gamma-curve = <2>; }; }; @@ -112,13 +113,6 @@ y-max = <320>; }; - // One-button mode: short press = select next, long press = press/enter (matches the - // deprecated HAL's old ButtonControl::createOneButtonControl(0)). - buttons { - compatible = "tactility,button-control"; - pin-primary = <&gpio0 0 GPIO_FLAG_NONE>; - }; - sdmmc0 { compatible = "espressif,esp32-sdmmc"; status = "disabled"; // Late-init is required, otherwise mounting fails diff --git a/Devices/unphone/CMakeLists.txt b/Devices/unphone/CMakeLists.txt index 0a4fc7e73..ce93d2491 100644 --- a/Devices/unphone/CMakeLists.txt +++ b/Devices/unphone/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_io_expander esp_io_expander_tca95xx_16bit BQ24295 + INCLUDE_DIRS "source" + REQUIRES Tactility bq24295-module ) diff --git a/Devices/unphone/Source/Configuration.cpp b/Devices/unphone/Source/Configuration.cpp deleted file mode 100644 index 75331ec46..000000000 --- a/Devices/unphone/Source/Configuration.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -bool initBoot(); - -extern const tt::hal::Configuration hardwareConfiguration = { - .initBoot = initBoot -}; diff --git a/Devices/unphone/Source/InitBoot.cpp b/Devices/unphone/Source/InitBoot.cpp deleted file mode 100644 index 3be9bb5cd..000000000 --- a/Devices/unphone/Source/InitBoot.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "UnPhoneFeatures.h" -#include -#include -#include -#include -#include - -constexpr auto* TAG = "unPhone"; - -std::shared_ptr unPhoneFeatures; -static std::unique_ptr powerThread; - -static const char* bootCountKey = "boot_count"; -static const char* powerOffCountKey = "power_off_count"; -static const char* powerSleepKey = "power_sleep_key"; - -class DeviceStats { - - tt::Preferences preferences = tt::Preferences("unphone"); - - int32_t getValue(const char* key) { - int32_t value = 0; - preferences.optInt32(key, value); - return value; - } - - void setValue(const char* key, int32_t value) { - preferences.putInt32(key, value); - } - - void increaseValue(const char* key) { - int32_t new_value = getValue(key) + 1; - setValue(key, new_value); - } - -public: - - void notifyBootStart() { - increaseValue(bootCountKey); - } - - void notifyPowerOff() { - increaseValue(powerOffCountKey); - } - - void notifyPowerSleep() { - increaseValue(powerSleepKey); - } - - void printInfo() { - LOG_I(TAG, "Device stats:"); - LOG_I(TAG, " boot: %d", (int)getValue(bootCountKey)); - LOG_I(TAG, " power off: %d", (int)getValue(powerOffCountKey)); - LOG_I(TAG, " power sleep: %d", (int)getValue(powerSleepKey)); - } -}; - -DeviceStats bootStats; - -enum class PowerState { - Initial, - On, - Off -}; - -#define DEBUG_POWER_STATES false - -#if DEBUG_POWER_STATES -/** Helper method to use the buzzer to signal the different power stages */ -static void powerInfoBuzz(uint8_t count) { - if (DEBUG_POWER_STATES) { - uint8_t index = 0; - while (index < count) { - unPhoneFeatures.setVibePower(true); - tt::kernel::delayMillis(50); - unPhoneFeatures.setVibePower(false); - - index++; - - if (index < count) { - tt::kernel::delayMillis(100); - } - } - } -} -#endif - -static void updatePowerSwitch() { - static PowerState last_state = PowerState::Initial; - - if (!unPhoneFeatures->isPowerSwitchOn()) { - if (last_state != PowerState::Off) { - last_state = PowerState::Off; - LOG_W(TAG, "Power off"); - } - - if (!unPhoneFeatures->isUsbPowerConnected()) { // and usb unplugged we go into shipping mode - LOG_W(TAG, "Shipping mode until USB connects"); - -#if DEBUG_POWER_STATES - unPhoneFeatures.setExpanderPower(true); - powerInfoBuzz(3); - unPhoneFeatures.setExpanderPower(false); -#endif - - unPhoneFeatures->turnPeripheralsOff(); - - bootStats.notifyPowerOff(); - - unPhoneFeatures->setShipping(true); // tell BM to stop supplying power until USB connects - } else { // When power switch is off, but USB is plugged in, we wait (deep sleep) until USB is unplugged. - LOG_W(TAG, "Waiting for USB disconnect to power off"); - -#if DEBUG_POWER_STATES - powerInfoBuzz(2); -#endif - - unPhoneFeatures->turnPeripheralsOff(); - - bootStats.notifyPowerSleep(); - - // Deep sleep for 1 minute, then awaken to check power state again - // GPIO trigger from power switch also awakens the device - unPhoneFeatures->wakeOnPowerSwitch(); - esp_sleep_enable_timer_wakeup(60000000); - esp_deep_sleep_start(); - } - } else { - if (last_state != PowerState::On) { - last_state = PowerState::On; - LOG_W(TAG, "Power on"); - -#if DEBUG_POWER_STATES - powerInfoBuzz(1); -#endif - } - } -} - -static int32_t powerSwitchMain() { // check power switch every 10th of sec - while (true) { - updatePowerSwitch(); - tt::kernel::delayMillis(200); - } -} - -static void startPowerSwitchThread() { - powerThread = std::make_unique( - "unphone_power_switch", - 4096, - []() { return powerSwitchMain(); } - ); - powerThread->start(); -} - -std::shared_ptr bq24295; - -static bool unPhonePowerOn() { - // Print early, in case of early crash (info will be from previous boot) - bootStats.printInfo(); - bootStats.notifyBootStart(); - - ::Device* i2c_internal = nullptr; - check(device_get_by_name("i2c_internal", &i2c_internal) == ERROR_NONE); - bq24295 = std::make_shared(i2c_internal); - device_put(i2c_internal); - - unPhoneFeatures = std::make_shared(bq24295); - - if (!unPhoneFeatures->init()) { - LOG_E(TAG, "UnPhoneFeatures init failed"); - return false; - } - - unPhoneFeatures->printInfo(); - - // Kernel devicetree devices (incl. the hx8357 display) already started by kernel_init() - // before initBoot() runs, so it's safe to turn the backlight on here now. - unPhoneFeatures->setBacklightPower(true); - unPhoneFeatures->setVibePower(false); - unPhoneFeatures->setIrPower(false); - unPhoneFeatures->setExpanderPower(false); - - // Turn off the device if power switch is on off state, - // instead of waiting for the Thread to start and continue booting - updatePowerSwitch(); - startPowerSwitchThread(); - - return true; -} - -bool initBoot() { - LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); - - if (!unPhonePowerOn()) { - LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); - return false; - } - - return true; -} diff --git a/Devices/unphone/Source/UnPhoneFeatures.cpp b/Devices/unphone/Source/UnPhoneFeatures.cpp deleted file mode 100644 index 14e2a2f10..000000000 --- a/Devices/unphone/Source/UnPhoneFeatures.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include "UnPhoneFeatures.h" - -#include -#include - -#include -#include -#include -#include -#include - -constexpr auto* TAG = "unPhoneFeatures"; - -namespace pin { - static const gpio_num_t BUTTON1 = GPIO_NUM_45; // left button - static const gpio_num_t BUTTON2 = GPIO_NUM_0; // middle button - static const gpio_num_t BUTTON3 = GPIO_NUM_21; // right button - static const gpio_num_t IR_LEDS = GPIO_NUM_12; - static const gpio_num_t LED_RED = GPIO_NUM_13; - static const gpio_num_t POWER_SWITCH = GPIO_NUM_18; -} // namespace pin - -namespace expanderpin { - static const esp_io_expander_pin_num_t BACKLIGHT = IO_EXPANDER_PIN_NUM_2; - static const esp_io_expander_pin_num_t EXPANDER_POWER = IO_EXPANDER_PIN_NUM_0; // enable exp brd if high - static const esp_io_expander_pin_num_t LED_GREEN = IO_EXPANDER_PIN_NUM_9; - static const esp_io_expander_pin_num_t LED_BLUE = IO_EXPANDER_PIN_NUM_13; - static const esp_io_expander_pin_num_t USB_VSENSE = IO_EXPANDER_PIN_NUM_14; - static const esp_io_expander_pin_num_t VIBE = IO_EXPANDER_PIN_NUM_7; -} // namespace expanderpin - -// TODO: Make part of a new type of UnPhoneFeatures data struct that holds all the thread-related data -QueueHandle_t interruptQueue; - -static void IRAM_ATTR navButtonInterruptHandler(void* args) { - int pinNumber = (int)args; - xQueueSendFromISR(interruptQueue, &pinNumber, NULL); -} - -static int32_t buttonHandlingThreadMain(const bool* interrupted) { - int pinNumber; - while (!*interrupted) { - if (xQueueReceive(interruptQueue, &pinNumber, portMAX_DELAY)) { - // The buttons might generate more than 1 click because of how they are built - LOG_I(TAG, "Pressed button %d", pinNumber); - if (pinNumber == pin::BUTTON1) { - tt::app::stop(); - } - - // Debounce all events for a short period of time - // This is easier than keeping track when each button was last pressed - tt::kernel::delayMillis(50); - xQueueReset(interruptQueue); - tt::kernel::delayMillis(50); - xQueueReset(interruptQueue); - } - } - return 0; -} - -UnPhoneFeatures::~UnPhoneFeatures() { - if (buttonHandlingThread.getState() != tt::Thread::State::Stopped) { - buttonHandlingThreadInterruptRequest = true; - buttonHandlingThread.join(); - } -} - -bool UnPhoneFeatures::initPowerSwitch() { - gpio_config_t config = { - .pin_bit_mask = BIT64(pin::POWER_SWITCH), - .mode = GPIO_MODE_INPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_POSEDGE, - }; - - if (gpio_config(&config) != ESP_OK) { - LOG_E(TAG, "Power pin init failed"); - return false; - } - - if (rtc_gpio_pullup_en(pin::POWER_SWITCH) == ESP_OK && - rtc_gpio_pulldown_en(pin::POWER_SWITCH) == ESP_OK) { - return true; - } else { - LOG_E(TAG, "Failed to set RTC for power switch"); - return false; - } -} - -bool UnPhoneFeatures::initNavButtons() { - if (!initGpioExpander()) { - LOG_E(TAG, "GPIO expander init failed"); - return false; - } - - interruptQueue = xQueueCreate(4, sizeof(int)); - - buttonHandlingThread.setName("unphone_buttons"); - buttonHandlingThread.setPriority(tt::Thread::Priority::High); - buttonHandlingThread.setStackSize(3072); - buttonHandlingThread.setMainFunction( - [this] { - return buttonHandlingThreadMain(&this->buttonHandlingThreadInterruptRequest); - } - ); - buttonHandlingThread.start(); - - uint64_t pin_mask = - BIT64(pin::BUTTON1) | - BIT64(pin::BUTTON2) | - BIT64(pin::BUTTON3); - - gpio_config_t config = { - .pin_bit_mask = pin_mask, - .mode = GPIO_MODE_INPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - /** - * We have to listen to the button release (= positive signal). - * If we listen to button press, the buttons might create more than 1 signal - * when they are continuously pressed. - */ - .intr_type = GPIO_INTR_POSEDGE, - }; - - if (gpio_config(&config) != ESP_OK) { - LOG_E(TAG, "Nav button pin init failed"); - return false; - } - - if ( - gpio_install_isr_service(0) != ESP_OK || - gpio_isr_handler_add(pin::BUTTON1, navButtonInterruptHandler, reinterpret_cast(pin::BUTTON1)) != ESP_OK || - gpio_isr_handler_add(pin::BUTTON2, navButtonInterruptHandler, reinterpret_cast(pin::BUTTON2)) != ESP_OK || - gpio_isr_handler_add(pin::BUTTON3, navButtonInterruptHandler, reinterpret_cast(pin::BUTTON3)) != ESP_OK - ) { - LOG_E(TAG, "Nav buttons ISR init failed"); - return false; - } - - return true; -} - -bool UnPhoneFeatures::initOutputPins() { - uint64_t output_pin_mask = - BIT64(pin::IR_LEDS) | - BIT64(pin::LED_RED); - - gpio_config_t config = { - .pin_bit_mask = output_pin_mask, - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - - if (gpio_config(&config) != ESP_OK) { - LOG_E(TAG, "Output pin init failed"); - return false; - } - - return true; -} - -bool UnPhoneFeatures::initGpioExpander() { - // ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110 corresponds with 0x26 from the docs at - // https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads#L206 - if (esp_io_expander_new_i2c_tca95xx_16bit(I2C_NUM_0, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110, &ioExpander) != ESP_OK) { - LOG_E(TAG, "IO expander init failed"); - return false; - } - assert(ioExpander != nullptr); - - // Output pins - - /** - * Important: - * If you clear the pins too late, the display or vibration motor might briefly turn on. - */ - - esp_io_expander_set_dir(ioExpander, expanderpin::BACKLIGHT, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, 0); - - esp_io_expander_set_dir(ioExpander, expanderpin::EXPANDER_POWER, IO_EXPANDER_OUTPUT); - - esp_io_expander_set_dir(ioExpander, expanderpin::LED_GREEN, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(ioExpander, expanderpin::LED_GREEN, 0); - - esp_io_expander_set_dir(ioExpander, expanderpin::LED_BLUE, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, 0); - - esp_io_expander_set_dir(ioExpander, expanderpin::VIBE, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(ioExpander, expanderpin::VIBE, 0); - - // Input pins - - esp_io_expander_set_dir(ioExpander, expanderpin::USB_VSENSE, IO_EXPANDER_INPUT); - - return true; -} - -bool UnPhoneFeatures::init() { - LOG_I(TAG, "init"); - - if (!initGpioExpander()) { - LOG_E(TAG, "GPIO expander init failed"); - return false; - } - - if (!initNavButtons()) { - LOG_E(TAG, "Input pin init failed"); - return false; - } - - if (!initOutputPins()) { - LOG_E(TAG, "Output pin init failed"); - return false; - } - - if (!initPowerSwitch()) { - LOG_E(TAG, "Power button init failed"); - return false; - } - - return true; -} - -void UnPhoneFeatures::printInfo() const { - esp_io_expander_print_state(ioExpander); - batteryManagement->printInfo(); - bool backlight_power; - const char* backlight_power_state = getBacklightPower(backlight_power) && backlight_power ? "on" : "off"; - LOG_I(TAG, "Backlight: %s", backlight_power_state); -} - -bool UnPhoneFeatures::setRgbLed(bool red, bool green, bool blue) const { - assert(ioExpander != nullptr); - return gpio_set_level(pin::LED_RED, red ? 1U : 0U) == ESP_OK && - esp_io_expander_set_level(ioExpander, expanderpin::LED_GREEN, green ? 1U : 0U) == ESP_OK && - esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, blue ? 1U : 0U) == ESP_OK; -} - -bool UnPhoneFeatures::setBacklightPower(bool on) const { - assert(ioExpander != nullptr); - return esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, on ? 1U : 0U) == ESP_OK; -} - -bool UnPhoneFeatures::getBacklightPower(bool& on) const { - assert(ioExpander != nullptr); - uint32_t level_mask; - if (esp_io_expander_get_level(ioExpander, expanderpin::BACKLIGHT, &level_mask) == ESP_OK) { - on = level_mask != 0U; - return true; - } else { - return false; - } -} - -bool UnPhoneFeatures::setIrPower(bool on) const { - assert(ioExpander != nullptr); - return gpio_set_level(pin::IR_LEDS, on ? 1U : 0U) == ESP_OK; -} - -bool UnPhoneFeatures::setVibePower(bool on) const { - assert(ioExpander != nullptr); - return esp_io_expander_set_level(ioExpander, expanderpin::VIBE, on ? 1U : 0U) == ESP_OK; -} - -bool UnPhoneFeatures::setExpanderPower(bool on) const { - assert(ioExpander != nullptr); - return esp_io_expander_set_level(ioExpander, expanderpin::EXPANDER_POWER, on ? 1U : 0U) == ESP_OK; -} - -bool UnPhoneFeatures::isPowerSwitchOn() const { - return gpio_get_level(pin::POWER_SWITCH) > 0; -} - -void UnPhoneFeatures::turnPeripheralsOff() const { - setExpanderPower(false); - setBacklightPower(false); - setIrPower(false); - setRgbLed(false, false, false); - setVibePower(false); -} - -bool UnPhoneFeatures::setShipping(bool on) const { - if (on) { - LOG_W(TAG, "setShipping: on"); - batteryManagement->setWatchDogTimer(Bq24295::WatchDogTimer::Disabled); - batteryManagement->setBatFetOn(false); - } else { - LOG_W(TAG, "setShipping: off"); - batteryManagement->setWatchDogTimer(Bq24295::WatchDogTimer::Enabled40s); - batteryManagement->setBatFetOn(true); - } - return true; -} - -void UnPhoneFeatures::wakeOnPowerSwitch() const { - esp_sleep_enable_ext0_wakeup(pin::POWER_SWITCH, 1); -} - -bool UnPhoneFeatures::isUsbPowerConnected() const { - return batteryManagement->isUsbPowerConnected(); -} diff --git a/Devices/unphone/Source/UnPhoneFeatures.h b/Devices/unphone/Source/UnPhoneFeatures.h deleted file mode 100644 index adc06ef38..000000000 --- a/Devices/unphone/Source/UnPhoneFeatures.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include -#include - -/** - * Easy access to GPIO pins - */ -class UnPhoneFeatures final { - -private: - - esp_io_expander_handle_t ioExpander = nullptr; - tt::Thread buttonHandlingThread; - bool buttonHandlingThreadInterruptRequest = false; - - bool initNavButtons(); - static bool initOutputPins(); - static bool initPowerSwitch(); - bool initGpioExpander(); - - std::shared_ptr batteryManagement; - -public: - - explicit UnPhoneFeatures(std::shared_ptr bq24295) : batteryManagement(std::move(bq24295)) { - assert(batteryManagement != nullptr); - } - - ~UnPhoneFeatures(); - - bool init(); - - bool setBacklightPower(bool on) const; - bool getBacklightPower(bool& on) const; - bool setIrPower(bool on) const; - bool setVibePower(bool on) const; - bool setExpanderPower(bool on) const; - - bool isPowerSwitchOn() const; - - void turnPeripheralsOff() const; - - /** Battery management (BQ24295) will stop supplying power until USB connects */ - bool setShipping(bool on) const; - - void wakeOnPowerSwitch() const; - - bool isUsbPowerConnected() const; - - bool setRgbLed(bool red, bool green, bool blue) const; - - void printInfo() const; -}; diff --git a/Devices/unphone/Source/module.cpp b/Devices/unphone/Source/module.cpp deleted file mode 100644 index 0c28db5a0..000000000 --- a/Devices/unphone/Source/module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -extern "C" { - -static error_t start() { - // Empty for now - return ERROR_NONE; -} - -static error_t stop() { - // Empty for now - return ERROR_NONE; -} - -struct Module unphone_module = { - .name = "unphone", - .start = start, - .stop = stop, - .symbols = nullptr, - .internal = nullptr -}; - -} diff --git a/Devices/unphone/bindings/unphone,nav-buttons.yaml b/Devices/unphone/bindings/unphone,nav-buttons.yaml new file mode 100644 index 000000000..61fad598a --- /dev/null +++ b/Devices/unphone/bindings/unphone,nav-buttons.yaml @@ -0,0 +1,20 @@ +description: > + unPhone physical navigation buttons: 3 buttons wired directly to native ESP32 GPIO + pins. Button 1 (left) stops the currently running app on release; buttons 2 and 3 + are read but currently have no assigned action. + +compatible: "unphone,nav-buttons" + +properties: + pin-button1: + type: phandles + required: true + description: GPIO pin connected to the left (button 1) nav button + pin-button2: + type: phandles + required: true + description: GPIO pin connected to the middle (button 2) nav button + pin-button3: + type: phandles + required: true + description: GPIO pin connected to the right (button 3) nav button diff --git a/Devices/unphone/bindings/unphone,power-switch.yaml b/Devices/unphone/bindings/unphone,power-switch.yaml new file mode 100644 index 000000000..56777e77d --- /dev/null +++ b/Devices/unphone/bindings/unphone,power-switch.yaml @@ -0,0 +1,11 @@ +description: > + unPhone physical power slide switch. Reflects whether the switch is in the "on" + position and can arm deep-sleep wakeup for when the switch is moved to "on". + +compatible: "unphone,power-switch" + +properties: + pin: + type: phandles + required: true + description: GPIO pin connected to the power switch diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties index 45de3205b..3d443791e 100644 --- a/Devices/unphone/device.properties +++ b/Devices/unphone/device.properties @@ -10,6 +10,8 @@ hardware.spiRamMode=OCT hardware.spiRamSpeed=80M hardware.bluetooth=true +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=3.5" diff --git a/Devices/unphone/devicetree.yaml b/Devices/unphone/devicetree.yaml index 1185f608a..4d2d659e6 100644 --- a/Devices/unphone/devicetree.yaml +++ b/Devices/unphone/devicetree.yaml @@ -2,4 +2,7 @@ dependencies: - Platforms/platform-esp32 - Drivers/hx8357-module - Drivers/xpt2046-module + - Drivers/tca95xx-16bit-module + - Drivers/bq24295-module +bindings: bindings dts: unphone.dts diff --git a/Devices/unphone/source/BatteryManager.cpp b/Devices/unphone/source/BatteryManager.cpp new file mode 100644 index 000000000..9804afc3f --- /dev/null +++ b/Devices/unphone/source/BatteryManager.cpp @@ -0,0 +1,72 @@ +#include "BatteryManager.h" + +#include +#include +#include +#include +#include +#include + +#include + +constexpr auto* TAG = "unPhoneFeatures"; + +namespace expanderpin { + static constexpr gpio_pin_t EXPANDER_POWER = 0; // enable exp brd if high +} + +bool BatteryManager::initGpioExpander() { + if (device_get_by_name("tca9535", &expander) != ERROR_NONE) { + LOG_E(TAG, "IO expander device not found"); + return false; + } + + expanderPowerPin = gpio_descriptor_acquire(expander, expanderpin::EXPANDER_POWER, GPIO_OWNER_GPIO); + check(expanderPowerPin != nullptr); + gpio_descriptor_set_flags(expanderPowerPin, GPIO_FLAG_DIRECTION_OUTPUT); + + device_put(expander); + + return true; +} + +bool BatteryManager::init() { + LOG_I(TAG, "init"); + + if (!initGpioExpander()) { + LOG_E(TAG, "GPIO expander init failed"); + return false; + } + + return true; +} + +bool BatteryManager::setExpanderPower(bool on) const { + return gpio_descriptor_set_level(expanderPowerPin, on) == ERROR_NONE; +} + +void BatteryManager::turnPeripheralsOff() const { + setExpanderPower(false); + + Device* backlight = nullptr; + check(device_get_by_name("display_backlight", &backlight) == ERROR_NONE); + backlight_set_brightness(backlight, 0); // Allowed to fail, we don't care about the result + device_put(backlight); +} + +void BatteryManager::setShipping(bool on) const { + if (on) { + LOG_W(TAG, "setShipping: on"); + bq24295_set_watchdog_timer(batteryManagement, BQ24295_WATCHDOG_DISABLED); + bq24295_set_bat_fet_on(batteryManagement, false); + } else { + LOG_W(TAG, "setShipping: off"); + bq24295_set_watchdog_timer(batteryManagement, BQ24295_WATCHDOG_ENABLED_40S); + bq24295_set_bat_fet_on(batteryManagement, true); + } +} + +bool BatteryManager::isUsbPowerConnected() const { + bool connected = false; + return bq24295_is_usb_power_connected(batteryManagement, &connected) == ERROR_NONE && connected; +} diff --git a/Devices/unphone/source/BatteryManager.h b/Devices/unphone/source/BatteryManager.h new file mode 100644 index 000000000..0c84b540f --- /dev/null +++ b/Devices/unphone/source/BatteryManager.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +struct Device; +struct GpioDescriptor; + +/** + * Easy access to GPIO pins + */ +class BatteryManager final { + + ::Device* expander = nullptr; + ::Device* batteryManagement = nullptr; + ::GpioDescriptor* expanderPowerPin = nullptr; + + bool initGpioExpander(); + + +public: + + explicit BatteryManager() { + check(device_get_by_name("bq24295", &batteryManagement) == ERROR_NONE); + } + + ~BatteryManager() { + device_put(batteryManagement); + } + + bool init(); + + bool setExpanderPower(bool on) const; + + void turnPeripheralsOff() const; + + /** Battery management (BQ24295) will stop supplying power until USB connects */ + void setShipping(bool on) const; + + bool isUsbPowerConnected() const; +}; diff --git a/Devices/unphone/source/bindings/unphone_nav_buttons.h b/Devices/unphone/source/bindings/unphone_nav_buttons.h new file mode 100644 index 000000000..b956e2d1c --- /dev/null +++ b/Devices/unphone/source/bindings/unphone_nav_buttons.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// The devicetree compiler derives the expected config typedef name from the compatible +// string's suffix (e.g. "unphone,nav-buttons" -> nav_buttons_config_dt), not from the +// node name or driver name, so the tag here must match that exactly. +DEFINE_DEVICETREE(nav_buttons, struct UnphoneNavButtonsConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Devices/unphone/source/bindings/unphone_power_switch.h b/Devices/unphone/source/bindings/unphone_power_switch.h new file mode 100644 index 000000000..e18b71a9c --- /dev/null +++ b/Devices/unphone/source/bindings/unphone_power_switch.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// The devicetree compiler derives the expected config typedef name from the compatible +// string's suffix (e.g. "unphone,power-switch" -> power_switch_config_dt), not from the +// node name or driver name, so the tag here must match that exactly. +DEFINE_DEVICETREE(power_switch, struct UnphonePowerSwitchConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Devices/unphone/source/drivers/unphone_nav_buttons.cpp b/Devices/unphone/source/drivers/unphone_nav_buttons.cpp new file mode 100644 index 000000000..3b28f2122 --- /dev/null +++ b/Devices/unphone/source/drivers/unphone_nav_buttons.cpp @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "unphone_nav_buttons.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#define TAG "UnphoneNavButtons" +#define GET_CONFIG(device) (static_cast((device)->config)) +#define GET_INTERNAL(device) (static_cast(device_get_driver_data(device))) + +namespace { +// Only button 1 currently drives any behavior (stopping the running app); 2 and 3 are +// read and queued like button 1 but nothing consumes them yet (mirrors the original +// unPhone nav button behavior). +constexpr int BUTTON1_INDEX = 1; +constexpr int BUTTON2_INDEX = 2; +constexpr int BUTTON3_INDEX = 3; +} + +struct UnphoneNavButtonsInternal { + GpioDescriptor* pin_button1 = nullptr; + GpioDescriptor* pin_button2 = nullptr; + GpioDescriptor* pin_button3 = nullptr; + + QueueHandle_t event_queue = nullptr; + tt::Thread thread; + bool thread_interrupt_requested = false; +}; + +// region ISR callbacks + +static void IRAM_ATTR on_button1(void* arg) { + auto* internal = static_cast(arg); + int index = BUTTON1_INDEX; + xQueueSendFromISR(internal->event_queue, &index, nullptr); +} + +static void IRAM_ATTR on_button2(void* arg) { + auto* internal = static_cast(arg); + int index = BUTTON2_INDEX; + xQueueSendFromISR(internal->event_queue, &index, nullptr); +} + +static void IRAM_ATTR on_button3(void* arg) { + auto* internal = static_cast(arg); + int index = BUTTON3_INDEX; + xQueueSendFromISR(internal->event_queue, &index, nullptr); +} + +// endregion + +// region Consumer thread + +static int32_t nav_buttons_thread_main(UnphoneNavButtonsInternal* internal) { + int button_index; + while (!internal->thread_interrupt_requested) { + if (xQueueReceive(internal->event_queue, &button_index, portMAX_DELAY)) { + // The buttons might generate more than 1 click because of how they are built + LOG_I(TAG, "Pressed button %d", button_index); + if (button_index == BUTTON1_INDEX) { + tt::app::stop(); + } + + // Debounce all events for a short period of time + // This is easier than keeping track when each button was last pressed + tt::kernel::delayMillis(50); + xQueueReset(internal->event_queue); + tt::kernel::delayMillis(50); + xQueueReset(internal->event_queue); + } + } + return 0; +} + +// endregion + +// region Pin acquisition + +static error_t acquire_button(const GpioPinSpec& pin, void (*callback)(void*), void* arg, GpioDescriptor** out_descriptor) { + auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr) { + return ERROR_RESOURCE; + } + + // Digital pull-up; the buttons pull the pin low while pressed. Listen for the release + // (positive edge) rather than the press - if we listen to the press, these buttons can + // generate more than one signal when held down (mirrors the original unPhone init). + gpio_flags_t flags = GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP; + flags = GPIO_FLAG_INTERRUPT_TO_OPTIONS(flags, GPIO_INTERRUPT_POS_EDGE); + + error_t error = gpio_descriptor_set_flags(descriptor, flags); + if (error == ERROR_NONE) { + error = gpio_descriptor_add_callback(descriptor, callback, arg); + } + if (error == ERROR_NONE) { + error = gpio_descriptor_enable_interrupt(descriptor); + } + + if (error != ERROR_NONE) { + gpio_descriptor_remove_callback(descriptor); + gpio_descriptor_release(descriptor); + return error; + } + + *out_descriptor = descriptor; + return ERROR_NONE; +} + +static void release_button(GpioDescriptor*& descriptor) { + if (descriptor == nullptr) { + return; + } + gpio_descriptor_disable_interrupt(descriptor); + gpio_descriptor_remove_callback(descriptor); + gpio_descriptor_release(descriptor); + descriptor = nullptr; +} + +// endregion + +extern "C" { + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = new(std::nothrow) UnphoneNavButtonsInternal(); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->event_queue = xQueueCreate(4, sizeof(int)); + if (internal->event_queue == nullptr) { + delete internal; + return ERROR_OUT_OF_MEMORY; + } + + error_t error = acquire_button(config->pin_button1, on_button1, internal, &internal->pin_button1); + if (error == ERROR_NONE) { + error = acquire_button(config->pin_button2, on_button2, internal, &internal->pin_button2); + } + if (error == ERROR_NONE) { + error = acquire_button(config->pin_button3, on_button3, internal, &internal->pin_button3); + } + + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to acquire nav button pins"); + release_button(internal->pin_button1); + release_button(internal->pin_button2); + release_button(internal->pin_button3); + vQueueDelete(internal->event_queue); + delete internal; + return error; + } + + internal->thread.setName("unphone_nav_buttons"); + internal->thread.setPriority(tt::Thread::Priority::High); + internal->thread.setStackSize(3072); + internal->thread.setMainFunction([internal] { return nav_buttons_thread_main(internal); }); + internal->thread.start(); + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = GET_INTERNAL(device); + + if (internal->thread.getState() != tt::Thread::State::Stopped) { + internal->thread_interrupt_requested = true; + internal->thread.join(); + } + + release_button(internal->pin_button1); + release_button(internal->pin_button2); + release_button(internal->pin_button3); + vQueueDelete(internal->event_queue); + + device_set_driver_data(device, nullptr); + delete internal; + return ERROR_NONE; +} + +extern Module unphone_module; + +Driver unphone_nav_buttons_driver = { + .name = "unphone_nav_buttons", + .compatible = (const char*[]) { "unphone,nav-buttons", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &unphone_module, + .internal = nullptr +}; + +} diff --git a/Devices/unphone/source/drivers/unphone_nav_buttons.h b/Devices/unphone/source/drivers/unphone_nav_buttons.h new file mode 100644 index 000000000..9f35e5e74 --- /dev/null +++ b/Devices/unphone/source/drivers/unphone_nav_buttons.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct UnphoneNavButtonsConfig { + struct GpioPinSpec pin_button1; + struct GpioPinSpec pin_button2; + struct GpioPinSpec pin_button3; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Devices/unphone/source/drivers/unphone_power_switch.cpp b/Devices/unphone/source/drivers/unphone_power_switch.cpp new file mode 100644 index 000000000..e676fbc8f --- /dev/null +++ b/Devices/unphone/source/drivers/unphone_power_switch.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "unphone_power_switch.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "UnphonePowerSwitch" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct UnphonePowerSwitchInternal { + GpioDescriptor* descriptor; + gpio_num_t native_pin; +}; + +extern "C" { + +extern Module unphone_module; + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr) { + LOG_E(TAG, "Failed to acquire GPIO descriptor"); + return ERROR_RESOURCE; + } + + if (gpio_descriptor_set_flags(descriptor, config->pin.flags | GPIO_FLAG_DIRECTION_INPUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure power switch pin as input"); + gpio_descriptor_release(descriptor); + return ERROR_RESOURCE; + } + + gpio_num_t native_pin; + if (gpio_descriptor_get_native_pin_number(descriptor, &native_pin) != ERROR_NONE) { + LOG_E(TAG, "Power switch pin has no native pin number"); + gpio_descriptor_release(descriptor); + return ERROR_NOT_SUPPORTED; + } + + // Digital pull resistors are powered down during deep sleep; the RTC domain's own pull + // resistors are what actually hold the pin state while asleep, so both must be armed + // here (mirrors the original unPhone power switch init). + if (rtc_gpio_pullup_en(native_pin) != ESP_OK || rtc_gpio_pulldown_en(native_pin) != ESP_OK) { + LOG_E(TAG, "Failed to configure RTC pull resistors for power switch"); + gpio_descriptor_release(descriptor); + return ERROR_RESOURCE; + } + + auto* internal = new UnphonePowerSwitchInternal { .descriptor = descriptor, .native_pin = native_pin }; + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + gpio_descriptor_release(internal->descriptor); + delete internal; + return ERROR_NONE; +} + +error_t unphone_power_switch_is_on(Device* device, bool* on) { + auto* internal = static_cast(device_get_driver_data(device)); + return gpio_descriptor_get_level(internal->descriptor, on); +} + +error_t unphone_power_switch_enable_wake(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + auto esp_error = esp_sleep_enable_ext0_wakeup(internal->native_pin, 1); + return esp_err_to_error(esp_error); +} + +Driver unphone_power_switch_driver = { + .name = "unphone_power_switch", + .compatible = (const char*[]) { "unphone,power-switch", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &unphone_module, + .internal = nullptr +}; + +} diff --git a/Devices/unphone/source/drivers/unphone_power_switch.h b/Devices/unphone/source/drivers/unphone_power_switch.h new file mode 100644 index 000000000..1713d12eb --- /dev/null +++ b/Devices/unphone/source/drivers/unphone_power_switch.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include + +struct UnphonePowerSwitchConfig { + struct GpioPinSpec pin; +}; + +/** @brief Reads whether the physical power switch is currently in the "on" position. */ +error_t unphone_power_switch_is_on(struct Device* device, bool* on); + +/** + * @brief Arms deep-sleep wakeup so the device wakes when the switch moves to "on". + * Only applies to the deep sleep entered right after this call. + */ +error_t unphone_power_switch_enable_wake(struct Device* device); + +#ifdef __cplusplus +} +#endif diff --git a/Devices/unphone/source/init_boot.cpp b/Devices/unphone/source/init_boot.cpp new file mode 100644 index 000000000..f3139abc1 --- /dev/null +++ b/Devices/unphone/source/init_boot.cpp @@ -0,0 +1,108 @@ +#include "BatteryManager.h" +#include "drivers/unphone_power_switch.h" + +#include +#include +#include + +#include +#include + +#include +#include + +constexpr auto* TAG = "unPhone"; + +std::shared_ptr batteryManagement; +static std::unique_ptr powerThread; + +enum class PowerState { + Initial, + On, + Off +}; + +static void update_power_switch(Device* power_switch) { + static PowerState last_state = PowerState::Initial; + + bool power_switch_on = false; + check(unphone_power_switch_is_on(power_switch, &power_switch_on) == ERROR_NONE); + + if (!power_switch_on) { + if (last_state != PowerState::Off) { + last_state = PowerState::Off; + LOG_W(TAG, "Power off"); + } + + if (!batteryManagement->isUsbPowerConnected()) { // and usb unplugged we go into shipping mode + LOG_W(TAG, "Shipping mode until USB connects"); + batteryManagement->turnPeripheralsOff(); + batteryManagement->setShipping(true); // tell BM to stop supplying power until USB connects + } else { // When power switch is off, but USB is plugged in, we wait (deep sleep) until USB is unplugged. + LOG_W(TAG, "Waiting for USB disconnect to power off"); + batteryManagement->turnPeripheralsOff(); + // Deep sleep for 1 minute, then awaken to check power state again + // GPIO trigger from power switch also awakens the device + unphone_power_switch_enable_wake(power_switch); + esp_sleep_enable_timer_wakeup(60000000); + esp_deep_sleep_start(); + } + } else { + if (last_state != PowerState::On) { + last_state = PowerState::On; + LOG_I(TAG, "Power on"); + } + } +} + +static int32_t power_switch_thread_main() { // check power switch every 10th of sec + Device* power_switch = nullptr; + check(device_get_by_name("power_switch", &power_switch) == ERROR_NONE); + + while (true) { + update_power_switch(power_switch); + vTaskDelay(200 / portTICK_PERIOD_MS); + } +} + +static void power_switch_thread_start() { + powerThread = std::make_unique( + "unphone_power_switch", + 4096, + []() -> int32_t { return power_switch_thread_main(); } + ); + powerThread->start(); +} + + +static bool power_on() { + batteryManagement = std::make_shared(); + if (!batteryManagement->init()) { + LOG_E(TAG, "UnPhoneFeatures init failed"); + return false; + } + + batteryManagement->setExpanderPower(false); + + // Turn off the device if power switch is on off state, + // instead of waiting for the Thread to start and continue booting + Device* power_switch = nullptr; + check(device_get_by_name("power_switch", &power_switch) == ERROR_NONE); + update_power_switch(power_switch); + device_put(power_switch); + + power_switch_thread_start(); + + return true; +} + +bool init_boot() { + LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); + + if (!power_on()) { + LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); + return false; + } + + return true; +} diff --git a/Devices/unphone/source/module.cpp b/Devices/unphone/source/module.cpp new file mode 100644 index 000000000..46a922f84 --- /dev/null +++ b/Devices/unphone/source/module.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#include + +bool init_boot(); + +extern "C" { + +extern Driver unphone_power_switch_driver; +extern Driver unphone_nav_buttons_driver; + +// The root device (the only device with no parent) reaching DEVICE_EVENT_STARTED means the +// whole devicetree has finished constructing/starting, so it's now safe to power on the +// unPhone-specific peripherals that initBoot() depends on (e.g. the bq24295 fuel gauge). +static void on_device_event(Device* device, DeviceEvent event, void* context) { + (void)context; + + static bool has_power_switch = false; + static bool has_bq24295 = false; + static bool has_backlight = false; + static bool did_init = false; + + if (event == DEVICE_EVENT_STARTED) { + if (strcmp(device->name, "power_switch") == 0) { has_power_switch = true; } + if (strcmp(device->name, "bq24295") == 0) { has_bq24295 = true; } + if (strcmp(device->name, "display_backlight") == 0) { has_backlight = true; } + } + + if (!did_init && has_power_switch && has_bq24295 && has_backlight) { + check(init_boot(), "unPhone initBoot failed"); + did_init = true; + } +} + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&unphone_power_switch_driver) == ERROR_NONE); + check(driver_construct_add(&unphone_nav_buttons_driver) == ERROR_NONE); + device_listener_add(on_device_event, nullptr); + return ERROR_NONE; +} + +static error_t stop() { + device_listener_remove(&on_device_event); + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&unphone_nav_buttons_driver) == ERROR_NONE); + check(driver_remove_destruct(&unphone_power_switch_driver) == ERROR_NONE); + return ERROR_NONE; +} + +struct Module unphone_module = { + .name = "unphone", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/unphone/unphone.dts b/Devices/unphone/unphone.dts index 892d68885..323ef0532 100644 --- a/Devices/unphone/unphone.dts +++ b/Devices/unphone/unphone.dts @@ -9,6 +9,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include / { compatible = "root"; @@ -35,6 +41,69 @@ clock-frequency = <400000>; pin-sda = <&gpio0 3 GPIO_FLAG_NONE>; pin-scl = <&gpio0 4 GPIO_FLAG_NONE>; + + tca9535 { + compatible = "ti,tca9535"; + reg = <0x26>; + }; + + bq24295 { + compatible = "ti,bq24295"; + reg = <0x6B>; + }; + }; + + power_switch { + compatible = "unphone,power-switch"; + pin = <&gpio0 18 GPIO_FLAG_NONE>; + }; + + nav_buttons { + compatible = "unphone,nav-buttons"; + pin-button1 = <&gpio0 45 GPIO_FLAG_NONE>; // left button + pin-button2 = <&gpio0 0 GPIO_FLAG_NONE>; // middle button + pin-button3 = <&gpio0 21 GPIO_FLAG_NONE>; // right button + }; + + display_backlight { + compatible = "espressif,esp32-gpio-backlight"; + pin-backlight = <&tca9535 2 GPIO_FLAG_NONE>; + }; + + usb_vsense { + compatible = "tactility,gpio-hog"; + pin = <&tca9535 14 GPIO_FLAG_NONE>; + mode = ; + }; + + vibration_motor { + compatible = "tactility,gpio-hog"; + pin = <&tca9535 7 GPIO_FLAG_NONE>; + mode = ; + }; + + ir_leds { + compatible = "tactility,gpio-hog"; + pin = <&gpio0 12 GPIO_FLAG_NONE>; + mode = ; + }; + + red_led { + compatible = "tactility,gpio-hog"; + pin = <&gpio0 13 GPIO_FLAG_NONE>; + mode = ; + }; + + green_led { + compatible = "tactility,gpio-hog"; + pin = <&tca9535 9 GPIO_FLAG_NONE>; + mode = ; + }; + + blue_led { + compatible = "tactility,gpio-hog"; + pin = <&tca9535 13 GPIO_FLAG_NONE>; + mode = ; }; spi0 { @@ -56,12 +125,14 @@ pixel-clock-hz = <26000000>; pin-dc = <&gpio0 47 GPIO_FLAG_NONE>; pin-reset = <&gpio0 46 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { compatible = "xptek,xpt2046"; x-max = <320>; y-max = <480>; + power-supply; }; sdcard@2 { diff --git a/Drivers/BQ24295/CMakeLists.txt b/Drivers/BQ24295/CMakeLists.txt deleted file mode 100644 index 8074f3b38..000000000 --- a/Drivers/BQ24295/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -idf_component_register( - SRC_DIRS "Source" - INCLUDE_DIRS "Source" - REQUIRES Tactility -) diff --git a/Drivers/BQ24295/README.md b/Drivers/BQ24295/README.md deleted file mode 100644 index 5ecc1181d..000000000 --- a/Drivers/BQ24295/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# BQ24295 - -Power management: I2C-controlled 3A single cell USB charger with narrow VDC 4.5-5.5V adjustable voltage at 1.5A synchronous boost operation. - -[Datasheet](https://www.ti.com/lit/ds/symlink/bq24295.pdf) diff --git a/Drivers/BQ24295/Source/Bq24295.cpp b/Drivers/BQ24295/Source/Bq24295.cpp deleted file mode 100644 index 730725e01..000000000 --- a/Drivers/BQ24295/Source/Bq24295.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "Bq24295.h" -#include - -constexpr auto* TAG = "BQ24295"; - -/** Reference: - * https://www.ti.com/lit/ds/symlink/bq24295.pdf - * https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads - */ -namespace registers { -static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl -static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control -static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status -static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status -} // namespace registers - -bool Bq24295::readChargeTermination(uint8_t& out) const { - return readRegister8(registers::CHARGE_TERMINATION, out); -} - -// region Watchdog -bool Bq24295::getWatchDogTimer(WatchDogTimer& out) const { - uint8_t value; - if (readChargeTermination(value)) { - uint8_t relevant_bits = value & (BIT(4) | BIT(5)); - switch (relevant_bits) { - case 0b000000: - out = WatchDogTimer::Disabled; - return true; - case 0b010000: - out = WatchDogTimer::Enabled40s; - return true; - case 0b100000: - out = WatchDogTimer::Enabled80s; - return true; - case 0b110000: - out = WatchDogTimer::Enabled160s; - return true; - default: - return false; - } - } - - return false; -} - -bool Bq24295::setWatchDogTimer(WatchDogTimer in) const { - uint8_t value; - if (readChargeTermination(value)) { - uint8_t bits_to_set = 0b00110000 & static_cast(in); - uint8_t value_cleared = value & 0b11001111; - uint8_t to_set = bits_to_set | value_cleared; - LOG_I(TAG, "WatchDogTimer: %02X -> %02X", value, to_set); - return writeRegister8(registers::CHARGE_TERMINATION, to_set); - } - - return false; -} - - -// endregoin - -// region Operation Control (REG07) - -bool Bq24295::setBatFetOn(bool on) const { - if (on) { - // bit 5 low means bat fet is on - return bitOff(registers::OPERATION_CONTROL, BIT(5)); - } else { - // bit 5 high means bat fet is off - return bitOn(registers::OPERATION_CONTROL, BIT(5)); - } -} - -// endregion - -// region Other - -bool Bq24295::getStatus(uint8_t& value) const { - return readRegister8(registers::STATUS, value); -} - -bool Bq24295::isUsbPowerConnected() const { - uint8_t status; - if (getStatus(status)) { - return (status & BIT(2)) != 0U; - } else { - return false; - } -} - -bool Bq24295::getVersion(uint8_t& value) const { - return readRegister8(registers::VERSION, value); -} - -void Bq24295::printInfo() const { - uint8_t version, status, charge_termination; - if (getStatus(status) && getVersion(version) && readChargeTermination(charge_termination)) { - LOG_I(TAG, "Version %d, status %02X, charge termination %02X", version, status, charge_termination); - } else { - LOG_E(TAG, "Failed to retrieve version and/or status"); - } -} - -// endregion \ No newline at end of file diff --git a/Drivers/BQ24295/Source/Bq24295.h b/Drivers/BQ24295/Source/Bq24295.h deleted file mode 100644 index ff69a6748..000000000 --- a/Drivers/BQ24295/Source/Bq24295.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include - -#define BQ24295_ADDRESS 0x6BU - -class Bq24295 final : public tt::hal::i2c::I2cDevice { - - - bool readChargeTermination(uint8_t& out) const; - -public: - - std::string getName() const final { return "BQ24295"; } - - std::string getDescription() const final { return "I2C-controlled single cell USB charger"; } - - enum class WatchDogTimer { - Disabled = 0b000000, - Enabled40s = 0b010000, - Enabled80s = 0b100000, - Enabled160s = 0b110000 - }; - - explicit Bq24295(::Device* controller) : I2cDevice(controller, BQ24295_ADDRESS) {} - - bool getWatchDogTimer(WatchDogTimer& out) const; - bool setWatchDogTimer(WatchDogTimer in) const; - - bool isUsbPowerConnected() const; - - bool setBatFetOn(bool on) const; - - bool getStatus(uint8_t& value) const; - bool getVersion(uint8_t& value) const; - - void printInfo() const; -}; diff --git a/Drivers/bq24295-module/CMakeLists.txt b/Drivers/bq24295-module/CMakeLists.txt new file mode 100644 index 000000000..a2f9a4729 --- /dev/null +++ b/Drivers/bq24295-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(bq24295-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/bq24295-module/LICENSE-Apache-2.0.md b/Drivers/bq24295-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bq24295-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/bq24295-module/README.md b/Drivers/bq24295-module/README.md new file mode 100644 index 000000000..bf299f986 --- /dev/null +++ b/Drivers/bq24295-module/README.md @@ -0,0 +1,7 @@ +# BQ24295 USB charger + +A driver for the TI `BQ24295` single-cell Li-ion USB battery charger: watchdog timer +control, BATFET (battery FET) enable/disable for shipping mode, USB power-good detection, +and status/version readback. + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/bq24295-module/bindings/ti,bq24295.yaml b/Drivers/bq24295-module/bindings/ti,bq24295.yaml new file mode 100644 index 000000000..f27191446 --- /dev/null +++ b/Drivers/bq24295-module/bindings/ti,bq24295.yaml @@ -0,0 +1,5 @@ +description: TI BQ24295 single-cell USB battery charger + +include: ["i2c-device.yaml"] + +compatible: "ti,bq24295" diff --git a/Drivers/bq24295-module/devicetree.yaml b/Drivers/bq24295-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/bq24295-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/bq24295-module/include/bindings/bq24295.h b/Drivers/bq24295-module/include/bindings/bq24295.h new file mode 100644 index 000000000..2c888b876 --- /dev/null +++ b/Drivers/bq24295-module/include/bindings/bq24295.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(bq24295, struct Bq24295Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bq24295-module/include/bq24295_module.h b/Drivers/bq24295-module/include/bq24295_module.h new file mode 100644 index 000000000..3ace9b196 --- /dev/null +++ b/Drivers/bq24295-module/include/bq24295_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module bq24295_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bq24295-module/include/drivers/bq24295.h b/Drivers/bq24295-module/include/drivers/bq24295.h new file mode 100644 index 000000000..a0114ff96 --- /dev/null +++ b/Drivers/bq24295-module/include/drivers/bq24295.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Bq24295Config { + /** Address on bus */ + uint8_t address; +}; + +enum Bq24295WatchDogTimer { + BQ24295_WATCHDOG_DISABLED = 0b000000, + BQ24295_WATCHDOG_ENABLED_40S = 0b010000, + BQ24295_WATCHDOG_ENABLED_80S = 0b100000, + BQ24295_WATCHDOG_ENABLED_160S = 0b110000 +}; + +error_t bq24295_get_watchdog_timer(struct Device* device, enum Bq24295WatchDogTimer* out); +error_t bq24295_set_watchdog_timer(struct Device* device, enum Bq24295WatchDogTimer in); + +error_t bq24295_is_usb_power_connected(struct Device* device, bool* connected); + +/** BATFET (battery FET): off disconnects the battery from the system (shipping mode). */ +error_t bq24295_set_bat_fet_on(struct Device* device, bool on); + +error_t bq24295_get_status(struct Device* device, uint8_t* value); +error_t bq24295_get_version(struct Device* device, uint8_t* value); + +void bq24295_print_info(struct Device* device); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bq24295-module/source/bq24295.cpp b/Drivers/bq24295-module/source/bq24295.cpp new file mode 100644 index 000000000..5def959d1 --- /dev/null +++ b/Drivers/bq24295-module/source/bq24295.cpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "BQ24295" +#define GET_CONFIG(device) (static_cast((device)->config)) + +/** Reference: + * https://www.ti.com/lit/ds/symlink/bq24295.pdf + * https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads + */ +static constexpr uint8_t REG_CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl +static constexpr uint8_t REG_OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control +static constexpr uint8_t REG_STATUS = 0x08U; // Datasheet page 38: System status +static constexpr uint8_t REG_VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status + +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); + +extern "C" { + +extern Module bq24295_module; + +static error_t read_charge_termination(Device* device, uint8_t* out) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + return i2c_controller_register8_get(parent, address, REG_CHARGE_TERMINATION, out, TIMEOUT); +} + +error_t bq24295_get_watchdog_timer(Device* device, Bq24295WatchDogTimer* out) { + uint8_t value; + error_t err = read_charge_termination(device, &value); + if (err != ERROR_NONE) { + return err; + } + + switch (value & 0b00110000) { + case BQ24295_WATCHDOG_DISABLED: + *out = BQ24295_WATCHDOG_DISABLED; + return ERROR_NONE; + case BQ24295_WATCHDOG_ENABLED_40S: + *out = BQ24295_WATCHDOG_ENABLED_40S; + return ERROR_NONE; + case BQ24295_WATCHDOG_ENABLED_80S: + *out = BQ24295_WATCHDOG_ENABLED_80S; + return ERROR_NONE; + case BQ24295_WATCHDOG_ENABLED_160S: + *out = BQ24295_WATCHDOG_ENABLED_160S; + return ERROR_NONE; + default: + return ERROR_RESOURCE; + } +} + +error_t bq24295_set_watchdog_timer(Device* device, Bq24295WatchDogTimer in) { + uint8_t value; + error_t err = read_charge_termination(device, &value); + if (err != ERROR_NONE) { + return err; + } + + uint8_t bits_to_set = 0b00110000 & static_cast(in); + uint8_t value_cleared = value & 0b11001111; + uint8_t to_set = bits_to_set | value_cleared; + LOG_I(TAG, "WatchDogTimer: %02X -> %02X", value, to_set); + + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + return i2c_controller_register8_set(parent, address, REG_CHARGE_TERMINATION, to_set, TIMEOUT); +} + +error_t bq24295_set_bat_fet_on(Device* device, bool on) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // bit 5 low means bat fet is on, bit 5 high means bat fet is off + if (on) { + return i2c_controller_register8_reset_bits(parent, address, REG_OPERATION_CONTROL, 1 << 5, TIMEOUT); + } else { + return i2c_controller_register8_set_bits(parent, address, REG_OPERATION_CONTROL, 1 << 5, TIMEOUT); + } +} + +error_t bq24295_get_status(Device* device, uint8_t* value) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + return i2c_controller_register8_get(parent, address, REG_STATUS, value, TIMEOUT); +} + +error_t bq24295_is_usb_power_connected(Device* device, bool* connected) { + uint8_t status; + error_t err = bq24295_get_status(device, &status); + if (err != ERROR_NONE) { + return err; + } + *connected = (status & (1 << 2)) != 0U; + return ERROR_NONE; +} + +error_t bq24295_get_version(Device* device, uint8_t* value) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + return i2c_controller_register8_get(parent, address, REG_VERSION, value, TIMEOUT); +} + +void bq24295_print_info(Device* device) { + uint8_t version, status, charge_termination; + if (bq24295_get_status(device, &status) == ERROR_NONE && + bq24295_get_version(device, &version) == ERROR_NONE && + read_charge_termination(device, &charge_termination) == ERROR_NONE) { + LOG_I(TAG, "Version %d, status %02X, charge termination %02X", version, status, charge_termination); + } else { + LOG_E(TAG, "Failed to retrieve version and/or status"); + } +} + +// region Power supply child device + +static bool ps_supports_property(Device*, PowerSupplyProperty property) { + return property == POWER_SUPPLY_PROP_IS_CHARGING; +} + +static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_IS_CHARGING) { + return ERROR_NOT_SUPPORTED; + } + + // device_get_parent() here is the bq24295 device itself (this child's parent), not the I2C bus. + uint8_t status; + error_t error = bq24295_get_status(device_get_parent(device), &status); + if (error != ERROR_NONE) { + return error; + } + + // Datasheet REG08 bits[5:4] (CHRG_STAT): 0 = not charging, 1 = pre-charge, + // 2 = fast charge, 3 = charge termination done. Only 1|2 count as actively charging. + uint8_t charge_status = (status >> 4) & 0x03; + out_value->int_value = (charge_status == 1 || charge_status == 2) ? 1 : 0; + return ERROR_NONE; +} + +static bool ps_supports_charge_control(Device*) { return false; } +static bool ps_is_allowed_to_charge(Device*) { return false; } +static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_quick_charge(Device*) { return false; } +static bool ps_is_quick_charge_enabled(Device*) { return false; } +static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_power_off(Device*) { return false; } +static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; } + +static constexpr PowerSupplyApi BQ24295_POWER_SUPPLY_API = { + .supports_property = ps_supports_property, + .get_property = ps_get_property, + .supports_charge_control = ps_supports_charge_control, + .is_allowed_to_charge = ps_is_allowed_to_charge, + .set_allowed_to_charge = ps_set_allowed_to_charge, + .supports_quick_charge = ps_supports_quick_charge, + .is_quick_charge_enabled = ps_is_quick_charge_enabled, + .set_quick_charge_enabled = ps_set_quick_charge_enabled, + .supports_power_off = ps_supports_power_off, + .power_off = ps_power_off, +}; + +// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal, +// but never matched against a devicetree node: bq24295_driver wires it up directly by pointer. +Driver bq24295_power_supply_driver = { + .name = "bq24295-power-supply", + .compatible = (const char*[]) { "bq24295-power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = &BQ24295_POWER_SUPPLY_API, + .device_type = &POWER_SUPPLY_TYPE, + .owner = &bq24295_module, + .internal = nullptr +}; + +struct Bq24295Internal { + Device* power_supply_device = nullptr; +}; + +static error_t create_power_supply_child(Device* parent, Device*& out_child) { + auto* child = new(std::nothrow) Device { .address = 0, .name = "bq24295-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr }; + if (child == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = device_construct(child); + if (error != ERROR_NONE) { + delete child; + return error; + } + + device_set_parent(child, parent); + device_set_driver(child, &bq24295_power_supply_driver); + + error = device_add(child); + if (error != ERROR_NONE) { + device_destruct(child); + delete child; + return error; + } + + error = device_start(child); + if (error != ERROR_NONE) { + device_remove(child); + device_destruct(child); + delete child; + return error; + } + + out_child = child; + return ERROR_NONE; +} + +static void destroy_power_supply_child(Device* child) { + check(device_stop(child) == ERROR_NONE); + check(device_remove(child) == ERROR_NONE); + check(device_destruct(child) == ERROR_NONE); + delete child; +} + +// endregion + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + + auto* internal = new(std::nothrow) Bq24295Internal(); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = create_power_supply_child(device, internal->power_supply_device); + if (error != ERROR_NONE) { + delete internal; + return error; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + destroy_power_supply_child(internal->power_supply_device); + device_set_driver_data(device, nullptr); + delete internal; + return ERROR_NONE; +} + +Driver bq24295_driver = { + .name = "bq24295", + .compatible = (const char*[]) { "ti,bq24295", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &bq24295_module, + .internal = nullptr +}; + +} diff --git a/Drivers/bq24295-module/source/module.cpp b/Drivers/bq24295-module/source/module.cpp new file mode 100644 index 000000000..09e7c8597 --- /dev/null +++ b/Drivers/bq24295-module/source/module.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver bq24295_driver; +extern Driver bq24295_power_supply_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&bq24295_driver) == ERROR_NONE); + check(driver_construct_add(&bq24295_power_supply_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&bq24295_power_supply_driver) == ERROR_NONE); + check(driver_remove_destruct(&bq24295_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module bq24295_module = { + .name = "bq24295", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Drivers/hx8357-module/source/hx8357.cpp b/Drivers/hx8357-module/source/hx8357.cpp index 2b3afbd22..6db46c09d 100644 --- a/Drivers/hx8357-module/source/hx8357.cpp +++ b/Drivers/hx8357-module/source/hx8357.cpp @@ -208,7 +208,7 @@ static error_t start(Device* device) { const auto* spi_config = static_cast(parent->config); const auto* config = GET_CONFIG(device); - struct GpioPinSpec cs_pin; + GpioPinSpec cs_pin; if (esp32_spi_get_cs_pin(device, &cs_pin) != ERROR_NONE) { LOG_E(TAG, "Failed to resolve CS pin"); return ERROR_RESOURCE; @@ -251,14 +251,13 @@ static error_t start(Device* device) { .queue_size = 1, }; - if (spi_bus_add_device((spi_host_device_t)spi_config->host, &device_config, &internal->spi_handle) != ESP_OK) { + if (spi_bus_add_device(spi_config->host, &device_config, &internal->spi_handle) != ESP_OK) { LOG_E(TAG, "Failed to add SPI device"); free(internal); return ERROR_RESOURCE; } - // Hardware reset, in addition to the SWRESET the bring-up command list sends - matches the - // deprecated HAL's Hx8357Display::start(), which did both. + // Hardware reset, in addition to the SWRESET the bring-up command list sends int reset_pin = pin_or_unused(config->pin_reset); if (reset_pin != -1) { gpio_config_t reset_config = { diff --git a/Drivers/hx8357-module/source/module.cpp b/Drivers/hx8357-module/source/module.cpp index 538243a77..bbce76d58 100644 --- a/Drivers/hx8357-module/source/module.cpp +++ b/Drivers/hx8357-module/source/module.cpp @@ -3,10 +3,10 @@ #include #include -extern "C" { - extern Driver hx8357_driver; +extern "C" { + static error_t start() { /* We crash when construct fails, because if a single driver fails to construct, * there is no guarantee that the previously constructed drivers can be destroyed */ diff --git a/Drivers/tca95xx-16bit-module/CMakeLists.txt b/Drivers/tca95xx-16bit-module/CMakeLists.txt new file mode 100644 index 000000000..2597358cb --- /dev/null +++ b/Drivers/tca95xx-16bit-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(tca95xx-16bit-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/tca95xx-16bit-module/LICENSE-Apache-2.0.md b/Drivers/tca95xx-16bit-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/tca95xx-16bit-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/tca95xx-16bit-module/README.md b/Drivers/tca95xx-16bit-module/README.md new file mode 100644 index 000000000..104d13a54 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/README.md @@ -0,0 +1,11 @@ +# TCA9535 / TCA9539 I/O expander + +A driver for the TI `TCA9535` and `TCA9539` 16-bit I2C-bus I/O expanders. Both parts +share the same PCA9555-compatible register map (differing only in package and interrupt +pin): two 8-bit ports for input, output, polarity inversion, and direction, addressed as +pins 0-15 (port 0 = pins 0-7, port 1 = pins 8-15). + +It does not support pull-up/down resistors or high-impedance outputs; requesting those +flags returns `ERROR_NOT_SUPPORTED`. + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/tca95xx-16bit-module/bindings/ti,tca9535.yaml b/Drivers/tca95xx-16bit-module/bindings/ti,tca9535.yaml new file mode 100644 index 000000000..ff732e367 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/bindings/ti,tca9535.yaml @@ -0,0 +1,5 @@ +description: TI TCA9535 16-bit I2C-bus I/O expander (PCA9555-register-compatible) + +include: ["i2c-device.yaml"] + +compatible: "ti,tca9535" diff --git a/Drivers/tca95xx-16bit-module/bindings/ti,tca9539.yaml b/Drivers/tca95xx-16bit-module/bindings/ti,tca9539.yaml new file mode 100644 index 000000000..9325dee64 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/bindings/ti,tca9539.yaml @@ -0,0 +1,5 @@ +description: TI TCA9539 16-bit I2C-bus I/O expander (PCA9555-register-compatible) + +include: ["i2c-device.yaml"] + +compatible: "ti,tca9539" diff --git a/Drivers/tca95xx-16bit-module/devicetree.yaml b/Drivers/tca95xx-16bit-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/tca95xx-16bit-module/include/bindings/tca95xx.h b/Drivers/tca95xx-16bit-module/include/bindings/tca95xx.h new file mode 100644 index 000000000..4820fd434 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/include/bindings/tca95xx.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// One DEFINE_DEVICETREE per compatible string -- the devicetree compiler derives the +// expected config typedef name from the compatible string's suffix (e.g. "ti,tca9535" +// -> tca9535_config_dt), not from a name we choose, so each supported chip needs its own +// typedef even though they share the same underlying config layout (see dummy-i2s-amp-module's +// bindings header for the same pattern). +DEFINE_DEVICETREE(tca9535, struct Tca95xxConfig) +DEFINE_DEVICETREE(tca9539, struct Tca95xxConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/tca95xx-16bit-module/include/drivers/tca95xx.h b/Drivers/tca95xx-16bit-module/include/drivers/tca95xx.h new file mode 100644 index 000000000..4d21654a1 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/include/drivers/tca95xx.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Tca95xxConfig { + /** Address on bus */ + uint8_t address; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/tca95xx-16bit-module/include/tca95xx_module.h b/Drivers/tca95xx-16bit-module/include/tca95xx_module.h new file mode 100644 index 000000000..2578bc1f9 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/include/tca95xx_module.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// The devicetree compiler derives the expected module symbol name from this component's +// folder name ("tca95xx-16bit-module" -> tca95xx_16bit_module), not from a name we choose. +extern struct Module tca95xx_16bit_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/tca95xx-16bit-module/source/module.cpp b/Drivers/tca95xx-16bit-module/source/module.cpp new file mode 100644 index 000000000..868430331 --- /dev/null +++ b/Drivers/tca95xx-16bit-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver tca95xx_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&tca95xx_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&tca95xx_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module tca95xx_16bit_module = { + .name = "tca95xx", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Drivers/tca95xx-16bit-module/source/tca95xx.cpp b/Drivers/tca95xx-16bit-module/source/tca95xx.cpp new file mode 100644 index 000000000..7e25cabda --- /dev/null +++ b/Drivers/tca95xx-16bit-module/source/tca95xx.cpp @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "TCA95XX" + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// PCA9555-compatible register map: one register per function per 8-pin port. +constexpr auto TCA95XX_REGISTER_INPUT_PORT0 = 0x00; +constexpr auto TCA95XX_REGISTER_OUTPUT_PORT0 = 0x02; +constexpr auto TCA95XX_REGISTER_POLARITY_PORT0 = 0x04; +constexpr auto TCA95XX_REGISTER_CONFIG_PORT0 = 0x06; + +static inline uint8_t port_of(GpioDescriptor* descriptor) { + return descriptor->pin >> 3; +} + +static inline uint8_t bit_of(GpioDescriptor* descriptor) { + return 1 << (descriptor->pin & 0x7); +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + + return gpio_controller_init_descriptors(device, 16, nullptr); +} + +static error_t stop(Device* device) { + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); + return ERROR_NONE; +} + +extern "C" { + +static error_t set_level(GpioDescriptor* descriptor, bool high) { + auto* device = descriptor->controller; + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto reg = static_cast(TCA95XX_REGISTER_OUTPUT_PORT0 + port_of(descriptor)); + auto bit = bit_of(descriptor); + + // i2c_controller_register8_{set,reset}_bits() do a separate read then + // write; without this lock, concurrent updates to different pins on the + // same output port register can clobber each other. + device_lock(device); + error_t err = high + ? i2c_controller_register8_set_bits(parent, address, reg, bit, portMAX_DELAY) + : i2c_controller_register8_reset_bits(parent, address, reg, bit, portMAX_DELAY); + device_unlock(device); + return err; +} + +static error_t get_level(GpioDescriptor* descriptor, bool* high) { + auto* device = descriptor->controller; + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto reg = static_cast(TCA95XX_REGISTER_INPUT_PORT0 + port_of(descriptor)); + uint8_t bits; + + error_t err = i2c_controller_register8_get(parent, address, reg, &bits, portMAX_DELAY); + if (err != ERROR_NONE) { + return err; + } + + *high = (bits & bit_of(descriptor)) != 0; + return ERROR_NONE; +} + +static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) { + // The TCA95xx only supports direction and polarity inversion. Pull-up/down and + // high-impedance are not present in its PCA9555-compatible register map. + if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN | GPIO_FLAG_HIGH_IMPEDANCE)) { + return ERROR_NOT_SUPPORTED; + } + + // The polarity register only inverts what's read back from an input pin; + // set_level() still drives outputs at the raw level. Accepting ACTIVE_LOW + // on an output would silently not do what it implies. + if ((flags & GPIO_FLAG_ACTIVE_LOW) && (flags & GPIO_FLAG_DIRECTION_OUTPUT)) { + return ERROR_NOT_SUPPORTED; + } + + auto* device = descriptor->controller; + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto config_reg = static_cast(TCA95XX_REGISTER_CONFIG_PORT0 + port_of(descriptor)); + auto polarity_reg = static_cast(TCA95XX_REGISTER_POLARITY_PORT0 + port_of(descriptor)); + auto bit = bit_of(descriptor); + error_t err; + + // Locked as a whole: direction and polarity are two separate RMW register + // writes, and both should apply atomically with respect to other set_flags + // / set_level calls on this device. + device_lock(device); + + // Direction: configuration bit is 1 for input, 0 for output. + if (flags & GPIO_FLAG_DIRECTION_OUTPUT) { + err = i2c_controller_register8_reset_bits(parent, address, config_reg, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_set_bits(parent, address, config_reg, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + device_unlock(device); + return err; + } + + // Polarity inversion (mainly relevant for active-low inputs). + if (flags & GPIO_FLAG_ACTIVE_LOW) { + err = i2c_controller_register8_set_bits(parent, address, polarity_reg, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, polarity_reg, bit, portMAX_DELAY); + } + + device_unlock(device); + return err; +} + +static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) { + auto* device = descriptor->controller; + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto config_reg = static_cast(TCA95XX_REGISTER_CONFIG_PORT0 + port_of(descriptor)); + auto polarity_reg = static_cast(TCA95XX_REGISTER_POLARITY_PORT0 + port_of(descriptor)); + auto bit = bit_of(descriptor); + uint8_t val; + error_t err; + + gpio_flags_t f = GPIO_FLAG_NONE; + + err = i2c_controller_register8_get(parent, address, config_reg, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + f |= (val & bit) ? GPIO_FLAG_DIRECTION_INPUT : GPIO_FLAG_DIRECTION_OUTPUT; + + err = i2c_controller_register8_get(parent, address, polarity_reg, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + f |= (val & bit) ? GPIO_FLAG_ACTIVE_LOW : GPIO_FLAG_ACTIVE_HIGH; + + *flags = f; + return ERROR_NONE; +} + +static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) { + return ERROR_NOT_SUPPORTED; +} + +static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + return ERROR_NOT_SUPPORTED; +} + +static error_t remove_callback(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; +} + +static error_t enable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; +} + +static error_t disable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; +} + +const static GpioControllerApi tca95xx_gpio_api = { + .set_level = set_level, + .get_level = get_level, + .set_flags = set_flags, + .get_flags = get_flags, + .get_native_pin_number = get_native_pin_number, + .add_callback = add_callback, + .remove_callback = remove_callback, + .enable_interrupt = enable_interrupt, + .disable_interrupt = disable_interrupt +}; + +Driver tca95xx_driver = { + .name = "tca95xx", + .compatible = (const char*[]) { "ti,tca9535", "ti,tca9539", nullptr }, + .start_device = start, + .stop_device = stop, + .api = static_cast(&tca95xx_gpio_api), + .device_type = &GPIO_CONTROLLER_TYPE, + .owner = &tca95xx_16bit_module, + .internal = nullptr +}; + +} diff --git a/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml b/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml index ba460e68e..4f4f31b48 100644 --- a/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml +++ b/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml @@ -25,3 +25,11 @@ properties: type: boolean default: false description: Mirror the Y axis + power-supply: + type: boolean + default: false + description: Expose a power-supply device that reports battery voltage/capacity read from the XPT2046's v-bat input + power-supply-reference-voltage-mv: + type: int + default: 4200 + description: Battery voltage (in mV) considered 100% capacity, used to compute POWER_SUPPLY_PROP_CAPACITY diff --git a/Drivers/xpt2046-module/include/drivers/xpt2046.h b/Drivers/xpt2046-module/include/drivers/xpt2046.h index df9e7492d..e559e1047 100644 --- a/Drivers/xpt2046-module/include/drivers/xpt2046.h +++ b/Drivers/xpt2046-module/include/drivers/xpt2046.h @@ -14,6 +14,10 @@ struct Xpt2046Config { bool swap_xy; bool mirror_x; bool mirror_y; + /** Expose a power-supply child device that reads battery voltage/capacity off the chip's v-bat input */ + bool power_supply; + /** Battery voltage (mV) considered 100% capacity, used to derive POWER_SUPPLY_PROP_CAPACITY */ + uint32_t power_supply_reference_voltage_mv; }; #ifdef __cplusplus diff --git a/Drivers/xpt2046-module/source/module.cpp b/Drivers/xpt2046-module/source/module.cpp index a22bd803d..5d5e8b532 100644 --- a/Drivers/xpt2046-module/source/module.cpp +++ b/Drivers/xpt2046-module/source/module.cpp @@ -6,17 +6,20 @@ extern "C" { extern Driver xpt2046_driver; +extern Driver xpt2046_power_supply_driver; static error_t start() { /* We crash when construct fails, because if a single driver fails to construct, * there is no guarantee that the previously constructed drivers can be destroyed */ check(driver_construct_add(&xpt2046_driver) == ERROR_NONE); + check(driver_construct_add(&xpt2046_power_supply_driver) == ERROR_NONE); return ERROR_NONE; } static error_t stop() { /* We crash when destruct fails, because if a single driver fails to destruct, * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&xpt2046_power_supply_driver) == ERROR_NONE); check(driver_remove_destruct(&xpt2046_driver) == ERROR_NONE); return ERROR_NONE; } diff --git a/Drivers/xpt2046-module/source/xpt2046.cpp b/Drivers/xpt2046-module/source/xpt2046.cpp index a202a9403..caea9a141 100644 --- a/Drivers/xpt2046-module/source/xpt2046.cpp +++ b/Drivers/xpt2046-module/source/xpt2046.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -17,15 +18,149 @@ #include #include +#include #define TAG "XPT2046" #define GET_CONFIG(device) (static_cast((device)->config)) +// Rough LiPo discharge curve floor, used together with the configured reference voltage +// (the 100% point) to estimate a charge percentage from the sensed v-bat voltage. +#define POWER_SUPPLY_MIN_MV 3200 + struct Xpt2046Internal { esp_lcd_panel_io_handle_t io_handle; esp_lcd_touch_handle_t touch_handle; + Device* power_supply_device; +}; + +// region Power supply + +static bool ps_supports_property(Device*, PowerSupplyProperty property) { + return property == POWER_SUPPLY_PROP_VOLTAGE || property == POWER_SUPPLY_PROP_CAPACITY; +} + +static int estimate_capacity_from_mv(int battery_mv, uint32_t reference_mv) { + if (battery_mv <= POWER_SUPPLY_MIN_MV) return 0; + if ((uint32_t)battery_mv >= reference_mv) return 100; + return (battery_mv - POWER_SUPPLY_MIN_MV) * 100 / ((int)reference_mv - POWER_SUPPLY_MIN_MV); +} + +// The v-bat reading is noisy (shared ADC, no dedicated sample/hold), so it's smoothed by +// averaging over several samples rather than trusting a single conversion. +#define POWER_SUPPLY_SAMPLE_COUNT 20 + +static error_t read_battery_mv(esp_lcd_touch_handle_t touch_handle, int* out_mv) { + float volts_sum = 0.0f; + + for (int i = 0; i < POWER_SUPPLY_SAMPLE_COUNT; i++) { + float volts; + if (esp_lcd_touch_xpt2046_read_battery_level(touch_handle, &volts) != ESP_OK) { + return ERROR_RESOURCE; + } + volts_sum += volts; + } + + *out_mv = (int)((volts_sum / POWER_SUPPLY_SAMPLE_COUNT) * 1000.0f); + return ERROR_NONE; +} + +static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_VOLTAGE && property != POWER_SUPPLY_PROP_CAPACITY) { + return ERROR_NOT_SUPPORTED; + } + + auto* parent = device_get_parent(device); + const auto* parent_config = GET_CONFIG(parent); + auto* parent_internal = static_cast(device_get_driver_data(parent)); + + int battery_mv; + error_t error = read_battery_mv(parent_internal->touch_handle, &battery_mv); + if (error != ERROR_NONE) { + return error; + } + + out_value->int_value = (property == POWER_SUPPLY_PROP_VOLTAGE) ? battery_mv : estimate_capacity_from_mv(battery_mv, parent_config->power_supply_reference_voltage_mv); + return ERROR_NONE; +} + +static bool ps_supports_charge_control(Device*) { return false; } +static bool ps_is_allowed_to_charge(Device*) { return false; } +static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_quick_charge(Device*) { return false; } +static bool ps_is_quick_charge_enabled(Device*) { return false; } +static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_power_off(Device*) { return false; } +static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; } + +static constexpr PowerSupplyApi XPT2046_POWER_SUPPLY_API = { + .supports_property = ps_supports_property, + .get_property = ps_get_property, + .supports_charge_control = ps_supports_charge_control, + .is_allowed_to_charge = ps_is_allowed_to_charge, + .set_allowed_to_charge = ps_set_allowed_to_charge, + .supports_quick_charge = ps_supports_quick_charge, + .is_quick_charge_enabled = ps_is_quick_charge_enabled, + .set_quick_charge_enabled = ps_set_quick_charge_enabled, + .supports_power_off = ps_supports_power_off, + .power_off = ps_power_off, +}; + +// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal, +// but never matched against a devicetree node: xpt2046 wires it up directly by pointer. +Driver xpt2046_power_supply_driver = { + .name = "xpt2046-power-supply", + .compatible = (const char*[]) { "xpt2046-power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = &XPT2046_POWER_SUPPLY_API, + .device_type = &POWER_SUPPLY_TYPE, + .owner = &xpt2046_module, + .internal = nullptr }; +static error_t create_power_supply_child(Device* parent, Device*& out_child) { + auto* child = new(std::nothrow) Device { .address = 0, .name = "xpt2046-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr }; + if (child == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = device_construct(child); + if (error != ERROR_NONE) { + delete child; + return error; + } + + device_set_parent(child, parent); + device_set_driver(child, &xpt2046_power_supply_driver); + + error = device_add(child); + if (error != ERROR_NONE) { + device_destruct(child); + delete child; + return error; + } + + error = device_start(child); + if (error != ERROR_NONE) { + device_remove(child); + device_destruct(child); + delete child; + return error; + } + + out_child = child; + return ERROR_NONE; +} + +static void destroy_power_supply_child(Device* child) { + check(device_stop(child) == ERROR_NONE); + check(device_remove(child) == ERROR_NONE); + check(device_destruct(child) == ERROR_NONE); + delete child; +} + +// endregion + // region Driver lifecycle static error_t start(Device* device) { @@ -82,13 +217,30 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } + internal->power_supply_device = nullptr; device_set_driver_data(device, internal); + + if (config->power_supply) { + error_t error = create_power_supply_child(device, internal->power_supply_device); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to create power-supply device"); + esp_lcd_touch_del(internal->touch_handle); + esp_lcd_panel_io_del(internal->io_handle); + free(internal); + return error; + } + } + return ERROR_NONE; } static error_t stop(Device* device) { auto* internal = static_cast(device_get_driver_data(device)); + if (internal->power_supply_device != nullptr) { + destroy_power_supply_child(internal->power_supply_device); + } + bool ok = true; // esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned diff --git a/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml b/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml index 3548cd48c..4346cfbff 100644 --- a/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml +++ b/Drivers/xpt2046-softspi-module/bindings/xptek,xpt2046-softspi.yaml @@ -42,3 +42,11 @@ properties: type: boolean default: false description: Mirror the Y axis + power-supply: + type: boolean + default: false + description: Expose a power-supply device that reports battery voltage/capacity read from the XPT2046's v-bat input + power-supply-reference-voltage-mv: + type: int + default: 4200 + description: Battery voltage (in mV) considered 100% capacity, used to compute POWER_SUPPLY_PROP_CAPACITY diff --git a/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h b/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h index f293edf0c..044a619ea 100644 --- a/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h +++ b/Drivers/xpt2046-softspi-module/include/drivers/xpt2046_softspi.h @@ -20,6 +20,10 @@ struct Xpt2046SoftSpiConfig { bool swap_xy; bool mirror_x; bool mirror_y; + /** Expose a power-supply child device that reads battery voltage/capacity off the chip's v-bat input */ + bool power_supply; + /** Battery voltage (mV) considered 100% capacity, used to derive POWER_SUPPLY_PROP_CAPACITY */ + uint32_t power_supply_reference_voltage_mv; }; #ifdef __cplusplus diff --git a/Drivers/xpt2046-softspi-module/source/module.cpp b/Drivers/xpt2046-softspi-module/source/module.cpp index e79883056..d7350a14b 100644 --- a/Drivers/xpt2046-softspi-module/source/module.cpp +++ b/Drivers/xpt2046-softspi-module/source/module.cpp @@ -6,17 +6,20 @@ extern "C" { extern Driver xpt2046_softspi_driver; +extern Driver xpt2046_softspi_power_supply_driver; static error_t start() { /* We crash when construct fails, because if a single driver fails to construct, * there is no guarantee that the previously constructed drivers can be destroyed */ check(driver_construct_add(&xpt2046_softspi_driver) == ERROR_NONE); + check(driver_construct_add(&xpt2046_softspi_power_supply_driver) == ERROR_NONE); return ERROR_NONE; } static error_t stop() { /* We crash when destruct fails, because if a single driver fails to destruct, * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&xpt2046_softspi_power_supply_driver) == ERROR_NONE); check(driver_remove_destruct(&xpt2046_softspi_driver) == ERROR_NONE); return ERROR_NONE; } diff --git a/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp b/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp index 1418173e1..81968362b 100644 --- a/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp +++ b/Drivers/xpt2046-softspi-module/source/xpt2046_softspi.cpp @@ -2,23 +2,33 @@ #include #include +#include #include #include #include #include +#include #include #include #include #include +#include #define TAG "XPT2046SoftSPI" #define GET_CONFIG(device) (static_cast((device)->config)) +// Rough LiPo discharge curve floor, used together with the configured reference voltage +// (the 100% point) to estimate a charge percentage from the sensed v-bat voltage. +#define POWER_SUPPLY_MIN_MV 3200 + namespace { constexpr uint8_t CMD_READ_X = 0xD0; constexpr uint8_t CMD_READ_Y = 0x90; +// BATTERY register (start=1, addr=010, 12-bit mode, single-ended, PD1=0/PD0=1 i.e. IRQ disabled +// between conversions), same encoding as the hardware-SPI XPT2046 driver's default mode. +constexpr uint8_t CMD_READ_BATTERY = 0xA7; constexpr int RAW_MIN_DEFAULT = 100; constexpr int RAW_MAX_DEFAULT = 1900; @@ -39,8 +49,12 @@ struct Xpt2046SoftSpiInternal { bool touched; uint16_t x; uint16_t y; + Device* power_supply_device; }; +static error_t create_power_supply_child(Device* parent, Device*& out_child); +static void destroy_power_supply_child(Device* child); + // region Driver lifecycle static void release_descriptors(Xpt2046SoftSpiInternal* internal) { @@ -95,11 +109,27 @@ static error_t start(Device* device) { internal->mirror_y = config->mirror_y; device_set_driver_data(device, internal); + + if (config->power_supply) { + error_t error = create_power_supply_child(device, internal->power_supply_device); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to create power-supply device"); + release_descriptors(internal); + free(internal); + return error; + } + } + return ERROR_NONE; } static error_t stop(Device* device) { auto* internal = static_cast(device_get_driver_data(device)); + + if (internal->power_supply_device != nullptr) { + destroy_power_supply_child(internal->power_supply_device); + } + release_descriptors(internal); free(internal); return ERROR_NONE; @@ -146,6 +176,127 @@ static int read_spi_command(Xpt2046SoftSpiInternal* internal, uint8_t command) { // endregion +// region Power supply + +// The v-bat reading is noisy (bit-banged SPI, no dedicated sample/hold), so it's smoothed by +// averaging over several samples rather than trusting a single conversion. +#define POWER_SUPPLY_SAMPLE_COUNT 20 + +// Same scaling as the hardware-SPI XPT2046 driver's esp_lcd_touch_xpt2046_read_battery_level(): +// the chip halves the v-bat voltage internally, and the raw code is relative to its internal +// 2.507V reference over 12 bits. +static int read_battery_mv(Xpt2046SoftSpiInternal* internal) { + int64_t raw_sum = 0; + for (int i = 0; i < POWER_SUPPLY_SAMPLE_COUNT; i++) { + raw_sum += read_spi_command(internal, CMD_READ_BATTERY); + } + + int64_t raw_avg = raw_sum / POWER_SUPPLY_SAMPLE_COUNT; + return (int)((raw_avg * 4 * 2507) / 4096); +} + +static bool ps_supports_property(Device*, PowerSupplyProperty property) { + return property == POWER_SUPPLY_PROP_VOLTAGE || property == POWER_SUPPLY_PROP_CAPACITY; +} + +static int estimate_capacity_from_mv(int battery_mv, uint32_t reference_mv) { + if (battery_mv <= POWER_SUPPLY_MIN_MV) return 0; + if ((uint32_t)battery_mv >= reference_mv) return 100; + return (battery_mv - POWER_SUPPLY_MIN_MV) * 100 / ((int)reference_mv - POWER_SUPPLY_MIN_MV); +} + +static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_VOLTAGE && property != POWER_SUPPLY_PROP_CAPACITY) { + return ERROR_NOT_SUPPORTED; + } + + auto* parent = device_get_parent(device); + const auto* parent_config = GET_CONFIG(parent); + auto* parent_internal = static_cast(device_get_driver_data(parent)); + + int battery_mv = read_battery_mv(parent_internal); + out_value->int_value = (property == POWER_SUPPLY_PROP_VOLTAGE) ? battery_mv : estimate_capacity_from_mv(battery_mv, parent_config->power_supply_reference_voltage_mv); + return ERROR_NONE; +} + +static bool ps_supports_charge_control(Device*) { return false; } +static bool ps_is_allowed_to_charge(Device*) { return false; } +static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_quick_charge(Device*) { return false; } +static bool ps_is_quick_charge_enabled(Device*) { return false; } +static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_power_off(Device*) { return false; } +static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; } + +static constexpr PowerSupplyApi XPT2046_SOFTSPI_POWER_SUPPLY_API = { + .supports_property = ps_supports_property, + .get_property = ps_get_property, + .supports_charge_control = ps_supports_charge_control, + .is_allowed_to_charge = ps_is_allowed_to_charge, + .set_allowed_to_charge = ps_set_allowed_to_charge, + .supports_quick_charge = ps_supports_quick_charge, + .is_quick_charge_enabled = ps_is_quick_charge_enabled, + .set_quick_charge_enabled = ps_set_quick_charge_enabled, + .supports_power_off = ps_supports_power_off, + .power_off = ps_power_off, +}; + +// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal, +// but never matched against a devicetree node: xpt2046_softspi wires it up directly by pointer. +Driver xpt2046_softspi_power_supply_driver = { + .name = "xpt2046-softspi-power-supply", + .compatible = (const char*[]) { "xpt2046-softspi-power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = &XPT2046_SOFTSPI_POWER_SUPPLY_API, + .device_type = &POWER_SUPPLY_TYPE, + .owner = &xpt2046_softspi_module, + .internal = nullptr +}; + +static error_t create_power_supply_child(Device* parent, Device*& out_child) { + auto* child = new(std::nothrow) Device { .address = 0, .name = "xpt2046-softspi-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr }; + if (child == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = device_construct(child); + if (error != ERROR_NONE) { + delete child; + return error; + } + + device_set_parent(child, parent); + device_set_driver(child, &xpt2046_softspi_power_supply_driver); + + error = device_add(child); + if (error != ERROR_NONE) { + device_destruct(child); + delete child; + return error; + } + + error = device_start(child); + if (error != ERROR_NONE) { + device_remove(child); + device_destruct(child); + delete child; + return error; + } + + out_child = child; + return ERROR_NONE; +} + +static void destroy_power_supply_child(Device* child) { + check(device_stop(child) == ERROR_NONE); + check(device_remove(child) == ERROR_NONE); + check(device_destruct(child) == ERROR_NONE); + delete child; +} + +// endregion + // region PointerApi static error_t xpt2046_softspi_enter_sleep(Device*) { diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 8be075bba..69a1698d9 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -32,8 +32,6 @@ dependencies: espressif/esp_lcd_touch_cst816s: "1.0.3" espressif/esp_lcd_touch_gt911: "1.1.3" espressif/esp_lcd_touch_ft5x06: "1.0.6~1" - espressif/esp_io_expander: "1.0.1" - espressif/esp_io_expander_tca95xx_16bit: "1.0.1" espressif/esp_lcd_axs15231b: "2.0.2" lambage/esp_lcd_touch_ft6336u: "1.0.8" espressif/esp_lcd_st7701: diff --git a/Modules/lvgl-module/source/lvgl_pointer.c b/Modules/lvgl-module/source/lvgl_pointer.c index 1f64c08a4..f01cf231f 100644 --- a/Modules/lvgl-module/source/lvgl_pointer.c +++ b/Modules/lvgl-module/source/lvgl_pointer.c @@ -5,6 +5,8 @@ #include +#define TAG "lvgl_pointer" + struct LvglPointerCtx { struct Device* device; bool calibration_enabled; @@ -49,40 +51,56 @@ static void lvgl_pointer_calibration_apply( if (mapped_y < 0) mapped_y = 0; if (mapped_y > target_y_max) mapped_y = target_y_max; + LOG_I(TAG, "Calibration mapping: %d,%d -> %d,%d", *x, *y, (int)mapped_x, (int)mapped_y); *x = (uint16_t)mapped_x; *y = (uint16_t)mapped_y; } +// Reads the touch controller and applies calibration entirely in the graphics driver's own +// native (LV_DISPLAY_ROTATION_0) coordinate space - native_x_max/native_y_max are just the panel's +// fixed pixel dimensions, not a rotation. This function has no notion of LVGL rotation at all: +// calibration corrects the raw sensor's fixed physical mapping, which never changes with on-screen +// orientation, so it doesn't belong anywhere near rotation math. +static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) { + if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) { + return false; + } + + uint8_t point_count = 0; + if (!pointer_get_touched_points(ctx->device, x, y, NULL, &point_count, 1) || point_count == 0) { + return false; + } + + if (ctx->calibration_enabled && native_x_max > 0 && native_y_max > 0) { + lvgl_pointer_calibration_apply(&ctx->calibration, native_x_max, native_y_max, x, y); + } + + return true; +} + +// The actual LVGL indev read callback: wraps lvgl_pointer_read_calibrated() and, only here, applies +// the rotation needed to place the (still native-space) point into the currently active LVGL +// logical space - unconditionally, since native-space coordinates always need this regardless of +// whether calibration is enabled. static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + lv_display_t* display = lv_indev_get_display(indev); - if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) { - data->state = LV_INDEV_STATE_RELEASED; - return; - } + // lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size, + // unaffected by the display's current rotation - no rotation lookup needed to get it. + int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0; + int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0; uint16_t x = 0; uint16_t y = 0; - uint8_t point_count = 0; - bool touched = pointer_get_touched_points(ctx->device, &x, &y, NULL, &point_count, 1); - - if (touched && point_count > 0) { - if (ctx->calibration_enabled) { - lv_display_t* display = lv_indev_get_display(indev); - if (display != NULL) { - int32_t target_x_max = lv_display_get_horizontal_resolution(display) - 1; - int32_t target_y_max = lv_display_get_vertical_resolution(display) - 1; - if (target_x_max > 0 && target_y_max > 0) { - lvgl_pointer_calibration_apply(&ctx->calibration, target_x_max, target_y_max, &x, &y); - } - } - } - data->point.x = x; - data->point.y = y; - data->state = LV_INDEV_STATE_PRESSED; - } else { + if (!lvgl_pointer_read_calibrated(ctx, native_x_max, native_y_max, &x, &y)) { data->state = LV_INDEV_STATE_RELEASED; + return; } + + data->point.x = x; + data->point.y = y; + data->state = LV_INDEV_STATE_PRESSED; } error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) { @@ -93,7 +111,7 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_ return ERROR_INVALID_ARGUMENT; } - struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)calloc(1, sizeof(struct LvglPointerCtx)); + struct LvglPointerCtx* ctx = calloc(1, sizeof(struct LvglPointerCtx)); if (ctx == NULL) { return ERROR_OUT_OF_MEMORY; } @@ -147,7 +165,7 @@ bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibrati if (indev == NULL || out_calibration == NULL) { return false; } - struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev); if (!ctx->calibration_enabled) { return false; } @@ -160,7 +178,7 @@ void lvgl_pointer_remove(lv_indev_t* indev) { return; } - struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev); if (default_pointer_indev == indev) { default_pointer_indev = NULL; } diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-gpio-backlight.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-gpio-backlight.yaml new file mode 100644 index 000000000..e60cdc12b --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-gpio-backlight.yaml @@ -0,0 +1,16 @@ +description: > + Simple GPIO-driven on/off backlight, for panels whose backlight is a single digital + enable pin rather than a PWM-dimmable one. Brightness is treated as boolean: any + value greater than 0 turns the backlight on, 0 turns it off. + +compatible: "espressif,esp32-gpio-backlight" + +properties: + pin-backlight: + type: phandles + required: true + description: Backlight enable output pin + default-on: + type: boolean + default: true + description: Whether the backlight is turned on by set_brightness_default() diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_gpio_backlight.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_gpio_backlight.h new file mode 100644 index 000000000..e91550339 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_gpio_backlight.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_gpio_backlight, struct Esp32GpioBacklightConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_gpio_backlight.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_gpio_backlight.h new file mode 100644 index 000000000..c64547428 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_gpio_backlight.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32GpioBacklightConfig { + struct GpioPinSpec pin_backlight; + bool default_on; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio_backlight.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio_backlight.cpp new file mode 100644 index 000000000..64fcb32d9 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio_backlight.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "Esp32GpioBacklight" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Esp32GpioBacklightInternal { + GpioDescriptor* descriptor; + uint8_t brightness; +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* descriptor = gpio_descriptor_acquire(config->pin_backlight.gpio_controller, config->pin_backlight.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr) { + LOG_E(TAG, "Failed to acquire GPIO descriptor"); + return ERROR_RESOURCE; + } + + if (gpio_descriptor_set_flags(descriptor, config->pin_backlight.flags | GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure backlight pin as output"); + gpio_descriptor_release(descriptor); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(Esp32GpioBacklightInternal))); + if (internal == nullptr) { + gpio_descriptor_release(descriptor); + return ERROR_OUT_OF_MEMORY; + } + internal->descriptor = descriptor; + internal->brightness = 0; + + device_set_driver_data(device, internal); + + backlight_set_brightness_default(device); // Allowed to fail, we don't care about the result + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + backlight_set_brightness(device, 0); // Allowed to fail, we don't care about the result + + auto* internal = static_cast(device_get_driver_data(device)); + gpio_descriptor_release(internal->descriptor); + free(internal); + + return ERROR_NONE; +} + +// endregion + +// region BacklightApi + +static error_t esp32_gpio_backlight_set_brightness(Device* device, uint8_t brightness) { + auto* internal = static_cast(device_get_driver_data(device)); + + error_t error = gpio_descriptor_set_level(internal->descriptor, brightness > 0); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to set backlight level"); + return error; + } + + internal->brightness = brightness; + return ERROR_NONE; +} + +static error_t esp32_gpio_backlight_set_brightness_default(Device* device) { + return esp32_gpio_backlight_set_brightness(device, GET_CONFIG(device)->default_on ? 1 : 0); +} + +static error_t esp32_gpio_backlight_get_brightness(Device* device, uint8_t* out_brightness) { + auto* internal = static_cast(device_get_driver_data(device)); + *out_brightness = internal->brightness; + return ERROR_NONE; +} + +static uint8_t esp32_gpio_backlight_get_min_brightness(Device*) { + return 0; +} + +static uint8_t esp32_gpio_backlight_get_max_brightness(Device*) { + return 1; +} + +// endregion + +static const BacklightApi esp32_gpio_backlight_api = { + .set_brightness = esp32_gpio_backlight_set_brightness, + .set_brightness_default = esp32_gpio_backlight_set_brightness_default, + .get_brightness = esp32_gpio_backlight_get_brightness, + .get_min_brightness = esp32_gpio_backlight_get_min_brightness, + .get_max_brightness = esp32_gpio_backlight_get_max_brightness, +}; + +extern Module platform_esp32_module; + +Driver esp32_gpio_backlight_driver = { + .name = "esp32_gpio_backlight", + .compatible = (const char*[]) { "espressif,esp32-gpio-backlight", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &esp32_gpio_backlight_api, + .device_type = &BACKLIGHT_TYPE, + .owner = &platform_esp32_module, + .internal = nullptr +}; diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index 89131f156..833be4c73 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -19,6 +19,7 @@ extern Driver esp32_i2s_driver; #if SOC_LCD_I80_SUPPORTED extern Driver esp32_i8080_driver; #endif +extern Driver esp32_gpio_backlight_driver; extern Driver esp32_ledc_backlight_driver; #if SOC_SDMMC_HOST_SUPPORTED extern Driver esp32_sdmmc_driver; @@ -55,6 +56,7 @@ static error_t start() { #if SOC_LCD_I80_SUPPORTED check(driver_construct_add(&esp32_i8080_driver) == ERROR_NONE); #endif + check(driver_construct_add(&esp32_gpio_backlight_driver) == ERROR_NONE); check(driver_construct_add(&esp32_ledc_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); @@ -110,6 +112,7 @@ static error_t stop() { check(driver_remove_destruct(&esp32_i8080_driver) == ERROR_NONE); #endif check(driver_remove_destruct(&esp32_ledc_backlight_driver) == ERROR_NONE); + check(driver_remove_destruct(&esp32_gpio_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); #endif diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index 8215f14df..5697a8509 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -24,16 +23,6 @@ static std::shared_ptr getHalDisplay() { return hal::findFirstDevice(hal::Device::Type::Display); } -static bool hasCalibratableTouchDevice() { - auto touch_devices = hal::findDevices(hal::Device::Type::Touch); - for (const auto& touch_device : touch_devices) { - if (touch_device != nullptr && touch_device->supportsCalibration()) { - return true; - } - } - return false; -} - class HalDisplayApp final : public App { settings::display::DisplaySettings displaySettings; @@ -131,10 +120,6 @@ class HalDisplayApp final : public App { } } - static void onCalibrateTouchClicked(lv_event_t*) { - app::start("TouchCalibration"); - } - public: void onShow(AppContext& app, lv_obj_t* parent) override { @@ -294,25 +279,6 @@ class HalDisplayApp final : public App { lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED); } } - - if (hasCalibratableTouchDevice()) { - auto* calibrate_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(calibrate_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(calibrate_wrapper, 0, LV_STATE_DEFAULT); - - auto* calibrate_label = lv_label_create(calibrate_wrapper); - lv_label_set_text(calibrate_label, "Touch calibration"); - lv_obj_align(calibrate_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* calibrate_button = lv_button_create(calibrate_wrapper); - lv_obj_align(calibrate_button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, this); - - auto* calibrate_button_label = lv_label_create(calibrate_button); - lv_label_set_text(calibrate_button_label, "Calibrate"); - lv_obj_center(calibrate_button_label); - } } void onHide(AppContext& app) override { diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp index 7efec6a21..77e42b31a 100644 --- a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -11,12 +11,10 @@ #include #endif #include -#include #include #include #include -#include #ifdef ESP_PLATFORM #include @@ -104,12 +102,6 @@ class KernelDisplayApp final : public App { } } -#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) - static void onCalibrateTouchClicked(lv_event_t*) { - app::touchcalibration::start(); - } -#endif - static void onScreensaverChanged(lv_event_t* event) { auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); @@ -262,27 +254,6 @@ class KernelDisplayApp final : public App { lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED); } } - -#if defined(CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED) - if (lvgl_pointer_get_default() != nullptr) { - auto* calibrate_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(calibrate_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(calibrate_wrapper, 0, LV_STATE_DEFAULT); - - auto* calibrate_label = lv_label_create(calibrate_wrapper); - lv_label_set_text(calibrate_label, "Touch calibration"); - lv_obj_align(calibrate_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* calibrate_button = lv_button_create(calibrate_wrapper); - lv_obj_align(calibrate_button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, this); - - auto* calibrate_button_label = lv_label_create(calibrate_button); - lv_label_set_text(calibrate_button_label, "Calibrate"); - lv_obj_center(calibrate_button_label); - } -#endif } void onHide(AppContext& app) override { diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 2152cb8f1..6c524c293 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -12,6 +12,8 @@ #include +#include + namespace tt::app::power { #define TAG "power" @@ -30,20 +32,32 @@ std::shared_ptr optApp() { } } -class PowerApp : public App { - - Timer update_timer = Timer(Timer::Type::Periodic, kernel::millisToTicks(1000),[]() { onTimer(); }); +namespace { +constexpr PowerSupplyProperty DISPLAYED_PROPERTIES[] = { + POWER_SUPPLY_PROP_IS_CHARGING, + POWER_SUPPLY_PROP_VOLTAGE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CURRENT, +}; +} // namespace - ::Device* power = nullptr; +struct PropertyWidget { + PowerSupplyProperty property; + lv_obj_t* label; +}; - lv_obj_t* enableLabel = nullptr; +struct DeviceEntry { + ::Device* device = nullptr; lv_obj_t* enableSwitch = nullptr; - lv_obj_t* quickChargeLabel = nullptr; lv_obj_t* quickChargeSwitch = nullptr; - lv_obj_t* batteryVoltageLabel = nullptr; - lv_obj_t* chargeStateLabel = nullptr; - lv_obj_t* chargeLevelLabel = nullptr; - lv_obj_t* currentLabel = nullptr; + std::vector propertyWidgets; +}; + +class PowerApp : public App { + + Timer update_timer = Timer(Timer::Type::Periodic, kernel::millisToTicks(1000),[]() { onTimer(); }); + + std::vector entries; static void onTimer() { auto app = optApp(); @@ -52,22 +66,31 @@ class PowerApp : public App { } } + static bool collectDevice(::Device* device, void* context) { + auto* devices = static_cast*>(context); + devices->push_back(device); + return true; + } + void onPowerEnabledChanged(lv_event_t* event) { lv_event_code_t code = lv_event_get_code(event); auto* enable_switch = static_cast(lv_event_get_target(event)); if (code == LV_EVENT_VALUE_CHANGED) { bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED); + auto* device = static_cast<::Device*>(lv_event_get_user_data(event)); - if (power_supply_is_allowed_to_charge(power) != is_on) { - power_supply_set_allowed_to_charge(power, is_on); + if (power_supply_is_allowed_to_charge(device) != is_on) { + power_supply_set_allowed_to_charge(device, is_on); updateUi(); } } } static void onPowerEnabledChangedCallback(lv_event_t* event) { - auto* app = (PowerApp*)lv_event_get_user_data(event); - app->onPowerEnabledChanged(event); + auto app = optApp(); + if (app != nullptr) { + app->onPowerEnabledChanged(event); + } } void onQuickChargeChanged(lv_event_t* event) { @@ -75,97 +98,61 @@ class PowerApp : public App { auto* qc_switch = static_cast(lv_event_get_target(event)); if (code == LV_EVENT_VALUE_CHANGED) { bool is_on = lv_obj_has_state(qc_switch, LV_STATE_CHECKED); + auto* device = static_cast<::Device*>(lv_event_get_user_data(event)); - if (power_supply_is_quick_charge_enabled(power) != is_on) { - power_supply_set_quick_charge_enabled(power, is_on); + if (power_supply_is_quick_charge_enabled(device) != is_on) { + power_supply_set_quick_charge_enabled(device, is_on); updateUi(); } } } static void onQuickChargeChangedCallback(lv_event_t* event) { - auto* app = (PowerApp*)lv_event_get_user_data(event); - app->onQuickChargeChanged(event); - } - - void updateUi() { - if (chargeStateLabel == nullptr) { - return; - } - - const char* charge_state; - PowerSupplyPropertyValue property_value; - if (power_supply_get_property(power, POWER_SUPPLY_PROP_IS_CHARGING, &property_value) == ERROR_NONE) { - charge_state = property_value.int_value ? "yes" : "no"; - } else { - charge_state = "N/A"; - } - - int charge_level; - bool charge_level_scaled_set = false; - if (power_supply_get_property(power, POWER_SUPPLY_PROP_CAPACITY, &property_value) == ERROR_NONE) { - charge_level = property_value.int_value; - charge_level_scaled_set = true; + auto app = optApp(); + if (app != nullptr) { + app->onQuickChargeChanged(event); } + } - bool charging_enabled_set = power_supply_supports_charge_control(power); - bool charging_enabled_and_allowed = charging_enabled_set && power_supply_is_allowed_to_charge(power); - - bool quick_charge_set = power_supply_supports_quick_charge(power); - bool quick_charge_enabled = quick_charge_set && power_supply_is_quick_charge_enabled(power); - - int current; - bool current_set = false; - if (power_supply_get_property(power, POWER_SUPPLY_PROP_CURRENT, &property_value) == ERROR_NONE) { - current = property_value.int_value; - current_set = true; + static void setPropertyLabelText(lv_obj_t* label, PowerSupplyProperty property, const PowerSupplyPropertyValue& value) { + switch (property) { + case POWER_SUPPLY_PROP_IS_CHARGING: + lv_label_set_text_fmt(label, "Charging: %s", value.int_value ? "yes" : "no"); + break; + case POWER_SUPPLY_PROP_VOLTAGE: + lv_label_set_text_fmt(label, "Battery voltage: %d mV", value.int_value); + break; + case POWER_SUPPLY_PROP_CAPACITY: + lv_label_set_text_fmt(label, "Charge level: %d%%", value.int_value); + break; + case POWER_SUPPLY_PROP_CURRENT: + lv_label_set_text_fmt(label, "Current: %d mA", value.int_value); + break; } + } - int battery_voltage; - bool battery_voltage_set = false; - if (power_supply_get_property(power, POWER_SUPPLY_PROP_VOLTAGE, &property_value) == ERROR_NONE) { - battery_voltage = property_value.int_value; - battery_voltage_set = true; + void updateUi() { + if (entries.empty()) { + return; } lvgl::lock(kernel::millisToTicks(1000)); - if (charging_enabled_set) { - lv_obj_set_state(enableSwitch, LV_STATE_CHECKED, charging_enabled_and_allowed); - lv_obj_remove_flag(enableSwitch, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(enableLabel, LV_OBJ_FLAG_HIDDEN); - } else { - lv_obj_add_flag(enableSwitch, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(enableLabel, LV_OBJ_FLAG_HIDDEN); - } - - if (quick_charge_set) { - lv_obj_set_state(quickChargeSwitch, LV_STATE_CHECKED, quick_charge_enabled); - lv_obj_remove_flag(quickChargeSwitch, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(quickChargeLabel, LV_OBJ_FLAG_HIDDEN); - } else { - lv_obj_add_flag(quickChargeSwitch, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(quickChargeLabel, LV_OBJ_FLAG_HIDDEN); - } - - lv_label_set_text_fmt(chargeStateLabel, "Charging: %s", charge_state); - - if (battery_voltage_set) { - lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: %d mV", battery_voltage); - } else { - lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: N/A"); - } + for (auto& entry : entries) { + if (entry.enableSwitch != nullptr) { + lv_obj_set_state(entry.enableSwitch, LV_STATE_CHECKED, power_supply_is_allowed_to_charge(entry.device)); + } - if (charge_level_scaled_set) { - lv_label_set_text_fmt(chargeLevelLabel, "Charge level: %d%%", charge_level); - } else { - lv_label_set_text_fmt(chargeLevelLabel, "Charge level: N/A"); - } + if (entry.quickChargeSwitch != nullptr) { + lv_obj_set_state(entry.quickChargeSwitch, LV_STATE_CHECKED, power_supply_is_quick_charge_enabled(entry.device)); + } - if (current_set) { - lv_label_set_text_fmt(currentLabel, "Current: %d mA", current); - } else { - lv_label_set_text_fmt(currentLabel, "Current: N/A"); + PowerSupplyPropertyValue value; + for (auto& widget : entry.propertyWidgets) { + if (power_supply_get_property(entry.device, widget.property, &value) == ERROR_NONE) { + setPropertyLabelText(widget.label, widget.property, value); + } + } } lvgl::unlock(); @@ -173,9 +160,7 @@ class PowerApp : public App { public: - void onCreate(AppContext& app) override { - power = device_find_first_active_by_type(&POWER_SUPPLY_TYPE); - } + void onCreate(AppContext& app) override {} void onShow(AppContext& app, lv_obj_t* parent) override { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); @@ -183,7 +168,10 @@ class PowerApp : public App { lvgl::toolbar_create(parent, app); - if (power == nullptr) { + std::vector<::Device*> devices; + device_for_each_of_type(&POWER_SUPPLY_TYPE, &devices, collectDevice); + + if (devices.empty()) { return; } @@ -193,60 +181,75 @@ class PowerApp : public App { lv_obj_set_flex_grow(wrapper, 1); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - // Row: charge enable/disable - lv_obj_t* switch_container = lv_obj_create(wrapper); - lv_obj_set_width(switch_container, LV_PCT(100)); - lv_obj_set_height(switch_container, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(switch_container, 0, 0); - lv_obj_set_style_pad_gap(switch_container, 0, 0); - lvgl::obj_set_style_bg_invisible(switch_container); - - enableLabel = lv_label_create(switch_container); - lv_label_set_text(enableLabel, "Charging enabled"); - lv_obj_set_align(enableLabel, LV_ALIGN_LEFT_MID); - - lv_obj_t* enable_switch = lv_switch_create(switch_container); - lv_obj_add_event_cb(enable_switch, onPowerEnabledChangedCallback, LV_EVENT_VALUE_CHANGED, this); - lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID); - enableSwitch = enable_switch; - - // Row: quick charge enable/disable - lv_obj_t* qc_container = lv_obj_create(wrapper); - lv_obj_set_width(qc_container, LV_PCT(100)); - lv_obj_set_height(qc_container, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(qc_container, 0, 0); - lv_obj_set_style_pad_gap(qc_container, 0, 0); - lvgl::obj_set_style_bg_invisible(qc_container); - - quickChargeLabel = lv_label_create(qc_container); - lv_label_set_text(quickChargeLabel, "Quick charge"); - lv_obj_set_align(quickChargeLabel, LV_ALIGN_LEFT_MID); - - lv_obj_t* qc_switch = lv_switch_create(qc_container); - lv_obj_add_event_cb(qc_switch, onQuickChargeChangedCallback, LV_EVENT_VALUE_CHANGED, this); - lv_obj_set_align(qc_switch, LV_ALIGN_RIGHT_MID); - quickChargeSwitch = qc_switch; - - chargeStateLabel = lv_label_create(wrapper); - chargeLevelLabel = lv_label_create(wrapper); - batteryVoltageLabel = lv_label_create(wrapper); - currentLabel = lv_label_create(wrapper); - - updateUi(); + entries.clear(); + entries.reserve(devices.size()); + + for (size_t i = 0; i < devices.size(); i++) { + ::Device* device = devices[i]; + + DeviceEntry entry; + entry.device = device; + + lv_obj_t* header = lv_label_create(wrapper); + lv_label_set_text_fmt(header, "%s:", device->name); + + if (power_supply_supports_charge_control(device)) { + lv_obj_t* switch_container = lv_obj_create(wrapper); + lv_obj_set_width(switch_container, LV_PCT(100)); + lv_obj_set_height(switch_container, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(switch_container, 0, 0); + lv_obj_set_style_pad_gap(switch_container, 0, 0); + lvgl::obj_set_style_bg_invisible(switch_container); + + lv_obj_t* label = lv_label_create(switch_container); + lv_label_set_text(label, "Charging enabled"); + lv_obj_set_align(label, LV_ALIGN_LEFT_MID); + + lv_obj_t* enable_switch = lv_switch_create(switch_container); + lv_obj_add_event_cb(enable_switch, onPowerEnabledChangedCallback, LV_EVENT_VALUE_CHANGED, device); + lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID); + lv_obj_set_state(enable_switch, LV_STATE_CHECKED, power_supply_is_allowed_to_charge(device)); + entry.enableSwitch = enable_switch; + } + + if (power_supply_supports_quick_charge(device)) { + lv_obj_t* qc_container = lv_obj_create(wrapper); + lv_obj_set_width(qc_container, LV_PCT(100)); + lv_obj_set_height(qc_container, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(qc_container, 0, 0); + lv_obj_set_style_pad_gap(qc_container, 0, 0); + lvgl::obj_set_style_bg_invisible(qc_container); + + lv_obj_t* label = lv_label_create(qc_container); + lv_label_set_text(label, "Quick charge"); + lv_obj_set_align(label, LV_ALIGN_LEFT_MID); + + lv_obj_t* qc_switch = lv_switch_create(qc_container); + lv_obj_add_event_cb(qc_switch, onQuickChargeChangedCallback, LV_EVENT_VALUE_CHANGED, device); + lv_obj_set_align(qc_switch, LV_ALIGN_RIGHT_MID); + lv_obj_set_state(qc_switch, LV_STATE_CHECKED, power_supply_is_quick_charge_enabled(device)); + entry.quickChargeSwitch = qc_switch; + } + + PowerSupplyPropertyValue value; + for (auto property : DISPLAYED_PROPERTIES) { + if (power_supply_get_property(device, property, &value) == ERROR_NONE) { + lv_obj_t* label = lv_label_create(wrapper); + lv_obj_set_style_margin_left(label, 24, LV_STATE_DEFAULT); + setPropertyLabelText(label, property, value); + entry.propertyWidgets.push_back({ property, label }); + } + } + + entries.push_back(entry); + } update_timer.start(); } void onHide(AppContext& app) override { update_timer.stop(); - enableLabel = nullptr; - enableSwitch = nullptr; - quickChargeLabel = nullptr; - quickChargeSwitch = nullptr; - chargeStateLabel = nullptr; - chargeLevelLabel = nullptr; - batteryVoltageLabel = nullptr; - currentLabel = nullptr; + entries.clear(); } }; diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 4ac68543f..cb9c8e7dc 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include diff --git a/TactilityKernel/include/tactility/device_listener.h b/TactilityKernel/include/tactility/device_listener.h index bfc912349..94001cb78 100644 --- a/TactilityKernel/include/tactility/device_listener.h +++ b/TactilityKernel/include/tactility/device_listener.h @@ -22,9 +22,9 @@ struct DeviceEventListener { void* callback_context; }; -void device_listener_add(DeviceListenerCallback* callback, void* context); +void device_listener_add(DeviceListenerCallback callback, void* context); -void device_listener_remove(DeviceListenerCallback* callback); +void device_listener_remove(DeviceListenerCallback callback); #ifdef __cplusplus } diff --git a/TactilityKernel/source/device_listener.cpp b/TactilityKernel/source/device_listener.cpp index bda49ec43..2d49c9122 100644 --- a/TactilityKernel/source/device_listener.cpp +++ b/TactilityKernel/source/device_listener.cpp @@ -22,13 +22,13 @@ static DeviceListenerLedger ledger; extern "C" { -void device_listener_add(DeviceListenerCallback* callback, void* context) { +void device_listener_add(DeviceListenerCallback callback, void* context) { ledger.lock(); ledger.listeners.push_back(DeviceEventListener{ *callback, context }); ledger.unlock(); } -void device_listener_remove(DeviceListenerCallback* callback) { +void device_listener_remove(DeviceListenerCallback callback) { ledger.lock(); const auto iterator = std::ranges::find_if(ledger.listeners, [callback](const DeviceEventListener& listener) { return listener.callback == *callback; diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 1a627c5ac..0d4945da2 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -14,13 +16,20 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include +#include #include +#include #include #include #include @@ -94,6 +103,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(audio_codec_get_native_sample_rate), DEFINE_MODULE_SYMBOL(audio_codec_get_native_channels), DEFINE_MODULE_SYMBOL(audio_codec_get_capabilities), + DEFINE_MODULE_SYMBOL(audio_codec_get_input_gain_multiplier), DEFINE_MODULE_SYMBOL(AUDIO_CODEC_TYPE), // drivers/audio_stream DEFINE_MODULE_SYMBOL(audio_stream_open_input), @@ -107,7 +117,40 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(audio_stream_get_mute), DEFINE_MODULE_SYMBOL(audio_stream_set_enabled), DEFINE_MODULE_SYMBOL(audio_stream_get_enabled), + DEFINE_MODULE_SYMBOL(audio_stream_is_supported), + DEFINE_MODULE_SYMBOL(audio_stream_set_change_callback), DEFINE_MODULE_SYMBOL(AUDIO_STREAM_TYPE), + // drivers/adc_controller + DEFINE_MODULE_SYMBOL(adc_controller_read_raw), + DEFINE_MODULE_SYMBOL(adc_channel_read_raw), + DEFINE_MODULE_SYMBOL(ADC_CONTROLLER_TYPE), + // drivers/backlight + DEFINE_MODULE_SYMBOL(backlight_set_brightness), + DEFINE_MODULE_SYMBOL(backlight_set_brightness_default), + DEFINE_MODULE_SYMBOL(backlight_get_brightness), + DEFINE_MODULE_SYMBOL(backlight_get_min_brightness), + DEFINE_MODULE_SYMBOL(backlight_get_max_brightness), + DEFINE_MODULE_SYMBOL(BACKLIGHT_TYPE), + // drivers/display + DEFINE_MODULE_SYMBOL(display_reset), + DEFINE_MODULE_SYMBOL(display_init), + DEFINE_MODULE_SYMBOL(display_draw_bitmap), + DEFINE_MODULE_SYMBOL(display_mirror), + DEFINE_MODULE_SYMBOL(display_swap_xy), + DEFINE_MODULE_SYMBOL(display_get_swap_xy), + DEFINE_MODULE_SYMBOL(display_get_mirror_x), + DEFINE_MODULE_SYMBOL(display_get_mirror_y), + DEFINE_MODULE_SYMBOL(display_set_gap), + DEFINE_MODULE_SYMBOL(display_invert_color), + DEFINE_MODULE_SYMBOL(display_disp_on_off), + DEFINE_MODULE_SYMBOL(display_disp_sleep), + DEFINE_MODULE_SYMBOL(display_get_color_format), + DEFINE_MODULE_SYMBOL(display_get_resolution_x), + DEFINE_MODULE_SYMBOL(display_get_resolution_y), + DEFINE_MODULE_SYMBOL(display_get_frame_buffer), + DEFINE_MODULE_SYMBOL(display_get_frame_buffer_count), + DEFINE_MODULE_SYMBOL(display_get_backlight), + DEFINE_MODULE_SYMBOL(DISPLAY_TYPE), // drivers/gpio_controller DEFINE_MODULE_SYMBOL(gpio_descriptor_acquire), DEFINE_MODULE_SYMBOL(gpio_descriptor_release), @@ -118,9 +161,14 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(gpio_descriptor_get_native_pin_number), DEFINE_MODULE_SYMBOL(gpio_descriptor_get_pin_number), DEFINE_MODULE_SYMBOL(gpio_descriptor_get_owner_type), + DEFINE_MODULE_SYMBOL(gpio_descriptor_add_callback), + DEFINE_MODULE_SYMBOL(gpio_descriptor_remove_callback), + DEFINE_MODULE_SYMBOL(gpio_descriptor_enable_interrupt), + DEFINE_MODULE_SYMBOL(gpio_descriptor_disable_interrupt), DEFINE_MODULE_SYMBOL(gpio_controller_get_pin_count), DEFINE_MODULE_SYMBOL(gpio_controller_init_descriptors), DEFINE_MODULE_SYMBOL(gpio_controller_deinit_descriptors), + DEFINE_MODULE_SYMBOL(gpio_controller_get_controller_context), DEFINE_MODULE_SYMBOL(GPIO_CONTROLLER_TYPE), // drivers/grove DEFINE_MODULE_SYMBOL(grove_set_mode), @@ -138,6 +186,10 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(i2c_controller_register8_get), DEFINE_MODULE_SYMBOL(i2c_controller_register8_set_bits), DEFINE_MODULE_SYMBOL(i2c_controller_register8_reset_bits), + DEFINE_MODULE_SYMBOL(i2c_controller_register16le_get), + DEFINE_MODULE_SYMBOL(i2c_controller_register16le_set), + DEFINE_MODULE_SYMBOL(i2c_controller_register16be_get), + DEFINE_MODULE_SYMBOL(i2c_controller_register16be_set), DEFINE_MODULE_SYMBOL(I2C_CONTROLLER_TYPE), // drivers/i2s_controller DEFINE_MODULE_SYMBOL(i2s_controller_read), @@ -146,18 +198,53 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(i2s_controller_get_config), DEFINE_MODULE_SYMBOL(i2s_controller_reset), DEFINE_MODULE_SYMBOL(i2s_controller_set_rx_tdm_config), + DEFINE_MODULE_SYMBOL(i2s_controller_set_rx_pdm_config), + DEFINE_MODULE_SYMBOL(i2s_controller_disable_direction), DEFINE_MODULE_SYMBOL(I2S_CONTROLLER_TYPE), + // drivers/i8080_controller + DEFINE_MODULE_SYMBOL(I8080_CONTROLLER_TYPE), + // drivers/keyboard + DEFINE_MODULE_SYMBOL(keyboard_read_key), + DEFINE_MODULE_SYMBOL(KEYBOARD_TYPE), + // drivers/pointer + DEFINE_MODULE_SYMBOL(pointer_enter_sleep), + DEFINE_MODULE_SYMBOL(pointer_exit_sleep), + DEFINE_MODULE_SYMBOL(pointer_read_data), + DEFINE_MODULE_SYMBOL(pointer_get_touched_points), + DEFINE_MODULE_SYMBOL(pointer_set_swap_xy), + DEFINE_MODULE_SYMBOL(pointer_get_swap_xy), + DEFINE_MODULE_SYMBOL(pointer_set_mirror_x), + DEFINE_MODULE_SYMBOL(pointer_get_mirror_x), + DEFINE_MODULE_SYMBOL(pointer_set_mirror_y), + DEFINE_MODULE_SYMBOL(pointer_get_mirror_y), + DEFINE_MODULE_SYMBOL(POINTER_TYPE), + // drivers/power_supply + DEFINE_MODULE_SYMBOL(power_supply_supports_property), + DEFINE_MODULE_SYMBOL(power_supply_get_property), + DEFINE_MODULE_SYMBOL(power_supply_supports_charge_control), + DEFINE_MODULE_SYMBOL(power_supply_is_allowed_to_charge), + DEFINE_MODULE_SYMBOL(power_supply_set_allowed_to_charge), + DEFINE_MODULE_SYMBOL(power_supply_supports_quick_charge), + DEFINE_MODULE_SYMBOL(power_supply_is_quick_charge_enabled), + DEFINE_MODULE_SYMBOL(power_supply_set_quick_charge_enabled), + DEFINE_MODULE_SYMBOL(power_supply_supports_power_off), + DEFINE_MODULE_SYMBOL(power_supply_power_off), + DEFINE_MODULE_SYMBOL(POWER_SUPPLY_TYPE), // drivers/root DEFINE_MODULE_SYMBOL(root_is_model), // drivers/rtc DEFINE_MODULE_SYMBOL(rtc_get_time), DEFINE_MODULE_SYMBOL(rtc_set_time), DEFINE_MODULE_SYMBOL(RTC_TYPE), + // drivers/sdcard + DEFINE_MODULE_SYMBOL(SDCARD_TYPE), // drivers/spi_controller DEFINE_MODULE_SYMBOL(spi_controller_lock), DEFINE_MODULE_SYMBOL(spi_controller_try_lock), DEFINE_MODULE_SYMBOL(spi_controller_unlock), DEFINE_MODULE_SYMBOL(SPI_CONTROLLER_TYPE), + // drivers/spi_peripheral + DEFINE_MODULE_SYMBOL(SPI_PERIPHERAL_TYPE), // drivers/uart_controller DEFINE_MODULE_SYMBOL(uart_controller_open), DEFINE_MODULE_SYMBOL(uart_controller_close), @@ -245,6 +332,8 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(WIFI_TYPE), // drivers/usb_host_hid DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected), + DEFINE_MODULE_SYMBOL(usb_host_hid_subscribe), + DEFINE_MODULE_SYMBOL(usb_host_hid_unsubscribe), DEFINE_MODULE_SYMBOL(USB_HOST_HID_TYPE), // drivers/usb_host_midi DEFINE_MODULE_SYMBOL(usb_midi_set_callback),