From 77e6872191048838ae50a479966e76453a46c17d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 01:03:50 +0200 Subject: [PATCH] WIP kernel drivers for T-Deck --- .../DevicetreeCompiler/source/generator.py | 5 + Devices/cyd-2432s024r/cyd,2432s024r.dts | 4 +- Devices/cyd-2432s028r/cyd,2432s028r.dts | 4 +- Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts | 4 +- Devices/cyd-e32r28t/cyd,e32r28t.dts | 4 +- Devices/cyd-e32r32p/cyd,e32r32p.dts | 4 +- .../elecrow,crowpanel-basic-28.dts | 4 +- .../elecrow,crowpanel-basic-35.dts | 4 +- Devices/lilygo-tdeck/CMakeLists.txt | 2 +- Devices/lilygo-tdeck/Source/Configuration.cpp | 15 +- Devices/lilygo-tdeck/Source/Drivers.cpp | 7 - Devices/lilygo-tdeck/Source/Init.cpp | 94 ------ .../KeyboardBacklight/KeyboardBacklight.cpp | 111 ------- .../KeyboardBacklight/KeyboardBacklight.h | 36 --- .../Source/bindings/tdeck_keyboard.h | 7 + .../bindings/tdeck_keyboard_backlight.h | 7 + .../lilygo-tdeck/Source/devices/Display.cpp | 53 ---- Devices/lilygo-tdeck/Source/devices/Display.h | 16 - .../Source/devices/KeyboardBacklight.cpp | 51 ---- .../Source/devices/KeyboardBacklight.h | 32 -- .../Source/devices/TdeckKeyboard.cpp | 86 ------ .../Source/devices/TdeckKeyboard.h | 25 -- .../Source/drivers/tdeck_keyboard.h | 16 + .../Source/drivers/tdeck_keyboard_backlight.h | 17 ++ Devices/lilygo-tdeck/Source/module.cpp | 103 ++++++- .../lilygo-tdeck/Source/tdeck_keyboard.cpp | 101 +++++++ .../Source/tdeck_keyboard_backlight.cpp | 122 ++++++++ .../lilygo,tdeck-keyboard-backlight.yaml | 13 + .../bindings/lilygo,tdeck-keyboard.yaml | 7 + Devices/lilygo-tdeck/devicetree.yaml | 3 + Devices/lilygo-tdeck/lilygo,tdeck.dts | 50 ++- Devices/lilygo-thmi/lilygo,thmi.dts | 4 +- Devices/unphone/unphone.dts | 4 +- Drivers/gt911-module/CMakeLists.txt | 11 + .../gt911-module/bindings/goodix,gt911.yaml | 45 +++ Drivers/gt911-module/devicetree.yaml | 3 + Drivers/gt911-module/include/bindings/gt911.h | 7 + Drivers/gt911-module/include/drivers/gt911.h | 31 ++ Drivers/gt911-module/include/gt911_module.h | 14 + Drivers/gt911-module/source/gt911.cpp | 205 +++++++++++++ Drivers/gt911-module/source/module.cpp | 32 ++ Drivers/st7789-module/CMakeLists.txt | 11 + .../bindings/sitronix,st7789.yaml | 71 +++++ Drivers/st7789-module/devicetree.yaml | 3 + .../st7789-module/include/bindings/st7789.h | 7 + .../st7789-module/include/drivers/st7789.h | 36 +++ Drivers/st7789-module/include/st7789_module.h | 14 + Drivers/st7789-module/source/module.cpp | 32 ++ Drivers/st7789-module/source/st7789.cpp | 278 +++++++++++++++++ .../include/tactility/lvgl_display.h | 61 ++++ .../include/tactility/lvgl_keyboard.h | 37 +++ .../include/tactility/lvgl_pointer.h | 37 +++ Modules/lvgl-module/source/arch/lvgl_esp32.c | 6 + Modules/lvgl-module/source/lvgl_devices.c | 54 ++++ Modules/lvgl-module/source/lvgl_display.c | 215 +++++++++++++ Modules/lvgl-module/source/lvgl_keyboard.c | 67 +++++ Modules/lvgl-module/source/lvgl_pointer.c | 76 +++++ .../espressif,esp32-ledc-backlight.yaml | 29 ++ .../tactility/bindings/esp32_ledc_backlight.h | 15 + .../tactility/drivers/esp32_ledc_backlight.h | 23 ++ .../source/drivers/esp32_ledc_backlight.cpp | 133 ++++++++ Platforms/platform-esp32/source/module.cpp | 3 + .../Include/Tactility/hal/power/PowerDevice.h | 16 +- .../Tactility/hal/touch/KernelTouchDriver.h | 2 +- Tactility/Source/Tactility.cpp | 13 +- Tactility/Source/app/boot/Boot.cpp | 39 ++- Tactility/Source/app/display/Display.cpp | 16 +- .../app/kerneldisplay/KernelDisplay.cpp | 284 ++++++++++++++++++ .../Source/app/keyboard/KeyboardSettings.cpp | 13 +- Tactility/Source/app/power/Power.cpp | 52 ++-- Tactility/Source/app/poweroff/PowerOff.cpp | 9 +- Tactility/Source/hal/power/PowerDevice.cpp | 172 +++++++++++ .../Source/hal/touch/KernelTouchDriver.cpp | 8 +- Tactility/Source/lvgl/Keyboard.cpp | 5 +- Tactility/Source/lvgl/Lvgl.cpp | 35 +-- .../service/keyboardidle/KeyboardIdle.cpp | 27 +- .../bindings/pointer-placeholder.yaml | 5 + .../bindings/touch-placeholder.yaml | 5 - ...ch_placeholder.h => pointer_placeholder.h} | 4 +- .../include/tactility/drivers/backlight.h | 96 ++++++ .../include/tactility/drivers/display.h | 52 ++++ .../include/tactility/drivers/keyboard.h | 51 ++++ .../tactility/drivers/{touch.h => pointer.h} | 70 ++--- ...ch_placeholder.h => pointer_placeholder.h} | 2 +- .../include/tactility/drivers/power_supply.h | 163 ++++++++++ TactilityKernel/source/drivers/backlight.cpp | 38 +++ TactilityKernel/source/drivers/display.cpp | 48 ++- TactilityKernel/source/drivers/grove.cpp | 6 +- TactilityKernel/source/drivers/keyboard.cpp | 18 ++ TactilityKernel/source/drivers/pointer.cpp | 63 ++++ ...laceholder.cpp => pointer_placeholder.cpp} | 12 +- .../source/drivers/power_supply.cpp | 63 ++++ TactilityKernel/source/drivers/rtc.cpp | 6 +- TactilityKernel/source/drivers/sdcard.cpp | 2 +- .../source/drivers/spi_controller.cpp | 8 +- TactilityKernel/source/drivers/touch.cpp | 63 ---- TactilityKernel/source/kernel_init.cpp | 4 +- 97 files changed, 3271 insertions(+), 770 deletions(-) delete mode 100644 Devices/lilygo-tdeck/Source/Drivers.cpp delete mode 100644 Devices/lilygo-tdeck/Source/Init.cpp delete mode 100644 Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp delete mode 100644 Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h create mode 100644 Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h create mode 100644 Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h delete mode 100644 Devices/lilygo-tdeck/Source/devices/Display.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/Display.h delete mode 100644 Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h delete mode 100644 Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h create mode 100644 Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h create mode 100644 Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h create mode 100644 Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp create mode 100644 Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp create mode 100644 Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml create mode 100644 Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml create mode 100644 Drivers/gt911-module/CMakeLists.txt create mode 100644 Drivers/gt911-module/bindings/goodix,gt911.yaml create mode 100644 Drivers/gt911-module/devicetree.yaml create mode 100644 Drivers/gt911-module/include/bindings/gt911.h create mode 100644 Drivers/gt911-module/include/drivers/gt911.h create mode 100644 Drivers/gt911-module/include/gt911_module.h create mode 100644 Drivers/gt911-module/source/gt911.cpp create mode 100644 Drivers/gt911-module/source/module.cpp create mode 100644 Drivers/st7789-module/CMakeLists.txt create mode 100644 Drivers/st7789-module/bindings/sitronix,st7789.yaml create mode 100644 Drivers/st7789-module/devicetree.yaml create mode 100644 Drivers/st7789-module/include/bindings/st7789.h create mode 100644 Drivers/st7789-module/include/drivers/st7789.h create mode 100644 Drivers/st7789-module/include/st7789_module.h create mode 100644 Drivers/st7789-module/source/module.cpp create mode 100644 Drivers/st7789-module/source/st7789.cpp create mode 100644 Modules/lvgl-module/include/tactility/lvgl_display.h create mode 100644 Modules/lvgl-module/include/tactility/lvgl_keyboard.h create mode 100644 Modules/lvgl-module/include/tactility/lvgl_pointer.h create mode 100644 Modules/lvgl-module/source/lvgl_devices.c create mode 100644 Modules/lvgl-module/source/lvgl_display.c create mode 100644 Modules/lvgl-module/source/lvgl_keyboard.c create mode 100644 Modules/lvgl-module/source/lvgl_pointer.c create mode 100644 Platforms/platform-esp32/bindings/espressif,esp32-ledc-backlight.yaml create mode 100644 Platforms/platform-esp32/include/tactility/bindings/esp32_ledc_backlight.h create mode 100644 Platforms/platform-esp32/include/tactility/drivers/esp32_ledc_backlight.h create mode 100644 Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp create mode 100644 Tactility/Source/app/kerneldisplay/KernelDisplay.cpp create mode 100644 Tactility/Source/hal/power/PowerDevice.cpp create mode 100644 TactilityKernel/bindings/pointer-placeholder.yaml delete mode 100644 TactilityKernel/bindings/touch-placeholder.yaml rename TactilityKernel/include/tactility/bindings/{touch_placeholder.h => pointer_placeholder.h} (57%) create mode 100644 TactilityKernel/include/tactility/drivers/backlight.h create mode 100644 TactilityKernel/include/tactility/drivers/keyboard.h rename TactilityKernel/include/tactility/drivers/{touch.h => pointer.h} (65%) rename TactilityKernel/include/tactility/drivers/{touch_placeholder.h => pointer_placeholder.h} (83%) create mode 100644 TactilityKernel/include/tactility/drivers/power_supply.h create mode 100644 TactilityKernel/source/drivers/backlight.cpp create mode 100644 TactilityKernel/source/drivers/keyboard.cpp create mode 100644 TactilityKernel/source/drivers/pointer.cpp rename TactilityKernel/source/drivers/{touch_placeholder.cpp => pointer_placeholder.cpp} (60%) create mode 100644 TactilityKernel/source/drivers/power_supply.cpp delete mode 100644 TactilityKernel/source/drivers/touch.cpp diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index 7980f31ce..9d3a12e99 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -79,6 +79,11 @@ def property_to_string(property: DeviceProperty, devices: list[Device]) -> str: value_list.append(str(item)) return "{ " + ",".join(value_list) + " }" elif type == "phandle": + # Mirrors the string-passthrough convention "phandles" already uses for sentinel defaults + # like GPIO_PIN_SPEC_NONE: a binding default of "NULL" represents "no device referenced", + # not an actual node name to resolve. + if isinstance(property.value, str) and property.value == "NULL": + return "NULL" return find_phandle(devices, property.value) elif type == "phandles": value_list = list() diff --git a/Devices/cyd-2432s024r/cyd,2432s024r.dts b/Devices/cyd-2432s024r/cyd,2432s024r.dts index 6e3953d6d..a5e3ccd0e 100644 --- a/Devices/cyd-2432s024r/cyd,2432s024r.dts +++ b/Devices/cyd-2432s024r/cyd,2432s024r.dts @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include / { @@ -37,7 +37,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/cyd-2432s028r/cyd,2432s028r.dts b/Devices/cyd-2432s028r/cyd,2432s028r.dts index 87e451588..141c9750e 100644 --- a/Devices/cyd-2432s028r/cyd,2432s028r.dts +++ b/Devices/cyd-2432s028r/cyd,2432s028r.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -46,7 +46,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts index 096799359..99ea163fe 100644 --- a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts +++ b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -46,7 +46,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/cyd-e32r28t/cyd,e32r28t.dts b/Devices/cyd-e32r28t/cyd,e32r28t.dts index 2c1f66a97..236b5637f 100644 --- a/Devices/cyd-e32r28t/cyd,e32r28t.dts +++ b/Devices/cyd-e32r28t/cyd,e32r28t.dts @@ -6,7 +6,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -36,7 +36,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/cyd-e32r32p/cyd,e32r32p.dts b/Devices/cyd-e32r32p/cyd,e32r32p.dts index 3ada265f2..b47d2735c 100644 --- a/Devices/cyd-e32r32p/cyd,e32r32p.dts +++ b/Devices/cyd-e32r32p/cyd,e32r32p.dts @@ -7,7 +7,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -45,7 +45,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; 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 b7d16a86b..e3a53374b 100644 --- a/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts +++ b/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -47,7 +47,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts b/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts index 0417ba553..c64969a02 100644 --- a/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts +++ b/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -47,7 +47,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/lilygo-tdeck/CMakeLists.txt b/Devices/lilygo-tdeck/CMakeLists.txt index 6b073058a..fc5c6b676 100644 --- a/Devices/lilygo-tdeck/CMakeLists.txt +++ b/Devices/lilygo-tdeck/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility EspLcdCompat ST7789 GT911 PwmBacklight EstimatedPower driver + REQUIRES Tactility EstimatedPower driver ) diff --git a/Devices/lilygo-tdeck/Source/Configuration.cpp b/Devices/lilygo-tdeck/Source/Configuration.cpp index fc6af509b..61a274f41 100644 --- a/Devices/lilygo-tdeck/Source/Configuration.cpp +++ b/Devices/lilygo-tdeck/Source/Configuration.cpp @@ -1,30 +1,19 @@ -#include "devices/Display.h" -#include "devices/KeyboardBacklight.h" #include "devices/Power.h" -#include "devices/TdeckKeyboard.h" #include "devices/TrackballDevice.h" #include -#include -#include bool initBoot(); using namespace tt::hal; static std::vector> createDevices() { - auto* i2c_internal = device_find_by_name("i2c0"); - check(i2c_internal); return { - createPower(), - createDisplay(), - std::make_shared(i2c_internal), - std::make_shared(), - std::make_shared(), + createPower() + // std::make_shared(), }; } extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, .createDevices = createDevices }; diff --git a/Devices/lilygo-tdeck/Source/Drivers.cpp b/Devices/lilygo-tdeck/Source/Drivers.cpp deleted file mode 100644 index c8a5c6658..000000000 --- a/Devices/lilygo-tdeck/Source/Drivers.cpp +++ /dev/null @@ -1,7 +0,0 @@ -extern "C" { - -extern void register_device_drivers() { - /* NO-OP */ -} - -} diff --git a/Devices/lilygo-tdeck/Source/Init.cpp b/Devices/lilygo-tdeck/Source/Init.cpp deleted file mode 100644 index 07ef9aca9..000000000 --- a/Devices/lilygo-tdeck/Source/Init.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "PwmBacklight.h" -#include "devices/KeyboardBacklight.h" -#include "devices/TrackballDevice.h" - -#include -#include -#include -#include -#include -#include - -constexpr auto* TAG = "T-Deck"; - -constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10; - -static bool powerOn() { - gpio_config_t device_power_signal_config = { - .pin_bit_mask = BIT64(TDECK_POWERON_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(&device_power_signal_config) != ESP_OK) { - return false; - } - - if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) { - return false; - } - - // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. - tt::kernel::delayMillis(100); - - return true; -} - -bool initBoot() { - LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); - if (!powerOn()) { - LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); - return false; - } - - /* 32 Khz and higher gives an issue where the screen starts dimming again above 80% brightness - * when moving the brightness slider rapidly from a lower setting to 100%. - * This is not a slider bug (data was debug-traced) */ - if (!driver::pwmbacklight::init(GPIO_NUM_42, 30000)) { - LOG_E(TAG, "Backlight init failed"); - return false; - } - - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - auto gps_service = tt::service::gps::findGpsService(); - if (gps_service != nullptr) { - std::vector gps_configurations; - gps_service->getGpsConfigurations(gps_configurations); - if (gps_configurations.empty()) { - if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) { - LOG_I(TAG, "Configured internal GPS"); - } else { - LOG_E(TAG, "Failed to configure internal GPS"); - } - } - } - }); - - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - auto kbBacklight = tt::hal::findDevice("Keyboard Backlight"); - if (kbBacklight != nullptr) { - LOG_I(TAG, "%s starting", kbBacklight->getName().c_str()); - auto kbDevice = std::static_pointer_cast(kbBacklight); - if (kbDevice->start()) { - LOG_I(TAG, "%s started", kbBacklight->getName().c_str()); - } else { - LOG_E(TAG, "%s start failed", kbBacklight->getName().c_str()); - } - } - - auto trackball = tt::hal::findDevice("Trackball"); - if (trackball != nullptr) { - LOG_I(TAG, "%s starting", trackball->getName().c_str()); - auto tbDevice = std::static_pointer_cast(trackball); - if (tbDevice->start()) { - LOG_I(TAG, "%s started", trackball->getName().c_str()); - } else { - LOG_E(TAG, "%s start failed", trackball->getName().c_str()); - } - } - }); - - return true; -} diff --git a/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp b/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp deleted file mode 100644 index e0ea70d8b..000000000 --- a/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "KeyboardBacklight.h" - -#include -#include -#include - -constexpr auto* TAG = "KeyboardBacklight"; - -namespace keyboardbacklight { - -static const uint8_t CMD_BRIGHTNESS = 0x01; -static const uint8_t CMD_DEFAULT_BRIGHTNESS = 0x02; - -static i2c_port_t g_i2cPort = I2C_NUM_MAX; -static uint8_t g_slaveAddress = 0x55; -static uint8_t g_currentBrightness = 127; - -// TODO: Umm...something. Calls xxxBrightness, ignores return values. -bool init(i2c_port_t i2cPort, uint8_t slaveAddress) { - g_i2cPort = i2cPort; - g_slaveAddress = slaveAddress; - - LOG_I(TAG, "Initialized on I2C port %d, address 0x%02X", static_cast(g_i2cPort), (unsigned)g_slaveAddress); - - // Set a reasonable default brightness - if (!setDefaultBrightness(127)) { - LOG_E(TAG, "Failed to set default brightness"); - return false; - } - - if (!setBrightness(127)) { - LOG_E(TAG, "Failed to set brightness"); - return false; - } - - return true; -} - -bool setBrightness(uint8_t brightness) { - if (g_i2cPort >= I2C_NUM_MAX) { - LOG_E(TAG, "Not initialized"); - return false; - } - - // Skip if brightness is already at target value (avoid I2C spam on every keypress) - if (brightness == g_currentBrightness) { - return true; - } - - LOG_I(TAG, "Setting brightness to %d on I2C port %d, address 0x%02X", brightness, static_cast(g_i2cPort), (unsigned)g_slaveAddress); - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true); - i2c_master_write_byte(cmd, CMD_BRIGHTNESS, true); - i2c_master_write_byte(cmd, brightness, true); - i2c_master_stop(cmd); - - esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100)); - i2c_cmd_link_delete(cmd); - - if (ret == ESP_OK) { - g_currentBrightness = brightness; - LOG_I(TAG, "Successfully set brightness to %d", brightness); - return true; - } else { - LOG_E(TAG, "Failed to set brightness: %s (0x%02X)", esp_err_to_name(ret), (unsigned)ret); - return false; - } -} - -bool setDefaultBrightness(uint8_t brightness) { - if (g_i2cPort >= I2C_NUM_MAX) { - LOG_E(TAG, "Not initialized"); - return false; - } - - // Clamp to valid range for default brightness - if (brightness < 30) { - brightness = 30; - } - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true); - i2c_master_write_byte(cmd, CMD_DEFAULT_BRIGHTNESS, true); - i2c_master_write_byte(cmd, brightness, true); - i2c_master_stop(cmd); - - esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100)); - i2c_cmd_link_delete(cmd); - - if (ret == ESP_OK) { - LOG_D(TAG, "Set default brightness to %d", brightness); - return true; - } else { - LOG_E(TAG, "Failed to set default brightness: %s", esp_err_to_name(ret)); - return false; - } -} - -uint8_t getBrightness() { - if (g_i2cPort >= I2C_NUM_MAX) { - LOG_E(TAG, "Not initialized"); - return 0; - } - - return g_currentBrightness; -} - -} diff --git a/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h b/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h deleted file mode 100644 index dad27c2be..000000000 --- a/Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include - -namespace keyboardbacklight { - -/** - * @brief Initialize keyboard backlight control - * @param i2cPort I2C port number (I2C_NUM_0 or I2C_NUM_1) - * @param slaveAddress I2C slave address (default 0x55 for T-Deck keyboard) - * @return true if initialization succeeded - */ -bool init(i2c_port_t i2cPort, uint8_t slaveAddress = 0x55); - -/** - * @brief Set keyboard backlight brightness - * @param brightness Brightness level (0-255, 0=off, 255=max) - * @return true if command succeeded - */ -bool setBrightness(uint8_t brightness); - -/** - * @brief Set default keyboard backlight brightness for ALT+B toggle - * @param brightness Default brightness level (30-255) - * @return true if command succeeded - */ -bool setDefaultBrightness(uint8_t brightness); - -/** - * @brief Get current keyboard backlight brightness - * @return Current brightness level (0-255) - */ -uint8_t getBrightness(); - -} diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h b/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h new file mode 100644 index 000000000..10f904a49 --- /dev/null +++ b/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(tdeck_keyboard, struct TdeckKeyboardConfig) diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h new file mode 100644 index 000000000..bad0bdcbf --- /dev/null +++ b/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(tdeck_keyboard_backlight, struct TdeckKeyboardBacklightConfig) diff --git a/Devices/lilygo-tdeck/Source/devices/Display.cpp b/Devices/lilygo-tdeck/Source/devices/Display.cpp deleted file mode 100644 index 6f2831f6e..000000000 --- a/Devices/lilygo-tdeck/Source/devices/Display.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "Display.h" - -#include -#include -#include -#include -#include - -static std::shared_ptr createTouch() { - auto* i2c = device_find_by_name("i2c0"); - check(i2c); - auto configuration = std::make_unique( - i2c, - 240, - 320, - true, - true, - false, - GPIO_NUM_NC, - GPIO_NUM_16 - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - St7789Display::Configuration panel_configuration = { - .horizontalResolution = 320, - .verticalResolution = 240, - .gapX = 0, - .gapY = 0, - .swapXY = true, - .mirrorX = true, - .mirrorY = false, - .invertColor = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .lvglSwapBytes = false, - .buffSpiram = false // Enabling leads to crashes when refreshing App Hub list - }; - - 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/lilygo-tdeck/Source/devices/Display.h b/Devices/lilygo-tdeck/Source/devices/Display.h deleted file mode 100644 index c6dc69649..000000000 --- a/Devices/lilygo-tdeck/Source/devices/Display.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include -#include - -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_12; -constexpr auto LCD_PIN_DC = GPIO_NUM_11; // RS -constexpr auto LCD_HORIZONTAL_RESOLUTION = 320; -constexpr auto LCD_VERTICAL_RESOLUTION = 240; -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/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp b/Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp deleted file mode 100644 index 42e70596e..000000000 --- a/Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "KeyboardBacklight.h" -#include // Driver -#include - -// TODO: Add Mutex and consider refactoring into a class -bool KeyboardBacklightDevice::start() { - if (initialized) { - return true; - } - - // T-Deck uses I2C_NUM_0 for internal peripherals - initialized = keyboardbacklight::init(I2C_NUM_0); - if (!initialized) { - return false; - } - - // Backlight doesn't seem to turn on until toggled on and off from keyboard settings... - // Or let the display and backlight sleep then wake it up. - // Then it works fine...until reboot, then you need to toggle again. - // The current keyboard firmware sets backlight duty to 0 on boot. - // https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/firmware/T-Keyboard_Keyboard_ESP32C3_250620.bin - // https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L25 - // https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L217 - auto kbSettings = tt::settings::keyboard::loadOrGetDefault(); - keyboardbacklight::setBrightness(kbSettings.backlightEnabled ? kbSettings.backlightBrightness : 0); - - return true; -} - -bool KeyboardBacklightDevice::stop() { - if (initialized) { - // Turn off backlight on shutdown - keyboardbacklight::setBrightness(0); - initialized = false; - } - return true; -} - -bool KeyboardBacklightDevice::setBrightness(uint8_t brightness) { - if (!initialized) { - return false; - } - return keyboardbacklight::setBrightness(brightness); -} - -uint8_t KeyboardBacklightDevice::getBrightness() const { - if (!initialized) { - return 0; - } - return keyboardbacklight::getBrightness(); -} diff --git a/Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h b/Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h deleted file mode 100644 index e3deb858a..000000000 --- a/Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -class KeyboardBacklightDevice final : public tt::hal::Device { - - bool initialized = false; - -public: - - tt::hal::Device::Type getType() const override { return tt::hal::Device::Type::I2c; } - std::string getName() const override { return "Keyboard Backlight"; } - std::string getDescription() const override { return "T-Deck keyboard backlight control"; } - - bool start(); - bool stop(); - bool isAttached() const { return initialized; } - - /** - * Set keyboard backlight brightness - * @param brightness 0-255 (0=off, 255=max) - */ - bool setBrightness(uint8_t brightness); - - /** - * Get current brightness - * @return 0-255 - */ - uint8_t getBrightness() const; -}; - diff --git a/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp b/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp deleted file mode 100644 index 5c26e0823..000000000 --- a/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "TdeckKeyboard.h" - -#include -#include -#include -#include -#include -#include -#include - -using tt::hal::findFirstDevice; - -constexpr auto* TAG = "TdeckKeyboard"; - -constexpr uint8_t TDECK_KEYBOARD_SLAVE_ADDRESS = 0x55; - -/** - * The callback simulates press and release events, because the T-Deck - * keyboard only publishes press events on I2C. - * LVGL currently works without those extra release events, but they - * are implemented for correctness and future compatibility. - * - * @param indev_drv - * @param data - */ -static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { - static uint8_t last_buffer = 0x00; - uint8_t read_buffer = 0x00; - - // Defaults - data->key = 0; - data->state = LV_INDEV_STATE_RELEASED; - - auto* keyboard = static_cast(lv_indev_get_user_data(indev)); - if (i2c_controller_read(keyboard->getI2cController(), TDECK_KEYBOARD_SLAVE_ADDRESS, &read_buffer, 1, 100 / portTICK_PERIOD_MS) == ERROR_NONE) { - if (read_buffer == 0 && read_buffer != last_buffer) { - LOG_D(TAG, "Released %d", last_buffer); - data->key = last_buffer; - data->state = LV_INDEV_STATE_RELEASED; - } else if (read_buffer != 0) { - LOG_D(TAG, "Pressed %d", read_buffer); - data->key = read_buffer; - data->state = LV_INDEV_STATE_PRESSED; - // TODO: Avoid performance hit by calling loadOrGetDefault() on each key press - // Ensure LVGL activity is triggered so idle services can wake the display - lv_display_trigger_activity(nullptr); - - // Actively wake display/backlights immediately on key press (independent of idle tick) - // Restore display backlight if off (we assume duty 0 means dimmed) - auto display = findFirstDevice(tt::hal::Device::Type::Display); - if (display && display->supportsBacklightDuty()) { - // Load display settings for target duty - auto dsettings = tt::settings::display::loadOrGetDefault(); - // Always set duty, harmless if already on - display->setBacklightDuty(dsettings.backlightDuty); - } - - // Restore keyboard backlight if enabled in settings - auto ksettings = tt::settings::keyboard::loadOrGetDefault(); - if (ksettings.backlightEnabled) { - keyboardbacklight::setBrightness(ksettings.backlightBrightness); - } - } - } - - last_buffer = read_buffer; -} - -bool TdeckKeyboard::startLvgl(lv_display_t* display) { - deviceHandle = lv_indev_create(); - lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_KEYPAD); - lv_indev_set_read_cb(deviceHandle, &keyboard_read_callback); - lv_indev_set_display(deviceHandle, display); - lv_indev_set_user_data(deviceHandle, this); - return true; -} - -bool TdeckKeyboard::stopLvgl() { - lv_indev_delete(deviceHandle); - deviceHandle = nullptr; - return true; -} - -bool TdeckKeyboard::isAttached() const { - return i2c_controller_has_device_at_address(i2cController, TDECK_KEYBOARD_SLAVE_ADDRESS, 100) == ERROR_NONE; -} diff --git a/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h b/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h deleted file mode 100644 index 5695ce82d..000000000 --- a/Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -struct Device; - -class TdeckKeyboard final : public tt::hal::keyboard::KeyboardDevice { - - ::Device* i2cController; - lv_indev_t* deviceHandle = nullptr; - -public: - - explicit TdeckKeyboard(::Device* i2cController) : i2cController(i2cController) {} - - std::string getName() const override { return "T-Deck Keyboard"; } - std::string getDescription() const override { return "I2C keyboard"; } - - ::Device* getI2cController() const { return i2cController; } - - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - bool isAttached() const override; - lv_indev_t* getLvglIndev() override { return deviceHandle; } -}; diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h b/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h new file mode 100644 index 000000000..c03f052cd --- /dev/null +++ b/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct TdeckKeyboardConfig { + uint8_t address; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h new file mode 100644 index 000000000..dc43a2b0c --- /dev/null +++ b/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct TdeckKeyboardBacklightConfig { + uint8_t address; + uint8_t brightness_default; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Devices/lilygo-tdeck/Source/module.cpp b/Devices/lilygo-tdeck/Source/module.cpp index 3048f642f..5f0341f6a 100644 --- a/Devices/lilygo-tdeck/Source/module.cpp +++ b/Devices/lilygo-tdeck/Source/module.cpp @@ -1,14 +1,113 @@ #include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "devices/TrackballDevice.h" + +#include + +constexpr auto* TAG = "T-Deck"; + +constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10; + extern "C" { +extern Driver tdeck_keyboard_driver; +extern Driver tdeck_keyboard_backlight_driver; + +static bool power_on() { + gpio_config_t device_power_signal_config = { + .pin_bit_mask = BIT64(TDECK_POWERON_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(&device_power_signal_config) != ESP_OK) { + return false; + } + + if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) { + return false; + } + + // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. + tt::kernel::delayMillis(100); + + return true; +} + +void subscribe_events() { + tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { + auto gps_service = tt::service::gps::findGpsService(); + if (gps_service != nullptr) { + std::vector gps_configurations; + gps_service->getGpsConfigurations(gps_configurations); + if (gps_configurations.empty()) { + if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) { + LOG_I(TAG, "Configured internal GPS"); + } else { + LOG_E(TAG, "Failed to configure internal GPS"); + } + } + } + }); + + tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { + auto trackball = tt::hal::findDevice("Trackball"); + if (trackball != nullptr) { + LOG_I(TAG, "%s starting", trackball->getName().c_str()); + auto tbDevice = std::static_pointer_cast(trackball); + if (tbDevice->start()) { + LOG_I(TAG, "%s started", trackball->getName().c_str()); + } else { + LOG_E(TAG, "%s start failed", trackball->getName().c_str()); + } + } + }); +} + static error_t start() { - // Empty for now + LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); + if (!power_on()) { + LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); + return ERROR_RESOURCE; + } + + if (driver_construct_add(&tdeck_keyboard_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to register keyboard driver"); + return ERROR_RESOURCE; + } + + if (driver_construct_add(&tdeck_keyboard_backlight_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to register keyboard backlight driver"); + return ERROR_RESOURCE; + } + + subscribe_events(); + return ERROR_NONE; } static error_t stop() { - // Empty for now + // We never stop this module, but keep driver registration symmetric in case that changes. + if (driver_remove_destruct(&tdeck_keyboard_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to unregister keyboard driver"); + return ERROR_RESOURCE; + } + if (driver_remove_destruct(&tdeck_keyboard_backlight_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to unregister keyboard backlight driver"); + return ERROR_RESOURCE; + } return ERROR_NONE; } diff --git a/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp b/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp new file mode 100644 index 000000000..a40c3b397 --- /dev/null +++ b/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "TdeckKeyboard" +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(100); + +struct TdeckKeyboardInternal { + // The T-Deck keyboard controller only ever reports the currently pressed key (0x00 = none) and + // never a release event, so release events are synthesized by comparing against this. + uint8_t last_key; +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + + auto address = GET_CONFIG(device)->address; + if (i2c_controller_has_device_at_address(parent, address, I2C_TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "No device found on I2C bus at address 0x%02X", address); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(TdeckKeyboardInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->last_key = 0; + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region KeyboardApi + +static error_t tdeck_keyboard_read_key(Device* device, KeyboardKeyData* data) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto* internal = static_cast(device_get_driver_data(device)); + + uint8_t read_buffer = 0; + if (i2c_controller_read(parent, address, &read_buffer, 1, I2C_TIMEOUT) != ERROR_NONE) { + return ERROR_RESOURCE; + } + + // This controller never buffers more than one key event. + data->continue_reading = false; + + if (read_buffer == 0 && internal->last_key != 0) { + data->key = internal->last_key; + data->pressed = false; + } else if (read_buffer != 0) { + data->key = read_buffer; + data->pressed = true; + } else { + data->key = 0; + data->pressed = false; + } + + internal->last_key = read_buffer; + return ERROR_NONE; +} + +// endregion + +static const KeyboardApi tdeck_keyboard_api = { + .read_key = tdeck_keyboard_read_key, +}; + +extern struct Module lilygo_tdeck_module; + +Driver tdeck_keyboard_driver = { + .name = "tdeck_keyboard", + .compatible = (const char*[]) { "lilygo,tdeck-keyboard", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &tdeck_keyboard_api, + .device_type = &KEYBOARD_TYPE, + .owner = &lilygo_tdeck_module, + .internal = nullptr +}; diff --git a/Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp b/Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp new file mode 100644 index 000000000..1df6e4e87 --- /dev/null +++ b/Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "TdeckKeyboardBacklight" +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(100); +static constexpr uint8_t CMD_BRIGHTNESS = 0x01; +static constexpr uint8_t CMD_DEFAULT_BRIGHTNESS = 0x02; + +struct TdeckKeyboardBacklightInternal { + // The controller is write-only for brightness, so the current value is cached here. + uint8_t current_brightness; +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + + auto address = GET_CONFIG(device)->address; + if (i2c_controller_has_device_at_address(parent, address, I2C_TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "No device found on I2C bus at address 0x%02X", address); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(TdeckKeyboardBacklightInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->current_brightness = 0; + device_set_driver_data(device, internal); + + auto brightness_default = GET_CONFIG(device)->brightness_default; + + // Configures the keyboard controller's own persisted default, used by its onboard ALT+B toggle. + if (i2c_controller_write_register(parent, address, CMD_DEFAULT_BRIGHTNESS, &brightness_default, 1, I2C_TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to set default brightness"); + } + + if (i2c_controller_write_register(parent, address, CMD_BRIGHTNESS, &brightness_default, 1, I2C_TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to set initial brightness"); + } else { + internal->current_brightness = brightness_default; + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region BacklightApi + +static error_t tdeck_keyboard_backlight_set_brightness(Device* device, uint8_t brightness) { + auto* parent = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + auto* internal = static_cast(device_get_driver_data(device)); + + if (i2c_controller_write_register(parent, address, CMD_BRIGHTNESS, &brightness, 1, I2C_TIMEOUT) != ERROR_NONE) { + return ERROR_RESOURCE; + } + + internal->current_brightness = brightness; + return ERROR_NONE; +} + +static error_t tdeck_keyboard_backlight_set_brightness_default(Device* device) { + return tdeck_keyboard_backlight_set_brightness(device, GET_CONFIG(device)->brightness_default); +} + +static error_t tdeck_keyboard_backlight_get_brightness(Device* device, uint8_t* out_brightness) { + auto* internal = static_cast(device_get_driver_data(device)); + *out_brightness = internal->current_brightness; + return ERROR_NONE; +} + +static uint8_t tdeck_keyboard_backlight_get_min_brightness(Device*) { + return 0; +} + +static uint8_t tdeck_keyboard_backlight_get_max_brightness(Device*) { + return 255; +} + +// endregion + +static const BacklightApi tdeck_keyboard_backlight_api = { + .set_brightness = tdeck_keyboard_backlight_set_brightness, + .set_brightness_default = tdeck_keyboard_backlight_set_brightness_default, + .get_brightness = tdeck_keyboard_backlight_get_brightness, + .get_min_brightness = tdeck_keyboard_backlight_get_min_brightness, + .get_max_brightness = tdeck_keyboard_backlight_get_max_brightness, +}; + +extern struct Module lilygo_tdeck_module; + +Driver tdeck_keyboard_backlight_driver = { + .name = "tdeck_keyboard_backlight", + .compatible = (const char*[]) { "lilygo,tdeck-keyboard-backlight", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &tdeck_keyboard_backlight_api, + .device_type = &BACKLIGHT_TYPE, + .owner = &lilygo_tdeck_module, + .internal = nullptr +}; diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml new file mode 100644 index 000000000..7265fc277 --- /dev/null +++ b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml @@ -0,0 +1,13 @@ +description: LilyGO T-Deck onboard keyboard backlight controller + +include: ["i2c-device.yaml"] + +compatible: "lilygo,tdeck-keyboard-backlight" + +bus: i2c + +properties: + brightness-default: + type: int + default: 127 + description: Default brightness level, applied on start and by set_brightness_default(). Range 0-255. diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml new file mode 100644 index 000000000..6562e945e --- /dev/null +++ b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml @@ -0,0 +1,7 @@ +description: LilyGO T-Deck onboard keyboard controller + +include: ["i2c-device.yaml"] + +compatible: "lilygo,tdeck-keyboard" + +bus: i2c diff --git a/Devices/lilygo-tdeck/devicetree.yaml b/Devices/lilygo-tdeck/devicetree.yaml index a19f9045d..36fa6d512 100644 --- a/Devices/lilygo-tdeck/devicetree.yaml +++ b/Devices/lilygo-tdeck/devicetree.yaml @@ -1,3 +1,6 @@ dependencies: - Platforms/platform-esp32 + - Drivers/st7789-module + - Drivers/gt911-module +bindings: bindings dts: lilygo,tdeck.dts diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 37186d554..b8f21e92a 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -7,10 +7,14 @@ #include #include #include +#include +#include +#include +#include #include #include #include -#include +#include // Reference: https://wiki.lilygo.cc/get_started/en/Wearable/T-Deck-Plus/T-Deck-Plus.html#Pin-Overview / { @@ -38,6 +42,26 @@ clock-frequency = <100000>; pin-sda = <&gpio0 18 GPIO_FLAG_NONE>; pin-scl = <&gpio0 8 GPIO_FLAG_NONE>; + + touch { + compatible = "goodix,gt911"; + reg = <0x5D>; + x-max = <240>; + y-max = <320>; + swap-xy; + mirror-x; + pin-interrupt = <&gpio0 16 GPIO_FLAG_NONE>; + }; + + keyboard { + compatible = "lilygo,tdeck-keyboard"; + reg = <0x55>; + }; + + keyboard_backlight { + compatible = "lilygo,tdeck-keyboard-backlight"; + reg = <0x55>; + }; }; i2s0 { @@ -48,6 +72,17 @@ pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so dispaly 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 42 GPIO_FLAG_NONE>; + // 32 KHz and higher causes the screen to start dimming again above 80% brightness + // when moving the brightness slider rapidly from a lower setting to 100% (debug-traced, not a slider bug). + frequency-hz = <30000>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -59,12 +94,21 @@ pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>; display@0 { - compatible = "display-placeholder"; + compatible = "sitronix,st7789"; + horizontal-resolution = <320>; + vertical-resolution = <240>; + swap-xy; + mirror-x; + invert-color; + pixel-clock-hz = <62500000>; + pin-dc = <&gpio0 11 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; + // Must be started after display sdcard@1 { compatible = "espressif,esp32-sdspi"; - status = "disabled"; // Must be started after display + status = "disabled"; frequency-khz = <20000>; }; }; diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index cb7768c18..3495a425b 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -43,7 +43,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; }; diff --git a/Devices/unphone/unphone.dts b/Devices/unphone/unphone.dts index df4966b52..da2c1f737 100644 --- a/Devices/unphone/unphone.dts +++ b/Devices/unphone/unphone.dts @@ -8,7 +8,7 @@ #include #include #include -#include +#include / { compatible = "root"; @@ -53,7 +53,7 @@ }; touch@1 { - compatible = "touch-placeholder"; + compatible = "pointer-placeholder"; }; sdcard@2 { diff --git a/Drivers/gt911-module/CMakeLists.txt b/Drivers/gt911-module/CMakeLists.txt new file mode 100644 index 000000000..04934857f --- /dev/null +++ b/Drivers/gt911-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(gt911-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_touch_gt911 esp_lcd driver +) diff --git a/Drivers/gt911-module/bindings/goodix,gt911.yaml b/Drivers/gt911-module/bindings/goodix,gt911.yaml new file mode 100644 index 000000000..f938d3491 --- /dev/null +++ b/Drivers/gt911-module/bindings/goodix,gt911.yaml @@ -0,0 +1,45 @@ +description: Goodix GT911 capacitive touch controller + +include: ["i2c-device.yaml"] + +compatible: "goodix,gt911" + +bus: i2c + +properties: + 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 + pin-reset: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Reset GPIO pin + pin-interrupt: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Interrupt GPIO pin + reset-active-high: + type: boolean + default: false + description: Whether the reset pin is active high + interrupt-active-high: + type: boolean + default: false + description: Whether the interrupt pin is active high diff --git a/Drivers/gt911-module/devicetree.yaml b/Drivers/gt911-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/gt911-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/gt911-module/include/bindings/gt911.h b/Drivers/gt911-module/include/bindings/gt911.h new file mode 100644 index 000000000..bc5fb9c58 --- /dev/null +++ b/Drivers/gt911-module/include/bindings/gt911.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(gt911, struct Gt911Config) diff --git a/Drivers/gt911-module/include/drivers/gt911.h b/Drivers/gt911-module/include/drivers/gt911.h new file mode 100644 index 000000000..e1f7a5512 --- /dev/null +++ b/Drivers/gt911-module/include/drivers/gt911.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +struct Gt911Config { + // Devicetree address hint. The driver auto-probes both known GT911 addresses + // (0x5D primary, 0x14 backup) regardless, since the effective address depends on + // the controller's INT pin strapping level at power-up, not a fixed board wiring choice. + uint8_t address; + uint16_t x_max; + uint16_t y_max; + bool swap_xy; + bool mirror_x; + bool mirror_y; + struct GpioPinSpec pin_reset; + struct GpioPinSpec pin_interrupt; + bool reset_active_high; + bool interrupt_active_high; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/gt911-module/include/gt911_module.h b/Drivers/gt911-module/include/gt911_module.h new file mode 100644 index 000000000..a06659d24 --- /dev/null +++ b/Drivers/gt911-module/include/gt911_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module gt911_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/gt911-module/source/gt911.cpp b/Drivers/gt911-module/source/gt911.cpp new file mode 100644 index 000000000..2625991e2 --- /dev/null +++ b/Drivers/gt911-module/source/gt911.cpp @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define TAG "GT911" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Gt911Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_touch_handle_t touch_handle; +}; + +static inline gpio_num_t pin_or_nc(const struct GpioPinSpec& pin) { + return pin.gpio_controller == nullptr ? GPIO_NUM_NC : static_cast(pin.pin); +} + +// region Driver lifecycle + +// GT911's I2C address depends on the controller's INT pin level at power-up (board-strapped, not +// devicetree-fixed), so both known addresses are probed regardless of the devicetree "reg" hint. +static esp_err_t create_io_handle(Device* parent, esp_lcd_panel_io_handle_t* out_handle) { + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + + if (i2c_controller_has_device_at_address(parent, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) { + io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS; + } else if (i2c_controller_has_device_at_address(parent, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, pdMS_TO_TICKS(10)) == ERROR_NONE) { + io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP; + } else { + LOG_E(TAG, "No GT911 found on I2C bus"); + return ESP_ERR_NOT_FOUND; + } + + auto* parent_driver = device_get_driver(parent); + if (driver_is_compatible(parent_driver, "espressif,esp32-i2c")) { + auto port = static_cast(parent->config)->port; + return esp_lcd_new_panel_io_i2c_v1(port, &io_config, out_handle); + } + if (driver_is_compatible(parent_driver, "espressif,esp32-i2c-master")) { + auto bus = esp32_i2c_master_get_bus_handle(parent); + io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(parent); + return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, out_handle); + } + + LOG_E(TAG, "Unsupported I2C driver"); + return ESP_ERR_NOT_SUPPORTED; +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(Gt911Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + esp_err_t ret = create_io_handle(parent, &internal->io_handle); + if (ret != ESP_OK) { + free(internal); + return ERROR_RESOURCE; + } + + esp_lcd_touch_config_t touch_config = { + .x_max = config->x_max, + .y_max = config->y_max, + .rst_gpio_num = pin_or_nc(config->pin_reset), + .int_gpio_num = pin_or_nc(config->pin_interrupt), + .levels = { + .reset = config->reset_active_high ? 1u : 0u, + .interrupt = config->interrupt_active_high ? 1u : 0u, + }, + .flags = { + .swap_xy = config->swap_xy ? 1u : 0u, + .mirror_x = config->mirror_x ? 1u : 0u, + .mirror_y = config->mirror_y ? 1u : 0u, + }, + .process_coordinates = nullptr, + .interrupt_callback = nullptr, + .user_data = nullptr, + .driver_data = nullptr, + }; + + ret = esp_lcd_touch_new_i2c_gt911(internal->io_handle, &touch_config, &internal->touch_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create touch handle: %s", esp_err_to_name(ret)); + esp_lcd_panel_io_del(internal->io_handle); + 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)); + + if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete touch handle"); + return ERROR_RESOURCE; + } + + free(internal); + return ERROR_NONE; +} + +// endregion + +// region PointerApi + +static error_t gt911_enter_sleep(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_enter_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_exit_sleep(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_exit_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_read_data(Device* device, TickType_t timeout) { + (void)timeout; // esp_lcd_touch_read_data() has no timeout parameter + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_read_data(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static bool gt911_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_get_coordinates(internal->touch_handle, x, y, strength, point_count, max_point_count); +} + +static error_t gt911_set_swap_xy(Device* device, bool swap) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_set_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_get_swap_xy(Device* device, bool* swap) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_get_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_set_mirror_x(Device* device, bool mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_set_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_get_mirror_x(Device* device, bool* mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_get_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_set_mirror_y(Device* device, bool mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_set_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t gt911_get_mirror_y(Device* device, bool* mirror) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_lcd_touch_get_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +// endregion + +static const PointerApi gt911_pointer_api = { + .enter_sleep = gt911_enter_sleep, + .exit_sleep = gt911_exit_sleep, + .read_data = gt911_read_data, + .get_touched_points = gt911_get_touched_points, + .set_swap_xy = gt911_set_swap_xy, + .get_swap_xy = gt911_get_swap_xy, + .set_mirror_x = gt911_set_mirror_x, + .get_mirror_x = gt911_get_mirror_x, + .set_mirror_y = gt911_set_mirror_y, + .get_mirror_y = gt911_get_mirror_y, +}; + +Driver gt911_driver = { + .name = "gt911", + .compatible = (const char*[]) { "goodix,gt911", nullptr }, + .start_device = start, + .stop_device = stop, + .api = >911_pointer_api, + .device_type = &POINTER_TYPE, + .owner = >911_module, + .internal = nullptr +}; diff --git a/Drivers/gt911-module/source/module.cpp b/Drivers/gt911-module/source/module.cpp new file mode 100644 index 000000000..ae2deaa75 --- /dev/null +++ b/Drivers/gt911-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver gt911_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(>911_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(>911_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module gt911_module = { + .name = "gt911", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/st7789-module/CMakeLists.txt b/Drivers/st7789-module/CMakeLists.txt new file mode 100644 index 000000000..d04ae3e0a --- /dev/null +++ b/Drivers/st7789-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-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd driver +) diff --git a/Drivers/st7789-module/bindings/sitronix,st7789.yaml b/Drivers/st7789-module/bindings/sitronix,st7789.yaml new file mode 100644 index 000000000..ce89db639 --- /dev/null +++ b/Drivers/st7789-module/bindings/sitronix,st7789.yaml @@ -0,0 +1,71 @@ +description: Sitronix ST7789 display panel + +compatible: "sitronix,st7789" + +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: 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 + bits-per-pixel: + type: int + default: 16 + description: Color depth in bits per pixel + pixel-clock-hz: + type: int + default: 80000000 + description: SPI pixel clock frequency in Hz + transaction-queue-depth: + type: int + default: 10 + description: Size of the internal SPI transaction queue + 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/st7789-module/devicetree.yaml b/Drivers/st7789-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/st7789-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/st7789-module/include/bindings/st7789.h b/Drivers/st7789-module/include/bindings/st7789.h new file mode 100644 index 000000000..341a9c0b9 --- /dev/null +++ b/Drivers/st7789-module/include/bindings/st7789.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st7789, struct St7789Config) diff --git a/Drivers/st7789-module/include/drivers/st7789.h b/Drivers/st7789-module/include/drivers/st7789.h new file mode 100644 index 000000000..87f53edf5 --- /dev/null +++ b/Drivers/st7789-module/include/drivers/st7789.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct St7789Config { + 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 bits_per_pixel; + uint32_t pixel_clock_hz; + uint8_t transaction_queue_depth; + 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/st7789-module/include/st7789_module.h b/Drivers/st7789-module/include/st7789_module.h new file mode 100644 index 000000000..44224ff2f --- /dev/null +++ b/Drivers/st7789-module/include/st7789_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module st7789_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/st7789-module/source/module.cpp b/Drivers/st7789-module/source/module.cpp new file mode 100644 index 000000000..dcf6735a8 --- /dev/null +++ b/Drivers/st7789-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver st7789_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_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_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module st7789_module = { + .name = "st7789", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/st7789-module/source/st7789.cpp b/Drivers/st7789-module/source/st7789.cpp new file mode 100644 index 000000000..1f086fdb9 --- /dev/null +++ b/Drivers/st7789-module/source/st7789.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define TAG "ST7789" + +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct St7789Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; +}; + +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) == &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(St7789Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = pin_or_unused(cs_pin), + .dc_gpio_num = pin_or_unused(config->pin_dc), + .spi_mode = 0, + .pclk_hz = config->pixel_clock_hz, + .trans_queue_depth = config->transaction_queue_depth, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 0, + .sio_mode = 1, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + + esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_config->host, &io_config, &internal->io_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret)); + 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 = config->bits_per_pixel, + .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); + free(internal); + return ERROR_RESOURCE; + } + + // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7789 panels). + // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel + // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. + bool ok = + esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && + esp_lcd_panel_init(internal->panel_handle) == ESP_OK && + (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + + 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); + 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)); + + if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel"); + return ERROR_RESOURCE; + } + if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel IO"); + return ERROR_RESOURCE; + } + + free(internal); + return ERROR_NONE; +} + +// endregion + +// region DisplayApi + +static error_t st7789_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_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_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)); + return esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t st7789_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_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; +} + +// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after +// start_device() (e.g. by an LVGL rotation binding) intentionally don't change what "rotation 0" means here. +static bool st7789_get_swap_xy(Device* device) { + return GET_CONFIG(device)->swap_xy; +} + +static bool st7789_get_mirror_x(Device* device) { + return GET_CONFIG(device)->mirror_x; +} + +static bool st7789_get_mirror_y(Device* device) { + return GET_CONFIG(device)->mirror_y; +} + +static error_t st7789_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_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_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_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; +} + +// SPI panels have no direct-mapped frame buffer; every draw is an SPI transaction, so the color +// format here is derived purely from the devicetree config (bgr_order), not queried from hardware. +// Only 16bpp (RGB565/BGR565) is representable in DisplayColorFormat; other bit depths are not +// distinguishable and fall back to the 16bpp mapping. +static enum DisplayColorFormat st7789_get_color_format(Device* device) { + const auto* config = GET_CONFIG(device); + return config->bgr_order ? DISPLAY_COLOR_FORMAT_BGR565 : DISPLAY_COLOR_FORMAT_RGB565; +} + +static uint16_t st7789_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t st7789_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void st7789_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t st7789_get_frame_buffer_count(Device*) { + return 0; +} + +static error_t st7789_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_display_api = { + .reset = st7789_reset, + .init = st7789_init, + .draw_bitmap = st7789_draw_bitmap, + .mirror = st7789_mirror, + .swap_xy = st7789_swap_xy, + .get_swap_xy = st7789_get_swap_xy, + .get_mirror_x = st7789_get_mirror_x, + .get_mirror_y = st7789_get_mirror_y, + .set_gap = st7789_set_gap, + .invert_color = st7789_invert_color, + .disp_on_off = st7789_disp_on_off, + .disp_sleep = st7789_disp_sleep, + .get_color_format = st7789_get_color_format, + .get_resolution_x = st7789_get_resolution_x, + .get_resolution_y = st7789_get_resolution_y, + .get_frame_buffer = st7789_get_frame_buffer, + .get_frame_buffer_count = st7789_get_frame_buffer_count, + .get_backlight = st7789_get_backlight, +}; + +Driver st7789_driver = { + .name = "st7789", + .compatible = (const char*[]) { "sitronix,st7789", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &st7789_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &st7789_module, + .internal = nullptr +}; diff --git a/Modules/lvgl-module/include/tactility/lvgl_display.h b/Modules/lvgl-module/include/tactility/lvgl_display.h new file mode 100644 index 000000000..106ae56d8 --- /dev/null +++ b/Modules/lvgl-module/include/tactility/lvgl_display.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +#include +#include + +/** + * @brief Configuration for binding a kernel DisplayApi device to an lv_display_t. + */ +struct LvglDisplayConfig { + /** + * Number of horizontal lines per draw buffer. 0 means the full vertical resolution. + * Ignored when the device exposes its own frame buffer(s) (display_get_frame_buffer_count() > 0). + */ + uint16_t buffer_height; + + /** + * Allocates a second draw buffer for double buffering. + * Ignored when the device exposes its own frame buffer(s). + */ + bool double_buffer; +}; + +/** + * @brief Creates an lv_display_t bound to the given DISPLAY_TYPE device and registers a flush callback + * that draws through the device's DisplayApi. + * + * The device's swap_xy/mirror_x/mirror_y state at the time of this call (via display_get_swap_xy() etc.) + * is treated as the LV_DISPLAY_ROTATION_0 baseline; LVGL-driven rotation changes are applied relative to it. + * + * @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from + * LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly. + * + * @param[in] device a device of type DISPLAY_TYPE + * @param[in] config binding configuration + * @param[out] out_display the created display, valid only when ERROR_NONE is returned + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_ARGUMENT if device, config or out_display is NULL, or device is not of type DISPLAY_TYPE + * @retval ERROR_NOT_SUPPORTED if the device's color format has no LVGL equivalent + * @retval ERROR_OUT_OF_MEMORY if buffer or lv_display_t allocation failed + */ +error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* config, lv_display_t** out_display); + +/** + * @brief Removes a display previously created with lvgl_display_add(), freeing any buffers it owns. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_display_remove(lv_display_t* display); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/tactility/lvgl_keyboard.h b/Modules/lvgl-module/include/tactility/lvgl_keyboard.h new file mode 100644 index 000000000..25a7d3b8c --- /dev/null +++ b/Modules/lvgl-module/include/tactility/lvgl_keyboard.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @brief Creates an lv_indev_t bound to the given KEYBOARD_TYPE device and registers a read callback + * that polls the device through its KeyboardApi. + * + * @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from + * LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly. + * + * @param[in] device a device of type KEYBOARD_TYPE + * @param[in] display the display this indev should be associated with, or NULL to leave it unset + * @param[out] out_indev the created indev, valid only when ERROR_NONE is returned + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type KEYBOARD_TYPE + * @retval ERROR_OUT_OF_MEMORY if allocation failed + */ +error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev); + +/** + * @brief Removes an indev previously created with lvgl_keyboard_add(). + * @warning Caller must hold the LVGL lock. + */ +void lvgl_keyboard_remove(lv_indev_t* indev); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/include/tactility/lvgl_pointer.h b/Modules/lvgl-module/include/tactility/lvgl_pointer.h new file mode 100644 index 000000000..363ae0bc8 --- /dev/null +++ b/Modules/lvgl-module/include/tactility/lvgl_pointer.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @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. + * + * @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from + * LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly. + * + * @param[in] device a device of type POINTER_TYPE + * @param[in] display the display this indev should be associated with, or NULL to leave it unset + * @param[out] out_indev the created indev, valid only when ERROR_NONE is returned + * @retval ERROR_NONE on success + * @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type POINTER_TYPE + * @retval ERROR_OUT_OF_MEMORY if allocation failed + */ +error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev); + +/** + * @brief Removes an indev previously created with lvgl_pointer_add(). + * @warning Caller must hold the LVGL lock. + */ +void lvgl_pointer_remove(lv_indev_t* indev); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/source/arch/lvgl_esp32.c b/Modules/lvgl-module/source/arch/lvgl_esp32.c index dd2b93d9e..f06d51066 100644 --- a/Modules/lvgl-module/source/arch/lvgl_esp32.c +++ b/Modules/lvgl-module/source/arch/lvgl_esp32.c @@ -8,6 +8,8 @@ #include extern struct LvglModuleConfig lvgl_module_config; +extern void lvgl_devices_attach(); +extern void lvgl_devices_detach(); static bool initialized = false; @@ -44,6 +46,8 @@ error_t lvgl_arch_start() { // devices and services. The latter might start adding widgets immediately. initialized = true; + lvgl_devices_attach(); + if (lvgl_module_config.on_start) lvgl_module_config.on_start(); return ERROR_NONE; @@ -52,6 +56,8 @@ error_t lvgl_arch_start() { error_t lvgl_arch_stop() { if (lvgl_module_config.on_stop) lvgl_module_config.on_stop(); + lvgl_devices_detach(); + if (lvgl_port_deinit() != ESP_OK) { // Call on_start again to recover if (lvgl_module_config.on_start) lvgl_module_config.on_start(); diff --git a/Modules/lvgl-module/source/lvgl_devices.c b/Modules/lvgl-module/source/lvgl_devices.c new file mode 100644 index 000000000..4dc77fd8e --- /dev/null +++ b/Modules/lvgl-module/source/lvgl_devices.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "lvgl" + +void lvgl_devices_attach() { + lv_disp_t* lvgl_display = NULL; + struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE); + if (kernel_display_device != NULL) { + struct LvglDisplayConfig lvgl_display_config = {}; + if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) { + LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name); + } else { + LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name); + } + } + + struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE); + lv_indev_t* lvgl_pointer_device; + if (kernel_pointer_device != NULL) { + if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) { + LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name); + } else { + LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name); + } + } + + struct Device* kernel_keyboard_device = device_find_first_by_type(&KEYBOARD_TYPE); + lv_indev_t* lvgl_keyboard_device; + if (kernel_keyboard_device != NULL) { + if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) { + LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name); + } else { + LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name); + } + } +} + +void lvgl_devices_detach() { + lv_indev_t* device = lv_indev_get_next(NULL); + while (device != NULL) { + lv_indev_delete(device); + // Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt + device = lv_indev_get_next(NULL); + } +} diff --git a/Modules/lvgl-module/source/lvgl_display.c b/Modules/lvgl-module/source/lvgl_display.c new file mode 100644 index 000000000..6e0110b4c --- /dev/null +++ b/Modules/lvgl-module/source/lvgl_display.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include + +#include + +#ifdef ESP_PLATFORM +#include +#endif + +#define TAG "lvgl_display" + +struct LvglDisplayCtx { + struct Device* device; + void* buf1; + void* buf2; + bool owns_buffers; // false when buf1/buf2 point at the device's own frame buffer(s) + // The device's swap_xy/mirror_x/mirror_y at bind time, queried once and treated as the LV_DISPLAY_ROTATION_0 baseline. + bool base_swap_xy; + bool base_mirror_x; + bool base_mirror_y; +}; + +static void* lvgl_display_alloc_buffer(size_t size_bytes) { +#ifdef ESP_PLATFORM + void* buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + if (buf == NULL) { + buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DEFAULT); + } + return buf; +#else + return malloc(size_bytes); +#endif +} + +static void lvgl_display_free_buffer(void* buf) { +#ifdef ESP_PLATFORM + heap_caps_free(buf); +#else + free(buf); +#endif +} + +static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_format_t* out) { + switch (in) { + case DISPLAY_COLOR_FORMAT_RGB565: + *out = LV_COLOR_FORMAT_RGB565; + return true; + case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED: + *out = LV_COLOR_FORMAT_RGB565_SWAPPED; + return true; + case DISPLAY_COLOR_FORMAT_RGB888: + *out = LV_COLOR_FORMAT_RGB888; + return true; + default: + // DISPLAY_COLOR_FORMAT_BGR565/_SWAPPED: no LVGL equivalent (channel order, not byte order). + // DISPLAY_COLOR_FORMAT_MONOCHROME: unsupported for now, no 1bpp conversion buffer implemented. + return false; + } +} + +static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_rotation_t rotation) { + bool swap_xy = ctx->base_swap_xy; + bool mirror_x = ctx->base_mirror_x; + bool mirror_y = ctx->base_mirror_y; + + switch (rotation) { + case LV_DISPLAY_ROTATION_0: + break; + case LV_DISPLAY_ROTATION_90: + swap_xy = !ctx->base_swap_xy; + if (ctx->base_swap_xy) { + mirror_x = !ctx->base_mirror_x; + } else { + mirror_y = !ctx->base_mirror_y; + } + break; + case LV_DISPLAY_ROTATION_180: + mirror_x = !ctx->base_mirror_x; + mirror_y = !ctx->base_mirror_y; + break; + case LV_DISPLAY_ROTATION_270: + swap_xy = !ctx->base_swap_xy; + if (ctx->base_swap_xy) { + mirror_y = !ctx->base_mirror_y; + } else { + mirror_x = !ctx->base_mirror_x; + } + break; + } + + display_swap_xy(ctx->device, swap_xy); + display_mirror(ctx->device, mirror_x, mirror_y); +} + +static void lvgl_display_rotation_event_cb(lv_event_t* e) { + struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_event_get_user_data(e); + lv_display_t* disp = (lv_display_t*)lv_event_get_current_target(e); + lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp)); +} + +static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) { + struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp); + // LVGL's area is inclusive; DisplayApi's draw_bitmap wants an exclusive end. + display_draw_bitmap(ctx->device, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map); + // DisplayApi has no async completion callback, so draw_bitmap is synchronous. + lv_display_flush_ready(disp); +} + +error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* config, lv_display_t** out_display) { + if (device == NULL || config == NULL || out_display == NULL) { + return ERROR_INVALID_ARGUMENT; + } + if (device_get_type(device) != &DISPLAY_TYPE) { + return ERROR_INVALID_ARGUMENT; + } + + lv_color_format_t lv_color_format; + enum DisplayColorFormat kernel_color_format = display_get_color_format(device); + if (!lvgl_display_map_color_format(kernel_color_format, &lv_color_format)) { + LOG_E(TAG, "Unsupported color format %d (no LVGL equivalent)", (int)kernel_color_format); + return ERROR_NOT_SUPPORTED; + } + + uint16_t hres = display_get_resolution_x(device); + uint16_t vres = display_get_resolution_y(device); + uint8_t fb_count = display_get_frame_buffer_count(device); + uint8_t bpp = lv_color_format_get_size(lv_color_format); + + struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)calloc(1, sizeof(struct LvglDisplayCtx)); + if (ctx == NULL) { + return ERROR_OUT_OF_MEMORY; + } + ctx->device = device; + ctx->base_swap_xy = display_get_swap_xy(device); + ctx->base_mirror_x = display_get_mirror_x(device); + ctx->base_mirror_y = display_get_mirror_y(device); + + lv_display_render_mode_t render_mode; + size_t buf_size_bytes; + + if (fb_count > 0) { + display_get_frame_buffer(device, 0, &ctx->buf1); + if (fb_count > 1) { + display_get_frame_buffer(device, 1, &ctx->buf2); + } + ctx->owns_buffers = false; + render_mode = LV_DISPLAY_RENDER_MODE_FULL; + buf_size_bytes = (size_t)hres * vres * bpp; + } else { + uint16_t buf_height = config->buffer_height > 0 ? config->buffer_height : vres; + buf_size_bytes = (size_t)hres * buf_height * bpp; + + ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes); + if (ctx->buf1 == NULL) { + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + if (config->double_buffer) { + ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes); + if (ctx->buf2 == NULL) { + lvgl_display_free_buffer(ctx->buf1); + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + } + ctx->owns_buffers = true; + render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL; + } + + lv_display_t* disp = lv_display_create(hres, vres); + if (disp == NULL) { + if (ctx->owns_buffers) { + lvgl_display_free_buffer(ctx->buf1); + lvgl_display_free_buffer(ctx->buf2); + } + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + + lv_display_set_color_format(disp, lv_color_format); + lv_display_set_buffers(disp, ctx->buf1, ctx->buf2, buf_size_bytes, render_mode); + lv_display_set_flush_cb(disp, lvgl_display_flush_cb); + lv_display_set_driver_data(disp, ctx); + lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, ctx); + + // Apply once explicitly, independent of whether LV_EVENT_RESOLUTION_CHANGED fires on creation. + lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp)); + + *out_display = disp; + return ERROR_NONE; +} + +void lvgl_display_remove(lv_display_t* display) { + if (display == NULL) { + return; + } + + struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(display); + lv_display_delete(display); + + if (ctx != NULL) { + if (ctx->owns_buffers) { + if (ctx->buf1 != NULL) { + lvgl_display_free_buffer(ctx->buf1); + } + if (ctx->buf2 != NULL) { + lvgl_display_free_buffer(ctx->buf2); + } + } + free(ctx); + } +} diff --git a/Modules/lvgl-module/source/lvgl_keyboard.c b/Modules/lvgl-module/source/lvgl_keyboard.c new file mode 100644 index 000000000..625bb71f4 --- /dev/null +++ b/Modules/lvgl-module/source/lvgl_keyboard.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include + +#include + +struct LvglKeyboardCtx { + struct Device* device; +}; + +static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { + struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev); + + struct KeyboardKeyData key_data = {0}; + if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) { + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = false; + return; + } + + // KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed. + data->key = key_data.key; + data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + data->continue_reading = key_data.continue_reading; +} + +error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) { + if (device == NULL || out_indev == NULL) { + return ERROR_INVALID_ARGUMENT; + } + if (device_get_type(device) != &KEYBOARD_TYPE) { + return ERROR_INVALID_ARGUMENT; + } + + struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)malloc(sizeof(struct LvglKeyboardCtx)); + if (ctx == NULL) { + return ERROR_OUT_OF_MEMORY; + } + ctx->device = device; + + lv_indev_t* indev = lv_indev_create(); + if (indev == NULL) { + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb); + lv_indev_set_driver_data(indev, ctx); + if (display != NULL) { + lv_indev_set_display(indev, display); + } + + *out_indev = indev; + return ERROR_NONE; +} + +void lvgl_keyboard_remove(lv_indev_t* indev) { + if (indev == NULL) { + return; + } + + struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev); + lv_indev_delete(indev); + free(ctx); +} diff --git a/Modules/lvgl-module/source/lvgl_pointer.c b/Modules/lvgl-module/source/lvgl_pointer.c new file mode 100644 index 000000000..21894464f --- /dev/null +++ b/Modules/lvgl-module/source/lvgl_pointer.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include + +#include + +struct LvglPointerCtx { + struct Device* device; +}; + +// 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); + +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); + + if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + 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) { + data->point.x = x; + data->point.y = y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) { + if (device == NULL || out_indev == NULL) { + return ERROR_INVALID_ARGUMENT; + } + if (device_get_type(device) != &POINTER_TYPE) { + return ERROR_INVALID_ARGUMENT; + } + + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)malloc(sizeof(struct LvglPointerCtx)); + if (ctx == NULL) { + return ERROR_OUT_OF_MEMORY; + } + ctx->device = device; + + lv_indev_t* indev = lv_indev_create(); + if (indev == NULL) { + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(indev, lvgl_pointer_read_cb); + lv_indev_set_driver_data(indev, ctx); + if (display != NULL) { + lv_indev_set_display(indev, display); + } + + *out_indev = indev; + return ERROR_NONE; +} + +void lvgl_pointer_remove(lv_indev_t* indev) { + if (indev == NULL) { + return; + } + + struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev); + lv_indev_delete(indev); + free(ctx); +} diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-ledc-backlight.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-ledc-backlight.yaml new file mode 100644 index 000000000..d91066e30 --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-ledc-backlight.yaml @@ -0,0 +1,29 @@ +description: ESP32 LEDC-backed PWM backlight + +compatible: "espressif,esp32-ledc-backlight" + +properties: + pin-backlight: + type: phandles + required: true + description: Backlight PWM output pin + frequency-hz: + type: int + default: 30000 + description: PWM frequency in Hz + brightness-level-range: + type: values + default: [0, 255] + description: Inclusive [min,max] brightness range. The minimum value turns the backlight off. + brightness-default: + type: int + default: 200 + description: Default brightness level, applied by set_brightness_default(). Should fall within brightness-level-range. + ledc-timer: + type: int + default: 0 + description: LEDC timer index, defined by ledc_timer_t + ledc-channel: + type: int + default: 0 + description: LEDC channel index, defined by ledc_channel_t diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_ledc_backlight.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_ledc_backlight.h new file mode 100644 index 000000000..625872d89 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_ledc_backlight.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_ledc_backlight, struct Esp32LedcBacklightConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_ledc_backlight.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_ledc_backlight.h new file mode 100644 index 000000000..3b75ccf59 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_ledc_backlight.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32LedcBacklightConfig { + struct GpioPinSpec pin_backlight; + uint32_t frequency_hz; + struct BrightnessLevelRange brightness_range; + uint8_t brightness_default; + ledc_timer_t ledc_timer; + ledc_channel_t ledc_channel; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp b/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp new file mode 100644 index 000000000..420639da1 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#define TAG "Esp32LedcBacklight" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Esp32LedcBacklightInternal { + uint8_t brightness; +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + ledc_timer_config_t timer_config = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_8_BIT, + .timer_num = config->ledc_timer, + .freq_hz = config->frequency_hz, + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false, + }; + if (ledc_timer_config(&timer_config) != ESP_OK) { + LOG_E(TAG, "Failed to configure LEDC timer"); + return ERROR_RESOURCE; + } + + ledc_channel_config_t channel_config = { + .gpio_num = (int)config->pin_backlight.pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = config->ledc_channel, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = config->ledc_timer, + .duty = config->brightness_range.min, + .hpoint = 0, + .sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD, + .flags = { + .output_invert = 0, + }, + }; + if (ledc_channel_config(&channel_config) != ESP_OK) { + LOG_E(TAG, "Failed to configure LEDC channel"); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(Esp32LedcBacklightInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->brightness = config->brightness_range.min; + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region BacklightApi + +static error_t esp32_ledc_backlight_set_brightness(Device* device, uint8_t brightness) { + const auto* config = GET_CONFIG(device); + auto* internal = static_cast(device_get_driver_data(device)); + + esp_err_t ret = ledc_set_duty(LEDC_LOW_SPEED_MODE, config->ledc_channel, brightness); + if (ret == ESP_OK) { + ret = ledc_update_duty(LEDC_LOW_SPEED_MODE, config->ledc_channel); + } + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to set brightness: %s", esp_err_to_name(ret)); + return ERROR_RESOURCE; + } + + internal->brightness = brightness; + return ERROR_NONE; +} + +static error_t esp32_ledc_backlight_set_brightness_default(Device* device) { + return esp32_ledc_backlight_set_brightness(device, GET_CONFIG(device)->brightness_default); +} + +static error_t esp32_ledc_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_ledc_backlight_get_min_brightness(Device* device) { + return GET_CONFIG(device)->brightness_range.min; +} + +static uint8_t esp32_ledc_backlight_get_max_brightness(Device* device) { + return GET_CONFIG(device)->brightness_range.max; +} + +// endregion + +static const BacklightApi esp32_ledc_backlight_api = { + .set_brightness = esp32_ledc_backlight_set_brightness, + .set_brightness_default = esp32_ledc_backlight_set_brightness_default, + .get_brightness = esp32_ledc_backlight_get_brightness, + .get_min_brightness = esp32_ledc_backlight_get_min_brightness, + .get_max_brightness = esp32_ledc_backlight_get_max_brightness, +}; + +extern Module platform_esp32_module; + +Driver esp32_ledc_backlight_driver = { + .name = "esp32_ledc_backlight", + .compatible = (const char*[]) { "espressif,esp32-ledc-backlight", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &esp32_ledc_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 f00c80858..6809ad484 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -15,6 +15,7 @@ extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2c_master_driver; extern Driver esp32_i2s_driver; +extern Driver esp32_ledc_backlight_driver; #if SOC_SDMMC_HOST_SUPPORTED extern Driver esp32_sdmmc_driver; #endif @@ -46,6 +47,7 @@ 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); + check(driver_construct_add(&esp32_ledc_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); #endif @@ -95,6 +97,7 @@ 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); + check(driver_remove_destruct(&esp32_ledc_backlight_driver) == ERROR_NONE); #if SOC_SDMMC_HOST_SUPPORTED check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); #endif diff --git a/Tactility/Include/Tactility/hal/power/PowerDevice.h b/Tactility/Include/Tactility/hal/power/PowerDevice.h index 1733f4741..04173422b 100644 --- a/Tactility/Include/Tactility/hal/power/PowerDevice.h +++ b/Tactility/Include/Tactility/hal/power/PowerDevice.h @@ -2,6 +2,7 @@ #include #include +#include namespace tt::hal::power { @@ -9,8 +10,8 @@ class PowerDevice : public Device { public: - PowerDevice() = default; - ~PowerDevice() override = default; + PowerDevice(); + ~PowerDevice() override; Type getType() const override { return Type::Power; } @@ -46,6 +47,17 @@ public: virtual bool supportsPowerOff() const { return false; } virtual void powerOff() { /* NO-OP*/ } + +private: + + /** Creates the kernel-level power_supply device that exposes this instance to TactilityKernel. */ + void createPowerSupplyDevice(); + + /** Destroys the kernel-level power_supply device created by createPowerSupplyDevice(). */ + void destroyPowerSupplyDevice(); + + std::string kernelDeviceName; + KernelDevice kernelDevice {}; }; } diff --git a/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h b/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h index ab410467e..30ff49e2e 100644 --- a/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h +++ b/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h @@ -7,7 +7,7 @@ namespace tt::hal::touch { -/** Wraps a TactilityKernel Device of type TOUCH_TYPE as a TouchDriver. */ +/** Wraps a TactilityKernel Device of type POINTER_TYPE as a TouchDriver. */ class KernelTouchDriver final : public TouchDriver { ::Device* device; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8090960f2..cc0b1eb1e 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -23,7 +23,9 @@ #include #include +#include #include +#include #include #include #include @@ -112,6 +114,7 @@ namespace app { namespace boot { extern const AppManifest manifest; } namespace development { extern const AppManifest manifest; } namespace display { extern const AppManifest manifest; } + namespace kerneldisplay { extern const AppManifest manifest; } namespace files { extern const AppManifest manifest; } namespace fileselection { extern const AppManifest manifest; } namespace gpssettings { extern const AppManifest manifest; } @@ -169,7 +172,11 @@ static void registerInternalApps() { addAppManifest(app::apphubdetails::manifest); addAppManifest(app::applist::manifest); addAppManifest(app::appsettings::manifest); - addAppManifest(app::display::manifest); + if (device_exists_of_type(&DISPLAY_TYPE)) { + addAppManifest(app::kerneldisplay::manifest); + } else if (hal::hasDevice(hal::Device::Type::Display)) { + addAppManifest(app::display::manifest); + } addAppManifest(app::files::manifest); addAppManifest(app::fileselection::manifest); addAppManifest(app::i2cscanner::manifest); @@ -178,7 +185,9 @@ static void registerInternalApps() { addAppManifest(app::launcher::manifest); addAppManifest(app::localesettings::manifest); addAppManifest(app::notes::manifest); - addAppManifest(app::poweroff::manifest); + if (device_exists_of_type(&POWER_SUPPLY_TYPE)) { + addAppManifest(app::poweroff::manifest); + } addAppManifest(app::settings::manifest); addAppManifest(app::selectiondialog::manifest); addAppManifest(app::setup::manifest); diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 4b219a7d6..5691e5fa7 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -1,8 +1,10 @@ #include "Tactility/lvgl/Lvgl.h" +#include "tactility/drivers/backlight.h" +#include "tactility/drivers/display.h" -#include #include #include +#include #include #include #include @@ -56,7 +58,7 @@ class BootApp : public App { getCpuAffinityConfiguration().system ); - static void setupDisplay() { + static void setupHalDisplay() { const auto hal_display = getHalDisplay(); if (hal_display == nullptr) { return; @@ -80,6 +82,36 @@ class BootApp : public App { } } + static void setupKernelDisplay() { + auto* display = device_find_first_by_type(&DISPLAY_TYPE); + if (display != nullptr) { + Device* backlight; + if (display_get_backlight(display, &backlight) == ERROR_NONE) { + if (!device_is_ready(backlight)) { + if (device_start(backlight) != ERROR_NONE) { + LOG_E(TAG, "Failed to start %s", backlight->name); + } + } + + settings::display::DisplaySettings settings; + if (settings::display::load(settings)) { + } else { + settings = settings::display::getDefault(); + } + + if (backlight_set_brightness(backlight, settings.backlightDuty) == ERROR_NONE) { + LOG_I(TAG, "Backlight for %s set to %d", display->name, settings.backlightDuty); + } else { + LOG_E(TAG, "Failed to set brightness of %s", backlight->name); + } + } else { + LOG_I(TAG, "No backlight for %s", display->name); + } + } else { + LOG_I(TAG, "No kernel display"); + } + } + static bool setupUsbBootMode() { if (!hal::usb::isUsbBootMode()) { return false; @@ -124,7 +156,8 @@ class BootApp : public App { // TODO: Support for multiple displays LOG_I(TAG, "Setup display"); - setupDisplay(); // Set backlight + setupHalDisplay(); + setupKernelDisplay(); prepareFileSystems(); #ifdef CONFIG_TT_USER_DATA_LOCATION_SD diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index 4dcb4a1b1..8215f14df 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -34,7 +34,7 @@ static bool hasCalibratableTouchDevice() { return false; } -class DisplayApp final : public App { +class HalDisplayApp final : public App { settings::display::DisplaySettings displaySettings; bool displaySettingsUpdated = false; @@ -44,7 +44,7 @@ class DisplayApp final : public App { static void onBacklightSliderEvent(lv_event_t* event) { auto* slider = static_cast(lv_event_get_target(event)); - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); auto hal_display = getHalDisplay(); assert(hal_display != nullptr); @@ -59,7 +59,7 @@ class DisplayApp final : public App { static void onGammaSliderEvent(lv_event_t* event) { auto* slider = static_cast(lv_event_get_target(event)); auto hal_display = hal::findFirstDevice(hal::Device::Type::Display); - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); assert(hal_display != nullptr); if (hal_display->getGammaCurveCount() > 0) { @@ -71,7 +71,7 @@ class DisplayApp final : public App { } static void onOrientationSet(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); uint32_t selected_index = lv_dropdown_get_selected(dropdown); LOG_I(TAG, "Selected %u", (unsigned)selected_index); @@ -84,7 +84,7 @@ class DisplayApp final : public App { } static void onTimeoutSwitch(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); auto* sw = static_cast(lv_event_get_target(event)); bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); app->displaySettings.backlightTimeoutEnabled = enabled; @@ -105,7 +105,7 @@ class DisplayApp final : public App { } static void onTimeoutChanged(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); uint32_t idx = lv_dropdown_get_selected(dropdown); // Map dropdown index to ms: 0=15s,1=30s,2=1m,3=2m,4=5m,5=Never @@ -117,7 +117,7 @@ class DisplayApp final : public App { } static void onScreensaverChanged(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); + auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); uint32_t idx = lv_dropdown_get_selected(dropdown); // Validate index bounds before casting to enum @@ -338,7 +338,7 @@ extern const AppManifest manifest = { .appName = "Display", .appIcon = LVGL_ICON_SHARED_DISPLAY_SETTINGS, .appCategory = Category::Settings, - .createApp = create + .createApp = create }; } // namespace diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp new file mode 100644 index 000000000..1d554ade5 --- /dev/null +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -0,0 +1,284 @@ +#include "../../../../TactilityKernel/include/tactility/drivers/display.h" +#include "../../../../TactilityKernel/include/tactility/error.h" + + +#include + +#include + +#ifdef ESP_PLATFORM +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace tt::app::kerneldisplay { + +constexpr auto* TAG = "KernelDisplay"; + +static Device* getBacklightDevice() { + Device* display = device_find_first_by_type(&DISPLAY_TYPE); + check(display); + Device* backlight = nullptr; + return display_get_backlight(display, &backlight) == ERROR_NONE ? backlight : nullptr; +} + +class KernelDisplayApp final : public App { + + settings::display::DisplaySettings displaySettings; + bool displaySettingsUpdated = false; + lv_obj_t* timeoutSwitch = nullptr; + lv_obj_t* timeoutDropdown = nullptr; + lv_obj_t* screensaverDropdown = nullptr; + + static void onBacklightSliderEvent(lv_event_t* event) { + auto* slider = static_cast(lv_event_get_target(event)); + auto* app = static_cast(lv_event_get_user_data(event)); + auto* backlight = getBacklightDevice(); + assert(backlight != nullptr); + + int32_t slider_value = lv_slider_get_value(slider); + app->displaySettings.backlightDuty = static_cast(slider_value); + app->displaySettingsUpdated = true; + backlight_set_brightness(backlight, app->displaySettings.backlightDuty); + } + + static void onOrientationSet(lv_event_t* event) { + auto* app = static_cast(lv_event_get_user_data(event)); + auto* dropdown = static_cast(lv_event_get_target(event)); + uint32_t selected_index = lv_dropdown_get_selected(dropdown); + LOG_I(TAG, "Selected %u", (unsigned)selected_index); + auto selected_orientation = static_cast(selected_index); + if (selected_orientation != app->displaySettings.orientation) { + app->displaySettings.orientation = selected_orientation; + app->displaySettingsUpdated = true; + lv_display_set_rotation(lv_display_get_default(), settings::display::toLvglDisplayRotation(selected_orientation)); + } + } + + static void onTimeoutSwitch(lv_event_t* event) { + auto* app = static_cast(lv_event_get_user_data(event)); + auto* sw = static_cast(lv_event_get_target(event)); + bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); + app->displaySettings.backlightTimeoutEnabled = enabled; + app->displaySettingsUpdated = true; + if (app->timeoutDropdown) { + if (enabled) { + lv_obj_clear_state(app->timeoutDropdown, LV_STATE_DISABLED); + if (app->screensaverDropdown) { + lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED); + } + } else { + lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED); + if (app->screensaverDropdown) { + lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED); + } + } + } + } + + static void onTimeoutChanged(lv_event_t* event) { + auto* app = static_cast(lv_event_get_user_data(event)); + auto* dropdown = static_cast(lv_event_get_target(event)); + uint32_t idx = lv_dropdown_get_selected(dropdown); + // Map dropdown index to ms: 0=15s,1=30s,2=1m,3=2m,4=5m,5=Never + static const uint32_t values_ms[] = {15000, 30000, 60000, 120000, 300000, 0}; + if (idx < (sizeof(values_ms)/sizeof(values_ms[0]))) { + app->displaySettings.backlightTimeoutMs = values_ms[idx]; + app->displaySettingsUpdated = true; + } + } + + 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)); + uint32_t idx = lv_dropdown_get_selected(dropdown); + // Validate index bounds before casting to enum + if (idx >= static_cast(settings::display::ScreensaverType::Count)) { + return; + } + auto selected_type = static_cast(idx); + if (selected_type != app->displaySettings.screensaverType) { + app->displaySettings.screensaverType = selected_type; + app->displaySettingsUpdated = true; + } + } + +public: + + void onShow(AppContext& app, lv_obj_t* parent) override { + displaySettings = settings::display::loadOrGetDefault(); + auto ui_density = lvgl_get_ui_density(); + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); + + auto* backlight = getBacklightDevice(); + + lvgl::toolbar_create(parent, app); + + auto* main_wrapper = lv_obj_create(parent); + lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_width(main_wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(main_wrapper, 1); + + // Backlight slider + // Note: no gamma slider here - unlike HalDisplayApp (app/display/Display.cpp), the kernel + // DisplayApi has no gamma curve control yet. + + if (backlight != nullptr) { + auto* brightness_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_hor(brightness_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(brightness_wrapper, 0, LV_STATE_DEFAULT); + if (ui_density != LVGL_UI_DENSITY_COMPACT) { + lv_obj_set_style_pad_ver(brightness_wrapper, 4, LV_STATE_DEFAULT); + } + + auto* brightness_label = lv_label_create(brightness_wrapper); + lv_label_set_text(brightness_label, "Brightness"); + lv_obj_align(brightness_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* brightness_slider = lv_slider_create(brightness_wrapper); + lv_obj_set_width(brightness_slider, LV_PCT(50)); + lv_obj_align(brightness_slider, LV_ALIGN_RIGHT_MID, 0, 0); + lv_slider_set_range(brightness_slider, backlight_get_min_brightness(backlight), backlight_get_max_brightness(backlight)); + lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, this); + + lv_slider_set_value(brightness_slider, displaySettings.backlightDuty, LV_ANIM_OFF); + } + + // Orientation + + auto* orientation_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(orientation_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(orientation_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(orientation_wrapper, 0, LV_STATE_DEFAULT); + + auto* orientation_label = lv_label_create(orientation_wrapper); + lv_label_set_text(orientation_label, "Orientation"); + lv_obj_align(orientation_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* orientation_dropdown = lv_dropdown_create(orientation_wrapper); + // Note: order correlates with settings::display::Orientation item order + lv_dropdown_set_options(orientation_dropdown, "Landscape\nPortrait Right\nLandscape Flipped\nPortrait Left"); + lv_obj_align(orientation_dropdown, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, this); + // Set the dropdown to match current orientation enum + lv_dropdown_set_selected(orientation_dropdown, static_cast(displaySettings.orientation)); + + // Screen timeout + // Note: DisplayIdleService doesn't act on these settings for kernel-driver displays yet + // (it only looks up the deprecated tt::hal::display::DisplayDevice), so these currently + // just get saved without taking effect. Kept for parity/forward-compatibility. + + if (backlight != nullptr) { + auto* timeout_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(timeout_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(timeout_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(timeout_wrapper, 0, LV_STATE_DEFAULT); + + auto* timeout_label = lv_label_create(timeout_wrapper); + lv_label_set_text(timeout_label, "Auto screen off"); + lv_obj_align(timeout_label, LV_ALIGN_LEFT_MID, 0, 0); + + timeoutSwitch = lv_switch_create(timeout_wrapper); + if (displaySettings.backlightTimeoutEnabled) { + lv_obj_add_state(timeoutSwitch, LV_STATE_CHECKED); + } + lv_obj_align(timeoutSwitch, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(timeoutSwitch, onTimeoutSwitch, LV_EVENT_VALUE_CHANGED, this); + + auto* timeout_select_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(timeout_select_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(timeout_select_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(timeout_select_wrapper, 0, LV_STATE_DEFAULT); + + auto* timeout_value_label = lv_label_create(timeout_select_wrapper); + lv_label_set_text(timeout_value_label, "Timeout"); + lv_obj_align(timeout_value_label, LV_ALIGN_LEFT_MID, 0, 0); + + timeoutDropdown = lv_dropdown_create(timeout_select_wrapper); + lv_dropdown_set_options(timeoutDropdown, "15 seconds\n30 seconds\n1 minute\n2 minutes\n5 minutes\nNever"); + lv_obj_align(timeoutDropdown, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(timeoutDropdown, onTimeoutChanged, LV_EVENT_VALUE_CHANGED, this); + // Initialize dropdown selection from settings + uint32_t ms = displaySettings.backlightTimeoutMs; + uint32_t idx = 2; // default 1 minute + if (ms == 15000) idx = 0; + else if (ms == 30000) + idx = 1; + else if (ms == 60000) + idx = 2; + else if (ms == 120000) + idx = 3; + else if (ms == 300000) + idx = 4; + else if (ms == 0) + idx = 5; + lv_dropdown_set_selected(timeoutDropdown, idx); + if (!displaySettings.backlightTimeoutEnabled) { + lv_obj_add_state(timeoutDropdown, LV_STATE_DISABLED); + } + + // Screensaver type + auto* screensaver_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(screensaver_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(screensaver_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(screensaver_wrapper, 0, LV_STATE_DEFAULT); + + auto* screensaver_label = lv_label_create(screensaver_wrapper); + lv_label_set_text(screensaver_label, "Screensaver"); + lv_obj_align(screensaver_label, LV_ALIGN_LEFT_MID, 0, 0); + + screensaverDropdown = lv_dropdown_create(screensaver_wrapper); + // Note: order correlates with settings::display::ScreensaverType enum order + lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan"); + lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this); + lv_dropdown_set_selected(screensaverDropdown, static_cast(displaySettings.screensaverType)); + if (!displaySettings.backlightTimeoutEnabled) { + lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED); + } + } + + // Note: no touch calibration section here - unlike HalDisplayApp, the kernel PointerApi has + // no calibration support yet. + } + + void onHide(AppContext& app) override { + if (displaySettingsUpdated) { + // Dispatch it, so file IO doesn't block the UI + const settings::display::DisplaySettings settings_to_save = displaySettings; + getMainDispatcher().dispatch([settings_to_save] { + settings::display::save(settings_to_save); +#ifdef ESP_PLATFORM + // Notify DisplayIdle service to reload settings + auto displayIdle = service::displayidle::findService(); + if (displayIdle) { + displayIdle->reloadSettings(); + } +#endif + }); + } + } +}; + +extern const AppManifest manifest = { + .appId = "Display", + .appName = "Display", + .appIcon = LVGL_ICON_SHARED_DISPLAY_SETTINGS, + .appCategory = Category::Settings, + .createApp = create +}; + +} // namespace diff --git a/Tactility/Source/app/keyboard/KeyboardSettings.cpp b/Tactility/Source/app/keyboard/KeyboardSettings.cpp index 5c6e3e5bc..e86ecb6df 100644 --- a/Tactility/Source/app/keyboard/KeyboardSettings.cpp +++ b/Tactility/Source/app/keyboard/KeyboardSettings.cpp @@ -5,15 +5,12 @@ #include #include +#include +#include #include #include -// Forward declare driver functions -namespace keyboardbacklight { - bool setBrightness(uint8_t brightness); -} - namespace tt::app::keyboardsettings { constexpr auto* TAG = "KeyboardSettings"; @@ -30,7 +27,11 @@ static uint32_t timeoutMsToIndex(uint32_t ms) { } static void applyKeyboardBacklight(bool enabled, uint8_t brightness) { - keyboardbacklight::setBrightness(enabled ? brightness : 0); + // TODO: Get keyboard backlight from (optional) keyboard child device + Device* backlight = device_find_by_name("keyboard_backlight"); + if (backlight != nullptr) { + backlight_set_brightness(backlight, enabled ? brightness : 0); + } } class KeyboardSettingsApp final : public App { diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 1eb4d948b..2152cb8f1 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -4,10 +4,10 @@ #include #include -#include #include -#include +#include +#include #include #include @@ -34,7 +34,7 @@ class PowerApp : public App { Timer update_timer = Timer(Timer::Type::Periodic, kernel::millisToTicks(1000),[]() { onTimer(); }); - std::shared_ptr power; + ::Device* power = nullptr; lv_obj_t* enableLabel = nullptr; lv_obj_t* enableSwitch = nullptr; @@ -58,8 +58,8 @@ class PowerApp : public App { if (code == LV_EVENT_VALUE_CHANGED) { bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED); - if (power->isAllowedToCharge() != is_on) { - power->setAllowedToCharge(is_on); + if (power_supply_is_allowed_to_charge(power) != is_on) { + power_supply_set_allowed_to_charge(power, is_on); updateUi(); } } @@ -76,8 +76,8 @@ class PowerApp : public App { if (code == LV_EVENT_VALUE_CHANGED) { bool is_on = lv_obj_has_state(qc_switch, LV_STATE_CHECKED); - if (power->isQuickChargeEnabled() != is_on) { - power->setQuickChargeEnabled(is_on); + if (power_supply_is_quick_charge_enabled(power) != is_on) { + power_supply_set_quick_charge_enabled(power, is_on); updateUi(); } } @@ -94,37 +94,37 @@ class PowerApp : public App { } const char* charge_state; - hal::power::PowerDevice::MetricData metric_data; - if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, metric_data)) { - charge_state = metric_data.valueAsBool ? "yes" : "no"; + 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"; } - uint8_t charge_level; + int charge_level; bool charge_level_scaled_set = false; - if (power->getMetric(hal::power::PowerDevice::MetricType::ChargeLevel, metric_data)) { - charge_level = metric_data.valueAsUint8; + 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; } - bool charging_enabled_set = power->supportsChargeControl(); - bool charging_enabled_and_allowed = power->supportsChargeControl() && power->isAllowedToCharge(); + 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->supportsQuickCharge(); - bool quick_charge_enabled = power->supportsQuickCharge() && power->isQuickChargeEnabled(); + bool quick_charge_set = power_supply_supports_quick_charge(power); + bool quick_charge_enabled = quick_charge_set && power_supply_is_quick_charge_enabled(power); - int32_t current; + int current; bool current_set = false; - if (power->getMetric(hal::power::PowerDevice::MetricType::Current, metric_data)) { - current = metric_data.valueAsInt32; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_CURRENT, &property_value) == ERROR_NONE) { + current = property_value.int_value; current_set = true; } - uint32_t battery_voltage; + int battery_voltage; bool battery_voltage_set = false; - if (power->getMetric(hal::power::PowerDevice::MetricType::BatteryVoltage, metric_data)) { - battery_voltage = metric_data.valueAsUint32; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_VOLTAGE, &property_value) == ERROR_NONE) { + battery_voltage = property_value.int_value; battery_voltage_set = true; } @@ -151,7 +151,7 @@ class PowerApp : public App { lv_label_set_text_fmt(chargeStateLabel, "Charging: %s", charge_state); if (battery_voltage_set) { - lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: %lu mV", battery_voltage); + lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: %d mV", battery_voltage); } else { lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: N/A"); } @@ -163,7 +163,7 @@ class PowerApp : public App { } if (current_set) { - lv_label_set_text_fmt(currentLabel, "Current: %ld mA", current); + lv_label_set_text_fmt(currentLabel, "Current: %d mA", current); } else { lv_label_set_text_fmt(currentLabel, "Current: N/A"); } @@ -174,7 +174,7 @@ class PowerApp : public App { public: void onCreate(AppContext& app) override { - power = hal::findFirstDevice(hal::Device::Type::Power); + power = device_find_first_active_by_type(&POWER_SUPPLY_TYPE); } void onShow(AppContext& app, lv_obj_t* parent) override { diff --git a/Tactility/Source/app/poweroff/PowerOff.cpp b/Tactility/Source/app/poweroff/PowerOff.cpp index 04e8c3c4d..a1b6e7e82 100644 --- a/Tactility/Source/app/poweroff/PowerOff.cpp +++ b/Tactility/Source/app/poweroff/PowerOff.cpp @@ -1,10 +1,11 @@ #include #include #include -#include #include #include +#include +#include #include #include #include @@ -48,15 +49,15 @@ class PowerOffApp final : public App { } static void onYesPressed(lv_event_t* /*event*/) { - auto power = hal::findFirstDevice(hal::Device::Type::Power); - if (power == nullptr || !power->supportsPowerOff()) { + auto* power = device_find_first_active_by_type(&POWER_SUPPLY_TYPE); + if (power == nullptr || !power_supply_supports_power_off(power)) { return; } auto display = hal::findFirstDevice(hal::Device::Type::Display); showPoweredOffScreenAndWait(display.get()); - power->powerOff(); + power_supply_power_off(power); } static void onNoPressed(lv_event_t* /*event*/) { diff --git a/Tactility/Source/hal/power/PowerDevice.cpp b/Tactility/Source/hal/power/PowerDevice.cpp new file mode 100644 index 000000000..9f3085b36 --- /dev/null +++ b/Tactility/Source/hal/power/PowerDevice.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include + +#include + +namespace tt::hal::power { + +#define GET_POWER_DEVICE(device) (static_cast(device_get_driver_data(device))) + +static PowerDevice::MetricType toMetricType(PowerSupplyProperty property) { + switch (property) { + case POWER_SUPPLY_PROP_IS_CHARGING: + return PowerDevice::MetricType::IsCharging; + case POWER_SUPPLY_PROP_CURRENT: + return PowerDevice::MetricType::Current; + case POWER_SUPPLY_PROP_VOLTAGE: + return PowerDevice::MetricType::BatteryVoltage; + case POWER_SUPPLY_PROP_CAPACITY: + default: + return PowerDevice::MetricType::ChargeLevel; + } +} + +static int toIntValue(PowerDevice::MetricType type, const PowerDevice::MetricData& data) { + switch (type) { + case PowerDevice::MetricType::IsCharging: + return data.valueAsBool ? 1 : 0; + case PowerDevice::MetricType::Current: + return data.valueAsInt32; + case PowerDevice::MetricType::BatteryVoltage: + return static_cast(data.valueAsUint32); + case PowerDevice::MetricType::ChargeLevel: + default: + return data.valueAsUint8; + } +} + +static bool apiSupportsProperty(::Device* device, PowerSupplyProperty property) { + return GET_POWER_DEVICE(device)->supportsMetric(toMetricType(property)); +} + +static error_t apiGetProperty(::Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* outValue) { + auto* power_device = GET_POWER_DEVICE(device); + auto metric_type = toMetricType(property); + if (!power_device->supportsMetric(metric_type)) { + return ERROR_NOT_SUPPORTED; + } + PowerDevice::MetricData data; + if (!power_device->getMetric(metric_type, data)) { + return ERROR_NOT_FOUND; + } + outValue->int_value = toIntValue(metric_type, data); + return ERROR_NONE; +} + +static bool apiSupportsChargeControl(::Device* device) { + return GET_POWER_DEVICE(device)->supportsChargeControl(); +} + +static bool apiIsAllowedToCharge(::Device* device) { + return GET_POWER_DEVICE(device)->isAllowedToCharge(); +} + +static error_t apiSetAllowedToCharge(::Device* device, bool allowed) { + auto* power_device = GET_POWER_DEVICE(device); + if (!power_device->supportsChargeControl()) { + return ERROR_NOT_SUPPORTED; + } + power_device->setAllowedToCharge(allowed); + return ERROR_NONE; +} + +static bool apiSupportsQuickCharge(::Device* device) { + return GET_POWER_DEVICE(device)->supportsQuickCharge(); +} + +static bool apiIsQuickChargeEnabled(::Device* device) { + return GET_POWER_DEVICE(device)->isQuickChargeEnabled(); +} + +static error_t apiSetQuickChargeEnabled(::Device* device, bool enabled) { + auto* power_device = GET_POWER_DEVICE(device); + if (!power_device->supportsQuickCharge()) { + return ERROR_NOT_SUPPORTED; + } + power_device->setQuickChargeEnabled(enabled); + return ERROR_NONE; +} + +static bool apiSupportsPowerOff(::Device* device) { + return GET_POWER_DEVICE(device)->supportsPowerOff(); +} + +static error_t apiPowerOff(::Device* device) { + auto* power_device = GET_POWER_DEVICE(device); + if (!power_device->supportsPowerOff()) { + return ERROR_NOT_SUPPORTED; + } + power_device->powerOff(); + return ERROR_NONE; +} + +static error_t startDevice(::Device*) { return ERROR_NONE; } +static error_t stopDevice(::Device*) { return ERROR_NONE; } + +static PowerSupplyApi powerSupplyApi { + .supports_property = apiSupportsProperty, + .get_property = apiGetProperty, + .supports_charge_control = apiSupportsChargeControl, + .is_allowed_to_charge = apiIsAllowedToCharge, + .set_allowed_to_charge = apiSetAllowedToCharge, + .supports_quick_charge = apiSupportsQuickCharge, + .is_quick_charge_enabled = apiIsQuickChargeEnabled, + .set_quick_charge_enabled = apiSetQuickChargeEnabled, + .supports_power_off = apiSupportsPowerOff, + .power_off = apiPowerOff, +}; + +static const char* powerSupplyCompatible[] = { "hal-power-device", nullptr }; + +static Driver powerSupplyDriver { + .name = "hal-power-device", + .compatible = powerSupplyCompatible, + .start_device = startDevice, + .stop_device = stopDevice, + .api = &powerSupplyApi, + .device_type = &POWER_SUPPLY_TYPE, + .owner = nullptr, + .internal = nullptr, +}; + +/** Registers the "hal-power-device" driver with the kernel on first use. */ +static Driver& getPowerSupplyDriver() { + static const bool registered = [] { + check(driver_construct_add(&powerSupplyDriver) == ERROR_NONE); + return true; + }(); + (void)registered; + return powerSupplyDriver; +} + +void PowerDevice::createPowerSupplyDevice() { + kernelDeviceName = std::format("power-supply-{}", getId()); + kernelDevice.name = kernelDeviceName.c_str(); + check(device_construct(&kernelDevice) == ERROR_NONE); + check(device_add(&kernelDevice) == ERROR_NONE); + device_set_driver(&kernelDevice, &getPowerSupplyDriver()); + check(device_start(&kernelDevice) == ERROR_NONE); + device_set_driver_data(&kernelDevice, this); +} + +void PowerDevice::destroyPowerSupplyDevice() { + device_set_driver_data(&kernelDevice, nullptr); + check(device_stop(&kernelDevice) == ERROR_NONE); + check(device_remove(&kernelDevice) == ERROR_NONE); + check(device_destruct(&kernelDevice) == ERROR_NONE); +} + +PowerDevice::PowerDevice() { + createPowerSupplyDevice(); +} + +PowerDevice::~PowerDevice() { + destroyPowerSupplyDevice(); +} + +} diff --git a/Tactility/Source/hal/touch/KernelTouchDriver.cpp b/Tactility/Source/hal/touch/KernelTouchDriver.cpp index 03bf938da..7f8de3f8f 100644 --- a/Tactility/Source/hal/touch/KernelTouchDriver.cpp +++ b/Tactility/Source/hal/touch/KernelTouchDriver.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include #include @@ -11,14 +11,14 @@ namespace tt::hal::touch { static constexpr TickType_t READ_TIMEOUT = pdMS_TO_TICKS(10); KernelTouchDriver::KernelTouchDriver(::Device* device) : device(device) { - assert(device_get_type(device) == &TOUCH_TYPE); + assert(device_get_type(device) == &POINTER_TYPE); } bool KernelTouchDriver::getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) { - if (touch_read_data(device, READ_TIMEOUT) != ERROR_NONE) { + if (pointer_read_data(device, READ_TIMEOUT) != ERROR_NONE) { return false; } - return touch_get_touched_points(device, x, y, strength, pointCount, maxPointCount); + return pointer_get_touched_points(device, x, y, strength, pointCount, maxPointCount); } } diff --git a/Tactility/Source/lvgl/Keyboard.cpp b/Tactility/Source/lvgl/Keyboard.cpp index 9092731d8..1894dc9ee 100644 --- a/Tactility/Source/lvgl/Keyboard.cpp +++ b/Tactility/Source/lvgl/Keyboard.cpp @@ -1,7 +1,8 @@ #include "Tactility/lvgl/Keyboard.h" #include "Tactility/service/gui/GuiService.h" -#include +#include +#include namespace tt::lvgl { @@ -46,7 +47,7 @@ void software_keyboard_deactivate() { } bool hardware_keyboard_is_available() { - return keyboard_device != nullptr; + return keyboard_device != nullptr || device_find_first_by_type(&KEYBOARD_TYPE) != nullptr; } void hardware_keyboard_set_indev(lv_indev_t* device) { diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index 48c596345..f1ee46ba7 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -32,37 +34,36 @@ void attachDevices() { // Start displays (their related touch devices start automatically within) LOG_I(TAG, "Start displays"); - auto displays = hal::findDevices(hal::Device::Type::Display); - for (const auto& display: displays) { + auto hal_displays= hal::findDevices(hal::Device::Type::Display); + for (const auto& display: hal_displays) { if (display->supportsLvgl()) { if (display->startLvgl()) { LOG_I(TAG, "Started %s", display->getName().c_str()); - auto lvgl_display = display->getLvglDisplay(); - assert(lvgl_display != nullptr); - auto settings = settings::display::loadOrGetDefault(); - lv_display_rotation_t rotation = settings::display::toLvglDisplayRotation(settings.orientation); - if (rotation != lv_display_get_rotation(lvgl_display)) { - lv_display_set_rotation(lvgl_display, rotation); - } } else { LOG_E(TAG, "Start failed for %s", display->getName().c_str()); } } } + auto* primary_lvgl_display = lv_disp_get_default(); + if (primary_lvgl_display != nullptr) { + LOG_I(TAG, "Set default display rotation"); + auto settings = settings::display::loadOrGetDefault(); + lv_display_rotation_t rotation = settings::display::toLvglDisplayRotation(settings.orientation); + if (rotation != lv_display_get_rotation(primary_lvgl_display)) { + lv_display_set_rotation(primary_lvgl_display, rotation); + } + } + // Start touch - // TODO: Consider implementing support for multiple displays - auto primary_display = !displays.empty() ? displays[0] : nullptr; - - // Start display-related peripherals - if (primary_display != nullptr) { + if (primary_lvgl_display != nullptr) { LOG_I(TAG, "Start touch devices"); auto touch_devices = hal::findDevices(hal::Device::Type::Touch); for (const auto& touch_device: touch_devices) { // Start any touch devices that haven't been started yet if (touch_device->supportsLvgl() && touch_device->getLvglIndev() == nullptr) { - if (touch_device->startLvgl(primary_display->getLvglDisplay())) { + if (touch_device->startLvgl(primary_lvgl_display)) { LOG_I(TAG, "Started %s", touch_device->getName().c_str()); } else { LOG_E(TAG, "Start failed for %s", touch_device->getName().c_str()); @@ -75,7 +76,7 @@ void attachDevices() { auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); for (const auto& keyboard: keyboards) { if (keyboard->isAttached()) { - if (keyboard->startLvgl(primary_display->getLvglDisplay())) { + if (keyboard->startLvgl(primary_lvgl_display)) { lv_indev_t* keyboard_indev = keyboard->getLvglIndev(); hardware_keyboard_set_indev(keyboard_indev); LOG_I(TAG, "Started %s", keyboard->getName().c_str()); @@ -89,7 +90,7 @@ void attachDevices() { LOG_I(TAG, "Start encoders"); auto encoders = hal::findDevices(hal::Device::Type::Encoder); for (const auto& encoder: encoders) { - if (encoder->startLvgl(primary_display->getLvglDisplay())) { + if (encoder->startLvgl(primary_lvgl_display)) { LOG_I(TAG, "Started %s", encoder->getName().c_str()); } else { LOG_E(TAG, "Start failed for %s", encoder->getName().c_str()); diff --git a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp index ff13d2814..aef3b0833 100644 --- a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp +++ b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp @@ -9,9 +9,8 @@ #include #include -namespace keyboardbacklight { - bool setBrightness(uint8_t brightness); -} +#include +#include namespace tt::service::keyboardidle { @@ -25,6 +24,17 @@ class KeyboardIdleService final : public Service { return hal::findFirstDevice(hal::Device::Type::Keyboard); } + static Device* getKeyboardBacklight() { + return device_find_by_name("keyboard_backlight"); + } + + void setKeyboardBacklightBrightness(uint8_t brightness) { + Device* backlight = getKeyboardBacklight(); + if (backlight != nullptr) { + backlight_set_brightness(backlight, brightness); + } + } + void tick() { // Settings are now cached and event-driven (no file I/O in timer callback!) // This prevents watchdog timeout from blocking the Timer Service task @@ -42,15 +52,15 @@ class KeyboardIdleService final : public Service { // If timeout disabled, ensure backlight restored if we had dimmed it if (!cachedKeyboardSettings.backlightTimeoutEnabled || cachedKeyboardSettings.backlightTimeoutMs == 0) { if (keyboardDimmed) { - keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); + setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); keyboardDimmed = false; } } else { if (!keyboardDimmed && inactive_ms >= cachedKeyboardSettings.backlightTimeoutMs) { - keyboardbacklight::setBrightness(0); + setKeyboardBacklightBrightness(0); keyboardDimmed = true; } else if (keyboardDimmed && inactive_ms < 100) { - keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); + setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); keyboardDimmed = false; } } @@ -62,7 +72,8 @@ public: // Load settings once at startup and cache them // This eliminates file I/O from timer callback (prevents watchdog timeout) cachedKeyboardSettings = settings::keyboard::loadOrGetDefault(); - + setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); + // Note: Settings changes require service restart to take effect // TODO: Add KeyboardSettingsChanged events for dynamic updates @@ -80,7 +91,7 @@ public: // Ensure keyboard restored on stop auto keyboard = getKeyboard(); if (keyboard && keyboardDimmed) { - keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); + setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); keyboardDimmed = false; } } diff --git a/TactilityKernel/bindings/pointer-placeholder.yaml b/TactilityKernel/bindings/pointer-placeholder.yaml new file mode 100644 index 000000000..50709d400 --- /dev/null +++ b/TactilityKernel/bindings/pointer-placeholder.yaml @@ -0,0 +1,5 @@ +description: Pointer placeholder + +compatible: "pointer-placeholder" + +properties: {} diff --git a/TactilityKernel/bindings/touch-placeholder.yaml b/TactilityKernel/bindings/touch-placeholder.yaml deleted file mode 100644 index e41b29659..000000000 --- a/TactilityKernel/bindings/touch-placeholder.yaml +++ /dev/null @@ -1,5 +0,0 @@ -description: Touch placeholder - -compatible: "touch-placeholder" - -properties: {} diff --git a/TactilityKernel/include/tactility/bindings/touch_placeholder.h b/TactilityKernel/include/tactility/bindings/pointer_placeholder.h similarity index 57% rename from TactilityKernel/include/tactility/bindings/touch_placeholder.h rename to TactilityKernel/include/tactility/bindings/pointer_placeholder.h index ac0524158..d5d026f49 100644 --- a/TactilityKernel/include/tactility/bindings/touch_placeholder.h +++ b/TactilityKernel/include/tactility/bindings/pointer_placeholder.h @@ -2,13 +2,13 @@ #pragma once #include -#include +#include #ifdef __cplusplus extern "C" { #endif -DEFINE_DEVICETREE(touch_placeholder, struct TouchPlaceholderConfig) +DEFINE_DEVICETREE(pointer_placeholder, struct PointerPlaceholderConfig) #ifdef __cplusplus } diff --git a/TactilityKernel/include/tactility/drivers/backlight.h b/TactilityKernel/include/tactility/drivers/backlight.h new file mode 100644 index 000000000..ff6e03baa --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/backlight.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @brief Inclusive range of brightness levels accepted by a backlight driver. + */ +struct BrightnessLevelRange { + uint8_t min; + uint8_t max; +}; + +/** + * @brief API for backlight drivers. + * + * @note The minimum brightness level (see get_min_brightness()) turns the backlight fully off. + * There is no separate on/off control: to turn the backlight off, call set_brightness() with + * the minimum value; to turn it back on, set any value above the minimum. + */ +struct BacklightApi { + /** + * @brief Sets the backlight brightness. + * @param[in] device the backlight device + * @param[in] brightness the brightness level, expected to be within [get_min_brightness(), get_max_brightness()] + * @retval ERROR_NONE when the operation was successful + */ + error_t (*set_brightness)(struct Device* device, uint8_t brightness); + + /** + * @brief Sets the backlight brightness to its devicetree-configured default level. + * @param[in] device the backlight device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*set_brightness_default)(struct Device* device); + + /** + * @brief Gets the current backlight brightness. + * @param[in] device the backlight device + * @param[out] out_brightness the current brightness level + * @retval ERROR_NONE when the operation was successful + */ + error_t (*get_brightness)(struct Device* device, uint8_t* out_brightness); + + /** + * @brief Gets the minimum brightness level. Setting the brightness to this value turns the backlight off. + * @param[in] device the backlight device + * @return the minimum brightness level + */ + uint8_t (*get_min_brightness)(struct Device* device); + + /** + * @brief Gets the maximum (full-strength) brightness level. + * @param[in] device the backlight device + * @return the maximum brightness level + */ + uint8_t (*get_max_brightness)(struct Device* device); +}; + +/** + * @brief Sets the backlight brightness using the specified backlight device. + */ +error_t backlight_set_brightness(struct Device* device, uint8_t brightness); + +/** + * @brief Sets the backlight brightness to its devicetree-configured default level using the specified backlight device. + */ +error_t backlight_set_brightness_default(struct Device* device); + +/** + * @brief Gets the current backlight brightness using the specified backlight device. + */ +error_t backlight_get_brightness(struct Device* device, uint8_t* out_brightness); + +/** + * @brief Gets the minimum brightness level using the specified backlight device. This level turns the backlight off. + */ +uint8_t backlight_get_min_brightness(struct Device* device); + +/** + * @brief Gets the maximum brightness level using the specified backlight device. + */ +uint8_t backlight_get_max_brightness(struct Device* device); + +extern const struct DeviceType BACKLIGHT_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/display.h b/TactilityKernel/include/tactility/drivers/display.h index d34dfd10c..7f303a978 100644 --- a/TactilityKernel/include/tactility/drivers/display.h +++ b/TactilityKernel/include/tactility/drivers/display.h @@ -66,6 +66,27 @@ struct DisplayApi { */ error_t (*swap_xy)(struct Device* device, bool swap_axes); + /** + * @brief Gets whether the X and Y axes are currently swapped. + * @param[in] device the display device + * @return true if swapped + */ + bool (*get_swap_xy)(struct Device* device); + + /** + * @brief Gets whether the X axis is currently mirrored. + * @param[in] device the display device + * @return true if mirrored + */ + bool (*get_mirror_x)(struct Device* device); + + /** + * @brief Gets whether the Y axis is currently mirrored. + * @param[in] device the display device + * @return true if mirrored + */ + bool (*get_mirror_y)(struct Device* device); + /** * @brief Sets a coordinate offset applied to all draw operations. * @param[in] device the display device @@ -129,6 +150,15 @@ struct DisplayApi { * @return the frame buffer count */ uint8_t (*get_frame_buffer_count)(struct Device* device); + + /** + * @brief Gets the backlight device associated with this display, if any. + * @param[in] device the display device + * @param[out] backlight the associated backlight device + * @retval ERROR_NONE when a backlight is available and *backlight was set + * @retval ERROR_NOT_SUPPORTED when this display has no associated backlight + */ + error_t (*get_backlight)(struct Device* device, struct Device** backlight); }; /** @@ -156,6 +186,21 @@ error_t display_mirror(struct Device* device, bool x_axis, bool y_axis); */ error_t display_swap_xy(struct Device* device, bool swap_axes); +/** + * @brief Gets whether the X and Y axes are currently swapped using the specified display. + */ +bool display_get_swap_xy(struct Device* device); + +/** + * @brief Gets whether the X axis is currently mirrored using the specified display. + */ +bool display_get_mirror_x(struct Device* device); + +/** + * @brief Gets whether the Y axis is currently mirrored using the specified display. + */ +bool display_get_mirror_y(struct Device* device); + /** * @brief Sets a coordinate offset applied to all draw operations using the specified display. */ @@ -201,6 +246,13 @@ void display_get_frame_buffer(struct Device* device, uint8_t index, void** out_b */ uint8_t display_get_frame_buffer_count(struct Device* device); +/** + * @brief Gets the backlight device associated with the specified display, if any. + * @retval ERROR_NONE when a backlight is available and *backlight was set + * @retval ERROR_NOT_SUPPORTED when the display has no associated backlight + */ +error_t display_get_backlight(struct Device* device, struct Device** backlight); + extern const struct DeviceType DISPLAY_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/drivers/keyboard.h b/TactilityKernel/include/tactility/drivers/keyboard.h new file mode 100644 index 000000000..d7ea86994 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/keyboard.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +/** + * @brief A single key event read from a keyboard device. + */ +struct KeyboardKeyData { + /** @brief The key code. Driver-defined (e.g. ASCII/UTF-8 codepoint, scan code, or LVGL key code). */ + uint32_t key; + /** @brief True if the key was pressed, false if released. */ + bool pressed; + /** + * @brief True if another key event is already queued and read_key() should be called again + * immediately to drain it. False if this was the last pending event. + */ + bool continue_reading; +}; + +/** + * @brief API for keyboard drivers. + */ +struct KeyboardApi { + /** + * @brief Reads the next pending key event, if any. + * @param[in] device the keyboard device + * @param[out] data the key event data + * @retval ERROR_NONE when the operation was successful + */ + error_t (*read_key)(struct Device* device, struct KeyboardKeyData* data); +}; + +/** + * @brief Reads the next pending key event using the specified keyboard device. + */ +error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data); + +extern const struct DeviceType KEYBOARD_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/touch.h b/TactilityKernel/include/tactility/drivers/pointer.h similarity index 65% rename from TactilityKernel/include/tactility/drivers/touch.h rename to TactilityKernel/include/tactility/drivers/pointer.h index fea8b1a7e..f346ac502 100644 --- a/TactilityKernel/include/tactility/drivers/touch.h +++ b/TactilityKernel/include/tactility/drivers/pointer.h @@ -12,19 +12,19 @@ extern "C" { #include /** - * @brief API for touch controller drivers. + * @brief API for pointer controller drivers. */ -struct TouchApi { +struct PointerApi { /** - * @brief Puts the touch controller into its low-power sleep mode. - * @param[in] device the touch device + * @brief Puts the pointer controller into its low-power sleep mode. + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*enter_sleep)(struct Device* device); /** - * @brief Wakes the touch controller from sleep mode. - * @param[in] device the touch device + * @brief Wakes the pointer controller from sleep mode. + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*exit_sleep)(struct Device* device); @@ -32,7 +32,7 @@ struct TouchApi { /** * @brief Performs a blocking read of the latest touch data from the controller into its internal state. * Call this before get_touched_points(). - * @param[in] device the touch device + * @param[in] device the pointer device * @param[in] timeout the maximum time to wait for the operation to complete * @retval ERROR_NONE when the read operation was successful * @retval ERROR_TIMEOUT when the operation timed out @@ -41,7 +41,7 @@ struct TouchApi { /** * @brief Retrieves the coordinates most recently read by read_data(). - * @param[in] device the touch device + * @param[in] device the pointer device * @param[out] x array of X coordinates * @param[out] y array of Y coordinates * @param[out] strength optional array of touch strengths (nullable) @@ -53,98 +53,98 @@ struct TouchApi { /** * @brief Sets whether the X and Y axes should be swapped. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*set_swap_xy)(struct Device* device, bool swap); /** * @brief Gets whether the X and Y axes are swapped. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*get_swap_xy)(struct Device* device, bool* swap); /** * @brief Sets whether the X axis should be mirrored. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*set_mirror_x)(struct Device* device, bool mirror); /** * @brief Gets whether the X axis is mirrored. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*get_mirror_x)(struct Device* device, bool* mirror); /** * @brief Sets whether the Y axis should be mirrored. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*set_mirror_y)(struct Device* device, bool mirror); /** * @brief Gets whether the Y axis is mirrored. - * @param[in] device the touch device + * @param[in] device the pointer device * @retval ERROR_NONE when the operation was successful */ error_t (*get_mirror_y)(struct Device* device, bool* mirror); }; /** - * @brief Puts the touch controller into its low-power sleep mode using the specified touch device. + * @brief Puts the pointer controller into its low-power sleep mode using the specified pointer device. */ -error_t touch_enter_sleep(struct Device* device); +error_t pointer_enter_sleep(struct Device* device); /** - * @brief Wakes the touch controller from sleep mode using the specified touch device. + * @brief Wakes the pointer controller from sleep mode using the specified pointer device. */ -error_t touch_exit_sleep(struct Device* device); +error_t pointer_exit_sleep(struct Device* device); /** - * @brief Performs a blocking read of the latest touch data using the specified touch device. + * @brief Performs a blocking read of the latest touch data using the specified pointer device. */ -error_t touch_read_data(struct Device* device, TickType_t timeout); +error_t pointer_read_data(struct Device* device, TickType_t timeout); /** - * @brief Retrieves the coordinates most recently read using the specified touch device. + * @brief Retrieves the coordinates most recently read using the specified pointer device. */ -bool touch_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count); +bool pointer_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count); /** - * @brief Sets whether the X and Y axes should be swapped using the specified touch device. + * @brief Sets whether the X and Y axes should be swapped using the specified pointer device. */ -error_t touch_set_swap_xy(struct Device* device, bool swap); +error_t pointer_set_swap_xy(struct Device* device, bool swap); /** - * @brief Gets whether the X and Y axes are swapped using the specified touch device. + * @brief Gets whether the X and Y axes are swapped using the specified pointer device. */ -error_t touch_get_swap_xy(struct Device* device, bool* swap); +error_t pointer_get_swap_xy(struct Device* device, bool* swap); /** - * @brief Sets whether the X axis should be mirrored using the specified touch device. + * @brief Sets whether the X axis should be mirrored using the specified pointer device. */ -error_t touch_set_mirror_x(struct Device* device, bool mirror); +error_t pointer_set_mirror_x(struct Device* device, bool mirror); /** - * @brief Gets whether the X axis is mirrored using the specified touch device. + * @brief Gets whether the X axis is mirrored using the specified pointer device. */ -error_t touch_get_mirror_x(struct Device* device, bool* mirror); +error_t pointer_get_mirror_x(struct Device* device, bool* mirror); /** - * @brief Sets whether the Y axis should be mirrored using the specified touch device. + * @brief Sets whether the Y axis should be mirrored using the specified pointer device. */ -error_t touch_set_mirror_y(struct Device* device, bool mirror); +error_t pointer_set_mirror_y(struct Device* device, bool mirror); /** - * @brief Gets whether the Y axis is mirrored using the specified touch device. + * @brief Gets whether the Y axis is mirrored using the specified pointer device. */ -error_t touch_get_mirror_y(struct Device* device, bool* mirror); +error_t pointer_get_mirror_y(struct Device* device, bool* mirror); -extern const struct DeviceType TOUCH_TYPE; +extern const struct DeviceType POINTER_TYPE; #ifdef __cplusplus } diff --git a/TactilityKernel/include/tactility/drivers/touch_placeholder.h b/TactilityKernel/include/tactility/drivers/pointer_placeholder.h similarity index 83% rename from TactilityKernel/include/tactility/drivers/touch_placeholder.h rename to TactilityKernel/include/tactility/drivers/pointer_placeholder.h index 62b46b91b..e8b1553a5 100644 --- a/TactilityKernel/include/tactility/drivers/touch_placeholder.h +++ b/TactilityKernel/include/tactility/drivers/pointer_placeholder.h @@ -7,7 +7,7 @@ extern "C" { #endif -struct TouchPlaceholderConfig { +struct PointerPlaceholderConfig { uint8_t _unused; }; diff --git a/TactilityKernel/include/tactility/drivers/power_supply.h b/TactilityKernel/include/tactility/drivers/power_supply.h new file mode 100644 index 000000000..109ac552a --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/power_supply.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +/** + * @brief Properties that a power supply device can report. + */ +enum PowerSupplyProperty { + /** [0, 1]: whether the battery is currently charging */ + POWER_SUPPLY_PROP_IS_CHARGING, + /** mA: battery current, positive while charging, negative while discharging */ + POWER_SUPPLY_PROP_CURRENT, + /** mV: battery voltage */ + POWER_SUPPLY_PROP_VOLTAGE, + /** [0, 100]: battery charge level */ + POWER_SUPPLY_PROP_CAPACITY, +}; + +/** + * @brief Holds the value of a single power supply property. + */ +union PowerSupplyPropertyValue { + int int_value; +}; + +/** + * @brief API for power supply drivers. + * + * @note supports_charge_control(), supports_quick_charge() and supports_power_off() gate + * their respective functions: when a capability is not supported, the driver's implementation + * of the related getter/setter is expected to be a NO-OP (getters returning false/0). + */ +struct PowerSupplyApi { + /** + * @brief Checks whether a property is supported by this device. + * @param[in] device the power supply device + * @param[in] property the property to check + * @return true if the property is supported + */ + bool (*supports_property)(struct Device* device, enum PowerSupplyProperty property); + + /** + * @brief Gets the current value of a property. + * @param[in] device the power supply device + * @param[in] property the property to read + * @param[out] out_value the property value + * @retval ERROR_NOT_SUPPORTED when the property is not supported + * @retval ERROR_NOT_FOUND when the property is (temporarily) not available + * @retval ERROR_NONE when the operation was successful + */ + error_t (*get_property)(struct Device* device, enum PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value); + + /** + * @brief Checks whether this device supports enabling/disabling charging. + */ + bool (*supports_charge_control)(struct Device* device); + + /** + * @brief Checks whether charging is currently allowed. + */ + bool (*is_allowed_to_charge)(struct Device* device); + + /** + * @brief Enables or disables charging. + * @retval ERROR_NOT_SUPPORTED when charge control is not supported + * @retval ERROR_NONE when the operation was successful + */ + error_t (*set_allowed_to_charge)(struct Device* device, bool allowed); + + /** + * @brief Checks whether this device supports quick charging. + */ + bool (*supports_quick_charge)(struct Device* device); + + /** + * @brief Checks whether quick charging is currently enabled. + */ + bool (*is_quick_charge_enabled)(struct Device* device); + + /** + * @brief Enables or disables quick charging. + * @retval ERROR_NOT_SUPPORTED when quick charging is not supported + * @retval ERROR_NONE when the operation was successful + */ + error_t (*set_quick_charge_enabled)(struct Device* device, bool enabled); + + /** + * @brief Checks whether this device can power off the system. + */ + bool (*supports_power_off)(struct Device* device); + + /** + * @brief Powers off the system. + * @retval ERROR_NOT_SUPPORTED when power off is not supported + * @retval ERROR_NONE when the operation was successful (this call typically does not return) + */ + error_t (*power_off)(struct Device* device); +}; + +/** + * @brief Checks whether a property is supported using the specified power supply device. + */ +bool power_supply_supports_property(struct Device* device, enum PowerSupplyProperty property); + +/** + * @brief Gets the current value of a property using the specified power supply device. + */ +error_t power_supply_get_property(struct Device* device, enum PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value); + +/** + * @brief Checks whether the specified power supply device supports enabling/disabling charging. + */ +bool power_supply_supports_charge_control(struct Device* device); + +/** + * @brief Checks whether charging is currently allowed on the specified power supply device. + */ +bool power_supply_is_allowed_to_charge(struct Device* device); + +/** + * @brief Enables or disables charging on the specified power supply device. + */ +error_t power_supply_set_allowed_to_charge(struct Device* device, bool allowed); + +/** + * @brief Checks whether the specified power supply device supports quick charging. + */ +bool power_supply_supports_quick_charge(struct Device* device); + +/** + * @brief Checks whether quick charging is currently enabled on the specified power supply device. + */ +bool power_supply_is_quick_charge_enabled(struct Device* device); + +/** + * @brief Enables or disables quick charging on the specified power supply device. + */ +error_t power_supply_set_quick_charge_enabled(struct Device* device, bool enabled); + +/** + * @brief Checks whether the specified power supply device can power off the system. + */ +bool power_supply_supports_power_off(struct Device* device); + +/** + * @brief Powers off the system using the specified power supply device. + */ +error_t power_supply_power_off(struct Device* device); + +extern const struct DeviceType POWER_SUPPLY_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/drivers/backlight.cpp b/TactilityKernel/source/drivers/backlight.cpp new file mode 100644 index 000000000..4981890cb --- /dev/null +++ b/TactilityKernel/source/drivers/backlight.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define BACKLIGHT_DRIVER_API(driver) ((struct BacklightApi*)driver->api) + +extern "C" { + +error_t backlight_set_brightness(Device* device, uint8_t brightness) { + const auto* driver = device_get_driver(device); + return BACKLIGHT_DRIVER_API(driver)->set_brightness(device, brightness); +} + +error_t backlight_set_brightness_default(Device* device) { + const auto* driver = device_get_driver(device); + return BACKLIGHT_DRIVER_API(driver)->set_brightness_default(device); +} + +error_t backlight_get_brightness(Device* device, uint8_t* out_brightness) { + const auto* driver = device_get_driver(device); + return BACKLIGHT_DRIVER_API(driver)->get_brightness(device, out_brightness); +} + +uint8_t backlight_get_min_brightness(Device* device) { + const auto* driver = device_get_driver(device); + return BACKLIGHT_DRIVER_API(driver)->get_min_brightness(device); +} + +uint8_t backlight_get_max_brightness(Device* device) { + const auto* driver = device_get_driver(device); + return BACKLIGHT_DRIVER_API(driver)->get_max_brightness(device); +} + +const DeviceType BACKLIGHT_TYPE { + .name = "backlight" +}; + +} diff --git a/TactilityKernel/source/drivers/display.cpp b/TactilityKernel/source/drivers/display.cpp index bc1a12e31..d0127b53d 100644 --- a/TactilityKernel/source/drivers/display.cpp +++ b/TactilityKernel/source/drivers/display.cpp @@ -6,76 +6,96 @@ extern "C" { -error_t display_reset(struct Device* device) { +error_t display_reset(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->reset(device); } -error_t display_init(struct Device* device) { +error_t display_init(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->init(device); } -error_t display_draw_bitmap(struct Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { +error_t display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->draw_bitmap(device, x_start, y_start, x_end, y_end, color_data); } -error_t display_mirror(struct Device* device, bool x_axis, bool y_axis) { +error_t display_mirror(Device* device, bool x_axis, bool y_axis) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->mirror(device, x_axis, y_axis); } -error_t display_swap_xy(struct Device* device, bool swap_axes) { +error_t display_swap_xy(Device* device, bool swap_axes) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->swap_xy(device, swap_axes); } -error_t display_set_gap(struct Device* device, int32_t x_gap, int32_t y_gap) { +bool display_get_swap_xy(Device* device) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->get_swap_xy(device); +} + +bool display_get_mirror_x(Device* device) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->get_mirror_x(device); +} + +bool display_get_mirror_y(Device* device) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->get_mirror_y(device); +} + +error_t display_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->set_gap(device, x_gap, y_gap); } -error_t display_invert_color(struct Device* device, bool invert_color_data) { +error_t display_invert_color(Device* device, bool invert_color_data) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->invert_color(device, invert_color_data); } -error_t display_disp_on_off(struct Device* device, bool on_off) { +error_t display_disp_on_off(Device* device, bool on_off) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->disp_on_off(device, on_off); } -error_t display_disp_sleep(struct Device* device, bool sleep) { +error_t display_disp_sleep(Device* device, bool sleep) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->disp_sleep(device, sleep); } -enum DisplayColorFormat display_get_color_format(struct Device* device) { +enum DisplayColorFormat display_get_color_format(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->get_color_format(device); } -uint16_t display_get_resolution_x(struct Device* device) { +uint16_t display_get_resolution_x(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->get_resolution_x(device); } -uint16_t display_get_resolution_y(struct Device* device) { +uint16_t display_get_resolution_y(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->get_resolution_y(device); } -void display_get_frame_buffer(struct Device* device, uint8_t index, void** out_buffer) { +void display_get_frame_buffer(Device* device, uint8_t index, void** out_buffer) { const auto* driver = device_get_driver(device); DISPLAY_DRIVER_API(driver)->get_frame_buffer(device, index, out_buffer); } -uint8_t display_get_frame_buffer_count(struct Device* device) { +uint8_t display_get_frame_buffer_count(Device* device) { const auto* driver = device_get_driver(device); return DISPLAY_DRIVER_API(driver)->get_frame_buffer_count(device); } +error_t display_get_backlight(Device* device, Device** backlight) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->get_backlight(device, backlight); +} + const struct DeviceType DISPLAY_TYPE { .name = "display" }; diff --git a/TactilityKernel/source/drivers/grove.cpp b/TactilityKernel/source/drivers/grove.cpp index 37c1cb796..7ad9bff40 100644 --- a/TactilityKernel/source/drivers/grove.cpp +++ b/TactilityKernel/source/drivers/grove.cpp @@ -6,17 +6,17 @@ extern "C" { -error_t grove_set_mode(struct Device* device, enum GroveMode mode) { +error_t grove_set_mode(Device* device, GroveMode mode) { const auto* driver = device_get_driver(device); return GROVE_DRIVER_API(driver)->set_mode(device, mode); } -error_t grove_get_mode(struct Device* device, enum GroveMode* mode) { +error_t grove_get_mode(Device* device, GroveMode* mode) { const auto* driver = device_get_driver(device); return GROVE_DRIVER_API(driver)->get_mode(device, mode); } -const struct DeviceType GROVE_TYPE { +const DeviceType GROVE_TYPE { .name = "grove" }; diff --git a/TactilityKernel/source/drivers/keyboard.cpp b/TactilityKernel/source/drivers/keyboard.cpp new file mode 100644 index 000000000..307590271 --- /dev/null +++ b/TactilityKernel/source/drivers/keyboard.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api) + +extern "C" { + +error_t keyboard_read_key(Device* device, KeyboardKeyData* data) { + const auto* driver = device_get_driver(device); + return KEYBOARD_DRIVER_API(driver)->read_key(device, data); +} + +const DeviceType KEYBOARD_TYPE { + .name = "keyboard" +}; + +} diff --git a/TactilityKernel/source/drivers/pointer.cpp b/TactilityKernel/source/drivers/pointer.cpp new file mode 100644 index 000000000..33d609c53 --- /dev/null +++ b/TactilityKernel/source/drivers/pointer.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define POINTER_DRIVER_API(driver) ((struct PointerApi*)driver->api) + +extern "C" { + +error_t pointer_enter_sleep(struct Device* device) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->enter_sleep(device); +} + +error_t pointer_exit_sleep(Device* device) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->exit_sleep(device); +} + +error_t pointer_read_data(Device* device, TickType_t timeout) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->read_data(device, timeout); +} + +bool pointer_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->get_touched_points(device, x, y, strength, point_count, max_point_count); +} + +error_t pointer_set_swap_xy(Device* device, bool swap) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->set_swap_xy(device, swap); +} + +error_t pointer_get_swap_xy(Device* device, bool* swap) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->get_swap_xy(device, swap); +} + +error_t pointer_set_mirror_x(Device* device, bool mirror) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->set_mirror_x(device, mirror); +} + +error_t pointer_get_mirror_x(Device* device, bool* mirror) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->get_mirror_x(device, mirror); +} + +error_t pointer_set_mirror_y(Device* device, bool mirror) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->set_mirror_y(device, mirror); +} + +error_t pointer_get_mirror_y(Device* device, bool* mirror) { + const auto* driver = device_get_driver(device); + return POINTER_DRIVER_API(driver)->get_mirror_y(device, mirror); +} + +const struct DeviceType POINTER_TYPE { + .name = "pointer" +}; + +} diff --git a/TactilityKernel/source/drivers/touch_placeholder.cpp b/TactilityKernel/source/drivers/pointer_placeholder.cpp similarity index 60% rename from TactilityKernel/source/drivers/touch_placeholder.cpp rename to TactilityKernel/source/drivers/pointer_placeholder.cpp index 4b76be3e2..b3d329af9 100644 --- a/TactilityKernel/source/drivers/touch_placeholder.cpp +++ b/TactilityKernel/source/drivers/pointer_placeholder.cpp @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include -#include +#include #include extern "C" { @@ -12,13 +12,13 @@ static error_t stop(Device*) { return ERROR_NONE; } extern Module root_module; -Driver touch_placeholder_driver = { - .name = "touch_placeholder", - .compatible = (const char*[]) { "touch-placeholder", nullptr }, +Driver pointer_placeholder_driver = { + .name = "pointer_placeholder", + .compatible = (const char*[]) { "pointer-placeholder", nullptr }, .start_device = start, .stop_device = stop, .api = nullptr, - .device_type = &TOUCH_TYPE, + .device_type = &POINTER_TYPE, .owner = &root_module, .internal = nullptr }; diff --git a/TactilityKernel/source/drivers/power_supply.cpp b/TactilityKernel/source/drivers/power_supply.cpp new file mode 100644 index 000000000..3a707639b --- /dev/null +++ b/TactilityKernel/source/drivers/power_supply.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define POWER_SUPPLY_DRIVER_API(driver) ((PowerSupplyApi*)driver->api) + +extern "C" { + +bool power_supply_supports_property(Device* device, PowerSupplyProperty property) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->supports_property(device, property); +} + +error_t power_supply_get_property(Device* device, PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->get_property(device, property, out_value); +} + +bool power_supply_supports_charge_control(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->supports_charge_control(device); +} + +bool power_supply_is_allowed_to_charge(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->is_allowed_to_charge(device); +} + +error_t power_supply_set_allowed_to_charge(Device* device, bool allowed) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->set_allowed_to_charge(device, allowed); +} + +bool power_supply_supports_quick_charge(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->supports_quick_charge(device); +} + +bool power_supply_is_quick_charge_enabled(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->is_quick_charge_enabled(device); +} + +error_t power_supply_set_quick_charge_enabled(Device* device, bool enabled) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->set_quick_charge_enabled(device, enabled); +} + +bool power_supply_supports_power_off(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->supports_power_off(device); +} + +error_t power_supply_power_off(Device* device) { + const auto* driver = device_get_driver(device); + return POWER_SUPPLY_DRIVER_API(driver)->power_off(device); +} + +const DeviceType POWER_SUPPLY_TYPE { + .name = "power_supply" +}; + +} diff --git a/TactilityKernel/source/drivers/rtc.cpp b/TactilityKernel/source/drivers/rtc.cpp index f600e1d2f..6b44d6895 100644 --- a/TactilityKernel/source/drivers/rtc.cpp +++ b/TactilityKernel/source/drivers/rtc.cpp @@ -6,17 +6,17 @@ extern "C" { -error_t rtc_get_time(struct Device* device, struct RtcDateTime* dt) { +error_t rtc_get_time(Device* device, RtcDateTime* dt) { const auto* driver = device_get_driver(device); return RTC_DRIVER_API(driver)->get_time(device, dt); } -error_t rtc_set_time(struct Device* device, const struct RtcDateTime* dt) { +error_t rtc_set_time(Device* device, const RtcDateTime* dt) { const auto* driver = device_get_driver(device); return RTC_DRIVER_API(driver)->set_time(device, dt); } -const struct DeviceType RTC_TYPE { +const DeviceType RTC_TYPE { .name = "rtc" }; diff --git a/TactilityKernel/source/drivers/sdcard.cpp b/TactilityKernel/source/drivers/sdcard.cpp index c49a8d78f..277375bd2 100644 --- a/TactilityKernel/source/drivers/sdcard.cpp +++ b/TactilityKernel/source/drivers/sdcard.cpp @@ -3,7 +3,7 @@ extern "C" { -const struct DeviceType SDCARD_TYPE { +const DeviceType SDCARD_TYPE { .name = "sdcard" }; diff --git a/TactilityKernel/source/drivers/spi_controller.cpp b/TactilityKernel/source/drivers/spi_controller.cpp index b5e654cfe..dde7b06e9 100644 --- a/TactilityKernel/source/drivers/spi_controller.cpp +++ b/TactilityKernel/source/drivers/spi_controller.cpp @@ -7,22 +7,22 @@ extern "C" { -error_t spi_controller_lock(struct Device* device) { +error_t spi_controller_lock(Device* device) { const auto* driver = device_get_driver(device); return SPI_DRIVER_API(driver)->lock(device); } -error_t spi_controller_try_lock(struct Device* device, TickType_t timeout) { +error_t spi_controller_try_lock(Device* device, TickType_t timeout) { const auto* driver = device_get_driver(device); return SPI_DRIVER_API(driver)->try_lock(device, timeout); } -error_t spi_controller_unlock(struct Device* device) { +error_t spi_controller_unlock(Device* device) { const auto* driver = device_get_driver(device); return SPI_DRIVER_API(driver)->unlock(device); } -const struct DeviceType SPI_CONTROLLER_TYPE { +const DeviceType SPI_CONTROLLER_TYPE { .name = "spi-controller" }; diff --git a/TactilityKernel/source/drivers/touch.cpp b/TactilityKernel/source/drivers/touch.cpp deleted file mode 100644 index 7cf0376d9..000000000 --- a/TactilityKernel/source/drivers/touch.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include -#include - -#define TOUCH_DRIVER_API(driver) ((struct TouchApi*)driver->api) - -extern "C" { - -error_t touch_enter_sleep(struct Device* device) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->enter_sleep(device); -} - -error_t touch_exit_sleep(struct Device* device) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->exit_sleep(device); -} - -error_t touch_read_data(struct Device* device, TickType_t timeout) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->read_data(device, timeout); -} - -bool touch_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->get_touched_points(device, x, y, strength, point_count, max_point_count); -} - -error_t touch_set_swap_xy(struct Device* device, bool swap) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->set_swap_xy(device, swap); -} - -error_t touch_get_swap_xy(struct Device* device, bool* swap) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->get_swap_xy(device, swap); -} - -error_t touch_set_mirror_x(struct Device* device, bool mirror) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->set_mirror_x(device, mirror); -} - -error_t touch_get_mirror_x(struct Device* device, bool* mirror) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->get_mirror_x(device, mirror); -} - -error_t touch_set_mirror_y(struct Device* device, bool mirror) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->set_mirror_y(device, mirror); -} - -error_t touch_get_mirror_y(struct Device* device, bool* mirror) { - const auto* driver = device_get_driver(device); - return TOUCH_DRIVER_API(driver)->get_mirror_y(device, mirror); -} - -const struct DeviceType TOUCH_TYPE { - .name = "touch" -}; - -} diff --git a/TactilityKernel/source/kernel_init.cpp b/TactilityKernel/source/kernel_init.cpp index 16d0be818..bd1e10619 100644 --- a/TactilityKernel/source/kernel_init.cpp +++ b/TactilityKernel/source/kernel_init.cpp @@ -16,8 +16,8 @@ static error_t start() { if (driver_construct_add(&root_driver) != ERROR_NONE) return ERROR_RESOURCE; extern Driver display_placeholder_driver; if (driver_construct_add(&display_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE; - extern Driver touch_placeholder_driver; - if (driver_construct_add(&touch_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE; + extern Driver pointer_placeholder_driver; + if (driver_construct_add(&pointer_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE; extern Driver spi_peripheral_driver; if (driver_construct_add(&spi_peripheral_driver) != ERROR_NONE) return ERROR_RESOURCE; return ERROR_NONE;