From 32022c7e670bd28dae018fc274d972d0f3cb0214 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 10 Jul 2026 00:31:24 +0200 Subject: [PATCH 01/16] Display and touch kernel drivers --- .../hal/display/KernelDisplayDriver.h | 26 +++ .../Tactility/hal/touch/KernelTouchDriver.h | 22 ++ .../Include/Tactility/hal/touch/TouchDriver.h | 2 + .../hal/display/KernelDisplayDriver.cpp | 59 ++++++ .../Source/hal/touch/KernelTouchDriver.cpp | 24 +++ .../include/tactility/drivers/display.h | 194 ++++++++++++++++++ .../include/tactility/drivers/touch.h | 137 +++++++++++++ TactilityKernel/source/drivers/display.cpp | 73 +++++++ TactilityKernel/source/drivers/touch.cpp | 53 +++++ 9 files changed, 590 insertions(+) create mode 100644 Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h create mode 100644 Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h create mode 100644 Tactility/Source/hal/display/KernelDisplayDriver.cpp create mode 100644 Tactility/Source/hal/touch/KernelTouchDriver.cpp diff --git a/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h b/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h new file mode 100644 index 000000000..757653690 --- /dev/null +++ b/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +namespace tt::hal::display { + +/** Wraps a TactilityKernel Device of type DISPLAY_TYPE as a DisplayDriver. */ +class KernelDisplayDriver final : public DisplayDriver { + + ::Device* device; + +public: + + explicit KernelDisplayDriver(::Device* device); + + ColorFormat getColorFormat() const override; + uint16_t getPixelWidth() const override; + uint16_t getPixelHeight() const override; + bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) override; + uint8_t getFrameBuffers(void* outBuffers[2]) const override; +}; + +} diff --git a/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h b/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h new file mode 100644 index 000000000..ab410467e --- /dev/null +++ b/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +namespace tt::hal::touch { + +/** Wraps a TactilityKernel Device of type TOUCH_TYPE as a TouchDriver. */ +class KernelTouchDriver final : public TouchDriver { + + ::Device* device; + +public: + + explicit KernelTouchDriver(::Device* device); + + bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) override; +}; + +} diff --git a/Tactility/Include/Tactility/hal/touch/TouchDriver.h b/Tactility/Include/Tactility/hal/touch/TouchDriver.h index 4db3b2c7f..58aa2515f 100644 --- a/Tactility/Include/Tactility/hal/touch/TouchDriver.h +++ b/Tactility/Include/Tactility/hal/touch/TouchDriver.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace tt::hal::touch { class TouchDriver { diff --git a/Tactility/Source/hal/display/KernelDisplayDriver.cpp b/Tactility/Source/hal/display/KernelDisplayDriver.cpp new file mode 100644 index 000000000..5808efbf7 --- /dev/null +++ b/Tactility/Source/hal/display/KernelDisplayDriver.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include + +#include +#include +#include + +namespace tt::hal::display { + +static ColorFormat toColorFormat(DisplayColorFormat format) { + switch (format) { + case DISPLAY_COLOR_FORMAT_MONOCHROME: + return ColorFormat::Monochrome; + case DISPLAY_COLOR_FORMAT_BGR565: + return ColorFormat::BGR565; + case DISPLAY_COLOR_FORMAT_BGR565_SWAPPED: + return ColorFormat::BGR565Swapped; + case DISPLAY_COLOR_FORMAT_RGB565: + return ColorFormat::RGB565; + case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED: + return ColorFormat::RGB565Swapped; + case DISPLAY_COLOR_FORMAT_RGB888: + return ColorFormat::RGB888; + default: + std::unreachable(); + } +} + +KernelDisplayDriver::KernelDisplayDriver(::Device* device) : device(device) { + assert(device_get_type(device) == &DISPLAY_TYPE); +} + +ColorFormat KernelDisplayDriver::getColorFormat() const { + return toColorFormat(display_get_color_format(device)); +} + +uint16_t KernelDisplayDriver::getPixelWidth() const { + return display_get_resolution_x(device); +} + +uint16_t KernelDisplayDriver::getPixelHeight() const { + return display_get_resolution_y(device); +} + +bool KernelDisplayDriver::drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) { + return display_draw_bitmap(device, xStart, yStart, xEnd, yEnd, pixelData) == ERROR_NONE; +} + +uint8_t KernelDisplayDriver::getFrameBuffers(void* outBuffers[2]) const { + uint8_t count = std::min(display_get_frame_buffer_count(device), 2); + for (uint8_t i = 0; i < count; i++) { + display_get_frame_buffer(device, i, &outBuffers[i]); + } + return count; +} + +} diff --git a/Tactility/Source/hal/touch/KernelTouchDriver.cpp b/Tactility/Source/hal/touch/KernelTouchDriver.cpp new file mode 100644 index 000000000..03bf938da --- /dev/null +++ b/Tactility/Source/hal/touch/KernelTouchDriver.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include + +#include + +namespace tt::hal::touch { + +// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll. +static constexpr TickType_t READ_TIMEOUT = pdMS_TO_TICKS(10); + +KernelTouchDriver::KernelTouchDriver(::Device* device) : device(device) { + assert(device_get_type(device) == &TOUCH_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) { + return false; + } + return touch_get_touched_points(device, x, y, strength, pointCount, maxPointCount); +} + +} diff --git a/TactilityKernel/include/tactility/drivers/display.h b/TactilityKernel/include/tactility/drivers/display.h index 982e679ef..d34dfd10c 100644 --- a/TactilityKernel/include/tactility/drivers/display.h +++ b/TactilityKernel/include/tactility/drivers/display.h @@ -5,7 +5,201 @@ extern "C" { #endif +#include + #include +#include + +/** + * @brief Pixel color formats supported by display panel drivers. + */ +enum DisplayColorFormat { + DISPLAY_COLOR_FORMAT_MONOCHROME = 0x0, // 1 bpp + DISPLAY_COLOR_FORMAT_BGR565 = 0x1, + DISPLAY_COLOR_FORMAT_BGR565_SWAPPED = 0x2, + DISPLAY_COLOR_FORMAT_RGB565 = 0x3, + DISPLAY_COLOR_FORMAT_RGB565_SWAPPED = 0x4, + DISPLAY_COLOR_FORMAT_RGB888 = 0x5, +}; + +/** + * @brief API for display panel drivers. + */ +struct DisplayApi { + /** + * @brief Performs a hardware reset of the panel (e.g. toggling a reset GPIO). + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*reset)(struct Device* device); + + /** + * @brief Sends the panel's initialization command sequence. Call after reset(). + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*init)(struct Device* device); + + /** + * @brief Draws pixel data into the given rectangle. + * @param[in] device the display device + * @param[in] x_start left edge (inclusive) + * @param[in] y_start top edge (inclusive) + * @param[in] x_end right edge (exclusive) + * @param[in] y_end bottom edge (exclusive) + * @param[in] color_data pixel data in the panel's configured color format + * @retval ERROR_NONE when the operation was successful + */ + error_t (*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); + + /** + * @brief Mirrors the image along the X and/or Y axis. + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*mirror)(struct Device* device, bool x_axis, bool y_axis); + + /** + * @brief Swaps the X and Y axes (rotates the coordinate system). + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*swap_xy)(struct Device* device, bool swap_axes); + + /** + * @brief Sets a coordinate offset applied to all draw operations. + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*set_gap)(struct Device* device, int32_t x_gap, int32_t y_gap); + + /** + * @brief Inverts the panel's color output. + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*invert_color)(struct Device* device, bool invert_color_data); + + /** + * @brief Turns the panel's display output on or off. + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*disp_on_off)(struct Device* device, bool on_off); + + /** + * @brief Puts the panel into or out of its low-power sleep mode. + * @param[in] device the display device + * @retval ERROR_NONE when the operation was successful + */ + error_t (*disp_sleep)(struct Device* device, bool sleep); + + /** + * @brief Gets the panel's pixel color format. + * @param[in] device the display device + * @return the color format + */ + enum DisplayColorFormat (*get_color_format)(struct Device* device); + + /** + * @brief Gets the panel's horizontal resolution in pixels. + * @param[in] device the display device + * @return the horizontal resolution + */ + uint16_t (*get_resolution_x)(struct Device* device); + + /** + * @brief Gets the panel's vertical resolution in pixels. + * @param[in] device the display device + * @return the vertical resolution + */ + uint16_t (*get_resolution_y)(struct Device* device); + + /** + * @brief Gets a pointer to one of the panel's frame buffers, for panels that expose direct frame buffer access. + * @param[in] device the display device + * @param[in] index the frame buffer index (see get_frame_buffer_count()) + * @param[out] out_buffer the buffer pointer + */ + void (*get_frame_buffer)(struct Device* device, uint8_t index, void** out_buffer); + + /** + * @brief Gets the number of frame buffers exposed by the panel via get_frame_buffer(). 0 when unsupported. + * @param[in] device the display device + * @return the frame buffer count + */ + uint8_t (*get_frame_buffer_count)(struct Device* device); +}; + +/** + * @brief Performs a hardware reset of the panel using the specified display. + */ +error_t display_reset(struct Device* device); + +/** + * @brief Sends the panel's initialization command sequence using the specified display. + */ +error_t display_init(struct Device* device); + +/** + * @brief Draws pixel data into the given rectangle using the specified display. + */ +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); + +/** + * @brief Mirrors the image along the X and/or Y axis using the specified display. + */ +error_t display_mirror(struct Device* device, bool x_axis, bool y_axis); + +/** + * @brief Swaps the X and Y axes using the specified display. + */ +error_t display_swap_xy(struct Device* device, bool swap_axes); + +/** + * @brief Sets a coordinate offset applied to all draw operations using the specified display. + */ +error_t display_set_gap(struct Device* device, int32_t x_gap, int32_t y_gap); + +/** + * @brief Inverts the panel's color output using the specified display. + */ +error_t display_invert_color(struct Device* device, bool invert_color_data); + +/** + * @brief Turns the panel's display output on or off using the specified display. + */ +error_t display_disp_on_off(struct Device* device, bool on_off); + +/** + * @brief Puts the panel into or out of its low-power sleep mode using the specified display. + */ +error_t display_disp_sleep(struct Device* device, bool sleep); + +/** + * @brief Gets the pixel color format using the specified display. + */ +enum DisplayColorFormat display_get_color_format(struct Device* device); + +/** + * @brief Gets the horizontal resolution in pixels using the specified display. + */ +uint16_t display_get_resolution_x(struct Device* device); + +/** + * @brief Gets the vertical resolution in pixels using the specified display. + */ +uint16_t display_get_resolution_y(struct Device* device); + +/** + * @brief Gets a pointer to one of the panel's frame buffers using the specified display. + */ +void display_get_frame_buffer(struct Device* device, uint8_t index, void** out_buffer); + +/** + * @brief Gets the number of frame buffers exposed by the panel using the specified display. + */ +uint8_t display_get_frame_buffer_count(struct Device* device); extern const struct DeviceType DISPLAY_TYPE; diff --git a/TactilityKernel/include/tactility/drivers/touch.h b/TactilityKernel/include/tactility/drivers/touch.h index 071aff778..fea8b1a7e 100644 --- a/TactilityKernel/include/tactility/drivers/touch.h +++ b/TactilityKernel/include/tactility/drivers/touch.h @@ -5,7 +5,144 @@ extern "C" { #endif +#include + #include +#include +#include + +/** + * @brief API for touch controller drivers. + */ +struct TouchApi { + /** + * @brief Puts the touch controller into its low-power sleep mode. + * @param[in] device the touch 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 + * @retval ERROR_NONE when the operation was successful + */ + error_t (*exit_sleep)(struct Device* device); + + /** + * @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] 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 + */ + error_t (*read_data)(struct Device* device, TickType_t timeout); + + /** + * @brief Retrieves the coordinates most recently read by read_data(). + * @param[in] device the touch device + * @param[out] x array of X coordinates + * @param[out] y array of Y coordinates + * @param[out] strength optional array of touch strengths (nullable) + * @param[out] point_count the number of points currently touched + * @param[in] max_point_count the maximum number of points the arrays can hold + * @return true when touched and coordinates are available + */ + bool (*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. + * @param[in] device the touch 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 + * @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 + * @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 + * @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 + * @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 + * @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. + */ +error_t touch_enter_sleep(struct Device* device); + +/** + * @brief Wakes the touch controller from sleep mode using the specified touch device. + */ +error_t touch_exit_sleep(struct Device* device); + +/** + * @brief Performs a blocking read of the latest touch data using the specified touch device. + */ +error_t touch_read_data(struct Device* device, TickType_t timeout); + +/** + * @brief Retrieves the coordinates most recently read using the specified touch 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); + +/** + * @brief Sets whether the X and Y axes should be swapped using the specified touch device. + */ +error_t touch_set_swap_xy(struct Device* device, bool swap); + +/** + * @brief Gets whether the X and Y axes are swapped using the specified touch device. + */ +error_t touch_get_swap_xy(struct Device* device, bool* swap); + +/** + * @brief Sets whether the X axis should be mirrored using the specified touch device. + */ +error_t touch_set_mirror_x(struct Device* device, bool mirror); + +/** + * @brief Gets whether the X axis is mirrored using the specified touch device. + */ +error_t touch_get_mirror_x(struct Device* device, bool* mirror); + +/** + * @brief Sets whether the Y axis should be mirrored using the specified touch device. + */ +error_t touch_set_mirror_y(struct Device* device, bool mirror); + +/** + * @brief Gets whether the Y axis is mirrored using the specified touch device. + */ +error_t touch_get_mirror_y(struct Device* device, bool* mirror); extern const struct DeviceType TOUCH_TYPE; diff --git a/TactilityKernel/source/drivers/display.cpp b/TactilityKernel/source/drivers/display.cpp index 3b49e4963..bc1a12e31 100644 --- a/TactilityKernel/source/drivers/display.cpp +++ b/TactilityKernel/source/drivers/display.cpp @@ -1,8 +1,81 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include + +#define DISPLAY_DRIVER_API(driver) ((struct DisplayApi*)driver->api) extern "C" { +error_t display_reset(struct Device* device) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->reset(device); +} + +error_t display_init(struct 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + const auto* driver = device_get_driver(device); + return DISPLAY_DRIVER_API(driver)->get_frame_buffer_count(device); +} + const struct DeviceType DISPLAY_TYPE { .name = "display" }; diff --git a/TactilityKernel/source/drivers/touch.cpp b/TactilityKernel/source/drivers/touch.cpp index f324ebb8f..7cf0376d9 100644 --- a/TactilityKernel/source/drivers/touch.cpp +++ b/TactilityKernel/source/drivers/touch.cpp @@ -1,8 +1,61 @@ // 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" }; From 77e6872191048838ae50a479966e76453a46c17d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 01:03:50 +0200 Subject: [PATCH 02/16] 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 @@ class PowerDevice : public Device { 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()); } } } - // Start touch + 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); + } + } - // TODO: Consider implementing support for multiple displays - auto primary_display = !displays.empty() ? displays[0] : nullptr; + // Start touch - // 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 @@ class KeyboardIdleService final : public Service { // 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 @@ class KeyboardIdleService final : public Service { // 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; From f9384d3533b40e382ec0eaca9eecf64b9455cf6c Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 02:04:18 +0200 Subject: [PATCH 03/16] Implement ADC controller and battery sense drivers --- Devices/lilygo-tdeck/CMakeLists.txt | 2 +- Devices/lilygo-tdeck/Source/Configuration.cpp | 19 --- Devices/lilygo-tdeck/Source/devices/Power.cpp | 10 -- Devices/lilygo-tdeck/Source/devices/Power.h | 5 - Devices/lilygo-tdeck/Source/module.cpp | 4 + .../lilygo-tdeck/Source/tdeck_keyboard.cpp | 2 +- Devices/lilygo-tdeck/lilygo,tdeck.dts | 27 ++- Platforms/platform-esp32/CMakeLists.txt | 2 +- .../bindings/espressif,esp32-adc-oneshot.yaml | 30 ++++ .../tactility/bindings/esp32_adc_oneshot.h | 15 ++ .../tactility/drivers/esp32_adc_oneshot.h | 31 ++++ .../source/drivers/esp32_adc_oneshot.cpp | 99 +++++++++++ Platforms/platform-esp32/source/module.cpp | 3 + Tactility/Source/Tactility.cpp | 2 +- .../Source/service/statusbar/Statusbar.cpp | 16 +- TactilityKernel/bindings/adc-controller.yaml | 1 + TactilityKernel/bindings/battery-sense.yaml | 19 +++ .../tactility/bindings/battery_sense.h | 15 ++ .../include/tactility/drivers/adc.h | 30 ++++ .../tactility/drivers/adc_controller.h | 56 +++++++ .../include/tactility/drivers/battery_sense.h | 22 +++ .../source/drivers/adc_controller.cpp | 22 +++ .../source/drivers/battery_sense.cpp | 155 ++++++++++++++++++ TactilityKernel/source/kernel_init.cpp | 4 + 24 files changed, 540 insertions(+), 51 deletions(-) delete mode 100644 Devices/lilygo-tdeck/Source/Configuration.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/Power.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/Power.h create mode 100644 Platforms/platform-esp32/bindings/espressif,esp32-adc-oneshot.yaml create mode 100644 Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h create mode 100644 Platforms/platform-esp32/include/tactility/drivers/esp32_adc_oneshot.h create mode 100644 Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp create mode 100644 TactilityKernel/bindings/adc-controller.yaml create mode 100644 TactilityKernel/bindings/battery-sense.yaml create mode 100644 TactilityKernel/include/tactility/bindings/battery_sense.h create mode 100644 TactilityKernel/include/tactility/drivers/adc.h create mode 100644 TactilityKernel/include/tactility/drivers/adc_controller.h create mode 100644 TactilityKernel/include/tactility/drivers/battery_sense.h create mode 100644 TactilityKernel/source/drivers/adc_controller.cpp create mode 100644 TactilityKernel/source/drivers/battery_sense.cpp diff --git a/Devices/lilygo-tdeck/CMakeLists.txt b/Devices/lilygo-tdeck/CMakeLists.txt index fc5c6b676..eda65fcd2 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 EstimatedPower driver + REQUIRES Tactility driver ) diff --git a/Devices/lilygo-tdeck/Source/Configuration.cpp b/Devices/lilygo-tdeck/Source/Configuration.cpp deleted file mode 100644 index 61a274f41..000000000 --- a/Devices/lilygo-tdeck/Source/Configuration.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "devices/Power.h" -#include "devices/TrackballDevice.h" - -#include - -bool initBoot(); - -using namespace tt::hal; - -static std::vector> createDevices() { - return { - createPower() - // std::make_shared(), - }; -} - -extern const Configuration hardwareConfiguration = { - .createDevices = createDevices -}; diff --git a/Devices/lilygo-tdeck/Source/devices/Power.cpp b/Devices/lilygo-tdeck/Source/devices/Power.cpp deleted file mode 100644 index 115a11c69..000000000 --- a/Devices/lilygo-tdeck/Source/devices/Power.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -std::shared_ptr createPower() { - ChargeFromAdcVoltage::Configuration configuration; - // 2.0 ratio, but +.11 added as display voltage sag compensation. - configuration.adcMultiplier = 2.11; - - return std::make_shared(configuration); -} diff --git a/Devices/lilygo-tdeck/Source/devices/Power.h b/Devices/lilygo-tdeck/Source/devices/Power.h deleted file mode 100644 index e2f20d5f6..000000000 --- a/Devices/lilygo-tdeck/Source/devices/Power.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -std::shared_ptr createPower(); diff --git a/Devices/lilygo-tdeck/Source/module.cpp b/Devices/lilygo-tdeck/Source/module.cpp index 5f0341f6a..5c239157c 100644 --- a/Devices/lilygo-tdeck/Source/module.cpp +++ b/Devices/lilygo-tdeck/Source/module.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,9 @@ constexpr auto* TAG = "T-Deck"; constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10; +// Legacy placeholder (required until legacy HAL is cleaned up everywhere) +extern const tt::hal::Configuration hardwareConfiguration = {}; + extern "C" { extern Driver tdeck_keyboard_driver; diff --git a/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp b/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp index a40c3b397..a41acc0b1 100644 --- a/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp +++ b/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp @@ -87,7 +87,7 @@ static const KeyboardApi tdeck_keyboard_api = { .read_key = tdeck_keyboard_read_key, }; -extern struct Module lilygo_tdeck_module; +extern Module lilygo_tdeck_module; Driver tdeck_keyboard_driver = { .name = "tdeck_keyboard", diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index b8f21e92a..e6abdf07c 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -1,26 +1,43 @@ /dts-v1/; +#include #include +#include #include -#include #include #include #include #include #include -#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 / { compatible = "root"; model = "LilyGO T-Deck"; + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channels = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2110>; + }; + wifi0 { compatible = "espressif,esp32-wifi-pinned"; status = "disabled"; diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index 548e94fc3..6d419cb1c 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -6,7 +6,7 @@ idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "include/" PRIV_INCLUDE_DIRS "private/" - REQUIRES TactilityKernel driver esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event + REQUIRES TactilityKernel driver esp_adc esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event ) idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc) diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-adc-oneshot.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-adc-oneshot.yaml new file mode 100644 index 000000000..cc35fedd0 --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-adc-oneshot.yaml @@ -0,0 +1,30 @@ +description: ESP32 ADC Controller (oneshot mode) + +include: ["adc-controller.yaml"] + +compatible: "espressif,esp32-adc-oneshot" + +properties: + unit-id: + type: int + required: true + description: | + The ADC unit, defined by adc_unit_t (e.g. ADC_UNIT_1, ADC_UNIT_2). + clk-src: + type: int + default: 0 + description: | + Clock source, defined by adc_oneshot_clk_src_t. 0 selects the default clock source. + ulp-mode: + type: int + default: 0 + description: | + ULP control mode, defined by adc_ulp_mode_t. 0 means ADC_ULP_MODE_DISABLE. + channels: + type: phandle-array + element-type: "struct Esp32AdcOneshotChannelConfig" + default: "{ }" + description: | + Per-channel configuration. Each entry is written as , + e.g. . + A consumer device refers to a channel with a phandle and channel number, e.g. `<&adc0 3>`. diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h new file mode 100644 index 000000000..3e2d13efe --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_adc_oneshot, struct Esp32AdcOneshotConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_adc_oneshot.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_adc_oneshot.h new file mode 100644 index 000000000..826a490a1 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_adc_oneshot.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32AdcOneshotChannelConfig { + adc_channel_t channel; + adc_atten_t atten; + adc_bitwidth_t bitwidth; +}; + +struct Esp32AdcOneshotConfig { + adc_unit_t unit_id; + adc_oneshot_clk_src_t clk_src; + adc_ulp_mode_t ulp_mode; + /** Per-channel configuration */ + const struct Esp32AdcOneshotChannelConfig* channels; + /** The item count of channels */ + size_t channel_count; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp b/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp new file mode 100644 index 000000000..1957142ca --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "esp32_adc_oneshot" + +#define GET_CONFIG(device) ((Esp32AdcOneshotConfig*)device->config) +#define GET_HANDLE(device) ((adc_oneshot_unit_handle_t)device_get_driver_data(device)) + +static const Esp32AdcOneshotChannelConfig* find_channel_config(const Esp32AdcOneshotConfig* dts_config, uint8_t channel_index) { + if (channel_index >= dts_config->channel_count) { + return nullptr; + } + return &dts_config->channels[channel_index]; +} + +extern "C" { + +static error_t read_raw(Device* device, uint8_t channel, int* out_raw, TickType_t timeout) { + if (xPortInIsrContext()) return ERROR_ISR_STATUS; + auto* dts_config = GET_CONFIG(device); + auto* channel_config = find_channel_config(dts_config, channel); + if (channel_config == nullptr) { + return ERROR_OUT_OF_RANGE; + } + + esp_err_t esp_error = adc_oneshot_read(GET_HANDLE(device), channel_config->channel, out_raw); + if (esp_error != ESP_OK) { + LOG_E(TAG, "read(channel=%u) failed: %s", channel, esp_err_to_name(esp_error)); + } + return esp_err_to_error(esp_error); +} + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* dts_config = GET_CONFIG(device); + + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = dts_config->unit_id, + .clk_src = dts_config->clk_src, + .ulp_mode = dts_config->ulp_mode, + }; + + adc_oneshot_unit_handle_t handle; + esp_err_t esp_error = adc_oneshot_new_unit(&init_config, &handle); + if (esp_error != ESP_OK) { + LOG_E(TAG, "Failed to create ADC unit %d: %s", (int)dts_config->unit_id, esp_err_to_name(esp_error)); + return ERROR_RESOURCE; + } + + for (size_t i = 0; i < dts_config->channel_count; i++) { + const auto& channel_config = dts_config->channels[i]; + adc_oneshot_chan_cfg_t chan_cfg = { + .atten = channel_config.atten, + .bitwidth = channel_config.bitwidth, + }; + esp_error = adc_oneshot_config_channel(handle, channel_config.channel, &chan_cfg); + if (esp_error != ESP_OK) { + LOG_E(TAG, "Failed to configure channel %d: %s", (int)channel_config.channel, esp_err_to_name(esp_error)); + adc_oneshot_del_unit(handle); + return ERROR_RESOURCE; + } + } + + device_set_driver_data(device, handle); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + LOG_I(TAG, "stop %s", device->name); + adc_oneshot_del_unit(GET_HANDLE(device)); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static constexpr AdcControllerApi ESP32_ADC_ONESHOT_API = { + .read_raw = read_raw +}; + +extern Module platform_esp32_module; + +Driver esp32_adc_oneshot_driver = { + .name = "esp32_adc_oneshot", + .compatible = (const char*[]) { "espressif,esp32-adc-oneshot", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &ESP32_ADC_ONESHOT_API, + .device_type = &ADC_CONTROLLER_TYPE, + .owner = &platform_esp32_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index 6809ad484..f4c3545ec 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -11,6 +11,7 @@ extern "C" { +extern Driver esp32_adc_oneshot_driver; extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2c_master_driver; @@ -43,6 +44,7 @@ extern Driver esp32_usbhost_msc_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(&esp32_adc_oneshot_driver) == ERROR_NONE); check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_master_driver) == ERROR_NONE); @@ -93,6 +95,7 @@ static error_t stop() { check(driver_remove_destruct(&esp32_ble_serial_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_bluetooth_driver) == ERROR_NONE); #endif + check(driver_remove_destruct(&esp32_adc_oneshot_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_master_driver) == ERROR_NONE); diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index cc0b1eb1e..66af4d9e9 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -231,7 +231,7 @@ static void registerInternalApps() { addAppManifest(app::gpssettings::manifest); } - if (hal::hasDevice(hal::Device::Type::Power)) { + if (device_exists_of_type(&POWER_SUPPLY_TYPE)) { addAppManifest(app::power::manifest); } diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 5269e2906..4ac68543f 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -90,10 +90,10 @@ static const char* getSdCardStatusIcon(bool mounted) { static const char* getPowerStatusIcon() { // TODO: Support multiple power devices? - std::shared_ptr power; - hal::findDevices(hal::Device::Type::Power, [&power](const auto& device) { - if (device->supportsMetric(hal::power::PowerDevice::MetricType::ChargeLevel)) { - power = device; + Device* power = nullptr; + device_for_each_of_type(&POWER_SUPPLY_TYPE, &power, [](Device* device, void* context) { + if (device_is_ready(device) && power_supply_supports_property(device, POWER_SUPPLY_PROP_CAPACITY)) { + *static_cast(context) = device; return false; } return true; @@ -103,12 +103,12 @@ static const char* getPowerStatusIcon() { return nullptr; } - hal::power::PowerDevice::MetricData charge_level; - if (!power->getMetric(hal::power::PowerDevice::MetricType::ChargeLevel, charge_level)) { + PowerSupplyPropertyValue charge_level; + if (power_supply_get_property(power, POWER_SUPPLY_PROP_CAPACITY, &charge_level) != ERROR_NONE) { return nullptr; } - uint8_t charge = charge_level.valueAsUint8; + int charge = charge_level.int_value; if (charge >= 95) { return LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_FULL; diff --git a/TactilityKernel/bindings/adc-controller.yaml b/TactilityKernel/bindings/adc-controller.yaml new file mode 100644 index 000000000..5c055e135 --- /dev/null +++ b/TactilityKernel/bindings/adc-controller.yaml @@ -0,0 +1 @@ +description: ADC controller diff --git a/TactilityKernel/bindings/battery-sense.yaml b/TactilityKernel/bindings/battery-sense.yaml new file mode 100644 index 000000000..0776e5885 --- /dev/null +++ b/TactilityKernel/bindings/battery-sense.yaml @@ -0,0 +1,19 @@ +description: Battery voltage sense via an ADC channel behind a voltage divider + +compatible: "battery-sense" + +properties: + io-channels: + type: phandles + required: true + description: The ADC channel connected to the battery sense circuit, e.g. `<&adc1 3>` + reference-voltage-mv: + type: int + required: true + description: The ADC's effective full-scale reference voltage at the channel's configured attenuation, in mV. + multiplier: + type: int + default: 1000 + description: | + Voltage divider correction factor, fixed-point with 3 decimals (1000 = 1.000, i.e. no division). + battery_mv = adc_mv * multiplier / 1000. diff --git a/TactilityKernel/include/tactility/bindings/battery_sense.h b/TactilityKernel/include/tactility/bindings/battery_sense.h new file mode 100644 index 000000000..438d20615 --- /dev/null +++ b/TactilityKernel/include/tactility/bindings/battery_sense.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(battery_sense, struct BatterySenseConfig) + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/adc.h b/TactilityKernel/include/tactility/drivers/adc.h new file mode 100644 index 000000000..f92b8ced4 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/adc.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct Device; + +#define ADC_CHANNEL_SPEC_NONE ((struct AdcChannelSpec) { NULL, 0 }) + +/** The index of a channel on an ADC controller */ +typedef uint8_t adc_channel_index_t; + +/** + * Specifies a channel on a specific ADC controller. + * Used by the devicetree, drivers and application code to refer to ADC channels. + */ +struct AdcChannelSpec { + /** ADC device controlling the channel */ + struct Device* adc_controller; + /** The channel's index on the device */ + adc_channel_index_t channel; +}; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/adc_controller.h b/TactilityKernel/include/tactility/drivers/adc_controller.h new file mode 100644 index 000000000..875ec2e47 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/adc_controller.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "adc.h" + +#include +#include + +struct Device; + +/** + * @brief API for ADC controller drivers. + */ +struct AdcControllerApi { + /** + * @brief Reads the raw conversion result of an ADC channel. + * @param[in] device the ADC controller device + * @param[in] channel the channel index to read + * @param[out] out_raw the raw conversion result + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the read operation was successful + * @retval ERROR_OUT_OF_RANGE when the channel index is not configured + * @retval ERROR_TIMEOUT when the operation timed out + */ + error_t (*read_raw)(struct Device* device, uint8_t channel, int* out_raw, TickType_t timeout); +}; + +/** + * @brief Reads the raw conversion result of an ADC channel using the specified controller. + * @param[in] device the ADC controller device + * @param[in] channel the channel index to read + * @param[out] out_raw the raw conversion result + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the read operation was successful + */ +error_t adc_controller_read_raw(struct Device* device, uint8_t channel, int* out_raw, TickType_t timeout); + +/** + * @brief Reads the raw conversion result of the ADC channel described by the given spec. + * @param[in] spec the channel spec, as acquired from a devicetree phandle reference (e.g. `<&adc0 3>`) + * @param[out] out_raw the raw conversion result + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the read operation was successful + */ +error_t adc_channel_read_raw(const struct AdcChannelSpec* spec, int* out_raw, TickType_t timeout); + +extern const struct DeviceType ADC_CONTROLLER_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/drivers/battery_sense.h b/TactilityKernel/include/tactility/drivers/battery_sense.h new file mode 100644 index 000000000..811fe9be7 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/battery_sense.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct BatterySenseConfig { + /** The ADC channel connected to the battery sense circuit */ + struct AdcChannelSpec io_channel; + /** The ADC's effective full-scale reference voltage at the channel's configured attenuation, in mV */ + uint32_t reference_voltage_mv; + /** Voltage divider correction factor, fixed-point with 3 decimals (1000 = 1.000, i.e. no division) */ + uint32_t multiplier; +}; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/drivers/adc_controller.cpp b/TactilityKernel/source/drivers/adc_controller.cpp new file mode 100644 index 000000000..e944abfa7 --- /dev/null +++ b/TactilityKernel/source/drivers/adc_controller.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define ADC_CONTROLLER_DRIVER_API(driver) ((struct AdcControllerApi*)driver->api) + +extern "C" { + +error_t adc_controller_read_raw(Device* device, uint8_t channel, int* out_raw, TickType_t timeout) { + const auto* driver = device_get_driver(device); + return ADC_CONTROLLER_DRIVER_API(driver)->read_raw(device, channel, out_raw, timeout); +} + +error_t adc_channel_read_raw(const struct AdcChannelSpec* spec, int* out_raw, TickType_t timeout) { + return adc_controller_read_raw(spec->adc_controller, spec->channel, out_raw, timeout); +} + +const struct DeviceType ADC_CONTROLLER_TYPE { + .name = "adc-controller" +}; + +} diff --git a/TactilityKernel/source/drivers/battery_sense.cpp b/TactilityKernel/source/drivers/battery_sense.cpp new file mode 100644 index 000000000..21b858e1d --- /dev/null +++ b/TactilityKernel/source/drivers/battery_sense.cpp @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include + +#include + +// Raw-to-millivolt conversion assumes a 12-bit ADC, since the generic ADC API doesn't expose resolution. +#define ADC_MAX_RAW 4095 + +// The power-supply child's config pointer isn't set; it reads its settings from its parent's config instead. +#define GET_PARENT_CONFIG(device) ((const BatterySenseConfig*)device_get_parent(device)->config) + +extern "C" { + +static bool supports_property(Device*, PowerSupplyProperty property) { + return property == POWER_SUPPLY_PROP_VOLTAGE; +} + +static error_t get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_VOLTAGE) { + return ERROR_NOT_SUPPORTED; + } + + const auto* config = GET_PARENT_CONFIG(device); + int raw; + error_t error = adc_channel_read_raw(&config->io_channel, &raw, portMAX_DELAY); + if (error != ERROR_NONE) { + return error; + } + + int64_t adc_mv = ((int64_t)raw * config->reference_voltage_mv) / ADC_MAX_RAW; + out_value->int_value = (int)((adc_mv * config->multiplier) / 1000); + return ERROR_NONE; +} + +static bool supports_charge_control(Device*) { return false; } +static bool is_allowed_to_charge(Device*) { return false; } +static error_t set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool supports_quick_charge(Device*) { return false; } +static bool is_quick_charge_enabled(Device*) { return false; } +static error_t set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool supports_power_off(Device*) { return false; } +static error_t power_off(Device*) { return ERROR_NOT_SUPPORTED; } + +static constexpr PowerSupplyApi BATTERY_SENSE_POWER_SUPPLY_API = { + .supports_property = supports_property, + .get_property = get_property, + .supports_charge_control = supports_charge_control, + .is_allowed_to_charge = is_allowed_to_charge, + .set_allowed_to_charge = set_allowed_to_charge, + .supports_quick_charge = supports_quick_charge, + .is_quick_charge_enabled = is_quick_charge_enabled, + .set_quick_charge_enabled = set_quick_charge_enabled, + .supports_power_off = supports_power_off, + .power_off = power_off, +}; + +// Registered (driver_construct_add() in kernel_init.cpp) so driver_bind() has a valid ->internal, +// but never matched against a devicetree node: battery_sense_driver wires it up directly by pointer. +Driver battery_sense_power_supply_driver = { + .name = "battery-sense-power-supply", + .compatible = (const char*[]) { "battery-sense-power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = &BATTERY_SENSE_POWER_SUPPLY_API, + .device_type = &POWER_SUPPLY_TYPE, + .owner = nullptr, + .internal = nullptr +}; + +struct BatterySenseInternal { + Device* power_supply_device = nullptr; +}; + +static error_t create_power_supply_child(Device* parent, Device*& out_child) { + auto* child = new(std::nothrow) Device { .address = 0, .name = "battery-sense-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr }; + if (child == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = device_construct(child); + if (error != ERROR_NONE) { + delete child; + return error; + } + + device_set_parent(child, parent); + device_set_driver(child, &battery_sense_power_supply_driver); + + error = device_add(child); + if (error != ERROR_NONE) { + device_destruct(child); + delete child; + return error; + } + + error = device_start(child); + if (error != ERROR_NONE) { + device_remove(child); + device_destruct(child); + delete child; + return error; + } + + out_child = child; + return ERROR_NONE; +} + +static void destroy_power_supply_child(Device* child) { + check(device_stop(child) == ERROR_NONE); + check(device_remove(child) == ERROR_NONE); + check(device_destruct(child) == ERROR_NONE); + delete child; +} + +static error_t start(Device* device) { + auto* internal = new(std::nothrow) BatterySenseInternal(); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = create_power_supply_child(device, internal->power_supply_device); + if (error != ERROR_NONE) { + delete internal; + return error; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = (BatterySenseInternal*)device_get_driver_data(device); + destroy_power_supply_child(internal->power_supply_device); + device_set_driver_data(device, nullptr); + delete internal; + return ERROR_NONE; +} + +Driver battery_sense_driver = { + .name = "battery-sense", + .compatible = (const char*[]) { "battery-sense", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = nullptr, + .internal = nullptr +}; + +} diff --git a/TactilityKernel/source/kernel_init.cpp b/TactilityKernel/source/kernel_init.cpp index bd1e10619..f6fc5148e 100644 --- a/TactilityKernel/source/kernel_init.cpp +++ b/TactilityKernel/source/kernel_init.cpp @@ -20,6 +20,10 @@ static error_t start() { 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; + extern Driver battery_sense_driver; + if (driver_construct_add(&battery_sense_driver) != ERROR_NONE) return ERROR_RESOURCE; + extern Driver battery_sense_power_supply_driver; + if (driver_construct_add(&battery_sense_power_supply_driver) != ERROR_NONE) return ERROR_RESOURCE; return ERROR_NONE; } From 24b12d763d55a91a075181f6930b3b97bc1912df Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 02:15:46 +0200 Subject: [PATCH 04/16] Update launcher for power kernel driver --- Tactility/Source/app/launcher/Launcher.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index ee510b033..425496339 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -4,13 +4,14 @@ #include #include #include -#include #include #include #include #include +#include +#include #include #include #include @@ -73,9 +74,9 @@ class LauncherApp final : public App { static bool shouldShowPowerButton() { bool show_power_button = false; - hal::findDevices(hal::Device::Type::Power, [&show_power_button](const auto& device) { - if (device->supportsPowerOff()) { - show_power_button = true; + device_for_each_of_type(&POWER_SUPPLY_TYPE, &show_power_button, [](Device* device, void* context) { + if (device_is_ready(device) && power_supply_supports_power_off(device)) { + *static_cast(context) = true; return false; // stop iterating } else { return true; // continue iterating From fcfaffd0df1ef91f1ceeca528066c3edabda1c42 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 02:20:41 +0200 Subject: [PATCH 05/16] Add estimated power percentage --- .../source/drivers/battery_sense.cpp | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/TactilityKernel/source/drivers/battery_sense.cpp b/TactilityKernel/source/drivers/battery_sense.cpp index 21b858e1d..67f2813ca 100644 --- a/TactilityKernel/source/drivers/battery_sense.cpp +++ b/TactilityKernel/source/drivers/battery_sense.cpp @@ -11,20 +11,21 @@ // Raw-to-millivolt conversion assumes a 12-bit ADC, since the generic ADC API doesn't expose resolution. #define ADC_MAX_RAW 4095 +// Rough LiPo discharge curve, used to estimate a charge percentage from the sensed voltage since +// this driver has no calibrated fuel gauge to read one from directly. +#define BATTERY_MIN_MV 3200 +#define BATTERY_MAX_MV 4200 + // The power-supply child's config pointer isn't set; it reads its settings from its parent's config instead. #define GET_PARENT_CONFIG(device) ((const BatterySenseConfig*)device_get_parent(device)->config) extern "C" { static bool supports_property(Device*, PowerSupplyProperty property) { - return property == POWER_SUPPLY_PROP_VOLTAGE; + return property == POWER_SUPPLY_PROP_VOLTAGE || property == POWER_SUPPLY_PROP_CAPACITY; } -static error_t get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { - if (property != POWER_SUPPLY_PROP_VOLTAGE) { - return ERROR_NOT_SUPPORTED; - } - +static error_t read_battery_mv(Device* device, int* out_mv) { const auto* config = GET_PARENT_CONFIG(device); int raw; error_t error = adc_channel_read_raw(&config->io_channel, &raw, portMAX_DELAY); @@ -33,7 +34,28 @@ static error_t get_property(Device* device, PowerSupplyProperty property, PowerS } int64_t adc_mv = ((int64_t)raw * config->reference_voltage_mv) / ADC_MAX_RAW; - out_value->int_value = (int)((adc_mv * config->multiplier) / 1000); + *out_mv = (int)((adc_mv * config->multiplier) / 1000); + return ERROR_NONE; +} + +static int estimate_capacity_from_mv(int battery_mv) { + if (battery_mv <= BATTERY_MIN_MV) return 0; + if (battery_mv >= BATTERY_MAX_MV) return 100; + return (battery_mv - BATTERY_MIN_MV) * 100 / (BATTERY_MAX_MV - BATTERY_MIN_MV); +} + +static error_t get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_VOLTAGE && property != POWER_SUPPLY_PROP_CAPACITY) { + return ERROR_NOT_SUPPORTED; + } + + int battery_mv; + error_t error = read_battery_mv(device, &battery_mv); + if (error != ERROR_NONE) { + return error; + } + + out_value->int_value = (property == POWER_SUPPLY_PROP_VOLTAGE) ? battery_mv : estimate_capacity_from_mv(battery_mv); return ERROR_NONE; } From 8e5d904297ca70feb21f1d4b45e8e3cf78aeda58 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 14:41:37 +0200 Subject: [PATCH 06/16] Trackball kernel driver --- .../Source/Trackball/Trackball.cpp | 324 ++++-------------- .../lilygo-tdeck/Source/Trackball/Trackball.h | 21 +- .../Source/bindings/tdeck_trackball.h | 7 + .../Source/devices/TrackballDevice.cpp | 57 --- .../Source/devices/TrackballDevice.h | 21 -- .../Source/drivers/tdeck_trackball.h | 62 ++++ Devices/lilygo-tdeck/Source/module.cpp | 36 +- .../lilygo-tdeck/Source/tdeck_trackball.cpp | 197 +++++++++++ .../bindings/lilygo,tdeck-trackball.yaml | 25 ++ Devices/lilygo-tdeck/lilygo,tdeck.dts | 10 + 10 files changed, 393 insertions(+), 367 deletions(-) create mode 100644 Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h delete mode 100644 Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp delete mode 100644 Devices/lilygo-tdeck/Source/devices/TrackballDevice.h create mode 100644 Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h create mode 100644 Devices/lilygo-tdeck/Source/tdeck_trackball.cpp create mode 100644 Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml diff --git a/Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp b/Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp index 98c76b683..bd07a096c 100644 --- a/Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp +++ b/Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp @@ -1,129 +1,67 @@ #include "Trackball.h" #include -#include +#include #include +#include + constexpr auto* TAG = "Trackball"; namespace trackball { -static TrackballConfig g_config; static lv_indev_t* g_indev = nullptr; -static std::atomic g_initialized{false}; -static std::atomic g_enabled{true}; -static std::atomic g_mode{Mode::Encoder}; - -// Interrupt-driven position tracking (atomic for ISR safety) -static std::atomic g_cursorX{160}; -static std::atomic g_cursorY{120}; -static std::atomic g_buttonPressed{false}; +static Device* g_device = nullptr; +static bool g_enabled = true; +static Mode g_mode = Mode::Encoder; +static uint8_t g_encoderSensitivity = 1; +static uint8_t g_pointerSensitivity = 10; -// Encoder mode: accumulated diff since last read -static std::atomic g_encoderDiff{0}; +// Pointer mode cursor position (screen-relative) +static int32_t g_cursorX = 160; +static int32_t g_cursorY = 120; -// Sensitivity cached for ISR access (atomic for thread safety) -static std::atomic g_encoderSensitivity{1}; // Steps per tick for encoder -static std::atomic g_pointerSensitivity{10}; // Pixels per tick for pointer - -// Cursor object for pointer mode static lv_obj_t* g_cursor = nullptr; // Screen dimensions (T-Deck: 320x240) static constexpr int32_t SCREEN_WIDTH = 320; static constexpr int32_t SCREEN_HEIGHT = 240; - static constexpr int32_t CURSOR_SIZE = 16; -// ISR handler for trackball directions -static void IRAM_ATTR trackball_isr_handler(void* arg) { - // Skip accumulating movement when disabled - if (!g_enabled.load(std::memory_order_relaxed)) { - return; - } - - gpio_num_t pin = static_cast(reinterpret_cast(arg)); - - if (g_mode.load(std::memory_order_relaxed) == Mode::Pointer) { - // Pointer mode: update absolute position using atomic fetch_add/sub - // Clamping is done in read_cb to avoid race conditions - int32_t step = g_pointerSensitivity.load(std::memory_order_relaxed); - if (pin == g_config.pinRight) { - g_cursorX.fetch_add(step, std::memory_order_relaxed); - } else if (pin == g_config.pinLeft) { - g_cursorX.fetch_sub(step, std::memory_order_relaxed); - } else if (pin == g_config.pinUp) { - g_cursorY.fetch_sub(step, std::memory_order_relaxed); - } else if (pin == g_config.pinDown) { - g_cursorY.fetch_add(step, std::memory_order_relaxed); - } - } else { - // Encoder mode: accumulate diff - int32_t step = g_encoderSensitivity.load(std::memory_order_relaxed); - if (pin == g_config.pinRight || pin == g_config.pinDown) { - g_encoderDiff.fetch_add(step, std::memory_order_relaxed); - } else if (pin == g_config.pinLeft || pin == g_config.pinUp) { - g_encoderDiff.fetch_sub(step, std::memory_order_relaxed); - } - } -} - -// ISR handler for button (any edge) -static void IRAM_ATTR button_isr_handler(void* arg) { - // Read current button state (active low) - bool pressed = gpio_get_level(g_config.pinClick) == 0; - g_buttonPressed.store(pressed, std::memory_order_relaxed); -} - -// Helper to clamp value to range static inline int32_t clamp(int32_t val, int32_t minVal, int32_t maxVal) { if (val < minVal) return minVal; if (val > maxVal) return maxVal; return val; } -static void read_cb(lv_indev_t* indev, lv_indev_data_t* data) { - Mode currentMode = g_mode.load(std::memory_order_relaxed); - - if (!g_initialized.load(std::memory_order_relaxed) || !g_enabled.load(std::memory_order_relaxed)) { - data->state = LV_INDEV_STATE_RELEASED; - if (currentMode == Mode::Encoder) { - data->enc_diff = 0; - } else { - // Clamp cursor position to screen bounds - int32_t x = clamp(g_cursorX.load(std::memory_order_relaxed), 0, SCREEN_WIDTH - CURSOR_SIZE - 1); - int32_t y = clamp(g_cursorY.load(std::memory_order_relaxed), 0, SCREEN_HEIGHT - CURSOR_SIZE - 1); - g_cursorX.store(x, std::memory_order_relaxed); - g_cursorY.store(y, std::memory_order_relaxed); - data->point.x = static_cast(x); - data->point.y = static_cast(y); - } - return; - } - - if (currentMode == Mode::Encoder) { - // Read and reset accumulated encoder diff - int32_t diff = g_encoderDiff.exchange(0); - data->enc_diff = static_cast(clamp(diff, INT16_MIN, INT16_MAX)); - - if (diff != 0) { +// Note: must be called from the LVGL thread (main thread), same as the setters below. +static void read_cb(lv_indev_t* /*indev*/, lv_indev_data_t* data) { + // Always drain accumulated movement so it doesn't jump on re-enable, but discard it while disabled. + int32_t dx = 0; + int32_t dy = 0; + tdeck_trackball_read_delta(g_device, &dx, &dy); + if (!g_enabled) { + dx = 0; + dy = 0; + } + + if (g_mode == Mode::Encoder) { + int32_t ticks = (dx + dy) * static_cast(g_encoderSensitivity); + data->enc_diff = static_cast(clamp(ticks, INT16_MIN, INT16_MAX)); + if (ticks != 0) { lv_display_trigger_activity(nullptr); } } else { - // Pointer mode: read and clamp cursor position - int32_t x = clamp(g_cursorX.load(std::memory_order_relaxed), 0, SCREEN_WIDTH - CURSOR_SIZE - 1); - int32_t y = clamp(g_cursorY.load(std::memory_order_relaxed), 0, SCREEN_HEIGHT - CURSOR_SIZE - 1); - - // Store clamped values back to prevent unbounded growth - g_cursorX.store(x, std::memory_order_relaxed); - g_cursorY.store(y, std::memory_order_relaxed); - - data->point.x = static_cast(x); - data->point.y = static_cast(y); + g_cursorX = clamp(g_cursorX + dx * static_cast(g_pointerSensitivity), 0, SCREEN_WIDTH - CURSOR_SIZE - 1); + g_cursorY = clamp(g_cursorY + dy * static_cast(g_pointerSensitivity), 0, SCREEN_HEIGHT - CURSOR_SIZE - 1); + data->point.x = static_cast(g_cursorX); + data->point.y = static_cast(g_cursorY); } - // Button state (same for both modes) - bool pressed = g_buttonPressed.load(std::memory_order_relaxed); + bool pressed = false; + if (g_enabled) { + tdeck_trackball_get_button_pressed(g_device, &pressed); + } data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; if (pressed) { @@ -131,136 +69,31 @@ static void read_cb(lv_indev_t* indev, lv_indev_data_t* data) { } } -lv_indev_t* init(const TrackballConfig& config) { - if (g_initialized.load(std::memory_order_relaxed)) { +lv_indev_t* init() { + if (g_indev != nullptr) { LOG_W(TAG, "Already initialized"); return g_indev; } - g_config = config; - - // Set default sensitivities if not specified - if (g_config.encoderSensitivity == 0) { - g_config.encoderSensitivity = 1; - } - if (g_config.pointerSensitivity == 0) { - g_config.pointerSensitivity = 10; - } - g_encoderSensitivity.store(g_config.encoderSensitivity, std::memory_order_relaxed); - g_pointerSensitivity.store(g_config.pointerSensitivity, std::memory_order_relaxed); - - // Initialize cursor position to center - g_cursorX.store(SCREEN_WIDTH / 2, std::memory_order_relaxed); - g_cursorY.store(SCREEN_HEIGHT / 2, std::memory_order_relaxed); - g_encoderDiff.store(0, std::memory_order_relaxed); - g_buttonPressed.store(false, std::memory_order_relaxed); - - // Configure direction pins as interrupt inputs (falling edge) - const gpio_num_t dirPins[4] = { - config.pinRight, - config.pinUp, - config.pinLeft, - config.pinDown - }; - - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_NEGEDGE; // Falling edge (active low) - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - - // Install GPIO ISR service (if not already installed) - static bool isr_service_installed = false; - if (!isr_service_installed) { - esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); - if (err == ESP_OK || err == ESP_ERR_INVALID_STATE) { - // ESP_ERR_INVALID_STATE means already installed, which is fine - isr_service_installed = true; - } else { - LOG_E(TAG, "Failed to install GPIO ISR service: %s", esp_err_to_name(err)); - return nullptr; - } - } - - // Track added handlers for cleanup on failure - int handlersAdded = 0; - - // Configure and attach ISR for direction pins - for (int i = 0; i < 4; i++) { - io_conf.pin_bit_mask = (1ULL << dirPins[i]); - esp_err_t err = gpio_config(&io_conf); - if (err != ESP_OK) { - LOG_E(TAG, "Failed to configure GPIO %d: %s", static_cast(dirPins[i]), esp_err_to_name(err)); - // Cleanup previously added handlers - for (int j = 0; j < handlersAdded; j++) { - gpio_isr_handler_remove(dirPins[j]); - } - return nullptr; - } - - err = gpio_isr_handler_add(dirPins[i], trackball_isr_handler, reinterpret_cast(static_cast(dirPins[i]))); - if (err != ESP_OK) { - LOG_E(TAG, "Failed to add ISR for GPIO %d: %s", static_cast(dirPins[i]), esp_err_to_name(err)); - // Cleanup previously added handlers - for (int j = 0; j < handlersAdded; j++) { - gpio_isr_handler_remove(dirPins[j]); - } - return nullptr; - } - handlersAdded++; - } - - // Configure button pin (any edge for press/release detection) - io_conf.intr_type = GPIO_INTR_ANYEDGE; - io_conf.pin_bit_mask = (1ULL << config.pinClick); - esp_err_t err = gpio_config(&io_conf); - if (err != ESP_OK) { - LOG_E(TAG, "Failed to configure button GPIO %d: %s", static_cast(config.pinClick), esp_err_to_name(err)); - // Cleanup direction handlers - for (int i = 0; i < 4; i++) { - gpio_isr_handler_remove(dirPins[i]); - } - return nullptr; - } - - err = gpio_isr_handler_add(config.pinClick, button_isr_handler, nullptr); - if (err != ESP_OK) { - LOG_E(TAG, "Failed to add button ISR: %s", esp_err_to_name(err)); - // Cleanup direction handlers - for (int i = 0; i < 4; i++) { - gpio_isr_handler_remove(dirPins[i]); - } + g_device = device_find_first_active_by_type(&TDECK_TRACKBALL_TYPE); + if (g_device == nullptr) { + LOG_E(TAG, "tdeck_trackball kernel device not found or not started"); return nullptr; } - // Read initial button state - g_buttonPressed.store(gpio_get_level(config.pinClick) == 0); + g_cursorX = SCREEN_WIDTH / 2; + g_cursorY = SCREEN_HEIGHT / 2; - // Register as LVGL encoder input device for group navigation (default mode) g_indev = lv_indev_create(); if (g_indev == nullptr) { LOG_E(TAG, "Failed to register LVGL input device"); - // Cleanup ISR handlers on failure - const gpio_num_t pins[5] = { - config.pinRight, config.pinUp, config.pinLeft, - config.pinDown, config.pinClick - }; - for (int i = 0; i < 5; i++) { - gpio_intr_disable(pins[i]); - gpio_isr_handler_remove(pins[i]); - } + g_device = nullptr; return nullptr; } lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER); lv_indev_set_read_cb(g_indev, read_cb); - g_initialized.store(true, std::memory_order_relaxed); - LOG_I(TAG, "Initialized with interrupts (R:%d U:%d L:%d D:%d Click:%d)", - static_cast(config.pinRight), - static_cast(config.pinUp), - static_cast(config.pinLeft), - static_cast(config.pinDown), - static_cast(config.pinClick)); + LOG_I(TAG, "Initialized"); return g_indev; } @@ -272,8 +105,6 @@ static void createCursor() { g_cursor = lv_image_create(lv_layer_sys()); if (g_cursor != nullptr) { lv_obj_remove_flag(g_cursor, LV_OBJ_FLAG_CLICKABLE); - - // Set cursor image lv_image_set_src(g_cursor, TT_ASSETS_UI_CURSOR); lv_indev_set_cursor(g_indev, g_cursor); LOG_D(TAG, "Cursor created"); @@ -291,67 +122,41 @@ static void destroyCursor() { } void deinit() { - if (!g_initialized.load(std::memory_order_relaxed)) return; + if (g_indev == nullptr) return; destroyCursor(); - // Disable interrupts and remove ISR handlers - const gpio_num_t pins[5] = { - g_config.pinRight, - g_config.pinUp, - g_config.pinLeft, - g_config.pinDown, - g_config.pinClick - }; - - for (int i = 0; i < 5; i++) { - gpio_intr_disable(pins[i]); - gpio_isr_handler_remove(pins[i]); - } + lv_indev_delete(g_indev); + g_indev = nullptr; + g_device = nullptr; - if (g_indev) { - lv_indev_delete(g_indev); - g_indev = nullptr; - } - - g_initialized.store(false, std::memory_order_relaxed); - g_mode.store(Mode::Encoder, std::memory_order_relaxed); - g_enabled.store(true, std::memory_order_relaxed); + g_mode = Mode::Encoder; + g_enabled = true; LOG_I(TAG, "Deinitialized"); } void setEncoderSensitivity(uint8_t sensitivity) { if (sensitivity > 0) { - // Only update the atomic - ISR reads from atomic, not g_config - g_encoderSensitivity.store(sensitivity, std::memory_order_relaxed); + g_encoderSensitivity = sensitivity; LOG_D(TAG, "Encoder sensitivity set to %d", sensitivity); } } void setPointerSensitivity(uint8_t sensitivity) { if (sensitivity > 0) { - // Only update the atomic - ISR reads from atomic, not g_config - g_pointerSensitivity.store(sensitivity, std::memory_order_relaxed); + g_pointerSensitivity = sensitivity; LOG_D(TAG, "Pointer sensitivity set to %d", sensitivity); } } void setEnabled(bool enabled) { - g_enabled.store(enabled, std::memory_order_relaxed); - - if (!enabled) { - // Clear accumulated state to prevent jumps on re-enable - g_encoderDiff.store(0, std::memory_order_relaxed); - } + g_enabled = enabled; - // Hide/show cursor based on enabled state when in pointer mode - // Note: Must be called from LVGL thread (main thread) for thread safety - lv_obj_t* cursor = g_cursor; // Local copy to avoid race with setMode - if (cursor != nullptr) { + if (g_cursor != nullptr) { if (enabled) { - lv_obj_clear_flag(cursor, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(g_cursor, LV_OBJ_FLAG_HIDDEN); } else { - lv_obj_add_flag(cursor, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN); } } @@ -359,40 +164,35 @@ void setEnabled(bool enabled) { } void setMode(Mode mode) { - // Note: Must be called from LVGL thread (main thread) for thread safety - if (!g_initialized.load(std::memory_order_relaxed) || g_indev == nullptr) { + if (g_indev == nullptr) { LOG_W(TAG, "Cannot set mode - not initialized"); return; } - if (g_mode.load(std::memory_order_relaxed) == mode) { + if (g_mode == mode) { return; } - g_mode.store(mode, std::memory_order_relaxed); + g_mode = mode; if (mode == Mode::Pointer) { - // Switch to pointer mode lv_indev_set_type(g_indev, LV_INDEV_TYPE_POINTER); createCursor(); - if (!g_enabled.load(std::memory_order_relaxed) && g_cursor != nullptr) { + if (!g_enabled && g_cursor != nullptr) { lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN); } - // Reset cursor to center when switching modes - g_cursorX.store(SCREEN_WIDTH / 2, std::memory_order_relaxed); - g_cursorY.store(SCREEN_HEIGHT / 2, std::memory_order_relaxed); + g_cursorX = SCREEN_WIDTH / 2; + g_cursorY = SCREEN_HEIGHT / 2; LOG_I(TAG, "Switched to Pointer mode"); } else { - // Switch to encoder mode destroyCursor(); lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER); - g_encoderDiff.store(0, std::memory_order_relaxed); // Reset encoder diff LOG_I(TAG, "Switched to Encoder mode"); } } Mode getMode() { - return g_mode.load(std::memory_order_relaxed); + return g_mode; } } diff --git a/Devices/lilygo-tdeck/Source/Trackball/Trackball.h b/Devices/lilygo-tdeck/Source/Trackball/Trackball.h index 4f67c9f87..500e21b72 100644 --- a/Devices/lilygo-tdeck/Source/Trackball/Trackball.h +++ b/Devices/lilygo-tdeck/Source/Trackball/Trackball.h @@ -1,6 +1,5 @@ #pragma once -#include #include namespace trackball { @@ -14,24 +13,10 @@ enum class Mode { }; /** - * @brief Trackball configuration structure + * @brief Initialize trackball as an LVGL input device, backed by the kernel tdeck_trackball driver. + * @return LVGL input device pointer, or nullptr if the kernel device isn't found/started */ -struct TrackballConfig { - gpio_num_t pinRight; // Right direction GPIO - gpio_num_t pinUp; // Up direction GPIO - gpio_num_t pinLeft; // Left direction GPIO - gpio_num_t pinDown; // Down direction GPIO - gpio_num_t pinClick; // Click/select button GPIO - uint8_t encoderSensitivity = 1; // Encoder mode: steps per tick - uint8_t pointerSensitivity = 10; // Pointer mode: pixels per tick -}; - -/** - * @brief Initialize trackball as LVGL input device - * @param config Trackball GPIO configuration - * @return LVGL input device pointer, or nullptr on failure - */ -lv_indev_t* init(const TrackballConfig& config); +lv_indev_t* init(); /** * @brief Deinitialize trackball diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h b/Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h new file mode 100644 index 000000000..2eb630b40 --- /dev/null +++ b/Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(tdeck_trackball, struct TdeckTrackballConfig) diff --git a/Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp b/Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp deleted file mode 100644 index 11c8d5459..000000000 --- a/Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "TrackballDevice.h" -#include // Driver -#include -#include -#include - -constexpr auto* TAG = "TrackballDevice"; - -bool TrackballDevice::start() { - if (initialized) { - return true; - } - - // T-Deck trackball GPIO configuration from LilyGo reference - trackball::TrackballConfig config = { - .pinRight = GPIO_NUM_2, // BOARD_TBOX_G02 - .pinUp = GPIO_NUM_3, // BOARD_TBOX_G01 - .pinLeft = GPIO_NUM_1, // BOARD_TBOX_G04 - .pinDown = GPIO_NUM_15, // BOARD_TBOX_G03 - .pinClick = GPIO_NUM_0, // BOARD_BOOT_PIN - .encoderSensitivity = 1, // 1 step per tick for menu navigation - .pointerSensitivity = 10 // 10 pixels per tick for cursor movement - }; - - indev = trackball::init(config); - if (indev == nullptr) { - return false; - } - - initialized = true; - - // Apply persisted trackball settings (requires LVGL lock for cursor manipulation) - auto tbSettings = tt::settings::trackball::loadOrGetDefault(); - if (tt::lvgl::lock(100)) { - trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer - ? trackball::Mode::Pointer - : trackball::Mode::Encoder); - trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); - trackball::setPointerSensitivity(tbSettings.pointerSensitivity); - trackball::setEnabled(tbSettings.trackballEnabled); - tt::lvgl::unlock(); - } else { - LOG_W(TAG, "Failed to acquire LVGL lock for trackball settings"); - } - - return true; -} - -bool TrackballDevice::stop() { - if (initialized) { - // LVGL will handle indev cleanup - trackball::deinit(); - indev = nullptr; - initialized = false; - } - return true; -} diff --git a/Devices/lilygo-tdeck/Source/devices/TrackballDevice.h b/Devices/lilygo-tdeck/Source/devices/TrackballDevice.h deleted file mode 100644 index 7a2492f73..000000000 --- a/Devices/lilygo-tdeck/Source/devices/TrackballDevice.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include - -class TrackballDevice : public tt::hal::Device { -public: - tt::hal::Device::Type getType() const override { return tt::hal::Device::Type::Other; } - std::string getName() const override { return "Trackball"; } - std::string getDescription() const override { return "5-way GPIO trackball navigation"; } - - bool start(); - bool stop(); - bool isAttached() const { return initialized; } - - lv_indev_t* getLvglIndev() const { return indev; } - -private: - lv_indev_t* indev = nullptr; - bool initialized = false; -}; diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h b/Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h new file mode 100644 index 000000000..f87b22dbb --- /dev/null +++ b/Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +struct Device; +struct DeviceType; + +struct TdeckTrackballConfig { + struct GpioPinSpec pin_right; + struct GpioPinSpec pin_up; + struct GpioPinSpec pin_left; + struct GpioPinSpec pin_down; + struct GpioPinSpec pin_click; +}; + +/** + * @brief API for the T-Deck 5-way trackball driver. + * Reports raw, unscaled movement: sensitivity and mode (encoder vs. pointer) are UI concerns + * layered on top by the consumer, not something this driver knows about. + */ +struct TdeckTrackballApi { + /** + * @brief Reads the accumulated movement since the last read, then resets it to zero. + * @param[in] device the trackball device + * @param[out] out_dx horizontal movement (right positive), in raw pulses + * @param[out] out_dy vertical movement (down positive), in raw pulses + * @retval ERROR_NONE when the operation was successful + */ + error_t (*read_delta)(struct Device* device, int32_t* out_dx, int32_t* out_dy); + + /** + * @brief Gets whether the click button is currently pressed. + * @param[in] device the trackball device + * @param[out] out_pressed true when pressed + * @retval ERROR_NONE when the operation was successful + */ + error_t (*get_button_pressed)(struct Device* device, bool* out_pressed); +}; + +/** + * @brief Reads the accumulated movement using the specified trackball device. + */ +error_t tdeck_trackball_read_delta(struct Device* device, int32_t* out_dx, int32_t* out_dy); + +/** + * @brief Gets whether the click button is currently pressed on the specified trackball device. + */ +error_t tdeck_trackball_get_button_pressed(struct Device* device, bool* out_pressed); + +extern const struct DeviceType TDECK_TRACKBALL_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/Devices/lilygo-tdeck/Source/module.cpp b/Devices/lilygo-tdeck/Source/module.cpp index 5c239157c..8f2741cf2 100644 --- a/Devices/lilygo-tdeck/Source/module.cpp +++ b/Devices/lilygo-tdeck/Source/module.cpp @@ -9,9 +9,11 @@ #include #include #include +#include #include +#include -#include "devices/TrackballDevice.h" +#include #include @@ -26,6 +28,7 @@ extern "C" { extern Driver tdeck_keyboard_driver; extern Driver tdeck_keyboard_backlight_driver; +extern Driver tdeck_trackball_driver; static bool power_on() { gpio_config_t device_power_signal_config = { @@ -66,16 +69,22 @@ void subscribe_events() { } }); + // The kernel trackball device is already started by kernel_init(); this just registers it as an + // LVGL input device and applies persisted settings, both of which require LVGL to be up first. 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()); + auto tbSettings = tt::settings::trackball::loadOrGetDefault(); + if (tt::lvgl::lock(100)) { + if (trackball::init() != nullptr) { + trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer + ? trackball::Mode::Pointer + : trackball::Mode::Encoder); + trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); + trackball::setPointerSensitivity(tbSettings.pointerSensitivity); + trackball::setEnabled(tbSettings.trackballEnabled); } + tt::lvgl::unlock(); + } else { + LOG_W(TAG, "Failed to acquire LVGL lock for trackball settings"); } }); } @@ -97,6 +106,11 @@ static error_t start() { return ERROR_RESOURCE; } + if (driver_construct_add(&tdeck_trackball_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to register trackball driver"); + return ERROR_RESOURCE; + } + subscribe_events(); return ERROR_NONE; @@ -112,6 +126,10 @@ static error_t stop() { LOG_E(TAG, "Failed to unregister keyboard backlight driver"); return ERROR_RESOURCE; } + if (driver_remove_destruct(&tdeck_trackball_driver) != ERROR_NONE) { + LOG_E(TAG, "Failed to unregister trackball driver"); + return ERROR_RESOURCE; + } return ERROR_NONE; } diff --git a/Devices/lilygo-tdeck/Source/tdeck_trackball.cpp b/Devices/lilygo-tdeck/Source/tdeck_trackball.cpp new file mode 100644 index 000000000..ec42f240d --- /dev/null +++ b/Devices/lilygo-tdeck/Source/tdeck_trackball.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "tdeck_trackball" +#define GET_CONFIG(device) (static_cast((device)->config)) +#define GET_INTERNAL(device) (static_cast(device_get_driver_data(device))) + +struct TdeckTrackballInternal { + GpioDescriptor* pin_right = nullptr; + GpioDescriptor* pin_up = nullptr; + GpioDescriptor* pin_left = nullptr; + GpioDescriptor* pin_down = nullptr; + GpioDescriptor* pin_click = nullptr; + + std::atomic dx {0}; + std::atomic dy {0}; + std::atomic button_pressed {false}; +}; + +// region ISR callbacks + +static void on_right(void* arg) { + static_cast(arg)->dx.fetch_add(1, std::memory_order_relaxed); +} + +static void on_left(void* arg) { + static_cast(arg)->dx.fetch_sub(1, std::memory_order_relaxed); +} + +static void on_down(void* arg) { + static_cast(arg)->dy.fetch_add(1, std::memory_order_relaxed); +} + +static void on_up(void* arg) { + static_cast(arg)->dy.fetch_sub(1, std::memory_order_relaxed); +} + +static void on_click(void* arg) { + auto* internal = static_cast(arg); + bool high = true; + gpio_descriptor_get_level(internal->pin_click, &high); + // Active low: pressed when level is low + internal->button_pressed.store(!high, std::memory_order_relaxed); +} + +// endregion + +// region Pin acquisition + +static error_t acquire_pin(const GpioPinSpec& spec, GpioInterruptType interrupt_type, void (*callback)(void*), void* arg, GpioDescriptor** out_descriptor) { + auto* descriptor = gpio_descriptor_acquire(spec.gpio_controller, spec.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr) { + return ERROR_RESOURCE; + } + + gpio_flags_t flags = GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP; + flags = GPIO_FLAG_INTERRUPT_TO_OPTIONS(flags, interrupt_type); + + error_t error = gpio_descriptor_set_flags(descriptor, flags); + if (error == ERROR_NONE) { + error = gpio_descriptor_add_callback(descriptor, callback, arg); + } + if (error == ERROR_NONE) { + error = gpio_descriptor_enable_interrupt(descriptor); + } + + if (error != ERROR_NONE) { + gpio_descriptor_remove_callback(descriptor); + gpio_descriptor_release(descriptor); + return error; + } + + *out_descriptor = descriptor; + return ERROR_NONE; +} + +static void release_pin(GpioDescriptor*& descriptor) { + if (descriptor == nullptr) { + return; + } + gpio_descriptor_disable_interrupt(descriptor); + gpio_descriptor_remove_callback(descriptor); + gpio_descriptor_release(descriptor); + descriptor = nullptr; +} + +static void release_all_pins(TdeckTrackballInternal* internal) { + release_pin(internal->pin_right); + release_pin(internal->pin_up); + release_pin(internal->pin_left); + release_pin(internal->pin_down); + release_pin(internal->pin_click); +} + +// endregion + +extern "C" { + +static error_t read_delta(Device* device, int32_t* out_dx, int32_t* out_dy) { + auto* internal = GET_INTERNAL(device); + *out_dx = internal->dx.exchange(0, std::memory_order_relaxed); + *out_dy = internal->dy.exchange(0, std::memory_order_relaxed); + return ERROR_NONE; +} + +static error_t get_button_pressed(Device* device, bool* out_pressed) { + *out_pressed = GET_INTERNAL(device)->button_pressed.load(std::memory_order_relaxed); + return ERROR_NONE; +} + +error_t tdeck_trackball_read_delta(Device* device, int32_t* out_dx, int32_t* out_dy) { + return read_delta(device, out_dx, out_dy); +} + +error_t tdeck_trackball_get_button_pressed(Device* device, bool* out_pressed) { + return get_button_pressed(device, out_pressed); +} + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* config = GET_CONFIG(device); + + auto* internal = new(std::nothrow) TdeckTrackballInternal(); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = acquire_pin(config->pin_right, GPIO_INTERRUPT_NEG_EDGE, on_right, internal, &internal->pin_right); + if (error == ERROR_NONE) { + error = acquire_pin(config->pin_up, GPIO_INTERRUPT_NEG_EDGE, on_up, internal, &internal->pin_up); + } + if (error == ERROR_NONE) { + error = acquire_pin(config->pin_left, GPIO_INTERRUPT_NEG_EDGE, on_left, internal, &internal->pin_left); + } + if (error == ERROR_NONE) { + error = acquire_pin(config->pin_down, GPIO_INTERRUPT_NEG_EDGE, on_down, internal, &internal->pin_down); + } + if (error == ERROR_NONE) { + error = acquire_pin(config->pin_click, GPIO_INTERRUPT_ANY_EDGE, on_click, internal, &internal->pin_click); + } + + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to acquire trackball pins: %s", error_to_string(error)); + release_all_pins(internal); + delete internal; + return error; + } + + // Read the click pin's initial level now that the descriptor is acquired. + on_click(internal); + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + LOG_I(TAG, "stop %s", device->name); + auto* internal = GET_INTERNAL(device); + release_all_pins(internal); + device_set_driver_data(device, nullptr); + delete internal; + return ERROR_NONE; +} + +static constexpr TdeckTrackballApi TDECK_TRACKBALL_API = { + .read_delta = read_delta, + .get_button_pressed = get_button_pressed, +}; + +const struct DeviceType TDECK_TRACKBALL_TYPE { + .name = "tdeck-trackball" +}; + +extern Module lilygo_tdeck_module; + +Driver tdeck_trackball_driver = { + .name = "tdeck_trackball", + .compatible = (const char*[]) { "lilygo,tdeck-trackball", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &TDECK_TRACKBALL_API, + .device_type = &TDECK_TRACKBALL_TYPE, + .owner = &lilygo_tdeck_module, + .internal = nullptr +}; + +} diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml new file mode 100644 index 000000000..5def8bba7 --- /dev/null +++ b/Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml @@ -0,0 +1,25 @@ +description: LilyGO T-Deck 5-way GPIO trackball (4 directions + click button) + +compatible: "lilygo,tdeck-trackball" + +properties: + pin-right: + type: phandles + required: true + description: Right-direction GPIO pin + pin-up: + type: phandles + required: true + description: Up-direction GPIO pin + pin-left: + type: phandles + required: true + description: Left-direction GPIO pin + pin-down: + type: phandles + required: true + description: Down-direction GPIO pin + pin-click: + type: phandles + required: true + description: Click button GPIO pin diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index e6abdf07c..75fb8bb9e 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -18,6 +18,7 @@ #include #include #include +#include // Reference: https://wiki.lilygo.cc/get_started/en/Wearable/T-Deck-Plus/T-Deck-Plus.html#Pin-Overview / { @@ -53,6 +54,15 @@ gpio-count = <49>; }; + trackball { + compatible = "lilygo,tdeck-trackball"; + pin-right = <&gpio0 2 GPIO_FLAG_NONE>; + pin-up = <&gpio0 3 GPIO_FLAG_NONE>; + pin-left = <&gpio0 1 GPIO_FLAG_NONE>; + pin-down = <&gpio0 15 GPIO_FLAG_NONE>; + pin-click = <&gpio0 0 GPIO_FLAG_NONE>; + }; + i2c_internal: i2c0 { compatible = "espressif,esp32-i2c"; port = ; From 2658ad5c5411c9a12a5ed2bace2350f418d7b2fa Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 14:42:10 +0200 Subject: [PATCH 07/16] Move temp --- Devices/lilygo-tdeck/{Source => temp}/Trackball/Trackball.cpp | 0 Devices/lilygo-tdeck/{Source => temp}/Trackball/Trackball.h | 0 Devices/lilygo-tdeck/{Source => temp}/bindings/tdeck_keyboard.h | 0 .../{Source => temp}/bindings/tdeck_keyboard_backlight.h | 0 Devices/lilygo-tdeck/{Source => temp}/bindings/tdeck_trackball.h | 0 Devices/lilygo-tdeck/{Source => temp}/drivers/tdeck_keyboard.h | 0 .../{Source => temp}/drivers/tdeck_keyboard_backlight.h | 0 Devices/lilygo-tdeck/{Source => temp}/drivers/tdeck_trackball.h | 0 Devices/lilygo-tdeck/{Source => temp}/module.cpp | 0 Devices/lilygo-tdeck/{Source => temp}/tdeck_keyboard.cpp | 0 .../lilygo-tdeck/{Source => temp}/tdeck_keyboard_backlight.cpp | 0 Devices/lilygo-tdeck/{Source => temp}/tdeck_trackball.cpp | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename Devices/lilygo-tdeck/{Source => temp}/Trackball/Trackball.cpp (100%) rename Devices/lilygo-tdeck/{Source => temp}/Trackball/Trackball.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/bindings/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/bindings/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/bindings/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/drivers/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/drivers/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/drivers/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{Source => temp}/module.cpp (100%) rename Devices/lilygo-tdeck/{Source => temp}/tdeck_keyboard.cpp (100%) rename Devices/lilygo-tdeck/{Source => temp}/tdeck_keyboard_backlight.cpp (100%) rename Devices/lilygo-tdeck/{Source => temp}/tdeck_trackball.cpp (100%) diff --git a/Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp b/Devices/lilygo-tdeck/temp/Trackball/Trackball.cpp similarity index 100% rename from Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp rename to Devices/lilygo-tdeck/temp/Trackball/Trackball.cpp diff --git a/Devices/lilygo-tdeck/Source/Trackball/Trackball.h b/Devices/lilygo-tdeck/temp/Trackball/Trackball.h similarity index 100% rename from Devices/lilygo-tdeck/Source/Trackball/Trackball.h rename to Devices/lilygo-tdeck/temp/Trackball/Trackball.h diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h b/Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h rename to Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h b/Devices/lilygo-tdeck/temp/bindings/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/Source/bindings/tdeck_trackball.h rename to Devices/lilygo-tdeck/temp/bindings/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h b/Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h rename to Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h b/Devices/lilygo-tdeck/temp/drivers/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/Source/drivers/tdeck_trackball.h rename to Devices/lilygo-tdeck/temp/drivers/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/Source/module.cpp b/Devices/lilygo-tdeck/temp/module.cpp similarity index 100% rename from Devices/lilygo-tdeck/Source/module.cpp rename to Devices/lilygo-tdeck/temp/module.cpp diff --git a/Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp b/Devices/lilygo-tdeck/temp/tdeck_keyboard.cpp similarity index 100% rename from Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp rename to Devices/lilygo-tdeck/temp/tdeck_keyboard.cpp diff --git a/Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp b/Devices/lilygo-tdeck/temp/tdeck_keyboard_backlight.cpp similarity index 100% rename from Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp rename to Devices/lilygo-tdeck/temp/tdeck_keyboard_backlight.cpp diff --git a/Devices/lilygo-tdeck/Source/tdeck_trackball.cpp b/Devices/lilygo-tdeck/temp/tdeck_trackball.cpp similarity index 100% rename from Devices/lilygo-tdeck/Source/tdeck_trackball.cpp rename to Devices/lilygo-tdeck/temp/tdeck_trackball.cpp From 19df546c3440dd906a88141dff3e831e0627b962 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 14:42:24 +0200 Subject: [PATCH 08/16] Rename final --- Devices/lilygo-tdeck/{temp => source}/Trackball/Trackball.cpp | 0 Devices/lilygo-tdeck/{temp => source}/Trackball/Trackball.h | 0 Devices/lilygo-tdeck/{temp => source}/bindings/tdeck_keyboard.h | 0 .../{temp => source}/bindings/tdeck_keyboard_backlight.h | 0 Devices/lilygo-tdeck/{temp => source}/bindings/tdeck_trackball.h | 0 Devices/lilygo-tdeck/{temp => source}/drivers/tdeck_keyboard.h | 0 .../{temp => source}/drivers/tdeck_keyboard_backlight.h | 0 Devices/lilygo-tdeck/{temp => source}/drivers/tdeck_trackball.h | 0 Devices/lilygo-tdeck/{temp => source}/module.cpp | 0 Devices/lilygo-tdeck/{temp => source}/tdeck_keyboard.cpp | 0 .../lilygo-tdeck/{temp => source}/tdeck_keyboard_backlight.cpp | 0 Devices/lilygo-tdeck/{temp => source}/tdeck_trackball.cpp | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename Devices/lilygo-tdeck/{temp => source}/Trackball/Trackball.cpp (100%) rename Devices/lilygo-tdeck/{temp => source}/Trackball/Trackball.h (100%) rename Devices/lilygo-tdeck/{temp => source}/bindings/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{temp => source}/bindings/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{temp => source}/bindings/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{temp => source}/drivers/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{temp => source}/drivers/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{temp => source}/drivers/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{temp => source}/module.cpp (100%) rename Devices/lilygo-tdeck/{temp => source}/tdeck_keyboard.cpp (100%) rename Devices/lilygo-tdeck/{temp => source}/tdeck_keyboard_backlight.cpp (100%) rename Devices/lilygo-tdeck/{temp => source}/tdeck_trackball.cpp (100%) diff --git a/Devices/lilygo-tdeck/temp/Trackball/Trackball.cpp b/Devices/lilygo-tdeck/source/Trackball/Trackball.cpp similarity index 100% rename from Devices/lilygo-tdeck/temp/Trackball/Trackball.cpp rename to Devices/lilygo-tdeck/source/Trackball/Trackball.cpp diff --git a/Devices/lilygo-tdeck/temp/Trackball/Trackball.h b/Devices/lilygo-tdeck/source/Trackball/Trackball.h similarity index 100% rename from Devices/lilygo-tdeck/temp/Trackball/Trackball.h rename to Devices/lilygo-tdeck/source/Trackball/Trackball.h diff --git a/Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard.h b/Devices/lilygo-tdeck/source/bindings/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard.h rename to Devices/lilygo-tdeck/source/bindings/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/source/bindings/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/temp/bindings/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/source/bindings/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/temp/bindings/tdeck_trackball.h b/Devices/lilygo-tdeck/source/bindings/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/temp/bindings/tdeck_trackball.h rename to Devices/lilygo-tdeck/source/bindings/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard.h b/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard.h rename to Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/temp/drivers/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/temp/drivers/tdeck_trackball.h b/Devices/lilygo-tdeck/source/drivers/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/temp/drivers/tdeck_trackball.h rename to Devices/lilygo-tdeck/source/drivers/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/temp/module.cpp b/Devices/lilygo-tdeck/source/module.cpp similarity index 100% rename from Devices/lilygo-tdeck/temp/module.cpp rename to Devices/lilygo-tdeck/source/module.cpp diff --git a/Devices/lilygo-tdeck/temp/tdeck_keyboard.cpp b/Devices/lilygo-tdeck/source/tdeck_keyboard.cpp similarity index 100% rename from Devices/lilygo-tdeck/temp/tdeck_keyboard.cpp rename to Devices/lilygo-tdeck/source/tdeck_keyboard.cpp diff --git a/Devices/lilygo-tdeck/temp/tdeck_keyboard_backlight.cpp b/Devices/lilygo-tdeck/source/tdeck_keyboard_backlight.cpp similarity index 100% rename from Devices/lilygo-tdeck/temp/tdeck_keyboard_backlight.cpp rename to Devices/lilygo-tdeck/source/tdeck_keyboard_backlight.cpp diff --git a/Devices/lilygo-tdeck/temp/tdeck_trackball.cpp b/Devices/lilygo-tdeck/source/tdeck_trackball.cpp similarity index 100% rename from Devices/lilygo-tdeck/temp/tdeck_trackball.cpp rename to Devices/lilygo-tdeck/source/tdeck_trackball.cpp From 68469df1c611823b7e886c7ea3b399eb17c59a45 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 14:51:40 +0200 Subject: [PATCH 09/16] file restructuring --- Devices/lilygo-tdeck/CMakeLists.txt | 4 ++-- .../{source => include}/bindings/tdeck_keyboard.h | 0 .../{source => include}/bindings/tdeck_keyboard_backlight.h | 0 .../{source => include}/bindings/tdeck_trackball.h | 0 .../{source => include}/drivers/tdeck_keyboard.h | 0 .../{source => include}/drivers/tdeck_keyboard_backlight.h | 0 .../{source => include}/drivers/tdeck_trackball.h | 0 .../Trackball/Trackball.h => include/drivers/trackball.h} | 0 Devices/lilygo-tdeck/source/{ => drivers}/tdeck_keyboard.cpp | 0 .../source/{ => drivers}/tdeck_keyboard_backlight.cpp | 0 .../lilygo-tdeck/source/{ => drivers}/tdeck_trackball.cpp | 0 .../{Trackball/Trackball.cpp => drivers/trackball.cpp} | 5 +++-- Devices/lilygo-tdeck/source/module.cpp | 2 +- 13 files changed, 6 insertions(+), 5 deletions(-) rename Devices/lilygo-tdeck/{source => include}/bindings/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{source => include}/bindings/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{source => include}/bindings/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{source => include}/drivers/tdeck_keyboard.h (100%) rename Devices/lilygo-tdeck/{source => include}/drivers/tdeck_keyboard_backlight.h (100%) rename Devices/lilygo-tdeck/{source => include}/drivers/tdeck_trackball.h (100%) rename Devices/lilygo-tdeck/{source/Trackball/Trackball.h => include/drivers/trackball.h} (100%) rename Devices/lilygo-tdeck/source/{ => drivers}/tdeck_keyboard.cpp (100%) rename Devices/lilygo-tdeck/source/{ => drivers}/tdeck_keyboard_backlight.cpp (100%) rename Devices/lilygo-tdeck/source/{ => drivers}/tdeck_trackball.cpp (100%) rename Devices/lilygo-tdeck/source/{Trackball/Trackball.cpp => drivers/trackball.cpp} (99%) diff --git a/Devices/lilygo-tdeck/CMakeLists.txt b/Devices/lilygo-tdeck/CMakeLists.txt index eda65fcd2..8481a43dc 100644 --- a/Devices/lilygo-tdeck/CMakeLists.txt +++ b/Devices/lilygo-tdeck/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" + INCLUDE_DIRS "include" REQUIRES Tactility driver ) diff --git a/Devices/lilygo-tdeck/source/bindings/tdeck_keyboard.h b/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/source/bindings/tdeck_keyboard.h rename to Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/source/bindings/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/source/bindings/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/source/bindings/tdeck_trackball.h b/Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/source/bindings/tdeck_trackball.h rename to Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.h b/Devices/lilygo-tdeck/include/drivers/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.h rename to Devices/lilygo-tdeck/include/drivers/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.h b/Devices/lilygo-tdeck/include/drivers/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.h rename to Devices/lilygo-tdeck/include/drivers/tdeck_keyboard_backlight.h diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_trackball.h b/Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/source/drivers/tdeck_trackball.h rename to Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/source/Trackball/Trackball.h b/Devices/lilygo-tdeck/include/drivers/trackball.h similarity index 100% rename from Devices/lilygo-tdeck/source/Trackball/Trackball.h rename to Devices/lilygo-tdeck/include/drivers/trackball.h diff --git a/Devices/lilygo-tdeck/source/tdeck_keyboard.cpp b/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp similarity index 100% rename from Devices/lilygo-tdeck/source/tdeck_keyboard.cpp rename to Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp diff --git a/Devices/lilygo-tdeck/source/tdeck_keyboard_backlight.cpp b/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp similarity index 100% rename from Devices/lilygo-tdeck/source/tdeck_keyboard_backlight.cpp rename to Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp diff --git a/Devices/lilygo-tdeck/source/tdeck_trackball.cpp b/Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp similarity index 100% rename from Devices/lilygo-tdeck/source/tdeck_trackball.cpp rename to Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp diff --git a/Devices/lilygo-tdeck/source/Trackball/Trackball.cpp b/Devices/lilygo-tdeck/source/drivers/trackball.cpp similarity index 99% rename from Devices/lilygo-tdeck/source/Trackball/Trackball.cpp rename to Devices/lilygo-tdeck/source/drivers/trackball.cpp index bd07a096c..63107a789 100644 --- a/Devices/lilygo-tdeck/source/Trackball/Trackball.cpp +++ b/Devices/lilygo-tdeck/source/drivers/trackball.cpp @@ -1,10 +1,11 @@ -#include "Trackball.h" +#include +#include #include + #include #include -#include constexpr auto* TAG = "Trackball"; diff --git a/Devices/lilygo-tdeck/source/module.cpp b/Devices/lilygo-tdeck/source/module.cpp index 8f2741cf2..ff895356b 100644 --- a/Devices/lilygo-tdeck/source/module.cpp +++ b/Devices/lilygo-tdeck/source/module.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include From a825f20f277676190ff1d4c333b662147091255b Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 20:47:30 +0200 Subject: [PATCH 10/16] Code review improvements --- Devices/lilygo-tdeck/lilygo,tdeck.dts | 2 +- Drivers/gt911-module/source/gt911.cpp | 13 ++++++++-- Modules/lvgl-module/source/lvgl_devices.c | 26 ++++++++++++++++++- .../source/drivers/esp32_adc_oneshot.cpp | 5 +++- .../source/drivers/esp32_ledc_backlight.cpp | 6 +++++ TactilityKernel/bindings/battery-sense.yaml | 4 +-- 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 75fb8bb9e..1362f801d 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -34,7 +34,7 @@ battery-sense { compatible = "battery-sense"; - io-channels = <&adc0 0>; + io-channel = <&adc0 0>; reference-voltage-mv = <3300>; multiplier = <2110>; }; diff --git a/Drivers/gt911-module/source/gt911.cpp b/Drivers/gt911-module/source/gt911.cpp index 2625991e2..93e2d9db3 100644 --- a/Drivers/gt911-module/source/gt911.cpp +++ b/Drivers/gt911-module/source/gt911.cpp @@ -114,13 +114,22 @@ static error_t start(Device* device) { static error_t stop(Device* device) { auto* internal = static_cast(device_get_driver_data(device)); + bool ok = true; + + // esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned + // separately and needs its own deletion. if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) { LOG_E(TAG, "Failed to delete touch handle"); - return ERROR_RESOURCE; + ok = false; + } + + if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel IO handle"); + ok = false; } free(internal); - return ERROR_NONE; + return ok ? ERROR_NONE : ERROR_RESOURCE; } // endregion diff --git a/Modules/lvgl-module/source/lvgl_devices.c b/Modules/lvgl-module/source/lvgl_devices.c index 4dc77fd8e..6d907d407 100644 --- a/Modules/lvgl-module/source/lvgl_devices.c +++ b/Modules/lvgl-module/source/lvgl_devices.c @@ -1,3 +1,6 @@ +#include "tactility/lvgl_module.h" + + #include #include #include @@ -12,6 +15,8 @@ #define TAG "lvgl" void lvgl_devices_attach() { + lvgl_lock(); + lv_disp_t* lvgl_display = NULL; struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE); if (kernel_display_device != NULL) { @@ -42,13 +47,32 @@ void lvgl_devices_attach() { LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name); } } + + lvgl_unlock(); } void lvgl_devices_detach() { + lvgl_lock(); + lv_indev_t* device = lv_indev_get_next(NULL); while (device != NULL) { - lv_indev_delete(device); + lv_indev_type_t type = lv_indev_get_type(device); + if (type == LV_INDEV_TYPE_POINTER) { + lvgl_pointer_remove(device); + } else if (type == LV_INDEV_TYPE_KEYPAD) { + lvgl_keyboard_remove(device); + } else { + 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); } + + lv_disp_t* display = lv_disp_get_next(NULL); + while (display != NULL) { + lv_display_delete(display); + display = lv_disp_get_next(NULL); + } + + lvgl_lock(); } diff --git a/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp b/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp index 1957142ca..51f115237 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp @@ -74,7 +74,10 @@ static error_t start(Device* device) { static error_t stop(Device* device) { LOG_I(TAG, "stop %s", device->name); - adc_oneshot_del_unit(GET_HANDLE(device)); + esp_err_t esp_error = adc_oneshot_del_unit(GET_HANDLE(device)); + if (esp_error != ESP_OK) { + LOG_E(TAG, "Deleting ADC unit failed: %s", esp_err_to_name(esp_error)); + } device_set_driver_data(device, nullptr); return ERROR_NONE; } diff --git a/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp b/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp index 420639da1..f1d075e26 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp @@ -61,12 +61,18 @@ static error_t start(Device* device) { internal->brightness = config->brightness_range.min; device_set_driver_data(device, internal); + + backlight_set_brightness_default(device); // Allowed to fail, we don't care about the result + return ERROR_NONE; } static error_t stop(Device* device) { + backlight_set_brightness(device, 0); // Allowed to fail, we don't care about the result + auto* internal = static_cast(device_get_driver_data(device)); free(internal); + return ERROR_NONE; } diff --git a/TactilityKernel/bindings/battery-sense.yaml b/TactilityKernel/bindings/battery-sense.yaml index 0776e5885..2e3f56a63 100644 --- a/TactilityKernel/bindings/battery-sense.yaml +++ b/TactilityKernel/bindings/battery-sense.yaml @@ -3,10 +3,10 @@ description: Battery voltage sense via an ADC channel behind a voltage divider compatible: "battery-sense" properties: - io-channels: + io-channel: type: phandles required: true - description: The ADC channel connected to the battery sense circuit, e.g. `<&adc1 3>` + description: The ADC object paired with a channel index e.g. `<&adc1 3>` reference-voltage-mv: type: int required: true From 57086981dd34a7e58652c7bd0b2bc93b5df755c3 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 21:05:48 +0200 Subject: [PATCH 11/16] Update checkout action to v6 --- .github/actions/build-firmware/action.yml | 2 +- .github/actions/build-sdk/action.yml | 2 +- .github/actions/build-simulator/action.yml | 2 +- .github/workflows/build-simulator.yml | 4 ++-- .github/workflows/build.yml | 14 +++++++------- .github/workflows/tests.yml | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/actions/build-firmware/action.yml b/.github/actions/build-firmware/action.yml index c8bc8de61..9bc197a97 100644 --- a/.github/actions/build-firmware/action.yml +++ b/.github/actions/build-firmware/action.yml @@ -11,7 +11,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: 'Board select' diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 4964f7039..ac0bfbe0c 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -11,7 +11,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: 'Board select' diff --git a/.github/actions/build-simulator/action.yml b/.github/actions/build-simulator/action.yml index 8f759699f..8507fcac5 100644 --- a/.github/actions/build-simulator/action.yml +++ b/.github/actions/build-simulator/action.yml @@ -15,7 +15,7 @@ runs: using: "composite" steps: - name: "Checkout repo" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: Install Linux Dependencies for SDL diff --git a/.github/workflows/build-simulator.yml b/.github/workflows/build-simulator.yml index b781524bb..6be91df20 100644 --- a/.github/workflows/build-simulator.yml +++ b/.github/workflows/build-simulator.yml @@ -10,7 +10,7 @@ jobs: Linux: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: "Build" @@ -22,7 +22,7 @@ jobs: macOS: runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: "Build" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7cdb6153..6236a0f5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: "Build SDK" @@ -36,7 +36,7 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - id: set-matrix @@ -47,7 +47,7 @@ jobs: strategy: matrix: ${{ fromJSON(needs.GenerateDeviceMatrix.outputs.matrix) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: "Build Firmware" @@ -62,7 +62,7 @@ jobs: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Bundle Artifacts" uses: ./.github/actions/bundle-artifacts PublishFirmwareSnapshot: @@ -70,7 +70,7 @@ jobs: needs: [ BundleArtifacts ] if: (github.event_name == 'push' && github.ref == 'refs/heads/main') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Publish Firmware Snapshot" env: CDN_ID: ${{ secrets.CDN_ID }} @@ -84,7 +84,7 @@ jobs: needs: [ BundleArtifacts ] if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Publish Firmware Stable" env: CDN_ID: ${{ secrets.CDN_ID }} @@ -98,7 +98,7 @@ jobs: needs: [ BundleArtifacts ] if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Publish SDKs" env: CDN_ID: ${{ secrets.CDN_ID }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8fcf1b14..5789acea2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout repo" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: "Configure Project" @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout repo" - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false submodules: recursive From be461eb6a390064ac5049328a62ca7e89b00554e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 21:24:01 +0200 Subject: [PATCH 12/16] Fix for deprecated esptool parameters --- Buildscripts/Flashing/flash.ps1 | 4 ++-- Buildscripts/Flashing/flash.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Buildscripts/Flashing/flash.ps1 b/Buildscripts/Flashing/flash.ps1 index 0fed47eb5..9e385f161 100755 --- a/Buildscripts/Flashing/flash.ps1 +++ b/Buildscripts/Flashing/flash.ps1 @@ -13,9 +13,9 @@ $jsonClean = $json.flash_files -replace '[\{\}\@\;]', '' $jsonClean = $jsonClean -replace '[\=]', ' ' cd Binaries -$command = "esptool --port $port erase_flash" +$command = "esptool --port $port erase-flash" Invoke-Expression $command -$command = "esptool --port $port write_flash $jsonClean" +$command = "esptool --port $port write-flash $jsonClean" Invoke-Expression $command cd .. diff --git a/Buildscripts/Flashing/flash.sh b/Buildscripts/Flashing/flash.sh index fa0f28970..e267c9cb3 100755 --- a/Buildscripts/Flashing/flash.sh +++ b/Buildscripts/Flashing/flash.sh @@ -49,7 +49,7 @@ fi # Take the flash_arg file contents and join each line in the file into a single line flash_args=`grep \n Binaries/flash_args | awk '{print}' ORS=' '` cd Binaries -$esptoolPath --port $1 erase_flash -$esptoolPath --port $1 write_flash $flash_args +$esptoolPath --port $1 erase-flash +$esptoolPath --port $1 write-flash $flash_args cd - From 7757b0f2f5ab486385f4baad8ced793b70cf5f02 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 21:26:31 +0200 Subject: [PATCH 13/16] Github actions correctness --- .github/workflows/build.yml | 2 ++ .github/workflows/tests.yml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6236a0f5b..6f4c67ed2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,6 +63,8 @@ jobs: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: "Bundle Artifacts" uses: ./.github/actions/bundle-artifacts PublishFirmwareSnapshot: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5789acea2..78ab3c9a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,6 @@ jobs: DevicetreeTests: runs-on: ubuntu-latest steps: - - name: "Checkout repo" - uses: actions/checkout@v6 with: persist-credentials: false From 7124353323620f5e5112da7b48d75cf24dea8969 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 23:15:16 +0200 Subject: [PATCH 14/16] Various fixes --- Buildscripts/sdkconfig/default.properties | 5 + Devices/lilygo-tdeck-plus/CMakeLists.txt | 6 + Devices/lilygo-tdeck-plus/device.properties | 23 +++ Devices/lilygo-tdeck-plus/devicetree.yaml | 6 + .../lilygo-tdeck-plus/lilygo,tdeck-plus.dts | 150 ++++++++++++++++++ Devices/lilygo-tdeck-plus/source/module.cpp | 86 ++++++++++ Devices/lilygo-tdeck/CMakeLists.txt | 3 +- Devices/lilygo-tdeck/device.properties | 2 +- Devices/lilygo-tdeck/devicetree.yaml | 2 +- Devices/lilygo-tdeck/lilygo,tdeck.dts | 22 +-- Devices/lilygo-tdeck/source/module.cpp | 109 +++---------- Drivers/lilygo-module/CMakeLists.txt | 11 ++ .../lilygo,tdeck-keyboard-backlight.yaml | 0 .../bindings/lilygo,tdeck-keyboard.yaml | 0 .../bindings/lilygo,tdeck-trackball.yaml | 0 Drivers/lilygo-module/devicetree.yaml | 3 + .../include/lilygo}/bindings/tdeck_keyboard.h | 2 +- .../bindings/tdeck_keyboard_backlight.h | 2 +- .../lilygo}/bindings/tdeck_trackball.h | 2 +- .../include/lilygo}/drivers/tdeck_keyboard.h | 0 .../drivers/tdeck_keyboard_backlight.h | 0 .../include/lilygo/drivers/tdeck_power_on.h | 14 ++ .../include/lilygo}/drivers/tdeck_trackball.h | 0 .../include/lilygo}/drivers/trackball.h | 0 Drivers/lilygo-module/include/lilygo_module.h | 14 ++ Drivers/lilygo-module/source/module.cpp | 38 +++++ .../lilygo-module/source}/tdeck_keyboard.cpp | 6 +- .../source}/tdeck_keyboard_backlight.cpp | 6 +- Drivers/lilygo-module/source/tdeck_power_on.c | 23 +++ .../lilygo-module/source}/tdeck_trackball.cpp | 7 +- .../lilygo-module/source}/trackball.cpp | 4 +- Firmware/CMakeLists.txt | 28 +++- Modules/lvgl-module/source/lvgl_devices.c | 9 ++ 33 files changed, 456 insertions(+), 127 deletions(-) create mode 100644 Devices/lilygo-tdeck-plus/CMakeLists.txt create mode 100644 Devices/lilygo-tdeck-plus/device.properties create mode 100644 Devices/lilygo-tdeck-plus/devicetree.yaml create mode 100644 Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts create mode 100644 Devices/lilygo-tdeck-plus/source/module.cpp create mode 100644 Drivers/lilygo-module/CMakeLists.txt rename {Devices/lilygo-tdeck => Drivers/lilygo-module}/bindings/lilygo,tdeck-keyboard-backlight.yaml (100%) rename {Devices/lilygo-tdeck => Drivers/lilygo-module}/bindings/lilygo,tdeck-keyboard.yaml (100%) rename {Devices/lilygo-tdeck => Drivers/lilygo-module}/bindings/lilygo,tdeck-trackball.yaml (100%) create mode 100644 Drivers/lilygo-module/devicetree.yaml rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/bindings/tdeck_keyboard.h (78%) rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/bindings/tdeck_keyboard_backlight.h (76%) rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/bindings/tdeck_trackball.h (78%) rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/drivers/tdeck_keyboard.h (100%) rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/drivers/tdeck_keyboard_backlight.h (100%) create mode 100644 Drivers/lilygo-module/include/lilygo/drivers/tdeck_power_on.h rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/drivers/tdeck_trackball.h (100%) rename {Devices/lilygo-tdeck/include => Drivers/lilygo-module/include/lilygo}/drivers/trackball.h (100%) create mode 100644 Drivers/lilygo-module/include/lilygo_module.h create mode 100644 Drivers/lilygo-module/source/module.cpp rename {Devices/lilygo-tdeck/source/drivers => Drivers/lilygo-module/source}/tdeck_keyboard.cpp (96%) rename {Devices/lilygo-tdeck/source/drivers => Drivers/lilygo-module/source}/tdeck_keyboard_backlight.cpp (97%) create mode 100644 Drivers/lilygo-module/source/tdeck_power_on.c rename {Devices/lilygo-tdeck/source/drivers => Drivers/lilygo-module/source}/tdeck_trackball.cpp (97%) rename {Devices/lilygo-tdeck/source/drivers => Drivers/lilygo-module/source}/trackball.cpp (98%) diff --git a/Buildscripts/sdkconfig/default.properties b/Buildscripts/sdkconfig/default.properties index f61a09ffa..6987bea55 100644 --- a/Buildscripts/sdkconfig/default.properties +++ b/Buildscripts/sdkconfig/default.properties @@ -44,6 +44,11 @@ CONFIG_WL_SECTOR_MODE_SAFE=y CONFIG_WL_SECTOR_MODE=1 # Allow new i2c_master API (used by Tab5Keyboard for LP_I2C_NUM_0) to coexist with legacy i2c driver CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y +# Allow new adc_oneshot API (esp32_adc_oneshot kernel driver, linked into every device via +# platform-esp32) to coexist with boards still reading battery voltage via the legacy ADC driver +# (e.g. m5stack-cardputer, m5stack-cardputer-adv, lilygo-thmi). Only one API is ever actually used +# per board at runtime, so this just bypasses ESP-IDF's link-time-only conflict abort. +CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK=y # Wi-Fi memory optimizations CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3 CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=5 diff --git a/Devices/lilygo-tdeck-plus/CMakeLists.txt b/Devices/lilygo-tdeck-plus/CMakeLists.txt new file mode 100644 index 000000000..34acc969a --- /dev/null +++ b/Devices/lilygo-tdeck-plus/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE SOURCE_FILES source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + REQUIRES Tactility driver lilygo-module +) diff --git a/Devices/lilygo-tdeck-plus/device.properties b/Devices/lilygo-tdeck-plus/device.properties new file mode 100644 index 000000000..462968449 --- /dev/null +++ b/Devices/lilygo-tdeck-plus/device.properties @@ -0,0 +1,23 @@ +general.vendor=LilyGO +general.name=T-Deck Plus + +apps.launcherAppId=Launcher + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=120M +hardware.tinyUsb=true +hardware.esptoolFlashFreq=120M +hardware.bluetooth=true + +storage.userDataLocation=SD + +display.size=2.8" +display.shape=rectangle +display.dpi=143 + +cdn.infoMessage=To put the device into bootloader mode:
1. Press the trackball and then the reset button at the same time,
2. Let go of the reset button, then the trackball.

When this website reports that flashing is finished, you likely have to press the reset button. + +lvgl.colorDepth=16 diff --git a/Devices/lilygo-tdeck-plus/devicetree.yaml b/Devices/lilygo-tdeck-plus/devicetree.yaml new file mode 100644 index 000000000..0093181fc --- /dev/null +++ b/Devices/lilygo-tdeck-plus/devicetree.yaml @@ -0,0 +1,6 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/st7789-module + - Drivers/gt911-module + - Drivers/lilygo-module +dts: lilygo,tdeck-plus.dts diff --git a/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts b/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts new file mode 100644 index 000000000..121c72c81 --- /dev/null +++ b/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts @@ -0,0 +1,150 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +// Reference: https://wiki.lilygo.cc/products/t-deck-series/t-deck-plus/ +/ { + compatible = "root"; + model = "LilyGO T-Deck Plus"; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2110>; + }; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + + ble0 { + compatible = "espressif,esp32-ble"; + status = "disabled"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + trackball { + compatible = "lilygo,tdeck-trackball"; + pin-right = <&gpio0 2 GPIO_FLAG_NONE>; + pin-up = <&gpio0 3 GPIO_FLAG_NONE>; + pin-left = <&gpio0 1 GPIO_FLAG_NONE>; + pin-down = <&gpio0 15 GPIO_FLAG_NONE>; + pin-click = <&gpio0 0 GPIO_FLAG_NONE>; + }; + + i2c_internal: i2c0 { + compatible = "espressif,esp32-i2c"; + port = ; + 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 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 7 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 5 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>; + }; + + 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 = ; + cs-gpios = <&gpio0 12 GPIO_FLAG_NONE>, // Display + <&gpio0 39 GPIO_FLAG_NONE>, // SD card + <&gpio0 9 GPIO_FLAG_NONE>; // Radio + pin-mosi = <&gpio0 41 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 38 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>; + + display@0 { + 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"; + frequency-khz = <20000>; + }; + }; + + uart0 { + compatible = "espressif,esp32-uart"; + port = ; + pin-tx = <&gpio0 43 GPIO_FLAG_NONE>; + pin-rx = <&gpio0 44 GPIO_FLAG_NONE>; + }; +}; diff --git a/Devices/lilygo-tdeck-plus/source/module.cpp b/Devices/lilygo-tdeck-plus/source/module.cpp new file mode 100644 index 000000000..1b4b4a863 --- /dev/null +++ b/Devices/lilygo-tdeck-plus/source/module.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +constexpr auto* TAG = "tdeck-plus"; + +// Legacy placeholder (required until legacy HAL is cleaned up everywhere) +extern const tt::hal::Configuration hardwareConfiguration = {}; + +extern "C" { + +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"); + } + } + } + }); + + // The kernel trackball device is already started by kernel_init(); this just registers it as an + // LVGL input device and applies persisted settings, both of which require LVGL to be up first. + tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { + auto tbSettings = tt::settings::trackball::loadOrGetDefault(); + lvgl_lock(); + if (trackball::init() != nullptr) { + trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer + ? trackball::Mode::Pointer + : trackball::Mode::Encoder); + trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); + trackball::setPointerSensitivity(tbSettings.pointerSensitivity); + trackball::setEnabled(tbSettings.trackballEnabled); + } + lvgl_unlock(); + }); +} + +static error_t start() { + LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); + if (!tdeck_power_on()) { + LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); + return ERROR_RESOURCE; + } + + // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. + tt::kernel::delayMillis(100); + + subscribe_events(); + + return ERROR_NONE; +} + +static error_t stop() { + return ERROR_NONE; +} + +Module lilygo_tdeck_plus_module = { + .name = "lilygo-tdeck-plus", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/lilygo-tdeck/CMakeLists.txt b/Devices/lilygo-tdeck/CMakeLists.txt index 8481a43dc..34acc969a 100644 --- a/Devices/lilygo-tdeck/CMakeLists.txt +++ b/Devices/lilygo-tdeck/CMakeLists.txt @@ -2,6 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "include" - REQUIRES Tactility driver + REQUIRES Tactility driver lilygo-module ) diff --git a/Devices/lilygo-tdeck/device.properties b/Devices/lilygo-tdeck/device.properties index 04207e460..8284f6c0b 100644 --- a/Devices/lilygo-tdeck/device.properties +++ b/Devices/lilygo-tdeck/device.properties @@ -1,5 +1,5 @@ general.vendor=LilyGO -general.name=T-Deck,T-Deck Plus +general.name=T-Deck apps.launcherAppId=Launcher diff --git a/Devices/lilygo-tdeck/devicetree.yaml b/Devices/lilygo-tdeck/devicetree.yaml index 36fa6d512..6624359c5 100644 --- a/Devices/lilygo-tdeck/devicetree.yaml +++ b/Devices/lilygo-tdeck/devicetree.yaml @@ -2,5 +2,5 @@ dependencies: - Platforms/platform-esp32 - Drivers/st7789-module - Drivers/gt911-module -bindings: bindings + - Drivers/lilygo-module dts: lilygo,tdeck.dts diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 1362f801d..91935b517 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -16,11 +16,12 @@ #include #include -#include -#include -#include -// Reference: https://wiki.lilygo.cc/get_started/en/Wearable/T-Deck-Plus/T-Deck-Plus.html#Pin-Overview +#include +#include +#include + +// Reference: https://wiki.lilygo.cc/products/t-deck-series/t-deck/ / { compatible = "root"; model = "LilyGO T-Deck"; @@ -140,10 +141,13 @@ }; }; - uart0 { - compatible = "espressif,esp32-uart"; - port = ; - pin-tx = <&gpio0 43 GPIO_FLAG_NONE>; - pin-rx = <&gpio0 44 GPIO_FLAG_NONE>; + grove0 { + compatible = "espressif,esp32-grove"; + defaultMode = ; + pinSdaTx = <&gpio0 43 GPIO_FLAG_NONE>; + pinSclRx = <&gpio0 44 GPIO_FLAG_NONE>; + uartPort = ; + i2cPort = ; + i2cClockFrequency = <400000>; }; }; diff --git a/Devices/lilygo-tdeck/source/module.cpp b/Devices/lilygo-tdeck/source/module.cpp index ff895356b..623e97253 100644 --- a/Devices/lilygo-tdeck/source/module.cpp +++ b/Devices/lilygo-tdeck/source/module.cpp @@ -1,115 +1,55 @@ #include -#include +#include "lilygo/drivers/tdeck_power_on.h" +#include "tactility/lvgl_module.h" + #include #include #include #include #include -#include #include #include #include #include -#include - -#include - -constexpr auto* TAG = "T-Deck"; +#include -constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10; +constexpr auto* TAG = "tdeck"; // Legacy placeholder (required until legacy HAL is cleaned up everywhere) extern const tt::hal::Configuration hardwareConfiguration = {}; extern "C" { -extern Driver tdeck_keyboard_driver; -extern Driver tdeck_keyboard_backlight_driver; -extern Driver tdeck_trackball_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"); - } - } - } - }); - // The kernel trackball device is already started by kernel_init(); this just registers it as an // LVGL input device and applies persisted settings, both of which require LVGL to be up first. tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { auto tbSettings = tt::settings::trackball::loadOrGetDefault(); - if (tt::lvgl::lock(100)) { - if (trackball::init() != nullptr) { - trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer - ? trackball::Mode::Pointer - : trackball::Mode::Encoder); - trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); - trackball::setPointerSensitivity(tbSettings.pointerSensitivity); - trackball::setEnabled(tbSettings.trackballEnabled); - } - tt::lvgl::unlock(); - } else { - LOG_W(TAG, "Failed to acquire LVGL lock for trackball settings"); + lvgl_lock(); + if (trackball::init() != nullptr) { + trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer + ? trackball::Mode::Pointer + : trackball::Mode::Encoder); + trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); + trackball::setPointerSensitivity(tbSettings.pointerSensitivity); + trackball::setEnabled(tbSettings.trackballEnabled); } + lvgl_unlock(); }); } static error_t start() { LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); - if (!power_on()) { + if (!tdeck_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; - } - - if (driver_construct_add(&tdeck_trackball_driver) != ERROR_NONE) { - LOG_E(TAG, "Failed to register trackball driver"); - return ERROR_RESOURCE; - } + // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. + tt::kernel::delayMillis(100); subscribe_events(); @@ -117,23 +57,10 @@ static error_t start() { } static error_t stop() { - // 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; - } - if (driver_remove_destruct(&tdeck_trackball_driver) != ERROR_NONE) { - LOG_E(TAG, "Failed to unregister trackball driver"); - return ERROR_RESOURCE; - } return ERROR_NONE; } -struct Module lilygo_tdeck_module = { +Module lilygo_tdeck_module = { .name = "lilygo-tdeck", .start = start, .stop = stop, diff --git a/Drivers/lilygo-module/CMakeLists.txt b/Drivers/lilygo-module/CMakeLists.txt new file mode 100644 index 000000000..0b2db8912 --- /dev/null +++ b/Drivers/lilygo-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(lilygo-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel Tactility lvgl +) diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml b/Drivers/lilygo-module/bindings/lilygo,tdeck-keyboard-backlight.yaml similarity index 100% rename from Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml rename to Drivers/lilygo-module/bindings/lilygo,tdeck-keyboard-backlight.yaml diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml b/Drivers/lilygo-module/bindings/lilygo,tdeck-keyboard.yaml similarity index 100% rename from Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml rename to Drivers/lilygo-module/bindings/lilygo,tdeck-keyboard.yaml diff --git a/Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml b/Drivers/lilygo-module/bindings/lilygo,tdeck-trackball.yaml similarity index 100% rename from Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml rename to Drivers/lilygo-module/bindings/lilygo,tdeck-trackball.yaml diff --git a/Drivers/lilygo-module/devicetree.yaml b/Drivers/lilygo-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/lilygo-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard.h similarity index 78% rename from Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h rename to Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard.h index 10f904a49..248291d4c 100644 --- a/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h +++ b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard.h @@ -2,6 +2,6 @@ #pragma once #include -#include +#include DEFINE_DEVICETREE(tdeck_keyboard, struct TdeckKeyboardConfig) diff --git a/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard_backlight.h similarity index 76% rename from Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h rename to Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard_backlight.h index bad0bdcbf..6dac1250a 100644 --- a/Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h +++ b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_keyboard_backlight.h @@ -2,6 +2,6 @@ #pragma once #include -#include +#include DEFINE_DEVICETREE(tdeck_keyboard_backlight, struct TdeckKeyboardBacklightConfig) diff --git a/Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_trackball.h similarity index 78% rename from Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h rename to Drivers/lilygo-module/include/lilygo/bindings/tdeck_trackball.h index 2eb630b40..244f78c89 100644 --- a/Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h +++ b/Drivers/lilygo-module/include/lilygo/bindings/tdeck_trackball.h @@ -2,6 +2,6 @@ #pragma once #include -#include +#include DEFINE_DEVICETREE(tdeck_trackball, struct TdeckTrackballConfig) diff --git a/Devices/lilygo-tdeck/include/drivers/tdeck_keyboard.h b/Drivers/lilygo-module/include/lilygo/drivers/tdeck_keyboard.h similarity index 100% rename from Devices/lilygo-tdeck/include/drivers/tdeck_keyboard.h rename to Drivers/lilygo-module/include/lilygo/drivers/tdeck_keyboard.h diff --git a/Devices/lilygo-tdeck/include/drivers/tdeck_keyboard_backlight.h b/Drivers/lilygo-module/include/lilygo/drivers/tdeck_keyboard_backlight.h similarity index 100% rename from Devices/lilygo-tdeck/include/drivers/tdeck_keyboard_backlight.h rename to Drivers/lilygo-module/include/lilygo/drivers/tdeck_keyboard_backlight.h diff --git a/Drivers/lilygo-module/include/lilygo/drivers/tdeck_power_on.h b/Drivers/lilygo-module/include/lilygo/drivers/tdeck_power_on.h new file mode 100644 index 000000000..89a4480bb --- /dev/null +++ b/Drivers/lilygo-module/include/lilygo/drivers/tdeck_power_on.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +bool tdeck_power_on(); + +#ifdef __cplusplus +} +#endif diff --git a/Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h b/Drivers/lilygo-module/include/lilygo/drivers/tdeck_trackball.h similarity index 100% rename from Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h rename to Drivers/lilygo-module/include/lilygo/drivers/tdeck_trackball.h diff --git a/Devices/lilygo-tdeck/include/drivers/trackball.h b/Drivers/lilygo-module/include/lilygo/drivers/trackball.h similarity index 100% rename from Devices/lilygo-tdeck/include/drivers/trackball.h rename to Drivers/lilygo-module/include/lilygo/drivers/trackball.h diff --git a/Drivers/lilygo-module/include/lilygo_module.h b/Drivers/lilygo-module/include/lilygo_module.h new file mode 100644 index 000000000..2fe3300a7 --- /dev/null +++ b/Drivers/lilygo-module/include/lilygo_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module lilygo_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/lilygo-module/source/module.cpp b/Drivers/lilygo-module/source/module.cpp new file mode 100644 index 000000000..a166f3fa7 --- /dev/null +++ b/Drivers/lilygo-module/source/module.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver tdeck_keyboard_driver; +extern Driver tdeck_keyboard_backlight_driver; +extern Driver tdeck_trackball_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(&tdeck_keyboard_driver) == ERROR_NONE); + check(driver_construct_add(&tdeck_keyboard_backlight_driver) == ERROR_NONE); + check(driver_construct_add(&tdeck_trackball_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(&tdeck_keyboard_driver) == ERROR_NONE); + check(driver_remove_destruct(&tdeck_keyboard_backlight_driver) == ERROR_NONE); + check(driver_remove_destruct(&tdeck_trackball_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module lilygo_module = { + .name = "lilygo", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp b/Drivers/lilygo-module/source/tdeck_keyboard.cpp similarity index 96% rename from Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp rename to Drivers/lilygo-module/source/tdeck_keyboard.cpp index a41acc0b1..c2690a182 100644 --- a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp +++ b/Drivers/lilygo-module/source/tdeck_keyboard.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include @@ -87,7 +87,7 @@ static const KeyboardApi tdeck_keyboard_api = { .read_key = tdeck_keyboard_read_key, }; -extern Module lilygo_tdeck_module; +extern Module lilygo_module; Driver tdeck_keyboard_driver = { .name = "tdeck_keyboard", @@ -96,6 +96,6 @@ Driver tdeck_keyboard_driver = { .stop_device = stop, .api = &tdeck_keyboard_api, .device_type = &KEYBOARD_TYPE, - .owner = &lilygo_tdeck_module, + .owner = &lilygo_module, .internal = nullptr }; diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp b/Drivers/lilygo-module/source/tdeck_keyboard_backlight.cpp similarity index 97% rename from Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp rename to Drivers/lilygo-module/source/tdeck_keyboard_backlight.cpp index 1df6e4e87..3d00d4a0d 100644 --- a/Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp +++ b/Drivers/lilygo-module/source/tdeck_keyboard_backlight.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include @@ -108,7 +108,7 @@ static const BacklightApi tdeck_keyboard_backlight_api = { .get_max_brightness = tdeck_keyboard_backlight_get_max_brightness, }; -extern struct Module lilygo_tdeck_module; +extern struct Module lilygo_module; Driver tdeck_keyboard_backlight_driver = { .name = "tdeck_keyboard_backlight", @@ -117,6 +117,6 @@ Driver tdeck_keyboard_backlight_driver = { .stop_device = stop, .api = &tdeck_keyboard_backlight_api, .device_type = &BACKLIGHT_TYPE, - .owner = &lilygo_tdeck_module, + .owner = &lilygo_module, .internal = nullptr }; diff --git a/Drivers/lilygo-module/source/tdeck_power_on.c b/Drivers/lilygo-module/source/tdeck_power_on.c new file mode 100644 index 000000000..ad20ffb09 --- /dev/null +++ b/Drivers/lilygo-module/source/tdeck_power_on.c @@ -0,0 +1,23 @@ +#include + +#define TDECK_POWERON_GPIO GPIO_NUM_10 + +bool tdeck_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; + } + + return true; +} diff --git a/Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp b/Drivers/lilygo-module/source/tdeck_trackball.cpp similarity index 97% rename from Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp rename to Drivers/lilygo-module/source/tdeck_trackball.cpp index ec42f240d..092f3852b 100644 --- a/Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp +++ b/Drivers/lilygo-module/source/tdeck_trackball.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -#include +#include -#include #include #include #include @@ -181,7 +180,7 @@ const struct DeviceType TDECK_TRACKBALL_TYPE { .name = "tdeck-trackball" }; -extern Module lilygo_tdeck_module; +extern Module lilygo_module; Driver tdeck_trackball_driver = { .name = "tdeck_trackball", @@ -190,7 +189,7 @@ Driver tdeck_trackball_driver = { .stop_device = stop, .api = &TDECK_TRACKBALL_API, .device_type = &TDECK_TRACKBALL_TYPE, - .owner = &lilygo_tdeck_module, + .owner = &lilygo_module, .internal = nullptr }; diff --git a/Devices/lilygo-tdeck/source/drivers/trackball.cpp b/Drivers/lilygo-module/source/trackball.cpp similarity index 98% rename from Devices/lilygo-tdeck/source/drivers/trackball.cpp rename to Drivers/lilygo-module/source/trackball.cpp index 63107a789..268874bb3 100644 --- a/Devices/lilygo-tdeck/source/drivers/trackball.cpp +++ b/Drivers/lilygo-module/source/trackball.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include diff --git a/Firmware/CMakeLists.txt b/Firmware/CMakeLists.txt index 98853765e..2058f5b40 100644 --- a/Firmware/CMakeLists.txt +++ b/Firmware/CMakeLists.txt @@ -102,21 +102,33 @@ endif () # Devicetree code generation # -add_custom_target(AlwaysRun - COMMAND ${CMAKE_COMMAND} -E rm -f "${GENERATED_DIR}/devicetree.c" -) -add_custom_command( - OUTPUT "${GENERATED_DIR}/devicetree.c" - "${GENERATED_DIR}/devicetree.h" +# A plain add_custom_command(OUTPUT ...) only reruns when its explicit DEPENDS (devicetree.yaml) +# changes, since ninja computes dirtiness up front, before any command in this invocation runs. +# The devicetree's real inputs span many files (dts, bindings yaml, driver headers) that a single +# DEPENDS can't enumerate, so this used to be forced to always-rerun via a separate "AlwaysRun" +# custom target that deleted devicetree.c first (DEPENDS on a *target* only creates an order-only +# ninja edge). That delete was invisible to ninja's DAG: on an up-to-date build, ninja would still +# run AlwaysRun (custom targets with no real output are always considered dirty) and delete the +# file, but *not* rerun the generation edge (its own explicit input hadn't changed) or recompile +# devicetree.c.obj (also considered clean) - silently leaving devicetree.c missing on disk until +# some later, unrelated build finally noticed and regenerated it. In between, any build that did +# need to (re)compile devicetree.c.obj hit "No such file or directory". +# +# add_custom_target(... COMMAND ...) has no such problem: unlike add_custom_command(OUTPUT ...), +# a custom target with a COMMAND always reruns on every ninja invocation, so generation and the +# stale artifact removal happen atomically in one edge. BYPRODUCTS tells ninja which files this +# target produces, so it still wires a proper (non-order-only) dependency for the sources that +# consume them. +add_custom_target(Generated ALL COMMAND python "${CMAKE_SOURCE_DIR}/Buildscripts/DevicetreeCompiler/compile.py" "${DEVICETREE_LOCATION}" "${GENERATED_DIR}" + BYPRODUCTS "${GENERATED_DIR}/devicetree.c" "${GENERATED_DIR}/devicetree.h" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - DEPENDS AlwaysRun "${DEVICETREE_LOCATION}/devicetree.yaml" # AlwaysRun ensures it always gets built COMMENT "Generating devicetree source files..." ) -add_custom_target(Generated DEPENDS "${GENERATED_DIR}/devicetree.c") set_source_files_properties("${GENERATED_DIR}/devicetree.c" PROPERTIES GENERATED TRUE) set_source_files_properties("${GENERATED_DIR}/devicetree.h" PROPERTIES GENERATED TRUE) # Update target for generated code target_sources(${COMPONENT_LIB} PRIVATE "${GENERATED_DIR}/devicetree.c") target_include_directories(${COMPONENT_LIB} PRIVATE "${GENERATED_DIR}") +add_dependencies(${COMPONENT_LIB} Generated) diff --git a/Modules/lvgl-module/source/lvgl_devices.c b/Modules/lvgl-module/source/lvgl_devices.c index 6d907d407..9d4d81c58 100644 --- a/Modules/lvgl-module/source/lvgl_devices.c +++ b/Modules/lvgl-module/source/lvgl_devices.c @@ -19,6 +19,11 @@ void lvgl_devices_attach() { lv_disp_t* lvgl_display = NULL; struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE); + // Placeholder drivers (boards not yet migrated to the kernel display driver) register with a + // NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to. + if (kernel_display_device != NULL && device_get_driver(kernel_display_device)->api == NULL) { + kernel_display_device = NULL; + } if (kernel_display_device != NULL) { struct LvglDisplayConfig lvgl_display_config = {}; if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) { @@ -29,6 +34,10 @@ void lvgl_devices_attach() { } struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE); + // Same placeholder situation as the display above. + if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) { + kernel_pointer_device = NULL; + } 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) { From 537e7e1067804deda5b64671c6fa78e5dbee0b9e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 23:27:53 +0200 Subject: [PATCH 15/16] Fix for crash --- Tactility/Source/app/boot/Boot.cpp | 5 ++++ .../app/kerneldisplay/KernelDisplay.cpp | 24 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 5691e5fa7..0838c6803 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -84,6 +84,11 @@ class BootApp : public App { static void setupKernelDisplay() { auto* display = device_find_first_by_type(&DISPLAY_TYPE); + // Boards not yet migrated to the kernel display driver register a placeholder device (so + // the devicetree node resolves) with a NULL api - nothing for this function to act on. + if (display != nullptr && device_get_driver(display)->api == nullptr) { + display = nullptr; + } if (display != nullptr) { Device* backlight; if (display_get_backlight(display, &backlight) == ERROR_NONE) { diff --git a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp index 1d554ade5..f1d2c0370 100644 --- a/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp +++ b/Tactility/Source/app/kerneldisplay/KernelDisplay.cpp @@ -1,24 +1,19 @@ -#include "../../../../TactilityKernel/include/tactility/drivers/display.h" -#include "../../../../TactilityKernel/include/tactility/error.h" - - -#include - +#include +#include #include +#include +#include +#include +#include +#include #ifdef ESP_PLATFORM #include #endif - #include #include #include -#include -#include -#include -#include - #include namespace tt::app::kerneldisplay { @@ -28,6 +23,11 @@ constexpr auto* TAG = "KernelDisplay"; static Device* getBacklightDevice() { Device* display = device_find_first_by_type(&DISPLAY_TYPE); check(display); + // Boards not yet migrated to the kernel display driver register a placeholder device (so the + // devicetree node resolves) with a NULL api - nothing for display_get_backlight() to act on. + if (device_get_driver(display)->api == nullptr) { + return nullptr; + } Device* backlight = nullptr; return display_get_backlight(display, &backlight) == ERROR_NONE ? backlight : nullptr; } From 3c9fa44d708197d1a6b0d223420c828a5cd252b2 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 11 Jul 2026 23:48:37 +0200 Subject: [PATCH 16/16] Add missing header --- Devices/lilygo-tdeck-plus/source/module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Devices/lilygo-tdeck-plus/source/module.cpp b/Devices/lilygo-tdeck-plus/source/module.cpp index 1b4b4a863..89f684cc6 100644 --- a/Devices/lilygo-tdeck-plus/source/module.cpp +++ b/Devices/lilygo-tdeck-plus/source/module.cpp @@ -13,6 +13,7 @@ #include #include +#include #include