diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f2d0aaf1..bce5ea474 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,10 @@ if (DEFINED ENV{ESP_IDF_VERSION}) else () message("Building for sim target") + # Devices/simulator/Source/Simulator.cpp always defines its own hardwareConfiguration; without + # this, Firmware/Source/Main.cpp's #else branch also defines an empty one (its ESP32-only + # fallback for devices migrated off the deprecated HAL), causing a duplicate-definition link error. + add_compile_definitions(CONFIG_TT_USE_DEPRECATED_HAL) add_compile_definitions(CONFIG_TT_DEVICE_ID="simulator") add_compile_definitions(CONFIG_TT_DEVICE_NAME="Simulator") add_compile_definitions(CONFIG_TT_DEVICE_VENDOR="") diff --git a/Devices/cyd-2432s024c/CMakeLists.txt b/Devices/cyd-2432s024c/CMakeLists.txt index 6774053f4..a832a83a2 100644 --- a/Devices/cyd-2432s024c/CMakeLists.txt +++ b/Devices/cyd-2432s024c/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x CST816S PwmBacklight driver vfs fatfs + REQUIRES TactilityKernel driver ) diff --git a/Devices/cyd-2432s024c/Source/Configuration.cpp b/Devices/cyd-2432s024c/Source/Configuration.cpp deleted file mode 100644 index 1674ceca4..000000000 --- a/Devices/cyd-2432s024c/Source/Configuration.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "devices/Display.h" -#include - -#include -#include - -static bool initBoot() { - // Set the RGB LED Pins to output and turn them off - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green - ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue - - // 0 on, 1 off - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue - - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT); -} - -static tt::hal::DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const tt::hal::Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/cyd-2432s024c/Source/devices/Display.cpp b/Devices/cyd-2432s024c/Source/devices/Display.cpp deleted file mode 100644 index cca60c3a3..000000000 --- a/Devices/cyd-2432s024c/Source/devices/Display.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "Display.h" -#include "Cst816Touch.h" - -#include -#include - -static std::shared_ptr createTouch() { - // Note: GPIO 25 for reset and GPIO 21 for interrupt? - auto configuration = std::make_unique( - I2C_NUM_0, - 240, - 320 - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - Ili934xDisplay::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 0, - .gapY = 0, - .swapXY = false, - .mirrorX = true, - .mirrorY = false, - .invertColor = false, - .swapBytes = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = createTouch(), - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_NC, - .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR - }; - - auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration, true); -} diff --git a/Devices/cyd-2432s024c/Source/devices/Display.h b/Devices/cyd-2432s024c/Source/devices/Display.h deleted file mode 100644 index da6b56e62..000000000 --- a/Devices/cyd-2432s024c/Source/devices/Display.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Display -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_27; -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_15; -constexpr auto LCD_PIN_DC = GPIO_NUM_2; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 320; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; -constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; - -std::shared_ptr createDisplay(); diff --git a/Devices/cyd-2432s024c/Source/module.cpp b/Devices/cyd-2432s024c/Source/module.cpp deleted file mode 100644 index cf80884a4..000000000 --- a/Devices/cyd-2432s024c/Source/module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -extern "C" { - -static error_t start() { - // Empty for now - return ERROR_NONE; -} - -static error_t stop() { - // Empty for now - return ERROR_NONE; -} - -struct Module cyd_2432s024c_module = { - .name = "cyd-2432s024c", - .start = start, - .stop = stop, - .symbols = nullptr, - .internal = nullptr -}; - -} diff --git a/Devices/cyd-2432s024c/cyd,2432s024c.dts b/Devices/cyd-2432s024c/cyd,2432s024c.dts index 5d14f02d9..a082d5136 100644 --- a/Devices/cyd-2432s024c/cyd,2432s024c.dts +++ b/Devices/cyd-2432s024c/cyd,2432s024c.dts @@ -6,7 +6,9 @@ #include #include #include -#include +#include +#include +#include / { compatible = "root"; @@ -28,6 +30,22 @@ clock-frequency = <400000>; pin-sda = <&gpio0 33 GPIO_FLAG_NONE>; pin-scl = <&gpio0 32 GPIO_FLAG_NONE>; + + touch { + compatible = "hynitron,cst816s"; + reg = <0x15>; + x-max = <240>; + y-max = <320>; + }; + }; + + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <512>; }; spi0 { @@ -36,9 +54,16 @@ cs-gpios = <&gpio0 15 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; - - display { - compatible = "display-placeholder"; + + display@0 { + compatible = "ilitek,ili9341"; + horizontal-resolution = <240>; + vertical-resolution = <320>; + mirror-x; + bgr-order; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; diff --git a/Devices/cyd-2432s024c/device.properties b/Devices/cyd-2432s024c/device.properties index 0f2a5c1de..2b710227f 100644 --- a/Devices/cyd-2432s024c/device.properties +++ b/Devices/cyd-2432s024c/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=2.4" diff --git a/Devices/cyd-2432s024c/devicetree.yaml b/Devices/cyd-2432s024c/devicetree.yaml index 18b3502e0..8a9a77296 100644 --- a/Devices/cyd-2432s024c/devicetree.yaml +++ b/Devices/cyd-2432s024c/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9341-module + - Drivers/cst816s-module dts: cyd,2432s024c.dts diff --git a/Devices/cyd-2432s024c/source/module.cpp b/Devices/cyd-2432s024c/source/module.cpp new file mode 100644 index 000000000..47010a742 --- /dev/null +++ b/Devices/cyd-2432s024c/source/module.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include + +extern "C" { + +static error_t start() { + // Set the RGB LED pins to output and turn them off (0 on, 1 off) + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red + gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green + gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue + + gpio_set_level(GPIO_NUM_4, 1); // Red + gpio_set_level(GPIO_NUM_16, 1); // Green + gpio_set_level(GPIO_NUM_17, 1); // Blue + + return ERROR_NONE; +} + +static error_t stop() { + return ERROR_NONE; +} + +Module cyd_2432s024c_module = { + .name = "cyd-2432s024c", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/elecrow-crowpanel-advance-35/CMakeLists.txt b/Devices/elecrow-crowpanel-advance-35/CMakeLists.txt index 36fd0a612..a832a83a2 100644 --- a/Devices/elecrow-crowpanel-advance-35/CMakeLists.txt +++ b/Devices/elecrow-crowpanel-advance-35/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI9488 GT911 PwmBacklight driver + REQUIRES TactilityKernel driver ) diff --git a/Devices/elecrow-crowpanel-advance-35/Source/Configuration.cpp b/Devices/elecrow-crowpanel-advance-35/Source/Configuration.cpp deleted file mode 100644 index 1e27e3438..000000000 --- a/Devices/elecrow-crowpanel-advance-35/Source/Configuration.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "devices/Display.h" - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(GPIO_NUM_38); -} - -static DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.cpp b/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.cpp deleted file mode 100644 index 8c7dbc61a..000000000 --- a/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.cpp +++ /dev/null @@ -1,41 +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, - 320, - 480 - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - auto touch = createTouch(); - - auto configuration = std::make_unique( - CROWPANEL_LCD_SPI_HOST, - CROWPANEL_LCD_PIN_CS, - CROWPANEL_LCD_PIN_DC, - CROWPANEL_LCD_HORIZONTAL_RESOLUTION, - CROWPANEL_LCD_VERTICAL_RESOLUTION, - touch, - false, - false, - false, - true - ); - - configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; - - auto display = std::make_shared(std::move(configuration)); - return std::reinterpret_pointer_cast(display); -} diff --git a/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.h b/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.h deleted file mode 100644 index 6a4f0d3bb..000000000 --- a/Devices/elecrow-crowpanel-advance-35/Source/devices/Display.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#define CROWPANEL_LCD_SPI_HOST SPI2_HOST -#define CROWPANEL_LCD_PIN_CS GPIO_NUM_40 -#define CROWPANEL_LCD_PIN_DC GPIO_NUM_41 // RS -#define CROWPANEL_LCD_HORIZONTAL_RESOLUTION 320 -#define CROWPANEL_LCD_VERTICAL_RESOLUTION 480 -#define CROWPANEL_LCD_SPI_TRANSFER_HEIGHT (CROWPANEL_LCD_VERTICAL_RESOLUTION / 10) - -std::shared_ptr createDisplay(); diff --git a/Devices/elecrow-crowpanel-advance-35/device.properties b/Devices/elecrow-crowpanel-advance-35/device.properties index 833744397..537a4838a 100644 --- a/Devices/elecrow-crowpanel-advance-35/device.properties +++ b/Devices/elecrow-crowpanel-advance-35/device.properties @@ -12,6 +12,8 @@ hardware.tinyUsb=true hardware.esptoolFlashFreq=120M hardware.bluetooth=true +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=3.5" diff --git a/Devices/elecrow-crowpanel-advance-35/devicetree.yaml b/Devices/elecrow-crowpanel-advance-35/devicetree.yaml index f2e8c40b4..60461df6a 100644 --- a/Devices/elecrow-crowpanel-advance-35/devicetree.yaml +++ b/Devices/elecrow-crowpanel-advance-35/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9488-module + - Drivers/gt911-module dts: elecrow,crowpanel-advance-35.dts diff --git a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts index 87b6ab5dc..d4b106b86 100644 --- a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts +++ b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts @@ -8,7 +8,9 @@ #include #include #include -#include +#include +#include +#include / { compatible = "root"; @@ -35,6 +37,22 @@ clock-frequency = <400000>; pin-sda = <&gpio0 15 GPIO_FLAG_NONE>; pin-scl = <&gpio0 16 GPIO_FLAG_NONE>; + + touch { + compatible = "goodix,gt911"; + reg = <0x5D>; + x-max = <320>; + y-max = <480>; + }; + }; + + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>; + frequency-hz = <512>; }; spi0 { @@ -43,9 +61,16 @@ cs-gpios = <&gpio0 40 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 39 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 42 GPIO_FLAG_NONE>; - - display { - compatible = "display-placeholder"; + + display@0 { + compatible = "ilitek,ili9488"; + horizontal-resolution = <320>; + vertical-resolution = <480>; + invert-color; + color-conversion-buffer-size = <15360>; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 41 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; diff --git a/Devices/elecrow-crowpanel-advance-35/Source/module.cpp b/Devices/elecrow-crowpanel-advance-35/source/module.cpp similarity index 74% rename from Devices/elecrow-crowpanel-advance-35/Source/module.cpp rename to Devices/elecrow-crowpanel-advance-35/source/module.cpp index b25d82be5..829e89181 100644 --- a/Devices/elecrow-crowpanel-advance-35/Source/module.cpp +++ b/Devices/elecrow-crowpanel-advance-35/source/module.cpp @@ -3,16 +3,14 @@ extern "C" { static error_t start() { - // Empty for now return ERROR_NONE; } static error_t stop() { - // Empty for now return ERROR_NONE; } -struct Module elecrow_crowpanel_advance_35_module = { +Module elecrow_crowpanel_advance_35_module = { .name = "elecrow-crowpanel-advance-35", .start = start, .stop = stop, diff --git a/Devices/elecrow-crowpanel-basic-35/CMakeLists.txt b/Devices/elecrow-crowpanel-basic-35/CMakeLists.txt index fd619a226..a832a83a2 100644 --- a/Devices/elecrow-crowpanel-basic-35/CMakeLists.txt +++ b/Devices/elecrow-crowpanel-basic-35/CMakeLists.txt @@ -1,7 +1,6 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI9488 XPT2046 PwmBacklight driver + REQUIRES TactilityKernel driver ) diff --git a/Devices/elecrow-crowpanel-basic-35/Source/Configuration.cpp b/Devices/elecrow-crowpanel-basic-35/Source/Configuration.cpp deleted file mode 100644 index a03e3856d..000000000 --- a/Devices/elecrow-crowpanel-basic-35/Source/Configuration.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "PwmBacklight.h" -#include "devices/Display.h" - -#include - -using namespace tt::hal; - -static bool initBoot() { - driver::pwmbacklight::init(GPIO_NUM_27); - // Delay is required, or GUI will lock up. - // It's possibly related to the increased power draw when turning on the backlight. - vTaskDelay(100); - return true; -} - -static DeviceVector createDevices() { - return { - createDisplay(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.cpp b/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.cpp deleted file mode 100644 index bef72235e..000000000 --- a/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "Display.h" - -#include -#include -#include - -std::shared_ptr createTouch() { - auto configuration = std::make_unique( - CROWPANEL_LCD_SPI_HOST, - CROWPANEL_TOUCH_PIN_CS, - 320, - 480, - false, - false, - true - ); - - return std::make_shared(std::move(configuration)); -} - -std::shared_ptr createDisplay() { - auto touch = createTouch(); - - auto configuration = std::make_unique( - CROWPANEL_LCD_SPI_HOST, - CROWPANEL_LCD_PIN_CS, - CROWPANEL_LCD_PIN_DC, - 320, - 480, - touch, - false, - false, - false - ); - - configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; - - auto display = std::make_shared(std::move(configuration)); - return std::reinterpret_pointer_cast(display); -} diff --git a/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.h b/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.h deleted file mode 100644 index 5080412f8..000000000 --- a/Devices/elecrow-crowpanel-basic-35/Source/devices/Display.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -#define CROWPANEL_LCD_SPI_HOST SPI2_HOST -#define CROWPANEL_LCD_PIN_CS GPIO_NUM_15 -#define CROWPANEL_TOUCH_PIN_CS GPIO_NUM_12 -#define CROWPANEL_LCD_PIN_DC GPIO_NUM_2 // RS -#define CROWPANEL_LCD_HORIZONTAL_RESOLUTION 320 -#define CROWPANEL_LCD_VERTICAL_RESOLUTION 240 -#define CROWPANEL_LCD_SPI_TRANSFER_HEIGHT (CROWPANEL_LCD_VERTICAL_RESOLUTION / 10) - -std::shared_ptr createDisplay(); diff --git a/Devices/elecrow-crowpanel-basic-35/device.properties b/Devices/elecrow-crowpanel-basic-35/device.properties index c9bb6094c..814042df4 100644 --- a/Devices/elecrow-crowpanel-basic-35/device.properties +++ b/Devices/elecrow-crowpanel-basic-35/device.properties @@ -7,6 +7,8 @@ hardware.target=ESP32 hardware.flashSize=4MB hardware.spiRam=false +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=3.5" diff --git a/Devices/elecrow-crowpanel-basic-35/devicetree.yaml b/Devices/elecrow-crowpanel-basic-35/devicetree.yaml index 04d739ccb..9d7187d9e 100644 --- a/Devices/elecrow-crowpanel-basic-35/devicetree.yaml +++ b/Devices/elecrow-crowpanel-basic-35/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/ili9488-module + - Drivers/xpt2046-module dts: elecrow,crowpanel-basic-35.dts 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 c64969a02..764b11c67 100644 --- a/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts +++ b/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts @@ -7,8 +7,9 @@ #include #include #include -#include -#include +#include +#include +#include / { compatible = "root"; @@ -32,6 +33,15 @@ pin-scl = <&gpio0 21 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; @@ -41,13 +51,22 @@ pin-miso = <&gpio0 33 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; max-transfer-size = <65536>; - + display@0 { - compatible = "display-placeholder"; + compatible = "ilitek,ili9488"; + horizontal-resolution = <320>; + vertical-resolution = <480>; + color-conversion-buffer-size = <15360>; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 2 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; touch@1 { - compatible = "pointer-placeholder"; + compatible = "xptek,xpt2046"; + x-max = <320>; + y-max = <480>; + mirror-y; }; }; diff --git a/Devices/elecrow-crowpanel-basic-35/Source/module.cpp b/Devices/elecrow-crowpanel-basic-35/source/module.cpp similarity index 74% rename from Devices/elecrow-crowpanel-basic-35/Source/module.cpp rename to Devices/elecrow-crowpanel-basic-35/source/module.cpp index 736f03f78..f7c18653c 100644 --- a/Devices/elecrow-crowpanel-basic-35/Source/module.cpp +++ b/Devices/elecrow-crowpanel-basic-35/source/module.cpp @@ -3,16 +3,14 @@ extern "C" { static error_t start() { - // Empty for now return ERROR_NONE; } static error_t stop() { - // Empty for now return ERROR_NONE; } -struct Module elecrow_crowpanel_basic_35_module = { +Module elecrow_crowpanel_basic_35_module = { .name = "elecrow-crowpanel-basic-35", .start = start, .stop = stop, diff --git a/Devices/m5stack-cardputer-adv/CMakeLists.txt b/Devices/m5stack-cardputer-adv/CMakeLists.txt index 931bc0ab1..09a2fdee3 100644 --- a/Devices/m5stack-cardputer-adv/CMakeLists.txt +++ b/Devices/m5stack-cardputer-adv/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs TCA8418 + INCLUDE_DIRS "source" + REQUIRES TactilityKernel ) diff --git a/Devices/m5stack-cardputer-adv/Source/Configuration.cpp b/Devices/m5stack-cardputer-adv/Source/Configuration.cpp deleted file mode 100644 index 81001b2c2..000000000 --- a/Devices/m5stack-cardputer-adv/Source/Configuration.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "devices/Display.h" -#include "devices/CardputerKeyboard.h" -#include "devices/CardputerPower.h" - -#include -#include - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512); -} - -static DeviceVector createDevices() { - auto tca8418 = std::make_shared(device_find_by_name("i2c_internal")); - return { - createDisplay(), - tca8418, - std::make_shared(tca8418), - std::make_shared() - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.cpp b/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.cpp deleted file mode 100644 index 80177eeca..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "CardputerKeyboard.h" -#include - -constexpr auto* TAG = "CardputerKeyb"; - -constexpr auto KB_ROWS = 14; -constexpr auto KB_COLS = 4; - -// Lowercase Keymap -static constexpr char keymap_lc[KB_COLS][KB_ROWS] = { - {'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '=', LV_KEY_BACKSPACE}, - {'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'}, - {'\0', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', LV_KEY_ENTER}, - {'\0', '\0', '\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' '} -}; - -// Uppercase Keymap -static constexpr char keymap_uc[KB_COLS][KB_ROWS] = { - {'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', LV_KEY_DEL}, - {'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'}, - {'\0', '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', LV_KEY_ENTER}, - {'\0', '\0', '\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' '} -}; - -// Symbol Keymap -static constexpr char keymap_sy[KB_COLS][KB_ROWS] = { - {LV_KEY_ESC, '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}, - {'\t', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}, - {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '\0', LV_KEY_ENTER}, - {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_LEFT, LV_KEY_NEXT, LV_KEY_RIGHT, '\0'} -}; - -void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { - auto keyboard = static_cast(lv_indev_get_user_data(indev)); - char keypress = 0; - - if (xQueueReceive(keyboard->queue, &keypress, 0) == pdPASS) { - data->key = keypress; - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->key = 0; - data->state = LV_INDEV_STATE_RELEASED; - } -} - -void CardputerKeyboard::remap(uint8_t& row, uint8_t& col) { - // Col - uint8_t coltemp = row * 2; - if (col > 3) coltemp++; - - // Row - uint8_t rowtemp = (col + 4) % 4; - - row = rowtemp; - col = coltemp; -} - -void CardputerKeyboard::processKeyboard() { - static bool shift_pressed = false; - static bool sym_pressed = false; - static bool cap_toggle = false; - static bool cap_toggle_armed = true; - - if (keypad->update()) { - // Check if symbol or shift is pressed - for (int i = 0; i < keypad->pressed_key_count; i++) { - // Swap rows and columns - uint8_t row = keypad->pressed_list[i].row; - uint8_t column = keypad->pressed_list[i].col; - remap(row, column); - - if ((row == 2) && (column == 0)) { - sym_pressed = true; - } - if ((row == 2) && (column == 1)) { - shift_pressed = true; - } - } - - // Toggle caps lock - if ((sym_pressed && shift_pressed) && cap_toggle_armed) { - cap_toggle = !cap_toggle; - cap_toggle_armed = false; - } - - // Process regular key input given the processed modifiers - for (int i = 0; i < keypad->pressed_key_count; i++) { - auto row = keypad->pressed_list[i].row; - auto column = keypad->pressed_list[i].col; - remap(row, column); - char chr = '\0'; - if (sym_pressed) { - chr = keymap_sy[row][column]; - } else if (shift_pressed || cap_toggle) { - chr = keymap_uc[row][column]; - } else { - chr = keymap_lc[row][column]; - } - - if (chr != '\0') xQueueSend(queue, &chr, 50 / portTICK_PERIOD_MS); - } - - for (int i = 0; i < keypad->released_key_count; i++) { - auto row = keypad->released_list[i].row; - auto column = keypad->released_list[i].col; - remap(row, column); - - if ((row == 2) && (column == 0)) { - sym_pressed = false; - } - if ((row == 2) && (column == 1)) { - shift_pressed = false; - } - } - - if ((!sym_pressed && !shift_pressed) && !cap_toggle_armed) { - cap_toggle_armed = true; - } - } -} - -bool CardputerKeyboard::startLvgl(lv_display_t* display) { - keypad->init(7, 8); - - assert(inputTimer == nullptr); - inputTimer = std::make_unique(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] { - processKeyboard(); - }); - - kbHandle = lv_indev_create(); - lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD); - lv_indev_set_read_cb(kbHandle, &readCallback); - lv_indev_set_display(kbHandle, display); - lv_indev_set_user_data(kbHandle, this); - - inputTimer->start(); - - return true; -} - -bool CardputerKeyboard::stopLvgl() { - assert(inputTimer); - inputTimer->stop(); - inputTimer = nullptr; - - lv_indev_delete(kbHandle); - kbHandle = nullptr; - return true; -} - -bool CardputerKeyboard::isAttached() const { - return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE; -} diff --git a/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.h b/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.h deleted file mode 100644 index 89f95ba9c..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -class CardputerKeyboard final : public tt::hal::keyboard::KeyboardDevice { - - lv_indev_t* kbHandle = nullptr; - QueueHandle_t queue = nullptr; - - std::shared_ptr keypad; - std::unique_ptr inputTimer; - - void processKeyboard(); - - static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); - - /** - * Remaps wiring coordinates to keyboard mapping coordinates. - * Wiring is 7x8 (rows & colums), but our keyboard definition is 4x14) - */ - static void remap(uint8_t& row, uint8_t& column); - -public: - - explicit CardputerKeyboard(const std::shared_ptr& tca) : keypad(tca) { - queue = xQueueCreate(20, sizeof(char)); - } - - ~CardputerKeyboard() override { - vQueueDelete(queue); - } - - std::string getName() const override { return "TCA8418"; } - std::string getDescription() const override { return "TCA8418 I2C keyboard"; } - - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - - bool isAttached() const override; - lv_indev_t* getLvglIndev() override { return kbHandle; } -}; diff --git a/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.cpp b/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.cpp deleted file mode 100644 index ab8c6df74..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "CardputerPower.h" - -#include -#include - -constexpr auto* TAG = "CardputerPower"; - -bool CardputerPower::adcInitCalibration() { - bool calibrated = false; - - esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT); - if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) { - LOG_W(TAG, "Calibration scheme not supported, skip software calibration"); - } else if (efuse_read_result == ESP_ERR_INVALID_VERSION) { - LOG_W(TAG, "eFuse not burnt, skip software calibration"); - } else if (efuse_read_result == ESP_OK) { - calibrated = true; - LOG_I(TAG, "Calibration success"); - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics); - } else { - LOG_W(TAG, "eFuse read failed, skipping calibration"); - } - - return calibrated; -} - -uint32_t CardputerPower::adcReadValue() const { - int adc_raw = adc1_get_raw(ADC1_CHANNEL_9); - LOG_D(TAG, "Raw data: %d", adc_raw); - float voltage; - if (calibrated) { - voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics); - LOG_D(TAG, "Calibrated data: %f mV", voltage); - } else { - voltage = 0.0f; - } - return voltage; -} - -bool CardputerPower::ensureInitialized() { - if (!initialized) { - calibrated = adcInitCalibration(); - - if (adc1_config_width(static_cast(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) { - LOG_E(TAG, "ADC1 config width failed"); - return false; - } - if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) { - LOG_E(TAG, "ADC1 config attenuation failed"); - return false; - } - - initialized = true; - } - - return true; -} - -bool CardputerPower::supportsMetric(MetricType type) const { - switch (type) { - using enum MetricType; - case BatteryVoltage: - case ChargeLevel: - return true; - default: - return false; - } -} - -bool CardputerPower::getMetric(MetricType type, MetricData& data) { - if (!ensureInitialized()) { - return false; - } - - switch (type) { - case MetricType::BatteryVoltage: - data.valueAsUint32 = adcReadValue() * 2; - return true; - case MetricType::ChargeLevel: - data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2); - return true; - default: - return false; - } -} diff --git a/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.h b/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.h deleted file mode 100644 index 76b71260f..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/CardputerPower.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using tt::hal::power::PowerDevice; - -class CardputerPower final : public PowerDevice { - - ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); - bool initialized = false; - esp_adc_cal_characteristics_t adcCharacteristics; - bool calibrated = false; - - bool adcInitCalibration(); - uint32_t adcReadValue() const; - - bool ensureInitialized(); - -public: - - std::string getName() const override { return "Cardputer Power"; } - std::string getDescription() const override { return "Power measurement via ADC"; } - - bool supportsMetric(MetricType type) const override; - bool getMetric(MetricType type, MetricData& data) override; -}; diff --git a/Devices/m5stack-cardputer-adv/Source/devices/Display.cpp b/Devices/m5stack-cardputer-adv/Source/devices/Display.cpp deleted file mode 100644 index 1af307320..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/Display.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "Display.h" - -#include -#include - -std::shared_ptr createDisplay() { - St7789Display::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom - .gapY = 40, - .swapXY = true, - .mirrorX = true, - .mirrorY = false, - .invertColor = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = nullptr, - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = LCD_PIN_RESET, - .lvglSwapBytes = false - }; - - auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 62'500'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration); -} diff --git a/Devices/m5stack-cardputer-adv/Source/devices/Display.h b/Devices/m5stack-cardputer-adv/Source/devices/Display.h deleted file mode 100644 index bb1aebbd8..000000000 --- a/Devices/m5stack-cardputer-adv/Source/devices/Display.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_37; -constexpr auto LCD_PIN_DC = GPIO_NUM_34; // RS -constexpr auto LCD_PIN_RESET = GPIO_NUM_33; -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_38; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 135; -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/m5stack-cardputer-adv/device.properties b/Devices/m5stack-cardputer-adv/device.properties index ab63ce706..cd1340bc2 100644 --- a/Devices/m5stack-cardputer-adv/device.properties +++ b/Devices/m5stack-cardputer-adv/device.properties @@ -10,6 +10,8 @@ hardware.tinyUsb=true hardware.esptoolFlashFreq=120M hardware.bluetooth=true +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=1.14" diff --git a/Devices/m5stack-cardputer-adv/devicetree.yaml b/Devices/m5stack-cardputer-adv/devicetree.yaml index d6b380731..67fa747f0 100644 --- a/Devices/m5stack-cardputer-adv/devicetree.yaml +++ b/Devices/m5stack-cardputer-adv/devicetree.yaml @@ -1,4 +1,6 @@ dependencies: - Platforms/platform-esp32 - Drivers/bmi270-module +- Drivers/st7789-module +- Drivers/m5stack-module dts: m5stack,cardputer-adv.dts diff --git a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts index 0a142a147..535dd0ebd 100644 --- a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts +++ b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts @@ -1,6 +1,8 @@ /dts-v1/; #include +#include +#include #include #include #include @@ -11,7 +13,10 @@ #include #include #include -#include +#include + +#include +#include // Reference: https://docs.m5stack.com/en/core/Cardputer-Adv / { @@ -33,6 +38,21 @@ gpio-count = <49>; }; + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + // GPIO10 (ADC1_CHANNEL_9 on ESP32-S3): battery voltage sense, behind a 1:2 divider. + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + i2c_internal { compatible = "espressif,esp32-i2c"; port = ; @@ -44,6 +64,11 @@ compatible = "bosch,bmi270"; reg = <0x69>; }; + + keyboard { + compatible = "m5stack,cardputer-adv-keyboard"; + reg = <0x34>; + }; }; port_a: grove0 { @@ -56,15 +81,36 @@ i2cClockFrequency = <400000>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; cs-gpios = <&gpio0 37 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 35 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>; - - display { - compatible = "display-placeholder"; + + display@0 { + compatible = "sitronix,st7789"; + horizontal-resolution = <240>; + vertical-resolution = <135>; + // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom + gap-x = <53>; + gap-y = <40>; + swap-xy; + mirror-x; + invert-color; + pixel-clock-hz = <62500000>; + pin-dc = <&gpio0 34 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 33 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; diff --git a/Devices/m5stack-cardputer-adv/Source/module.cpp b/Devices/m5stack-cardputer-adv/source/module.cpp similarity index 100% rename from Devices/m5stack-cardputer-adv/Source/module.cpp rename to Devices/m5stack-cardputer-adv/source/module.cpp diff --git a/Devices/m5stack-cardputer/CMakeLists.txt b/Devices/m5stack-cardputer/CMakeLists.txt index 1e525df34..09a2fdee3 100644 --- a/Devices/m5stack-cardputer/CMakeLists.txt +++ b/Devices/m5stack-cardputer/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs + INCLUDE_DIRS "source" + REQUIRES TactilityKernel ) diff --git a/Devices/m5stack-cardputer/Source/Configuration.cpp b/Devices/m5stack-cardputer/Source/Configuration.cpp deleted file mode 100644 index cb40a59d5..000000000 --- a/Devices/m5stack-cardputer/Source/Configuration.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "devices/Display.h" -#include "devices/CardputerEncoder.h" -#include "devices/CardputerKeyboard.h" -#include "devices/CardputerPower.h" - -#include -#include - -using namespace tt::hal; - -static bool initBoot() { - return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512); -} - -static DeviceVector createDevices() { - return { - createDisplay(), - std::make_shared(), - std::make_shared(), - std::make_shared() - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.cpp b/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.cpp deleted file mode 100644 index 07b8a7018..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "CardputerEncoder.h" - -void CardputerEncoder::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { - CardputerEncoder* self = static_cast(lv_indev_get_user_data(indev)); - - self->keyboard.updateKeyList(); - - data->state = LV_INDEV_STATE_RELEASED; - - if (self->keyboard.keyList().size() != self->lastKeyNum) { - // If key pressed - if (self->keyboard.keyList().size() != 0) { - // Update states and values - self->keyboard.updateKeysState(); - if (self->keyboard.keysState().fn) { - if (self->keyboard.keysState().enter) { - data->key = LV_KEY_ENTER; - data->state = LV_INDEV_STATE_PRESSED; - } else { - for (auto& i : self->keyboard.keysState().values) { - if (i == ';') { // Up - data->enc_diff = -1; - } else if (i == '.') { // Down - data->enc_diff = 1; - } - break; // We only care about the first value - } - } - } - self->lastKeyNum = self->keyboard.keyList().size(); - } else { - self->lastKeyNum = 0; - } - } -} - -bool CardputerEncoder::startLvgl(lv_display_t* display) { - keyboard.init(); - - lvglDevice = lv_indev_create(); - lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_ENCODER); - lv_indev_set_read_cb(lvglDevice, &readCallback); - lv_indev_set_display(lvglDevice, display); - lv_indev_set_user_data(lvglDevice, this); - - return true; -} - -bool CardputerEncoder::stopLvgl() { - lv_indev_delete(lvglDevice); - lvglDevice = nullptr; - - return true; -} diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.h b/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.h deleted file mode 100644 index 0deea817b..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerEncoder.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "keyboard/keyboard.h" - -/** - * Wrapper around the keyboard that uses the following buttons to simulate an encoder: - * - Up - * - Down - * - ok (fn + enter) - */ -class CardputerEncoder final : public tt::hal::encoder::EncoderDevice { - KEYBOARD::Keyboard keyboard; - int lastKeyNum = 0; - lv_indev_t* lvglDevice = nullptr; - - static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); - -public: - - std::string getName() const override { return "Cardputer Encoder"; } - std::string getDescription() const override { return "Cardputer keyboard up/down acting as encoder"; } - - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - - lv_indev_t* getLvglIndev() override { return lvglDevice; } -}; diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.cpp b/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.cpp deleted file mode 100644 index 8f9b45659..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "CardputerKeyboard.h" - -#include - -constexpr auto* TAG = "Keyboard"; - -bool CardputerKeyboard::startLvgl(lv_display_t* display) { - keyboard.init(); - - lvglDevice = lv_indev_create(); - lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_KEYPAD); - lv_indev_set_read_cb(lvglDevice, &readCallback); - lv_indev_set_display(lvglDevice, display); - lv_indev_set_user_data(lvglDevice, this); - - return true; -} - -bool CardputerKeyboard::stopLvgl() { - lv_indev_delete(lvglDevice); - lvglDevice = nullptr; - - return true; -} - -void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { - CardputerKeyboard* self = static_cast(lv_indev_get_user_data(indev)); - - data->key = 0; - data->state = LV_INDEV_STATE_RELEASED; - - self->keyboard.updateKeyList(); - - if (self->keyboard.keyList().size() != self->lastKeyNum) { - // If key pressed - if (self->keyboard.keyList().size() != 0) { - // Update states and values - self->keyboard.updateKeysState(); - if (!self->keyboard.keysState().fn) { - if (self->keyboard.keysState().enter) { - data->key = LV_KEY_ENTER; - data->state = LV_INDEV_STATE_PRESSED; - } else if (self->keyboard.keysState().space) { - data->key = ' '; - data->state = LV_INDEV_STATE_PRESSED; - } else if (self->keyboard.keysState().del) { - data->key = LV_KEY_BACKSPACE; - data->state = LV_INDEV_STATE_PRESSED; - } else { - // Normal chars - for (auto& i : self->keyboard.keysState().values) { - data->key = i; - data->state = LV_INDEV_STATE_PRESSED; - break; // We only support 1 keypress for now - } - } - } else { - if (self->keyboard.keysState().del) { - LOG_I(TAG, "del"); - data->key = LV_KEY_DEL; - data->state = LV_INDEV_STATE_PRESSED; - } else { - for (auto& i : self->keyboard.keysState().values) { - if (i == ';') { // Up - /* - * WARNING: - * lv_switch picks up on this and toggles it, while the CardputerEncoder uses it for scrolling. - * We disable the keypress so the encoder can work properly. - */ - // TODO: Can we detect the active widget and ignore it in case it's a switch? - // data->key = LV_KEY_UP; - // data->state = LV_INDEV_STATE_PRESSED; - } else if (i == '.') { // Down - /* - * WARNING: - * lv_switch picks up on this and toggles it, while the CardputerEncoder uses it for scrolling. - * We disable the keypress so the encoder can work properly. - */ - // TODO: Can we detect the active widget and ignore it in case it's a switch? - // data->key = LV_KEY_DOWN; - // data->state = LV_INDEV_STATE_PRESSED; - } else if (i == ',') { // Left - data->key = LV_KEY_LEFT; - data->state = LV_INDEV_STATE_PRESSED; - } else if (i == '/') { // Right - data->key = LV_KEY_RIGHT; - data->state = LV_INDEV_STATE_PRESSED; - } else if (i == '`') { // Escape - data->key = LV_KEY_ESC; - data->state = LV_INDEV_STATE_PRESSED; - } - - break; // We only support 1 keypress for now - } - } - } - self->lastKeyNum = self->keyboard.keyList().size(); - } else { - self->lastKeyNum = 0; - } - } -} diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.h b/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.h deleted file mode 100644 index 4501ffbd7..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerKeyboard.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include - -#include "keyboard/keyboard.h" - -class CardputerKeyboard : public tt::hal::keyboard::KeyboardDevice { - KEYBOARD::Keyboard keyboard; - int lastKeyNum = 0; - lv_indev_t* lvglDevice = nullptr; - - static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); - -public: - - std::string getName() const override { return "Cardputer Keyboard"; } - std::string getDescription() const override { return "Cardputer internal keyboard"; } - - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - - bool isAttached() const override { return true; } - - lv_indev_t* getLvglIndev() override { return lvglDevice; } -}; diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerPower.cpp b/Devices/m5stack-cardputer/Source/devices/CardputerPower.cpp deleted file mode 100644 index ab8c6df74..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerPower.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "CardputerPower.h" - -#include -#include - -constexpr auto* TAG = "CardputerPower"; - -bool CardputerPower::adcInitCalibration() { - bool calibrated = false; - - esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT); - if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) { - LOG_W(TAG, "Calibration scheme not supported, skip software calibration"); - } else if (efuse_read_result == ESP_ERR_INVALID_VERSION) { - LOG_W(TAG, "eFuse not burnt, skip software calibration"); - } else if (efuse_read_result == ESP_OK) { - calibrated = true; - LOG_I(TAG, "Calibration success"); - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics); - } else { - LOG_W(TAG, "eFuse read failed, skipping calibration"); - } - - return calibrated; -} - -uint32_t CardputerPower::adcReadValue() const { - int adc_raw = adc1_get_raw(ADC1_CHANNEL_9); - LOG_D(TAG, "Raw data: %d", adc_raw); - float voltage; - if (calibrated) { - voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics); - LOG_D(TAG, "Calibrated data: %f mV", voltage); - } else { - voltage = 0.0f; - } - return voltage; -} - -bool CardputerPower::ensureInitialized() { - if (!initialized) { - calibrated = adcInitCalibration(); - - if (adc1_config_width(static_cast(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) { - LOG_E(TAG, "ADC1 config width failed"); - return false; - } - if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) { - LOG_E(TAG, "ADC1 config attenuation failed"); - return false; - } - - initialized = true; - } - - return true; -} - -bool CardputerPower::supportsMetric(MetricType type) const { - switch (type) { - using enum MetricType; - case BatteryVoltage: - case ChargeLevel: - return true; - default: - return false; - } -} - -bool CardputerPower::getMetric(MetricType type, MetricData& data) { - if (!ensureInitialized()) { - return false; - } - - switch (type) { - case MetricType::BatteryVoltage: - data.valueAsUint32 = adcReadValue() * 2; - return true; - case MetricType::ChargeLevel: - data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2); - return true; - default: - return false; - } -} diff --git a/Devices/m5stack-cardputer/Source/devices/CardputerPower.h b/Devices/m5stack-cardputer/Source/devices/CardputerPower.h deleted file mode 100644 index 76b71260f..000000000 --- a/Devices/m5stack-cardputer/Source/devices/CardputerPower.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using tt::hal::power::PowerDevice; - -class CardputerPower final : public PowerDevice { - - ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); - bool initialized = false; - esp_adc_cal_characteristics_t adcCharacteristics; - bool calibrated = false; - - bool adcInitCalibration(); - uint32_t adcReadValue() const; - - bool ensureInitialized(); - -public: - - std::string getName() const override { return "Cardputer Power"; } - std::string getDescription() const override { return "Power measurement via ADC"; } - - bool supportsMetric(MetricType type) const override; - bool getMetric(MetricType type, MetricData& data) override; -}; diff --git a/Devices/m5stack-cardputer/Source/devices/Display.cpp b/Devices/m5stack-cardputer/Source/devices/Display.cpp deleted file mode 100644 index 1af307320..000000000 --- a/Devices/m5stack-cardputer/Source/devices/Display.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "Display.h" - -#include -#include - -std::shared_ptr createDisplay() { - St7789Display::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom - .gapY = 40, - .swapXY = true, - .mirrorX = true, - .mirrorY = false, - .invertColor = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = nullptr, - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = LCD_PIN_RESET, - .lvglSwapBytes = false - }; - - auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 62'500'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration); -} diff --git a/Devices/m5stack-cardputer/Source/devices/Display.h b/Devices/m5stack-cardputer/Source/devices/Display.h deleted file mode 100644 index bb1aebbd8..000000000 --- a/Devices/m5stack-cardputer/Source/devices/Display.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_37; -constexpr auto LCD_PIN_DC = GPIO_NUM_34; // RS -constexpr auto LCD_PIN_RESET = GPIO_NUM_33; -constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_38; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 240; -constexpr auto LCD_VERTICAL_RESOLUTION = 135; -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/m5stack-cardputer/Source/keyboard/README.md b/Devices/m5stack-cardputer/Source/keyboard/README.md deleted file mode 100644 index 8acfc1bee..000000000 --- a/Devices/m5stack-cardputer/Source/keyboard/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# keyboard folder - -The keyboard code in this folder is from https://github.com/m5stack/M5Cardputer-UserDemo/tree/main/main/hal/keyboard - -# License - -The original license is an MIT license: - -``` -MIT License - -Copyright (c) 2023 M5Stack - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` \ No newline at end of file diff --git a/Devices/m5stack-cardputer/Source/keyboard/keyboard.cpp b/Devices/m5stack-cardputer/Source/keyboard/keyboard.cpp deleted file mode 100644 index 1d28ef960..000000000 --- a/Devices/m5stack-cardputer/Source/keyboard/keyboard.cpp +++ /dev/null @@ -1,266 +0,0 @@ -/** - * @file keyboard.cpp - * @author Forairaaaaa - * @brief - * @version 0.1 - * @date 2023-09-22 - * - * @copyright Copyright (c) 2023 - * - */ -#include "keyboard.h" -#include - -#define digitalWrite(pin, level) gpio_set_level((gpio_num_t)pin, level) -#define digitalRead(pin) gpio_get_level((gpio_num_t)pin) - - -using namespace KEYBOARD; - -void Keyboard::_set_output(const std::vector& pinList, uint8_t output) -{ - output = output & 0B00000111; - - digitalWrite(pinList[0], (output & 0B00000001)); - digitalWrite(pinList[1], (output & 0B00000010)); - digitalWrite(pinList[2], (output & 0B00000100)); -} - - -uint8_t Keyboard::_get_input(const std::vector& pinList) -{ - uint8_t buffer = 0x00; - uint8_t pin_value = 0x00; - - for (int i = 0; i < 7; i++) - { - pin_value = (digitalRead(pinList[i]) == 1) ? 0x00 : 0x01; - pin_value = pin_value << i; - buffer = buffer | pin_value; - } - - return buffer; -} - - -void Keyboard::init() -{ - for (auto i : output_list) - { - gpio_reset_pin((gpio_num_t)i); - gpio_set_direction((gpio_num_t)i, GPIO_MODE_OUTPUT); - gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_PULLDOWN); - digitalWrite(i, 0); - } - - for (auto i : input_list) - { - gpio_reset_pin((gpio_num_t)i); - gpio_set_direction((gpio_num_t)i, GPIO_MODE_INPUT); - gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_ONLY); - } - - _set_output(output_list, 0); -} - - -Point2D_t Keyboard::getKey() -{ - Point2D_t coor; - coor.x = -1; - coor.y = -1; - - uint8_t input_value = 0; - - for (int i = 0; i < 8; i++) { - _set_output(output_list, i); - // printf("% 3d,\t", get_input(inputList)); - - input_value = _get_input(input_list); - - /* If key pressed */ - if (input_value) { - - /* Get X */ - for (int j = 0; j < 7; j++) { - if (input_value == X_map_chart[j].value) { - coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2; - break; - } - } - - /* Get Y */ - coor.y = (i > 3) ? (i - 4) : i; - - /* Keep the same as picture */ - coor.y = -coor.y; - coor.y = coor.y + 3; - - - break; - } - } - - // printf("%d,%d\n", x, y); - return coor; -} - - -uint8_t Keyboard::getKeyNum(Point2D_t keyCoor) -{ - uint8_t ret = 0; - - if ((keyCoor.x < 0) || (keyCoor.y < 0)) { - return 0; - } - - ret = (keyCoor.y * 14) + (keyCoor.x + 1); - - return ret; -} - - -void Keyboard::updateKeyList() -{ - _key_list_buffer.clear(); - - Point2D_t coor; - - uint8_t input_value = 0; - - for (int i = 0; i < 8; i++) { - _set_output(output_list, i); - input_value = _get_input(input_list); - - /* If key pressed */ - if (input_value) { - /* Get X */ - for (int j = 0; j < 7; j++) { - if (input_value & (0x01 << j)) { - coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2; - - /* Get Y */ - coor.y = (i > 3) ? (i - 4) : i; - // printf("%d,%d\t", coor.x, coor.y); - - /* Keep the same as picture */ - coor.y = -coor.y; - coor.y = coor.y + 3; - - _key_list_buffer.push_back(coor); - } - } - } - } -} - - -bool Keyboard::isKeyPressing(int keyNum) -{ - if (_key_list_buffer.size()) - { - for (const auto& i : _key_list_buffer) - { - if (getKeyNum(i) == keyNum) - return true; - } - } - return false; -} - - -#include - -void Keyboard::updateKeysState() -{ - _keys_state_buffer.reset(); - _key_values_without_special_keys.clear(); - - // Get special keys - for (auto& i : _key_list_buffer) - { - if (strcmp(getKeyValue(i).value_first, "tab") == 0) - { - _keys_state_buffer.tab = true; - continue; - } - - if (strcmp(getKeyValue(i).value_first, "fn") == 0) - { - _keys_state_buffer.fn = true; - continue; - } - - if (strcmp(getKeyValue(i).value_first, "shift") == 0) - { - _keys_state_buffer.shift = true; - continue; - } - - if (strcmp(getKeyValue(i).value_first, "ctrl") == 0) - { - _keys_state_buffer.ctrl = true; - continue; - } - if (strcmp(getKeyValue(i).value_first, "opt") == 0) - { - _keys_state_buffer.opt = true; - continue; - } - - if (strcmp(getKeyValue(i).value_first, "alt") == 0) - { - _keys_state_buffer.alt = true; - continue; - } - - if (strcmp(getKeyValue(i).value_first, "del") == 0) - { - _keys_state_buffer.del = true; - _keys_state_buffer.hidKey.push_back(KEY_BACKSPACE); - continue; - } - - if (strcmp(getKeyValue(i).value_first, "enter") == 0) - { - _keys_state_buffer.enter = true; - _keys_state_buffer.hidKey.push_back(KEY_ENTER); - continue; - } - - if (strcmp(getKeyValue(i).value_first, "space") == 0) - { - _keys_state_buffer.space = true; - _keys_state_buffer.hidKey.push_back(KEY_SPACE); - continue; - } - - _key_values_without_special_keys.push_back(i); - } - - // Deal what left - for (auto& i : _key_values_without_special_keys) - { - if (_keys_state_buffer.ctrl || _keys_state_buffer.shift || _is_caps_locked) - { - _keys_state_buffer.values.push_back(*getKeyValue(i).value_second); - _keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_second); - } - else - { - _keys_state_buffer.values.push_back(*getKeyValue(i).value_first); - _keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_first); - } - } -} - - -bool Keyboard::isChanged() -{ - if (_last_key_size != _key_list_buffer.size()) - { - _last_key_size = _key_list_buffer.size(); - return true; - } - return false; -} diff --git a/Devices/m5stack-cardputer/Source/keyboard/keyboard.h b/Devices/m5stack-cardputer/Source/keyboard/keyboard.h deleted file mode 100644 index 81a7a530a..000000000 --- a/Devices/m5stack-cardputer/Source/keyboard/keyboard.h +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @file keyboard.h - * @author Forairaaaaa - * @brief - * @version 0.1 - * @date 2023-09-22 - * - * @copyright Copyright (c) 2023 - * - */ -#pragma once -#include -#include -#include "keymap.h" - - -namespace KEYBOARD -{ - struct Chart_t - { - uint8_t value; - uint8_t x_1; - uint8_t x_2; - }; - - struct Point2D_t - { - int x; - int y; - }; - - const std::vector output_list = {8, 9, 11}; - const std::vector input_list = {13, 15, 3, 4, 5, 6, 7}; - // const std::vector input_list = {1, 2, 3, 4, 5, 6, 7}; - - const Chart_t X_map_chart[7] = - { - {1, 0, 1}, - {2, 2, 3}, - {4, 4, 5}, - {8, 6, 7}, - {16, 8, 9}, - {32, 10, 11}, - {64, 12, 13} - }; - - struct KeyValue_t - { - const char* value_first; - const int value_num_first; - const char* value_second; - const int value_num_second; - }; - - const KeyValue_t _key_value_map[4][14] = { - {{"`", KEY_GRAVE, "~", KEY_GRAVE}, - {"1", KEY_1, "!", KEY_1}, - {"2", KEY_2, "@", KEY_2}, - {"3", KEY_3, "#", KEY_3}, - {"4", KEY_4, "$", KEY_4}, - {"5", KEY_5, "%", KEY_5}, - {"6", KEY_6, "^", KEY_6}, - {"7", KEY_7, "&", KEY_7}, - {"8", KEY_8, "*", KEY_KPASTERISK}, - {"9", KEY_9, "(", KEY_KPLEFTPAREN}, - {"0", KEY_0, ")", KEY_KPRIGHTPAREN}, - {"-", KEY_MINUS, "_", KEY_KPMINUS}, - {"=", KEY_EQUAL, "+", KEY_KPPLUS}, - {"del", KEY_BACKSPACE, "del", KEY_BACKSPACE}}, - {{"tab", KEY_TAB, "tab", KEY_TAB}, - {"q", KEY_Q, "Q", KEY_Q}, - {"w", KEY_W, "W", KEY_W}, - {"e", KEY_E, "E", KEY_E}, - {"r", KEY_R, "R", KEY_R}, - {"t", KEY_T, "T", KEY_T}, - {"y", KEY_Y, "Y", KEY_Y}, - {"u", KEY_U, "U", KEY_U}, - {"i", KEY_I, "I", KEY_I}, - {"o", KEY_O, "O", KEY_O}, - {"p", KEY_P, "P", KEY_P}, - {"[", KEY_LEFTBRACE, "{", KEY_LEFTBRACE}, - {"]", KEY_RIGHTBRACE, "}", KEY_RIGHTBRACE}, - {"\\", KEY_BACKSLASH, "|", KEY_BACKSLASH}}, - {{"fn", 0, "fn", 0}, - {"shift", 0, "shift", 0}, - {"a", KEY_A, "A", KEY_A}, - {"s", KEY_S, "S", KEY_S}, - {"d", KEY_D, "D", KEY_D}, - {"f", KEY_F, "F", KEY_F}, - {"g", KEY_G, "G", KEY_G}, - {"h", KEY_H, "H", KEY_H}, - {"j", KEY_J, "J", KEY_J}, - {"k", KEY_K, "K", KEY_K}, - {"l", KEY_L, "L", KEY_L}, - {";", KEY_SEMICOLON, ":", KEY_SEMICOLON}, - {"'", KEY_APOSTROPHE, "\"", KEY_APOSTROPHE}, - {"enter", KEY_ENTER, "enter", KEY_ENTER}}, - {{"ctrl", KEY_LEFTCTRL, "ctrl", KEY_LEFTCTRL}, - {"opt", 0, "opt", 0}, - {"alt", KEY_LEFTALT, "alt", KEY_LEFTALT}, - {"z", KEY_Z, "Z", KEY_Z}, - {"x", KEY_X, "X", KEY_X}, - {"c", KEY_C, "C", KEY_C}, - {"v", KEY_V, "V", KEY_V}, - {"b", KEY_B, "B", KEY_B}, - {"n", KEY_N, "N", KEY_N}, - {"m", KEY_M, "M", KEY_M}, - {",", KEY_COMMA, "<", KEY_COMMA}, - {".", KEY_DOT, ">", KEY_DOT}, - {"/", KEY_KPSLASH, "?", KEY_KPSLASH}, - {"space", KEY_SPACE, "space", KEY_SPACE}}}; - - class Keyboard - { - public: - - struct KeysState - { - bool tab = false; - bool fn = false; - bool shift = false; - bool ctrl = false; - bool opt = false; - bool alt = false; - bool del = false; - bool enter = false; - bool space = false; - - std::vector values; - std::vector hidKey; - - void reset() - { - tab = false; - fn = false; - shift = false; - ctrl = false; - opt = false; - alt = false; - del = false; - enter = false; - space = false; - - values.clear(); - hidKey.clear(); - } - }; - - - private: - std::vector _key_list_buffer; - - std::vector _key_values_without_special_keys; - KeysState _keys_state_buffer; - - bool _is_caps_locked; - uint8_t _last_key_size; - - - void _set_output(const std::vector& pinList, uint8_t output); - uint8_t _get_input(const std::vector& pinList); - - - public: - Keyboard() : - _is_caps_locked(false), - _last_key_size(0) - {} - - void init(); - - Point2D_t getKey(); - - uint8_t getKeyNum(Point2D_t keyCoor); - - void updateKeyList(); - inline std::vector& keyList() { return _key_list_buffer; } - - inline KeyValue_t getKeyValue(const Point2D_t& keyCoor) { return _key_value_map[keyCoor.y][keyCoor.x]; } - - bool isKeyPressing(int keyNum); - - void updateKeysState(); - inline KeysState& keysState() { return _keys_state_buffer; } - - inline bool capslocked(void) { return _is_caps_locked; } - inline void setCapsLocked(bool isLocked) { _is_caps_locked = isLocked; } - - bool isChanged(); - inline uint8_t isPressed() { return _key_list_buffer.size(); } - }; -} diff --git a/Devices/m5stack-cardputer/Source/keyboard/keymap.h b/Devices/m5stack-cardputer/Source/keyboard/keymap.h deleted file mode 100644 index 14dafc639..000000000 --- a/Devices/m5stack-cardputer/Source/keyboard/keymap.h +++ /dev/null @@ -1,366 +0,0 @@ -/** - * This file is part of esp32s3-keyboard. - * - * Copyright (C) 2020-2021 Yuquan He - * (Institute of Computing Technology, Chinese Academy of Sciences) - * - * esp32s3-keyboard is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * esp32s3-keyboard is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with esp32s3-keyboard. If not, see . - */ - -/** - * USB HID format: 8 bytes - * Byte 0: Keyboard modifier bits (SHIFT, ALT, CTRL etc) - * Byte 1: reserved - * Byte 2-7: Up to six keyboard usage indexes representing the keys that are - * currently "pressed". Order is not important, a key is either - * pressed (present in the buffer) or not pressed. - */ - -#ifndef MY_KEYMAP_H -#define MY_KEYMAP_H - -#include - -/** - * Modifier masks - used for the first byte in the HID report. - * NOTE: The second byte in the report is reserved, 0x00 - */ -#define KEY_MOD_LCTRL 0x01 -#define KEY_MOD_LSHIFT 0x02 -#define KEY_MOD_LALT 0x04 -#define KEY_MOD_LMETA 0x08 -#define KEY_MOD_RCTRL 0x10 -#define KEY_MOD_RSHIFT 0x20 -#define KEY_MOD_RALT 0x40 -#define KEY_MOD_RMETA 0x80 - -/** - * Scan codes - last N slots in the HID report (usually 6). - * 0x00 if no key pressed. - * - * If more than N keys are pressed, the HID reports - * KEY_ERR_OVF in all slots to indicate this condition. - */ - -#define KEY_NONE 0x00 // No key pressed -#define KEY_ERR_OVF \ - 0x01 // Keyboard Error Roll Over - used for all slots if too many keys are - // pressed ("Phantom key") -// 0x02 // Keyboard POST Fail -// 0x03 // Keyboard Error Undefined -#define KEY_A 0x04 // Keyboard a and A -#define KEY_B 0x05 // Keyboard b and B -#define KEY_C 0x06 // Keyboard c and C -#define KEY_D 0x07 // Keyboard d and D -#define KEY_E 0x08 // Keyboard e and E -#define KEY_F 0x09 // Keyboard f and F -#define KEY_G 0x0a // Keyboard g and G -#define KEY_H 0x0b // Keyboard h and H -#define KEY_I 0x0c // Keyboard i and I -#define KEY_J 0x0d // Keyboard j and J -#define KEY_K 0x0e // Keyboard k and K -#define KEY_L 0x0f // Keyboard l and L -#define KEY_M 0x10 // Keyboard m and M -#define KEY_N 0x11 // Keyboard n and N -#define KEY_O 0x12 // Keyboard o and O -#define KEY_P 0x13 // Keyboard p and P -#define KEY_Q 0x14 // Keyboard q and Q -#define KEY_R 0x15 // Keyboard r and R -#define KEY_S 0x16 // Keyboard s and S -#define KEY_T 0x17 // Keyboard t and T -#define KEY_U 0x18 // Keyboard u and U -#define KEY_V 0x19 // Keyboard v and V -#define KEY_W 0x1a // Keyboard w and W -#define KEY_X 0x1b // Keyboard x and X -#define KEY_Y 0x1c // Keyboard y and Y -#define KEY_Z 0x1d // Keyboard z and Z - -#define KEY_1 0x1e // Keyboard 1 and ! -#define KEY_2 0x1f // Keyboard 2 and @ -#define KEY_3 0x20 // Keyboard 3 and # -#define KEY_4 0x21 // Keyboard 4 and $ -#define KEY_5 0x22 // Keyboard 5 and % -#define KEY_6 0x23 // Keyboard 6 and ^ -#define KEY_7 0x24 // Keyboard 7 and & -#define KEY_8 0x25 // Keyboard 8 and * -#define KEY_9 0x26 // Keyboard 9 and ( -#define KEY_0 0x27 // Keyboard 0 and ) - -#define KEY_ENTER 0x28 // Keyboard Return (ENTER) -#define KEY_ESC 0x29 // Keyboard ESCAPE -#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace) -#define KEY_TAB 0x2b // Keyboard Tab -#define KEY_SPACE 0x2c // Keyboard Spacebar -#define KEY_MINUS 0x2d // Keyboard - and _ -#define KEY_EQUAL 0x2e // Keyboard = and + -#define KEY_LEFTBRACE 0x2f // Keyboard [ and { -#define KEY_RIGHTBRACE 0x30 // Keyboard ] and } -#define KEY_BACKSLASH 0x31 // Keyboard \ and | -#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~ -#define KEY_SEMICOLON 0x33 // Keyboard ; and : -#define KEY_APOSTROPHE 0x34 // Keyboard ' and " -#define KEY_GRAVE 0x35 // Keyboard ` and ~ -#define KEY_COMMA 0x36 // Keyboard , and < -#define KEY_DOT 0x37 // Keyboard . and > -#define KEY_SLASH 0x38 // Keyboard / and ? -#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock - -#define KEY_F1 0x3a // Keyboard F1 -#define KEY_F2 0x3b // Keyboard F2 -#define KEY_F3 0x3c // Keyboard F3 -#define KEY_F4 0x3d // Keyboard F4 -#define KEY_F5 0x3e // Keyboard F5 -#define KEY_F6 0x3f // Keyboard F6 -#define KEY_F7 0x40 // Keyboard F7 -#define KEY_F8 0x41 // Keyboard F8 -#define KEY_F9 0x42 // Keyboard F9 -#define KEY_F10 0x43 // Keyboard F10 -#define KEY_F11 0x44 // Keyboard F11 -#define KEY_F12 0x45 // Keyboard F12 - -#define KEY_PRTSC 0x46 // Keyboard Print Screen -#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock -#define KEY_PAUSE 0x48 // Keyboard Pause -#define KEY_INSERT 0x49 // Keyboard Insert -#define KEY_HOME 0x4a // Keyboard Home -#define KEY_PAGEUP 0x4b // Keyboard Page Up -// #define KEY_DELETE 0x4c // Keyboard Delete Forward -#define KEY_DELETE 0xD4 // Keyboard Delete Forward -#define KEY_END 0x4d // Keyboard End -#define KEY_PAGEDOWN 0x4e // Keyboard Page Down -#define KEY_RIGHT 0x4f // Keyboard Right Arrow -#define KEY_LEFT 0x50 // Keyboard Left Arrow -#define KEY_DOWN 0x51 // Keyboard Down Arrow -#define KEY_UP 0x52 // Keyboard Up Arrow - -#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear -#define KEY_KPSLASH 0x54 // Keypad / -#define KEY_KPASTERISK 0x55 // Keypad * -#define KEY_KPMINUS 0x56 // Keypad - -#define KEY_KPPLUS 0x57 // Keypad + -#define KEY_KPENTER 0x58 // Keypad ENTER -#define KEY_KP1 0x59 // Keypad 1 and End -#define KEY_KP2 0x5a // Keypad 2 and Down Arrow -#define KEY_KP3 0x5b // Keypad 3 and PageDn -#define KEY_KP4 0x5c // Keypad 4 and Left Arrow -#define KEY_KP5 0x5d // Keypad 5 -#define KEY_KP6 0x5e // Keypad 6 and Right Arrow -#define KEY_KP7 0x5f // Keypad 7 and Home -#define KEY_KP8 0x60 // Keypad 8 and Up Arrow -#define KEY_KP9 0x61 // Keypad 9 and Page Up -#define KEY_KP0 0x62 // Keypad 0 and Insert -#define KEY_KPDOT 0x63 // Keypad . and Delete - -#define KEY_102ND 0x64 // Keyboard Non-US \ and | -#define KEY_COMPOSE 0x65 // Keyboard Application -#define KEY_POWER 0x66 // Keyboard Power -#define KEY_KPEQUAL 0x67 // Keypad = - -#define KEY_F13 0x68 // Keyboard F13 -#define KEY_F14 0x69 // Keyboard F14 -#define KEY_F15 0x6a // Keyboard F15 -#define KEY_F16 0x6b // Keyboard F16 -#define KEY_F17 0x6c // Keyboard F17 -#define KEY_F18 0x6d // Keyboard F18 -#define KEY_F19 0x6e // Keyboard F19 -#define KEY_F20 0x6f // Keyboard F20 -#define KEY_F21 0x70 // Keyboard F21 -#define KEY_F22 0x71 // Keyboard F22 -#define KEY_F23 0x72 // Keyboard F23 -#define KEY_F24 0x73 // Keyboard F24 - -#define KEY_OPEN 0x74 // Keyboard Execute -#define KEY_HELP 0x75 // Keyboard Help -#define KEY_PROPS 0x76 // Keyboard Menu -#define KEY_FRONT 0x77 // Keyboard Select -#define KEY_STOP 0x78 // Keyboard Stop -#define KEY_AGAIN 0x79 // Keyboard Again -#define KEY_UNDO 0x7a // Keyboard Undo -#define KEY_CUT 0x7b // Keyboard Cut -#define KEY_COPY 0x7c // Keyboard Copy -#define KEY_PASTE 0x7d // Keyboard Paste -#define KEY_FIND 0x7e // Keyboard Find -#define KEY_MUTE 0x7f // Keyboard Mute -#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up -#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down -// 0x82 Keyboard Locking Caps Lock -// 0x83 Keyboard Locking Num Lock -// 0x84 Keyboard Locking Scroll Lock -#define KEY_KPCOMMA 0x85 // Keypad Comma -// 0x86 Keypad Equal Sign -#define KEY_RO 0x87 // Keyboard International1 -#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2 -#define KEY_YEN 0x89 // Keyboard International3 -#define KEY_HENKAN 0x8a // Keyboard International4 -#define KEY_MUHENKAN 0x8b // Keyboard International5 -#define KEY_KPJPCOMMA 0x8c // Keyboard International6 -// 0x8d Keyboard International7 -// 0x8e Keyboard International8 -// 0x8f Keyboard International9 -#define KEY_HANGEUL 0x90 // Keyboard LANG1 -#define KEY_HANJA 0x91 // Keyboard LANG2 -#define KEY_KATAKANA 0x92 // Keyboard LANG3 -#define KEY_HIRAGANA 0x93 // Keyboard LANG4 -#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5 -// 0x95 Keyboard LANG6 -// 0x96 Keyboard LANG7 -// 0x97 Keyboard LANG8 -// 0x98 Keyboard LANG9 -// 0x99 Keyboard Alternate Erase -// 0x9a Keyboard SysReq/Attention -// 0x9b Keyboard Cancel -// 0x9c Keyboard Clear -// 0x9d Keyboard Prior -// 0x9e Keyboard Return -// 0x9f Keyboard Separator -// 0xa0 Keyboard Out -// 0xa1 Keyboard Oper -// 0xa2 Keyboard Clear/Again -// 0xa3 Keyboard CrSel/Props -// 0xa4 Keyboard ExSel - -// 0xb0 Keypad 00 -// 0xb1 Keypad 000 -// 0xb2 Thousands Separator -// 0xb3 Decimal Separator -// 0xb4 Currency Unit -// 0xb5 Currency Sub-unit -#define KEY_KPLEFTPAREN 0xb6 // Keypad ( -#define KEY_KPRIGHTPAREN 0xb7 // Keypad ) -// 0xb8 Keypad { -// 0xb9 Keypad } -// 0xba Keypad Tab -#define KEY_KPBACKSPACE 0xbb // Keypad Backspace -// 0xbc Keypad A -// 0xbd Keypad B -// 0xbe Keypad C -// 0xbf Keypad D -// 0xc0 Keypad E -// 0xc1 Keypad F -// 0xc2 Keypad XOR -// 0xc3 Keypad ^ -// 0xc4 Keypad % -// 0xc5 Keypad < -// 0xc6 Keypad > -// 0xc7 Keypad & -// 0xc8 Keypad && -// 0xc9 Keypad | -// 0xca Keypad || -// 0xcb Keypad : -// 0xcc Keypad # -// 0xcd Keypad Space -// 0xce Keypad @ -// 0xcf Keypad ! -// 0xd0 Keypad Memory Store -// 0xd1 Keypad Memory Recall -// 0xd2 Keypad Memory Clear -// 0xd3 Keypad Memory Add -// 0xd4 Keypad Memory Subtract -// 0xd5 Keypad Memory Multiply -// 0xd6 Keypad Memory Divide -// 0xd7 Keypad +/- -// 0xd8 Keypad Clear -// 0xd9 Keypad Clear Entry -// 0xda Keypad Binary -// 0xdb Keypad Octal -// 0xdc Keypad Decimal -// 0xdd Keypad Hexadecimal - -#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control -#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift -#define KEY_LEFTALT 0xe2 // Keyboard Left Alt -#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI -#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control -#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift -#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt -#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI - -#define KEY_MEDIA_PLAYPAUSE 0xe8 -#define KEY_MEDIA_STOPCD 0xe9 -#define KEY_MEDIA_PREVIOUSSONG 0xea -#define KEY_MEDIA_NEXTSONG 0xeb -#define KEY_MEDIA_EJECTCD 0xec -#define KEY_MEDIA_VOLUMEUP 0xed -#define KEY_MEDIA_VOLUMEDOWN 0xee -#define KEY_MEDIA_MUTE 0xef -#define KEY_MEDIA_WWW 0xf0 -#define KEY_MEDIA_BACK 0xf1 -#define KEY_MEDIA_FORWARD 0xf2 -#define KEY_MEDIA_STOP 0xf3 -#define KEY_MEDIA_FIND 0xf4 -#define KEY_MEDIA_SCROLLUP 0xf5 -#define KEY_MEDIA_SCROLLDOWN 0xf6 -#define KEY_MEDIA_EDIT 0xf7 -#define KEY_MEDIA_SLEEP 0xf8 -#define KEY_MEDIA_COFFEE 0xf9 -#define KEY_MEDIA_REFRESH 0xfa -#define KEY_MEDIA_CALC 0xfb - -enum { - // Generic Control - KEY_CONSUMER_CONTROL = 0x0001, - - // Power Control - KEY_CONSUMER_POWER = 0x0030, - KEY_CONSUMER_RESET = 0x0031, - KEY_CONSUMER_SLEEP = 0x0032, - - // Screen Brightness - KEY_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, - KEY_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, - - // These HID usages operate only on mobile systems (battery powered) and - // require Windows 8 (build 8302 or greater). - KEY_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, - KEY_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, - KEY_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, - KEY_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, - - // Media Control - KEY_CONSUMER_PLAY_PAUSE = 0x00CD, - KEY_CONSUMER_SCAN_NEXT = 0x00B5, - KEY_CONSUMER_SCAN_PREVIOUS = 0x00B6, - KEY_CONSUMER_STOP = 0x00B7, - KEY_CONSUMER_VOLUME = 0x00E0, - KEY_CONSUMER_MUTE = 0x00E2, - KEY_CONSUMER_BASS = 0x00E3, - KEY_CONSUMER_TREBLE = 0x00E4, - KEY_CONSUMER_BASS_BOOST = 0x00E5, - KEY_CONSUMER_VOLUME_INCREMENT = 0x00E9, - KEY_CONSUMER_VOLUME_DECREMENT = 0x00EA, - KEY_CONSUMER_BASS_INCREMENT = 0x0152, - KEY_CONSUMER_BASS_DECREMENT = 0x0153, - KEY_CONSUMER_TREBLE_INCREMENT = 0x0154, - KEY_CONSUMER_TREBLE_DECREMENT = 0x0155, - - // Application Launcher - KEY_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, - KEY_CONSUMER_AL_EMAIL_READER = 0x018A, - KEY_CONSUMER_AL_CALCULATOR = 0x0192, - KEY_CONSUMER_AL_LOCAL_BROWSER = 0x0194, - - // Browser/Explorer Specific - KEY_CONSUMER_AC_SEARCH = 0x0221, - KEY_CONSUMER_AC_HOME = 0x0223, - KEY_CONSUMER_AC_BACK = 0x0224, - KEY_CONSUMER_AC_FORWARD = 0x0225, - KEY_CONSUMER_AC_STOP = 0x0226, - KEY_CONSUMER_AC_REFRESH = 0x0227, - KEY_CONSUMER_AC_BOOKMARKS = 0x022A, - - // Mouse Horizontal scroll - KEY_CONSUMER_AC_PAN = 0x0238, -}; - -#endif diff --git a/Devices/m5stack-cardputer/device.properties b/Devices/m5stack-cardputer/device.properties index f2545fab2..3f66c75a4 100644 --- a/Devices/m5stack-cardputer/device.properties +++ b/Devices/m5stack-cardputer/device.properties @@ -10,6 +10,8 @@ hardware.tinyUsb=true hardware.esptoolFlashFreq=120M hardware.bluetooth=true +dependencies.useDeprecatedHal=false + storage.userDataLocation=SD display.size=1.14" diff --git a/Devices/m5stack-cardputer/devicetree.yaml b/Devices/m5stack-cardputer/devicetree.yaml index b82a4df72..5145267e9 100644 --- a/Devices/m5stack-cardputer/devicetree.yaml +++ b/Devices/m5stack-cardputer/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - Platforms/platform-esp32 + - Drivers/st7789-module + - Drivers/m5stack-module dts: m5stack,cardputer.dts diff --git a/Devices/m5stack-cardputer/m5stack,cardputer.dts b/Devices/m5stack-cardputer/m5stack,cardputer.dts index 39980fcdf..a6030fb69 100644 --- a/Devices/m5stack-cardputer/m5stack,cardputer.dts +++ b/Devices/m5stack-cardputer/m5stack,cardputer.dts @@ -1,6 +1,8 @@ /dts-v1/; #include +#include +#include #include #include #include @@ -10,7 +12,10 @@ #include #include #include -#include +#include + +#include +#include // Reference: https://docs.m5stack.com/en/core/Cardputer / { @@ -32,6 +37,21 @@ gpio-count = <49>; }; + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + // GPIO10 (ADC1_CHANNEL_9 on ESP32-S3): battery voltage sense, behind a 1:2 divider. + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + port_a: grove0 { compatible = "espressif,esp32-grove"; defaultMode = ; @@ -42,15 +62,36 @@ i2cClockFrequency = <400000>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; cs-gpios = <&gpio0 37 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 35 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>; - - display { - compatible = "display-placeholder"; + + display@0 { + compatible = "sitronix,st7789"; + horizontal-resolution = <240>; + vertical-resolution = <135>; + // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom + gap-x = <53>; + gap-y = <40>; + swap-xy; + mirror-x; + invert-color; + pixel-clock-hz = <62500000>; + pin-dc = <&gpio0 34 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 33 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; @@ -68,6 +109,20 @@ }; }; + keyboard { + compatible = "m5stack,cardputer-keyboard"; + pins-output = <&gpio0 8 GPIO_FLAG_NONE>, + <&gpio0 9 GPIO_FLAG_NONE>, + <&gpio0 11 GPIO_FLAG_NONE>; + pins-input = <&gpio0 13 GPIO_FLAG_NONE>, + <&gpio0 15 GPIO_FLAG_NONE>, + <&gpio0 3 GPIO_FLAG_NONE>, + <&gpio0 4 GPIO_FLAG_NONE>, + <&gpio0 5 GPIO_FLAG_NONE>, + <&gpio0 6 GPIO_FLAG_NONE>, + <&gpio0 7 GPIO_FLAG_NONE>; + }; + // Speaker and microphone // TODO: Microphone I2C init code from https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L936 i2s0 { diff --git a/Devices/m5stack-cardputer/Source/module.cpp b/Devices/m5stack-cardputer/source/module.cpp similarity index 87% rename from Devices/m5stack-cardputer/Source/module.cpp rename to Devices/m5stack-cardputer/source/module.cpp index 3f8245ada..76d48d2cb 100644 --- a/Devices/m5stack-cardputer/Source/module.cpp +++ b/Devices/m5stack-cardputer/source/module.cpp @@ -12,7 +12,7 @@ static error_t stop() { return ERROR_NONE; } -struct Module m5stack_cardputer_module = { +Module m5stack_cardputer_module = { .name = "m5stack-cardputer", .start = start, .stop = stop, diff --git a/Devices/m5stack-stickc-plus2/CMakeLists.txt b/Devices/m5stack-stickc-plus2/CMakeLists.txt index ac0756d32..38c007833 100644 --- a/Devices/m5stack-stickc-plus2/CMakeLists.txt +++ b/Devices/m5stack-stickc-plus2/CMakeLists.txt @@ -1,7 +1,7 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE SOURCE_FILES source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} - INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl + INCLUDE_DIRS "source" + REQUIRES TactilityKernel driver ) diff --git a/Devices/m5stack-stickc-plus2/Source/Configuration.cpp b/Devices/m5stack-stickc-plus2/Source/Configuration.cpp deleted file mode 100644 index c4bc35148..000000000 --- a/Devices/m5stack-stickc-plus2/Source/Configuration.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "devices/Display.h" - -#include -#include -#include - -using namespace tt::hal; - -bool initBoot() { - // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity - // Setting output to high adds a bias of 3.3 V and suppresses over-voltage: - gpio::configure(0, gpio::Mode::Output, false, false); - gpio::setLevel(0, true); - - // "Hold power" pin: must be set to high to keep the device powered on: - gpio::configure(4, gpio::Mode::Output, false, false); - gpio::setLevel(4, true); - return driver::pwmbacklight::init(GPIO_NUM_27, 512); -} - -static DeviceVector createDevices() { - return { - ButtonControl::createTwoButtonControl(37, 39), - createDisplay() - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = initBoot, - .createDevices = createDevices -}; diff --git a/Devices/m5stack-stickc-plus2/Source/devices/Display.cpp b/Devices/m5stack-stickc-plus2/Source/devices/Display.cpp deleted file mode 100644 index 5b732b068..000000000 --- a/Devices/m5stack-stickc-plus2/Source/devices/Display.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "Display.h" - -#include -#include - -std::shared_ptr createDisplay() { - St7789Display::Configuration panel_configuration = { - .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, - .verticalResolution = LCD_VERTICAL_RESOLUTION, - .gapX = 52, - .gapY = 40, - .swapXY = false, - .mirrorX = false, - .mirrorY = false, - .invertColor = true, - .bufferSize = LCD_BUFFER_SIZE, - .touch = nullptr, - .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = LCD_PIN_RESET, - .lvglSwapBytes = false - }; - - auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { - .spiHostDevice = LCD_SPI_HOST, - .csPin = LCD_PIN_CS, - .dcPin = LCD_PIN_DC, - .pixelClockFrequency = 40'000'000, - .transactionQueueDepth = 10 - }); - - return std::make_shared(panel_configuration, spi_configuration); -} diff --git a/Devices/m5stack-stickc-plus2/Source/devices/Display.h b/Devices/m5stack-stickc-plus2/Source/devices/Display.h deleted file mode 100644 index 396fe2d98..000000000 --- a/Devices/m5stack-stickc-plus2/Source/devices/Display.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Tactility/hal/display/DisplayDevice.h" -#include -#include -#include - -constexpr auto LCD_SPI_HOST = SPI2_HOST; -constexpr auto LCD_PIN_CS = GPIO_NUM_5; -constexpr auto LCD_PIN_DC = GPIO_NUM_14; -constexpr auto LCD_PIN_RESET = GPIO_NUM_12; -constexpr auto LCD_HORIZONTAL_RESOLUTION = 135; -constexpr auto LCD_VERTICAL_RESOLUTION = 240; -constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3; -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/m5stack-stickc-plus2/Source/module.cpp b/Devices/m5stack-stickc-plus2/Source/module.cpp deleted file mode 100644 index 9c0e96f73..000000000 --- a/Devices/m5stack-stickc-plus2/Source/module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -extern "C" { - -static error_t start() { - // Empty for now - return ERROR_NONE; -} - -static error_t stop() { - // Empty for now - return ERROR_NONE; -} - -struct Module m5stack_stickc_plus2_module = { - .name = "m5stack-stickc-plus2", - .start = start, - .stop = stop, - .symbols = nullptr, - .internal = nullptr -}; - -} diff --git a/Devices/m5stack-stickc-plus2/device.properties b/Devices/m5stack-stickc-plus2/device.properties index 9178ad240..d6bd399ff 100644 --- a/Devices/m5stack-stickc-plus2/device.properties +++ b/Devices/m5stack-stickc-plus2/device.properties @@ -10,6 +10,8 @@ hardware.spiRamMode=QUAD hardware.spiRamSpeed=80M hardware.esptoolFlashFreq=80M +dependencies.useDeprecatedHal=false + storage.userDataLocation=Internal display.size=1.14" diff --git a/Devices/m5stack-stickc-plus2/devicetree.yaml b/Devices/m5stack-stickc-plus2/devicetree.yaml index 13b717386..448674de6 100644 --- a/Devices/m5stack-stickc-plus2/devicetree.yaml +++ b/Devices/m5stack-stickc-plus2/devicetree.yaml @@ -2,4 +2,6 @@ dependencies: - Platforms/platform-esp32 - Drivers/mpu6886-module - Drivers/bm8563-module +- Drivers/st7789-module +- Drivers/button-control-module dts: m5stack,stickc-plus2.dts diff --git a/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts b/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts index b783f05fd..47a5dac7a 100644 --- a/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts +++ b/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts @@ -7,7 +7,10 @@ #include #include #include -#include +#include + +#include +#include / { compatible = "root"; @@ -49,18 +52,42 @@ pin-scl = <&gpio0 33 GPIO_FLAG_NONE>; }; + display_backlight { + compatible = "espressif,esp32-ledc-backlight"; + // Off by default so display power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>; + frequency-hz = <512>; + }; + spi0 { compatible = "espressif,esp32-spi"; host = ; cs-gpios = <&gpio0 5 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 13 GPIO_FLAG_NONE>; - - display { - compatible = "display-placeholder"; + + display@0 { + compatible = "sitronix,st7789"; + horizontal-resolution = <135>; + vertical-resolution = <240>; + gap-x = <52>; + gap-y = <40>; + invert-color; + pixel-clock-hz = <40000000>; + pin-dc = <&gpio0 14 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 12 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; }; }; + buttons { + compatible = "tactility,button-control"; + pin-primary = <&gpio0 37 GPIO_FLAG_NONE>; + pin-secondary = <&gpio0 39 GPIO_FLAG_NONE>; + }; + uart_grove: uart1 { compatible = "espressif,esp32-uart"; status = "disabled"; diff --git a/Devices/m5stack-stickc-plus2/source/module.cpp b/Devices/m5stack-stickc-plus2/source/module.cpp new file mode 100644 index 000000000..97b6903af --- /dev/null +++ b/Devices/m5stack-stickc-plus2/source/module.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include + +constexpr auto* TAG = "StickCPlus2"; + +extern "C" { + +static bool init_power() { + // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity. + // Setting output to high adds a bias of 3.3 V and suppresses over-voltage: + gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_0, 1); + + // "Hold power" pin: must be set to high to keep the device powered on: + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_NUM_4, 1); + + return true; +} + +static error_t start() { + init_power(); + return ERROR_NONE; +} + +static error_t stop() { + // Empty for now + return ERROR_NONE; +} + +Module m5stack_stickc_plus2_module = { + .name = "m5stack-stickc-plus2", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Documentation/ideas.md b/Documentation/ideas.md index e5540bee5..f0fd99590 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -37,6 +37,7 @@ ## Medium Priority +- Implement a LED kernel driver (single colour and RGB, plain GPIO and PWM) - Make USB host driver disabled by default, so it doesn't consume memory - Filtering for apps in App Hub: - apps that only work on a specific device diff --git a/Drivers/ButtonControl/Source/ButtonControl.h b/Drivers/ButtonControl/Source/ButtonControl.h index 5aab931b9..053cdf91b 100644 --- a/Drivers/ButtonControl/Source/ButtonControl.h +++ b/Drivers/ButtonControl/Source/ButtonControl.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/Drivers/button-control-module/CMakeLists.txt b/Drivers/button-control-module/CMakeLists.txt new file mode 100644 index 000000000..20e49e561 --- /dev/null +++ b/Drivers/button-control-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(button-control-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 lvgl driver +) diff --git a/Drivers/button-control-module/LICENSE-Apache-2.0.md b/Drivers/button-control-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/button-control-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/button-control-module/README.md b/Drivers/button-control-module/README.md new file mode 100644 index 000000000..2af24174c --- /dev/null +++ b/Drivers/button-control-module/README.md @@ -0,0 +1,30 @@ +# Button Control + +Kernel driver for navigating apps with 1 or 2 GPIO buttons. Kernel-driver counterpart to +`Drivers/ButtonControl`, exposing a `KEYBOARD_TYPE` device (`tactility/drivers/keyboard.h`) +instead of an LVGL encoder indev. + +Devicetree binding: `tactility,button-control` (see `bindings/tactility,button-control.yaml`). + +## Modes + +- **One-button** (`pin-secondary` absent): short press = `LV_KEY_NEXT`, long press = `LV_KEY_ENTER`. +- **Two-button** (`pin-secondary` present): primary short = `LV_KEY_ENTER`, primary long = + `LV_KEY_ESC`, secondary short = `LV_KEY_NEXT`, secondary long = `LV_KEY_PREV`. + +Short vs. long press is only decided when the button is released, by comparing the hold +duration against `long-press-ms` - not at the moment it's pressed. + +## Example + +```dts +buttons { + compatible = "tactility,button-control"; + pin-primary = <&gpio0 37 GPIO_FLAG_NONE>; + pin-secondary = <&gpio0 39 GPIO_FLAG_NONE>; +}; +``` + +## License + +[Apache License Version 2.0](LICENSE-Apache-2.0.md) \ No newline at end of file diff --git a/Drivers/button-control-module/bindings/tactility,button-control.yaml b/Drivers/button-control-module/bindings/tactility,button-control.yaml new file mode 100644 index 000000000..1ff025914 --- /dev/null +++ b/Drivers/button-control-module/bindings/tactility,button-control.yaml @@ -0,0 +1,36 @@ +description: > + GPIO button(s) that emulate UI navigation keys, for boards with only 1 or 2 physical + buttons instead of a full keyboard or touchscreen. Exposes a KEYBOARD_TYPE device: each + button's short/long press is translated to an LVGL navigation key, decided once the + button is released (so a long press is only recognized after release, matching how long + a button was actually held). + +compatible: "tactility,button-control" + +properties: + pin-primary: + type: phandles + required: true + description: > + Primary button GPIO pin. One-button mode (pin-secondary absent): short press = select + next, long press = press/enter. Two-button mode (pin-secondary present): short press = + press/enter, long press = close app. + pin-secondary: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: > + Optional secondary button GPIO pin. When present, enables two-button mode: short press + = select next, long press = select previous. When absent, the device runs in + one-button mode (see pin-primary). + active-low: + type: boolean + default: true + description: Whether a pressed button reads as a logic low level + debounce-ms: + type: int + default: 20 + description: Minimum time between recognized level changes, for software debouncing + long-press-ms: + type: int + default: 500 + description: Minimum hold duration (from press to release) recognized as a long press diff --git a/Drivers/button-control-module/devicetree.yaml b/Drivers/button-control-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/button-control-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/button-control-module/include/bindings/button_control.h b/Drivers/button-control-module/include/bindings/button_control.h new file mode 100644 index 000000000..ca398518c --- /dev/null +++ b/Drivers/button-control-module/include/bindings/button_control.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(button_control, struct ButtonControlConfig) diff --git a/Drivers/button-control-module/include/button_control_module.h b/Drivers/button-control-module/include/button_control_module.h new file mode 100644 index 000000000..620831fce --- /dev/null +++ b/Drivers/button-control-module/include/button_control_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module button_control_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/button-control-module/include/drivers/button_control.h b/Drivers/button-control-module/include/drivers/button_control.h new file mode 100644 index 000000000..80fa6339b --- /dev/null +++ b/Drivers/button-control-module/include/drivers/button_control.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +struct ButtonControlConfig { + struct GpioPinSpec pin_primary; + // GPIO_PIN_SPEC_NONE selects one-button mode; see the binding description for the + // resulting short/long press key mapping in each mode. + struct GpioPinSpec pin_secondary; + bool active_low; + uint32_t debounce_ms; + uint32_t long_press_ms; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/button-control-module/source/button_control.cpp b/Drivers/button-control-module/source/button_control.cpp new file mode 100644 index 000000000..97500746a --- /dev/null +++ b/Drivers/button-control-module/source/button_control.cpp @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define TAG "ButtonControl" +#define GET_CONFIG(device) (static_cast((device)->config)) + +// Worst case: both buttons complete a short/long press gesture (press + release, 2 events each) +// within the same read_key() polling interval. +constexpr auto BUTTON_CONTROL_PENDING_CAPACITY = 4; + +struct ButtonControlPendingEvent { + uint32_t key; + bool pressed; +}; + +struct ButtonControlButtonState { + GpioDescriptor* descriptor; + bool in_use; + bool debounced_pressed; + uint32_t press_start_time; + uint32_t last_change_time; + uint32_t short_press_key; + uint32_t long_press_key; +}; + +struct ButtonControlInternal { + ButtonControlButtonState primary; + ButtonControlButtonState secondary; + ButtonControlPendingEvent pending[BUTTON_CONTROL_PENDING_CAPACITY]; + uint8_t pending_head; + uint8_t pending_count; +}; + +static void push_pending(ButtonControlInternal* internal, uint32_t key, bool pressed) { + if (internal->pending_count >= BUTTON_CONTROL_PENDING_CAPACITY) { + LOG_W(TAG, "Pending event queue full, dropping event"); + return; + } + uint8_t tail = (internal->pending_head + internal->pending_count) % BUTTON_CONTROL_PENDING_CAPACITY; + internal->pending[tail] = { .key = key, .pressed = pressed }; + internal->pending_count++; +} + +static bool pop_pending(ButtonControlInternal* internal, ButtonControlPendingEvent* out_event) { + if (internal->pending_count == 0) { + return false; + } + *out_event = internal->pending[internal->pending_head]; + internal->pending_head = (internal->pending_head + 1) % BUTTON_CONTROL_PENDING_CAPACITY; + internal->pending_count--; + return true; +} + +// region Driver lifecycle + +static error_t acquire_button(const GpioPinSpec& pin, uint32_t short_press_key, uint32_t long_press_key, ButtonControlButtonState* out_state) { + if (pin.gpio_controller == nullptr) { + *out_state = {}; + return ERROR_NONE; + } + + auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr) { + LOG_E(TAG, "Failed to acquire GPIO descriptor"); + return ERROR_RESOURCE; + } + + if (gpio_descriptor_set_flags(descriptor, pin.flags | GPIO_FLAG_DIRECTION_INPUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure GPIO as input"); + gpio_descriptor_release(descriptor); + return ERROR_RESOURCE; + } + + *out_state = { + .descriptor = descriptor, + .in_use = true, + .debounced_pressed = false, + .press_start_time = 0, + .last_change_time = 0, + .short_press_key = short_press_key, + .long_press_key = long_press_key, + }; + return ERROR_NONE; +} + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(ButtonControlInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + *internal = {}; + + bool two_button_mode = config->pin_secondary.gpio_controller != nullptr; + + error_t error = acquire_button( + config->pin_primary, + two_button_mode ? LV_KEY_ENTER : LV_KEY_NEXT, + two_button_mode ? LV_KEY_ESC : LV_KEY_ENTER, + &internal->primary + ); + if (error != ERROR_NONE) { + free(internal); + return error; + } + + error = acquire_button(config->pin_secondary, LV_KEY_NEXT, LV_KEY_PREV, &internal->secondary); + if (error != ERROR_NONE) { + if (internal->primary.in_use) { + gpio_descriptor_release(internal->primary.descriptor); + } + free(internal); + return error; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + if (internal->primary.in_use) { + gpio_descriptor_release(internal->primary.descriptor); + } + if (internal->secondary.in_use) { + gpio_descriptor_release(internal->secondary.descriptor); + } + + free(internal); + return ERROR_NONE; +} + +// endregion + +// region KeyboardApi + +// Long press is only distinguished from short press at release time, by how long the button +// was held - not at the moment it's first pressed. This matches physical button behavior: +// you can't know a press is "long" until it either ends or the threshold is reached. +static void poll_button(const ButtonControlConfig* config, ButtonControlInternal* internal, ButtonControlButtonState* state) { + if (!state->in_use) { + return; + } + + bool high = false; + if (gpio_descriptor_get_level(state->descriptor, &high) != ERROR_NONE) { + return; + } + bool raw_pressed = config->active_low ? !high : high; + + uint32_t now = get_millis(); + if ((now - state->last_change_time) < config->debounce_ms) { + return; + } + + if (raw_pressed == state->debounced_pressed) { + return; + } + state->last_change_time = now; + state->debounced_pressed = raw_pressed; + + if (raw_pressed) { + state->press_start_time = now; + return; + } + + // Release: decide short vs. long press by elapsed hold duration, then queue a synthetic + // key tap (press followed by release) for the LVGL key this gesture maps to. + uint32_t held_ms = now - state->press_start_time; + uint32_t key = held_ms < config->long_press_ms ? state->short_press_key : state->long_press_key; + push_pending(internal, key, true); + push_pending(internal, key, false); +} + +static error_t button_control_read_key(Device* device, KeyboardKeyData* data) { + const auto* config = GET_CONFIG(device); + auto* internal = static_cast(device_get_driver_data(device)); + + poll_button(config, internal, &internal->primary); + poll_button(config, internal, &internal->secondary); + + ButtonControlPendingEvent event; + if (pop_pending(internal, &event)) { + data->key = event.key; + data->pressed = event.pressed; + data->continue_reading = internal->pending_count > 0; + } else { + data->key = 0; + data->pressed = false; + data->continue_reading = false; + } + + return ERROR_NONE; +} + +// endregion + +static constexpr KeyboardApi button_control_api = { + .read_key = button_control_read_key, +}; + +Driver button_control_driver = { + .name = "button_control", + .compatible = (const char*[]) { "tactility,button-control", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &button_control_api, + .device_type = &KEYBOARD_TYPE, + .owner = &button_control_module, + .internal = nullptr +}; diff --git a/Drivers/button-control-module/source/module.cpp b/Drivers/button-control-module/source/module.cpp new file mode 100644 index 000000000..bd92f5dda --- /dev/null +++ b/Drivers/button-control-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver button_control_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(&button_control_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(&button_control_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module button_control_module = { + .name = "button-control", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/cst816s-module/CMakeLists.txt b/Drivers/cst816s-module/CMakeLists.txt new file mode 100644 index 000000000..5a4848cec --- /dev/null +++ b/Drivers/cst816s-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(cst816s-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_touch_cst816s esp_lcd driver +) diff --git a/Drivers/cst816s-module/bindings/hynitron,cst816s.yaml b/Drivers/cst816s-module/bindings/hynitron,cst816s.yaml new file mode 100644 index 000000000..948ee1d2a --- /dev/null +++ b/Drivers/cst816s-module/bindings/hynitron,cst816s.yaml @@ -0,0 +1,45 @@ +description: Hynitron CST816S capacitive touch controller + +include: ["i2c-device.yaml"] + +compatible: "hynitron,cst816s" + +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/cst816s-module/devicetree.yaml b/Drivers/cst816s-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/cst816s-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/cst816s-module/include/bindings/cst816s.h b/Drivers/cst816s-module/include/bindings/cst816s.h new file mode 100644 index 000000000..a39c74f54 --- /dev/null +++ b/Drivers/cst816s-module/include/bindings/cst816s.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(cst816s, struct Cst816sConfig) diff --git a/Drivers/cst816s-module/include/cst816s_module.h b/Drivers/cst816s-module/include/cst816s_module.h new file mode 100644 index 000000000..39e69c22e --- /dev/null +++ b/Drivers/cst816s-module/include/cst816s_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module cst816s_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/cst816s-module/include/drivers/cst816s.h b/Drivers/cst816s-module/include/drivers/cst816s.h new file mode 100644 index 000000000..bf36dbfa6 --- /dev/null +++ b/Drivers/cst816s-module/include/drivers/cst816s.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +struct Cst816sConfig { + // Devicetree address hint. Unused by the driver: the CST816S always sits at a fixed + // I2C address (0x15, see ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS), unlike GT911's + // strapping-dependent address. + 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/cst816s-module/source/cst816s.cpp b/Drivers/cst816s-module/source/cst816s.cpp new file mode 100644 index 000000000..fd38472c4 --- /dev/null +++ b/Drivers/cst816s-module/source/cst816s.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 "CST816S" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Cst816sInternal { + 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 + +// CST816S always sits at a fixed I2C address, unlike GT911's strapping-dependent address, +// so no bus probing is needed here. +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_CST816S_CONFIG(); + + 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(Cst816sInternal))); + 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_cst816s(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)); + + 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"); + 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 ok ? ERROR_NONE : ERROR_RESOURCE; +} + +// endregion + +// region PointerApi + +static error_t cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_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 cst816s_pointer_api = { + .enter_sleep = cst816s_enter_sleep, + .exit_sleep = cst816s_exit_sleep, + .read_data = cst816s_read_data, + .get_touched_points = cst816s_get_touched_points, + .set_swap_xy = cst816s_set_swap_xy, + .get_swap_xy = cst816s_get_swap_xy, + .set_mirror_x = cst816s_set_mirror_x, + .get_mirror_x = cst816s_get_mirror_x, + .set_mirror_y = cst816s_set_mirror_y, + .get_mirror_y = cst816s_get_mirror_y, +}; + +Driver cst816s_driver = { + .name = "cst816s", + .compatible = (const char*[]) { "hynitron,cst816s", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &cst816s_pointer_api, + .device_type = &POINTER_TYPE, + .owner = &cst816s_module, + .internal = nullptr +}; diff --git a/Drivers/cst816s-module/source/module.cpp b/Drivers/cst816s-module/source/module.cpp new file mode 100644 index 000000000..79b17c2aa --- /dev/null +++ b/Drivers/cst816s-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver cst816s_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(&cst816s_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(&cst816s_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module cst816s_module = { + .name = "cst816s", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/gt911-module/LICENSE-Apache-2.0.md b/Drivers/gt911-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/gt911-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/ili9341-module/CMakeLists.txt b/Drivers/ili9341-module/CMakeLists.txt new file mode 100644 index 000000000..8e7666c75 --- /dev/null +++ b/Drivers/ili9341-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(ili9341-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_ili9341 driver +) diff --git a/Drivers/ili9341-module/LICENSE-Apache-2.0.md b/Drivers/ili9341-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/ili9341-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml new file mode 100644 index 000000000..1588890d6 --- /dev/null +++ b/Drivers/ili9341-module/bindings/ilitek,ili9341.yaml @@ -0,0 +1,71 @@ +description: Ilitek ILI9341 display panel + +compatible: "ilitek,ili9341" + +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: 40000000 + 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/ili9341-module/devicetree.yaml b/Drivers/ili9341-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/ili9341-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/ili9341-module/include/bindings/ili9341.h b/Drivers/ili9341-module/include/bindings/ili9341.h new file mode 100644 index 000000000..5bb91100a --- /dev/null +++ b/Drivers/ili9341-module/include/bindings/ili9341.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(ili9341, struct Ili9341Config) diff --git a/Drivers/ili9341-module/include/drivers/ili9341.h b/Drivers/ili9341-module/include/drivers/ili9341.h new file mode 100644 index 000000000..8c1b64a39 --- /dev/null +++ b/Drivers/ili9341-module/include/drivers/ili9341.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct Ili9341Config { + 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/ili9341-module/include/ili9341_module.h b/Drivers/ili9341-module/include/ili9341_module.h new file mode 100644 index 000000000..711800ef0 --- /dev/null +++ b/Drivers/ili9341-module/include/ili9341_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module ili9341_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/ili9341-module/source/ili9341.cpp b/Drivers/ili9341-module/source/ili9341.cpp new file mode 100644 index 000000000..84d2cbeac --- /dev/null +++ b/Drivers/ili9341-module/source/ili9341.cpp @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define TAG "ILI9341" + +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Ili9341Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; + // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically + // completes. draw_bitmap() blocks on this so it can honor DisplayApi's synchronous contract + // (see lvgl_display.c: the caller reuses/overwrites the color buffer as soon as draw_bitmap + // returns) - esp_lcd_panel_draw_bitmap() itself only queues the transfer and returns early. + SemaphoreHandle_t draw_done_semaphore; +}; + +// Fires for every completed SPI transaction on this IO (not just draw_bitmap's color transfers - +// bring-up commands like reset/init/gap go through the same IO), called from ISR context. +static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) { + auto* internal = static_cast(user_ctx); + BaseType_t high_task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken); + return high_task_woken == pdTRUE; +} + +static int pin_or_unused(const struct GpioPinSpec& pin) { + return pin.gpio_controller == nullptr ? -1 : static_cast(pin.pin); +} + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &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(Ili9341Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->draw_done_semaphore = xSemaphoreCreateBinary(); + if (internal->draw_done_semaphore == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_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 = on_color_trans_done, + .user_ctx = internal, + .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)); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = pin_or_unused(config->pin_reset), + .rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = config->bits_per_pixel, + .flags = { .reset_active_high = config->reset_active_high }, + .vendor_config = nullptr, + }; + + ret = esp_lcd_new_panel_ili9341(internal->io_handle, &panel_config, &internal->panel_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret)); + esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9341 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); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + error_t result = ERROR_NONE; + + if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel"); + result = ERROR_RESOURCE; + } + if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + LOG_E(TAG, "Failed to delete panel IO"); + result = ERROR_RESOURCE; + } + + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return result; +} + +// endregion + +// region DisplayApi + +static error_t ili9341_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 ili9341_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 ili9341_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)); + + // Drain any stale signal left over from a prior non-draw transaction (bring-up commands like + // reset/gap also complete through on_color_trans_done), so the take() below can only be + // satisfied by this draw's own transfer completing. + xSemaphoreTake(internal->draw_done_semaphore, 0); + + if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { + return ERROR_RESOURCE; + } + + // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous + // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite + // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the + // transfer and returns once it's handed to the SPI peripheral, not once it's finished. + xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); + return ERROR_NONE; +} + +static error_t ili9341_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 ili9341_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 ili9341_get_swap_xy(Device* device) { + return GET_CONFIG(device)->swap_xy; +} + +static bool ili9341_get_mirror_x(Device* device) { + return GET_CONFIG(device)->mirror_x; +} + +static bool ili9341_get_mirror_y(Device* device) { + return GET_CONFIG(device)->mirror_y; +} + +static error_t ili9341_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 ili9341_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 ili9341_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 ili9341_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; +} + +// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), below) so the +// R/B swap happens in hardware over SPI. LVGL always fills RGB565 buffers either way - it has no +// BGR565 format (lvgl_display.c rejects it, "no LVGL equivalent"), and none is needed here. +// +// _SWAPPED (not plain RGB565): the panel expects each 16-bit pixel high-byte-first over SPI, but +// this CPU is little-endian, so a plain RGB565 buffer arrives byte-swapped per pixel - confirmed +// on real hardware: black/white rendered correctly (0x0000/0xFFFF are swap-invariant) while every +// other color was wrong. The old deprecated-HAL driver had the same requirement (its equivalent +// knob was esp_lvgl_port's `swapBytes = true` in Devices/*/Source/devices/Display.cpp). +static enum DisplayColorFormat ili9341_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED; +} + +static uint16_t ili9341_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t ili9341_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void ili9341_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t ili9341_get_frame_buffer_count(Device*) { + return 0; +} + +static error_t ili9341_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 ili9341_display_api = { + .reset = ili9341_reset, + .init = ili9341_init, + .draw_bitmap = ili9341_draw_bitmap, + .mirror = ili9341_mirror, + .swap_xy = ili9341_swap_xy, + .get_swap_xy = ili9341_get_swap_xy, + .get_mirror_x = ili9341_get_mirror_x, + .get_mirror_y = ili9341_get_mirror_y, + .set_gap = ili9341_set_gap, + .invert_color = ili9341_invert_color, + .disp_on_off = ili9341_disp_on_off, + .disp_sleep = ili9341_disp_sleep, + .get_color_format = ili9341_get_color_format, + .get_resolution_x = ili9341_get_resolution_x, + .get_resolution_y = ili9341_get_resolution_y, + .get_frame_buffer = ili9341_get_frame_buffer, + .get_frame_buffer_count = ili9341_get_frame_buffer_count, + .get_backlight = ili9341_get_backlight, +}; + +Driver ili9341_driver = { + .name = "ili9341", + .compatible = (const char*[]) { "ilitek,ili9341", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &ili9341_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &ili9341_module, + .internal = nullptr +}; diff --git a/Drivers/ili9341-module/source/module.cpp b/Drivers/ili9341-module/source/module.cpp new file mode 100644 index 000000000..5fdd1649a --- /dev/null +++ b/Drivers/ili9341-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver ili9341_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(&ili9341_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(&ili9341_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module ili9341_module = { + .name = "ili9341", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/ili9488-module/CMakeLists.txt b/Drivers/ili9488-module/CMakeLists.txt new file mode 100644 index 000000000..46e1286d2 --- /dev/null +++ b/Drivers/ili9488-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(ili9488-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_ili9488 driver +) diff --git a/Drivers/ili9488-module/LICENSE-Apache-2.0.md b/Drivers/ili9488-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/ili9488-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml b/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml new file mode 100644 index 000000000..797390bfd --- /dev/null +++ b/Drivers/ili9488-module/bindings/ilitek,ili9488.yaml @@ -0,0 +1,75 @@ +description: Ilitek ILI9488 display panel + +compatible: "ilitek,ili9488" + +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: true + description: Use BGR element order instead of RGB + bits-per-pixel: + type: int + default: 18 + description: Color depth in bits per pixel. The panel requires 18 over SPI + color-conversion-buffer-size: + type: int + required: true + description: Size (in pixels) of the internal buffer used to convert 16bpp draw data to the panel's 18bpp format + pixel-clock-hz: + type: int + default: 40000000 + 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/ili9488-module/devicetree.yaml b/Drivers/ili9488-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/ili9488-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/ili9488-module/include/bindings/ili9488.h b/Drivers/ili9488-module/include/bindings/ili9488.h new file mode 100644 index 000000000..ec67e4a16 --- /dev/null +++ b/Drivers/ili9488-module/include/bindings/ili9488.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(ili9488, struct Ili9488Config) diff --git a/Drivers/ili9488-module/include/drivers/ili9488.h b/Drivers/ili9488-module/include/drivers/ili9488.h new file mode 100644 index 000000000..6eceecb5e --- /dev/null +++ b/Drivers/ili9488-module/include/drivers/ili9488.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct Ili9488Config { + 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; + // Size (in pixels) of the internal buffer the panel driver uses to convert + // 16bpp draw data to the panel's native 18bpp format. + uint32_t color_conversion_buffer_size; + 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/ili9488-module/include/ili9488_module.h b/Drivers/ili9488-module/include/ili9488_module.h new file mode 100644 index 000000000..ad40aed40 --- /dev/null +++ b/Drivers/ili9488-module/include/ili9488_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module ili9488_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/ili9488-module/source/ili9488.cpp b/Drivers/ili9488-module/source/ili9488.cpp new file mode 100644 index 000000000..952295fc9 --- /dev/null +++ b/Drivers/ili9488-module/source/ili9488.cpp @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define TAG "ILI9488" + +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Ili9488Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; + // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically + // completes. draw_bitmap() blocks on this so it can honor DisplayApi's synchronous contract + // (see lvgl_display.c: the caller reuses/overwrites the color buffer as soon as draw_bitmap + // returns) - esp_lcd_panel_draw_bitmap() itself only queues the transfer and returns early. + SemaphoreHandle_t draw_done_semaphore; +}; + +// Fires for every completed SPI transaction on this IO (not just draw_bitmap's color transfers - +// bring-up commands like reset/init/gap go through the same IO), called from ISR context. +static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) { + auto* internal = static_cast(user_ctx); + BaseType_t high_task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken); + return high_task_woken == pdTRUE; +} + +static int pin_or_unused(const struct GpioPinSpec& pin) { + return pin.gpio_controller == nullptr ? -1 : static_cast(pin.pin); +} + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &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(Ili9488Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->draw_done_semaphore = xSemaphoreCreateBinary(); + if (internal->draw_done_semaphore == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_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 = on_color_trans_done, + .user_ctx = internal, + .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)); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = pin_or_unused(config->pin_reset), + .rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = config->bits_per_pixel, + .flags = { .reset_active_high = config->reset_active_high }, + .vendor_config = nullptr, + }; + + // Unlike st7789/ili9341, ili9488 needs an internal conversion buffer: over SPI the + // panel only accepts its native 18bpp format, so the driver repacks each transfer through + // this buffer. + ret = esp_lcd_new_panel_ili9488(internal->io_handle, &panel_config, config->color_conversion_buffer_size, &internal->panel_handle); + if (ret != ESP_OK) { + LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret)); + esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9488 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); + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_RESOURCE; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + 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; + } + + vSemaphoreDelete(internal->draw_done_semaphore); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region DisplayApi + +static error_t ili9488_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 ili9488_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 ili9488_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)); + + // Drain any stale signal left over from a prior non-draw transaction (bring-up commands like + // reset/gap also complete through on_color_trans_done), so the take() below can only be + // satisfied by this draw's own transfer completing. + xSemaphoreTake(internal->draw_done_semaphore, 0); + + if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { + return ERROR_RESOURCE; + } + + // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous + // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite + // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the + // transfer and returns once it's handed to the SPI peripheral, not once it's finished. + xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); + return ERROR_NONE; +} + +static error_t ili9488_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 ili9488_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 ili9488_get_swap_xy(Device* device) { + return GET_CONFIG(device)->swap_xy; +} + +static bool ili9488_get_mirror_x(Device* device) { + return GET_CONFIG(device)->mirror_x; +} + +static bool ili9488_get_mirror_y(Device* device) { + return GET_CONFIG(device)->mirror_y; +} + +static error_t ili9488_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 ili9488_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 ili9488_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 ili9488_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; +} + +// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the +// R/B swap happens in hardware as part of the internal 16bpp->18bpp conversion. LVGL always fills +// plain RGB565 buffers either way - it has no BGR565 format (lvgl_display.c rejects it, "no LVGL +// equivalent"), and none is needed here. Unlike ili9341/st7789, no byte-swap is needed either: the +// old deprecated-HAL driver for this exact panel (Ili9488Display.cpp) used swap_bytes=false, and +// the esp_lcd_ili9488 component's 16->18bpp conversion buffer apparently already accounts for it. +static enum DisplayColorFormat ili9488_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_RGB565; +} + +static uint16_t ili9488_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t ili9488_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void ili9488_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t ili9488_get_frame_buffer_count(Device*) { + return 0; +} + +static error_t ili9488_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 ili9488_display_api = { + .reset = ili9488_reset, + .init = ili9488_init, + .draw_bitmap = ili9488_draw_bitmap, + .mirror = ili9488_mirror, + .swap_xy = ili9488_swap_xy, + .get_swap_xy = ili9488_get_swap_xy, + .get_mirror_x = ili9488_get_mirror_x, + .get_mirror_y = ili9488_get_mirror_y, + .set_gap = ili9488_set_gap, + .invert_color = ili9488_invert_color, + .disp_on_off = ili9488_disp_on_off, + .disp_sleep = ili9488_disp_sleep, + .get_color_format = ili9488_get_color_format, + .get_resolution_x = ili9488_get_resolution_x, + .get_resolution_y = ili9488_get_resolution_y, + .get_frame_buffer = ili9488_get_frame_buffer, + .get_frame_buffer_count = ili9488_get_frame_buffer_count, + .get_backlight = ili9488_get_backlight, +}; + +Driver ili9488_driver = { + .name = "ili9488", + .compatible = (const char*[]) { "ilitek,ili9488", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &ili9488_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &ili9488_module, + .internal = nullptr +}; diff --git a/Drivers/ili9488-module/source/module.cpp b/Drivers/ili9488-module/source/module.cpp new file mode 100644 index 000000000..aa5a31c18 --- /dev/null +++ b/Drivers/ili9488-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver ili9488_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(&ili9488_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(&ili9488_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module ili9488_module = { + .name = "ili9488", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/ina226-module/LICENSE-Apache-2.0.md b/Drivers/ina226-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/ina226-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/lilygo-module/LICENSE-Apache-2.0.md b/Drivers/lilygo-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/lilygo-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/m5stack-module/CMakeLists.txt b/Drivers/m5stack-module/CMakeLists.txt new file mode 100644 index 000000000..e0691365b --- /dev/null +++ b/Drivers/m5stack-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(m5stack-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 lvgl driver +) diff --git a/Drivers/m5stack-module/LICENSE-Apache-2.0.md b/Drivers/m5stack-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/m5stack-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/m5stack-module/bindings/m5stack,cardputer-adv-keyboard.yaml b/Drivers/m5stack-module/bindings/m5stack,cardputer-adv-keyboard.yaml new file mode 100644 index 000000000..801efe430 --- /dev/null +++ b/Drivers/m5stack-module/bindings/m5stack,cardputer-adv-keyboard.yaml @@ -0,0 +1,12 @@ +description: > + M5Stack Cardputer Adv keyboard, a TCA8418 I2C keypad-scan IC wired as a 7x8 matrix behind + a 4x14 key grid identical to the base Cardputer's. Exposes a KEYBOARD_TYPE device. + Sym (fn) and shift are consumed internally and never emitted as their own key events; + sym+shift together toggles caps lock. Regular keys emit their lowercase/uppercase/sym-layer + ASCII character or LVGL navigation key depending on which layer is active. + +include: ["i2c-device.yaml"] + +compatible: "m5stack,cardputer-adv-keyboard" + +bus: i2c diff --git a/Drivers/m5stack-module/bindings/m5stack,cardputer-keyboard.yaml b/Drivers/m5stack-module/bindings/m5stack,cardputer-keyboard.yaml new file mode 100644 index 000000000..88ca4949a --- /dev/null +++ b/Drivers/m5stack-module/bindings/m5stack,cardputer-keyboard.yaml @@ -0,0 +1,21 @@ +description: > + M5Stack Cardputer internal GPIO matrix keyboard: 4 rows x 14 columns, scanned via 3 + binary-encoded column-select output pins and 7 row input pins. Exposes a KEYBOARD_TYPE + device. Modifier keys (fn/shift/ctrl/opt/alt/tab) are consumed internally and never emitted + as their own key events. Regular keys emit their ASCII character (shifted variant when + shift or ctrl is held). With fn held: del -> forward delete, enter -> enter, ` -> escape, + , -> left, / -> right, ; -> previous (group navigation), . -> next (group navigation). + +compatible: "m5stack,cardputer-keyboard" + +properties: + pins-output: + type: phandle-array + element-type: "struct GpioPinSpec" + required: true + description: 3 GPIO pins driving the binary-encoded column-select lines, in scan-bit order + pins-input: + type: phandle-array + element-type: "struct GpioPinSpec" + required: true + description: 7 GPIO pins reading the row lines, in scan-bit order diff --git a/Drivers/m5stack-module/devicetree.yaml b/Drivers/m5stack-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/m5stack-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/m5stack-module/include/bindings/cardputer_adv_keyboard.h b/Drivers/m5stack-module/include/bindings/cardputer_adv_keyboard.h new file mode 100644 index 000000000..582218901 --- /dev/null +++ b/Drivers/m5stack-module/include/bindings/cardputer_adv_keyboard.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(cardputer_adv_keyboard, struct M5stackCardputerAdvKeyboardConfig) diff --git a/Drivers/m5stack-module/include/bindings/cardputer_keyboard.h b/Drivers/m5stack-module/include/bindings/cardputer_keyboard.h new file mode 100644 index 000000000..ae1372970 --- /dev/null +++ b/Drivers/m5stack-module/include/bindings/cardputer_keyboard.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(cardputer_keyboard, struct M5stackCardputerKeyboardConfig) diff --git a/Drivers/m5stack-module/include/drivers/cardputer_adv_keyboard.h b/Drivers/m5stack-module/include/drivers/cardputer_adv_keyboard.h new file mode 100644 index 000000000..e726e0f53 --- /dev/null +++ b/Drivers/m5stack-module/include/drivers/cardputer_adv_keyboard.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct M5stackCardputerAdvKeyboardConfig { + // Devicetree address hint. The TCA8418 has a fixed I2C address (0x34), but the field is + // still populated from the standard i2c-device "reg" property for consistency with other + // I2C child device bindings. + uint8_t address; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5stack-module/include/drivers/cardputer_keyboard.h b/Drivers/m5stack-module/include/drivers/cardputer_keyboard.h new file mode 100644 index 000000000..08211326a --- /dev/null +++ b/Drivers/m5stack-module/include/drivers/cardputer_keyboard.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +struct M5stackCardputerKeyboardConfig { + struct GpioPinSpec* pins_output; + uint8_t pins_output_count; + struct GpioPinSpec* pins_input; + uint8_t pins_input_count; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5stack-module/include/m5stack_module.h b/Drivers/m5stack-module/include/m5stack_module.h new file mode 100644 index 000000000..755ae66ad --- /dev/null +++ b/Drivers/m5stack-module/include/m5stack_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module m5stack_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5stack-module/source/cardputer_adv_keyboard.cpp b/Drivers/m5stack-module/source/cardputer_adv_keyboard.cpp new file mode 100644 index 000000000..72db94b07 --- /dev/null +++ b/Drivers/m5stack-module/source/cardputer_adv_keyboard.cpp @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: Apache-2.0 +// TCA8418 register protocol and Cardputer Adv row/col remap ported from +// Devices/m5stack-cardputer-adv/source/devices/CardputerKeyboard.cpp and +// Drivers/TCA8418/Source/Tca8418.cpp (itself ported from +// https://github.com/adafruit/Adafruit_TCA8418, MIT licensed). +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static constexpr const char* TAG = "CardputerAdvKeyboard"; +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(100); + +// Fixed by the board's wiring (see Tca8418::init(7, 8) in the pre-kernel driver). +static constexpr int TCA8418_ROWS = 7; +static constexpr int TCA8418_COLS = 8; + +static constexpr uint8_t TCA8418_REG_CFG = 0x01U; +static constexpr uint8_t TCA8418_REG_KEY_EVENT_A = 0x04U; +static constexpr uint8_t TCA8418_REG_KP_GPIO1 = 0x1DU; +static constexpr uint8_t TCA8418_REG_KP_GPIO2 = 0x1EU; + +// AI=1, GPI_E_CFG=0, OVR_FLOW_M=0 (overflow disabled), INT_CFG=1, *_IEN=0 except KE_IEN=1. +static constexpr uint8_t TCA8418_CFG_VALUE = 0x99U; + +// Worst case per read_key() call: drain this many queued chip events in one go. +static constexpr int CARDPUTER_ADV_MAX_EVENTS_PER_SCAN = 10; +static constexpr int CARDPUTER_ADV_PENDING_CAPACITY = 10; +static constexpr int CARDPUTER_ADV_ACTIVE_KEY_CAPACITY = 4; + +static constexpr int CARDPUTER_ADV_ROWS = 4; +static constexpr int CARDPUTER_ADV_COLS = 14; + +// [row][col] on the 4x14 grid, matching the base Cardputer's physical layout. 0 means the cell +// emits nothing (used for the sym/shift cells themselves, and unwired cells on this board). +static const uint32_t cardputer_adv_keymap_lc[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = { + { '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', LV_KEY_BACKSPACE }, + { '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\' }, + { 0, 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', LV_KEY_ENTER }, + { 0, 0, 0, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' ' }, +}; + +static const uint32_t cardputer_adv_keymap_uc[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = { + { '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', LV_KEY_DEL }, + { '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|' }, + { 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', LV_KEY_ENTER }, + { 0, 0, 0, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' ' }, +}; + +static const uint32_t cardputer_adv_keymap_sym[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = { + { LV_KEY_ESC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LV_KEY_PREV, 0, LV_KEY_ENTER }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LV_KEY_LEFT, LV_KEY_NEXT, LV_KEY_RIGHT, 0 }, +}; + +struct CardputerAdvActiveKey { + bool in_use; + uint8_t row; + uint8_t col; + uint32_t key; +}; + +struct CardputerAdvPendingEvent { + uint32_t key; + bool pressed; +}; + +struct CardputerAdvKeyboardInternal { + bool sym_pressed; + bool shift_pressed; + bool caps_lock; + // Only allows one caps-lock toggle per sym+shift co-press, requiring both to be released + // before it can be toggled again. + bool caps_lock_armed; + + CardputerAdvActiveKey active_keys[CARDPUTER_ADV_ACTIVE_KEY_CAPACITY]; + + CardputerAdvPendingEvent pending[CARDPUTER_ADV_PENDING_CAPACITY]; + uint8_t pending_head; + uint8_t pending_count; +}; + +static void push_pending(CardputerAdvKeyboardInternal* internal, uint32_t key, bool pressed) { + if (internal->pending_count >= CARDPUTER_ADV_PENDING_CAPACITY) { + LOG_W(TAG, "Pending event queue full, dropping event"); + return; + } + uint8_t tail = (internal->pending_head + internal->pending_count) % CARDPUTER_ADV_PENDING_CAPACITY; + internal->pending[tail] = { .key = key, .pressed = pressed }; + internal->pending_count++; +} + +static bool pop_pending(CardputerAdvKeyboardInternal* internal, CardputerAdvPendingEvent* out_event) { + if (internal->pending_count == 0) { + return false; + } + *out_event = internal->pending[internal->pending_head]; + internal->pending_head = (internal->pending_head + 1) % CARDPUTER_ADV_PENDING_CAPACITY; + internal->pending_count--; + return true; +} + +// region Driver lifecycle + +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); + + if (i2c_controller_has_device_at_address(parent, config->address, I2C_TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "No device found on I2C bus at address 0x%02X", config->address); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(malloc(sizeof(CardputerAdvKeyboardInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + *internal = {}; + internal->caps_lock_armed = true; + + // KP_GPIO1/2: mark all TCA8418_ROWS rows and TCA8418_COLS columns as keypad matrix pins. + uint8_t row_mask = (1U << TCA8418_ROWS) - 1; + uint8_t col_mask = (1U << TCA8418_COLS) - 1; + bool ok = + i2c_controller_register8_set(parent, config->address, TCA8418_REG_KP_GPIO1, row_mask, I2C_TIMEOUT) == ERROR_NONE && + i2c_controller_register8_set(parent, config->address, TCA8418_REG_KP_GPIO2, col_mask, I2C_TIMEOUT) == ERROR_NONE && + i2c_controller_register8_set(parent, config->address, TCA8418_REG_CFG, TCA8418_CFG_VALUE, I2C_TIMEOUT) == ERROR_NONE; + if (!ok) { + LOG_E(TAG, "Failed to configure TCA8418"); + 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)); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region KeyboardApi + +// Wiring is a 7x8 matrix, but the keymap tables are laid out as a 4x14 grid (matching the base +// Cardputer's physical key positions). +static void remap_to_grid(uint8_t wiring_row, uint8_t wiring_col, uint8_t* grid_row, uint8_t* grid_col) { + uint8_t col = wiring_row * 2; + if (wiring_col > 3) { + col++; + } + *grid_row = (wiring_col + 4) % 4; + *grid_col = col; +} + +static uint32_t resolve_key(const CardputerAdvKeyboardInternal* internal, uint8_t row, uint8_t col) { + if (internal->sym_pressed) { + return cardputer_adv_keymap_sym[row][col]; + } + if (internal->shift_pressed || internal->caps_lock) { + return cardputer_adv_keymap_uc[row][col]; + } + return cardputer_adv_keymap_lc[row][col]; +} + +static void handle_key_event(CardputerAdvKeyboardInternal* internal, uint8_t row, uint8_t col, bool pressed) { + // Sym and shift are modifiers: tracked as state, never emitted as their own key events. + if (row == 2 && col == 0) { + internal->sym_pressed = pressed; + } else if (row == 2 && col == 1) { + internal->shift_pressed = pressed; + } else if (pressed) { + if (internal->sym_pressed && internal->shift_pressed && internal->caps_lock_armed) { + internal->caps_lock = !internal->caps_lock; + internal->caps_lock_armed = false; + } + + uint32_t key = resolve_key(internal, row, col); + if (key != 0) { + for (auto& active_key : internal->active_keys) { + if (!active_key.in_use) { + active_key = { .in_use = true, .row = row, .col = col, .key = key }; + push_pending(internal, key, true); + break; + } + } + } + } else { + // Release: report the key that was actually pressed, even if the active modifier layer + // changed while it was held. + for (auto& active_key : internal->active_keys) { + if (active_key.in_use && active_key.row == row && active_key.col == col) { + push_pending(internal, active_key.key, false); + active_key.in_use = false; + break; + } + } + } + + if (!internal->sym_pressed && !internal->shift_pressed) { + internal->caps_lock_armed = true; + } +} + +static error_t cardputer_adv_keyboard_read_key(Device* device, KeyboardKeyData* data) { + auto* parent = device_get_parent(device); + const auto* config = GET_CONFIG(device); + auto* internal = static_cast(device_get_driver_data(device)); + + for (int i = 0; i < CARDPUTER_ADV_MAX_EVENTS_PER_SCAN; i++) { + uint8_t key_event = 0; + if (i2c_controller_register8_get(parent, config->address, TCA8418_REG_KEY_EVENT_A, &key_event, I2C_TIMEOUT) != ERROR_NONE) { + break; + } + if (key_event == 0) { + break; // Chip event FIFO is empty. + } + + bool pressed = (key_event & 0x80) != 0; + uint8_t raw_code = key_event & 0x7F; + if (raw_code == 0) { + continue; // Invalid: code 0 underflows below when decremented. + } + uint8_t code = raw_code - 1; + uint8_t wiring_row = code / 10; + uint8_t wiring_col = code % 10; + if (wiring_row >= TCA8418_ROWS || wiring_col >= TCA8418_COLS) { + continue; // Out of range for the physical matrix: would index the keymaps out of bounds. + } + + uint8_t grid_row, grid_col; + remap_to_grid(wiring_row, wiring_col, &grid_row, &grid_col); + handle_key_event(internal, grid_row, grid_col, pressed); + } + + CardputerAdvPendingEvent event; + if (pop_pending(internal, &event)) { + data->key = event.key; + data->pressed = event.pressed; + data->continue_reading = internal->pending_count > 0; + } else { + data->key = 0; + data->pressed = false; + data->continue_reading = false; + } + + return ERROR_NONE; +} + +// endregion + +static const KeyboardApi cardputer_adv_keyboard_api = { + .read_key = cardputer_adv_keyboard_read_key, +}; + +Driver cardputer_adv_keyboard_driver = { + .name = "cardputer_adv_keyboard", + .compatible = (const char*[]) { "m5stack,cardputer-adv-keyboard", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &cardputer_adv_keyboard_api, + .device_type = &KEYBOARD_TYPE, + .owner = &m5stack_module, + .internal = nullptr +}; diff --git a/Drivers/m5stack-module/source/cardputer_keyboard.cpp b/Drivers/m5stack-module/source/cardputer_keyboard.cpp new file mode 100644 index 000000000..c8016ba8a --- /dev/null +++ b/Drivers/m5stack-module/source/cardputer_keyboard.cpp @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: Apache-2.0 +// Matrix-scan and keymap logic ported from the M5Stack Cardputer keyboard driver at +// https://github.com/m5stack/M5Cardputer-UserDemo/tree/main/main/hal/keyboard (MIT licensed). +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static constexpr const char* TAG = "CardputerKeyboard"; +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr int CARDPUTER_OUTPUT_COUNT = 3; +static constexpr int CARDPUTER_INPUT_COUNT = 7; +static constexpr int CARDPUTER_ROWS = 4; +static constexpr int CARDPUTER_COLS = 14; +// Worst case per scan: the previously active key releases and a different key is now active, +// i.e. one release event and one press event. +static constexpr int CARDPUTER_PENDING_CAPACITY = 2; + +enum CardputerKeyRole { + CARDPUTER_KEY_CHAR, + CARDPUTER_KEY_TAB, + CARDPUTER_KEY_FN, + CARDPUTER_KEY_SHIFT, + CARDPUTER_KEY_CTRL, + CARDPUTER_KEY_OPT, + CARDPUTER_KEY_ALT, + CARDPUTER_KEY_DEL, + CARDPUTER_KEY_ENTER, + CARDPUTER_KEY_SPACE, +}; + +struct CardputerKeyDef { + CardputerKeyRole role; + // Only meaningful when role == CARDPUTER_KEY_CHAR. + char normal; + char shifted; +}; + +#define K(normal, shifted) { CARDPUTER_KEY_CHAR, normal, shifted } + +// [row][col], matching the physical key grid. Modifier/action cells carry a role instead of +// a character; their normal/shifted fields are unused. +static const CardputerKeyDef cardputer_key_map[CARDPUTER_ROWS][CARDPUTER_COLS] = { + { K('`', '~'), K('1', '!'), K('2', '@'), K('3', '#'), K('4', '$'), K('5', '%'), K('6', '^'), + K('7', '&'), K('8', '*'), K('9', '('), K('0', ')'), K('-', '_'), K('=', '+'), { CARDPUTER_KEY_DEL, 0, 0 } }, + { { CARDPUTER_KEY_TAB, 0, 0 }, K('q', 'Q'), K('w', 'W'), K('e', 'E'), K('r', 'R'), K('t', 'T'), K('y', 'Y'), + K('u', 'U'), K('i', 'I'), K('o', 'O'), K('p', 'P'), K('[', '{'), K(']', '}'), K('\\', '|') }, + { { CARDPUTER_KEY_FN, 0, 0 }, { CARDPUTER_KEY_SHIFT, 0, 0 }, K('a', 'A'), K('s', 'S'), K('d', 'D'), K('f', 'F'), K('g', 'G'), + K('h', 'H'), K('j', 'J'), K('k', 'K'), K('l', 'L'), K(';', ':'), K('\'', '"'), { CARDPUTER_KEY_ENTER, 0, 0 } }, + { { CARDPUTER_KEY_CTRL, 0, 0 }, { CARDPUTER_KEY_OPT, 0, 0 }, { CARDPUTER_KEY_ALT, 0, 0 }, K('z', 'Z'), K('x', 'X'), K('c', 'C'), K('v', 'V'), + K('b', 'B'), K('n', 'N'), K('m', 'M'), K(',', '<'), K('.', '>'), K('/', '?'), { CARDPUTER_KEY_SPACE, 0, 0 } }, +}; + +#undef K + +struct CardputerKeyboardPendingEvent { + uint32_t key; + bool pressed; +}; + +struct CardputerKeyboardInternal { + GpioDescriptor* output_descriptors[CARDPUTER_OUTPUT_COUNT]; + GpioDescriptor* input_descriptors[CARDPUTER_INPUT_COUNT]; + // 0 when no actionable key is currently held; otherwise the LVGL key code last reported + // via read_key(). Only ever one actionable key at a time (matches original hardware driver: + // modifier keys are consumed internally, and only the first non-modifier key found in a + // scan is reported). + uint32_t active_key; + CardputerKeyboardPendingEvent pending[CARDPUTER_PENDING_CAPACITY]; + uint8_t pending_head; + uint8_t pending_count; +}; + +static void push_pending(CardputerKeyboardInternal* internal, uint32_t key, bool pressed) { + if (internal->pending_count >= CARDPUTER_PENDING_CAPACITY) { + LOG_W(TAG, "Pending event queue full, dropping event"); + return; + } + uint8_t tail = (internal->pending_head + internal->pending_count) % CARDPUTER_PENDING_CAPACITY; + internal->pending[tail] = { .key = key, .pressed = pressed }; + internal->pending_count++; +} + +static bool pop_pending(CardputerKeyboardInternal* internal, CardputerKeyboardPendingEvent* out_event) { + if (internal->pending_count == 0) { + return false; + } + *out_event = internal->pending[internal->pending_head]; + internal->pending_head = (internal->pending_head + 1) % CARDPUTER_PENDING_CAPACITY; + internal->pending_count--; + return true; +} + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + if (config->pins_output_count != CARDPUTER_OUTPUT_COUNT || config->pins_input_count != CARDPUTER_INPUT_COUNT) { + LOG_E(TAG, "Expected %d output pins and %d input pins", CARDPUTER_OUTPUT_COUNT, CARDPUTER_INPUT_COUNT); + return ERROR_INVALID_ARGUMENT; + } + + auto* internal = static_cast(malloc(sizeof(CardputerKeyboardInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + *internal = {}; + + for (int i = 0; i < CARDPUTER_OUTPUT_COUNT; i++) { + const auto& pin = config->pins_output[i]; + auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO); + if (descriptor != nullptr && gpio_descriptor_set_flags(descriptor, pin.flags | GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) { + gpio_descriptor_release(descriptor); + descriptor = nullptr; + } + if (descriptor == nullptr) { + LOG_E(TAG, "Failed to configure output pin %d", i); + for (int j = 0; j < i; j++) { + gpio_descriptor_release(internal->output_descriptors[j]); + } + free(internal); + return ERROR_RESOURCE; + } + gpio_descriptor_set_level(descriptor, false); + internal->output_descriptors[i] = descriptor; + } + + for (int i = 0; i < CARDPUTER_INPUT_COUNT; i++) { + const auto& pin = config->pins_input[i]; + // Rows float high and are pulled low through a pressed key by the active output line, + // so an internal pull-up is required regardless of what the devicetree pin flags say. + auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO); + if (descriptor == nullptr || gpio_descriptor_set_flags(descriptor, pin.flags | GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure input pin %d", i); + for (int j = 0; j < CARDPUTER_OUTPUT_COUNT; j++) { + gpio_descriptor_release(internal->output_descriptors[j]); + } + for (int j = 0; j < i; j++) { + gpio_descriptor_release(internal->input_descriptors[j]); + } + free(internal); + return ERROR_RESOURCE; + } + internal->input_descriptors[i] = descriptor; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + for (auto* descriptor : internal->output_descriptors) { + gpio_descriptor_release(descriptor); + } + for (auto* descriptor : internal->input_descriptors) { + gpio_descriptor_release(descriptor); + } + + free(internal); + return ERROR_NONE; +} + +// endregion + +// region KeyboardApi + +static void set_output(CardputerKeyboardInternal* internal, uint8_t value) { + for (int i = 0; i < CARDPUTER_OUTPUT_COUNT; i++) { + gpio_descriptor_set_level(internal->output_descriptors[i], ((value >> i) & 0x01) != 0); + } +} + +static uint8_t read_input(CardputerKeyboardInternal* internal) { + uint8_t mask = 0; + for (int j = 0; j < CARDPUTER_INPUT_COUNT; j++) { + bool high = true; + gpio_descriptor_get_level(internal->input_descriptors[j], &high); + if (!high) { // active-low: LOW means pressed + mask |= (1 << j); + } + } + return mask; +} + +// Scans the full matrix and resolves it to a single LVGL key code (0 if none), applying the +// same priority as the original driver: enter > space > backspace > first regular character +// found in scan order, with fn changing the interpretation of backspace/enter/punctuation. +// Modifier keys (fn/shift/ctrl/opt/alt/tab) are never reported themselves. +static uint32_t scan_key(CardputerKeyboardInternal* internal) { + bool fn = false, shift = false, ctrl = false; + bool del_flag = false, enter_flag = false, space_flag = false; + bool has_regular = false; + char regular_normal = 0, regular_shifted = 0; + + // 8 binary-encoded column-select codes x 7 row inputs = 4 rows x 14 columns. + for (uint8_t i = 0; i < 8; i++) { + set_output(internal, i); + uint8_t input_mask = read_input(internal); + if (input_mask == 0) { + continue; + } + + for (int j = 0; j < CARDPUTER_INPUT_COUNT; j++) { + if ((input_mask & (1 << j)) == 0) { + continue; + } + + int row = 3 - (i % 4); + int col = (i >= 4) ? (2 * j) : (2 * j + 1); + const auto& def = cardputer_key_map[row][col]; + + switch (def.role) { + case CARDPUTER_KEY_TAB: + case CARDPUTER_KEY_OPT: + case CARDPUTER_KEY_ALT: + break; // consumed, never affects output + case CARDPUTER_KEY_FN: + fn = true; + break; + case CARDPUTER_KEY_SHIFT: + shift = true; + break; + case CARDPUTER_KEY_CTRL: + ctrl = true; + break; + case CARDPUTER_KEY_DEL: + del_flag = true; + break; + case CARDPUTER_KEY_ENTER: + enter_flag = true; + break; + case CARDPUTER_KEY_SPACE: + space_flag = true; + break; + case CARDPUTER_KEY_CHAR: + if (!has_regular) { + has_regular = true; + regular_normal = def.normal; + regular_shifted = def.shifted; + } + break; + } + } + } + + char resolved_char = has_regular ? ((ctrl || shift) ? regular_shifted : regular_normal) : 0; + + if (!fn) { + if (enter_flag) return LV_KEY_ENTER; + if (space_flag) return (uint32_t)' '; + if (del_flag) return LV_KEY_BACKSPACE; + if (has_regular) return (uint32_t)(uint8_t)resolved_char; + return 0; + } + + // fn combos: forward-delete, enter, and group navigation (using PREV/NEXT rather than + // UP/DOWN so widgets like lv_switch that toggle on arrow keys aren't affected). + if (del_flag) return LV_KEY_DEL; + if (enter_flag) return LV_KEY_ENTER; + if (has_regular) { + switch (resolved_char) { + case '`': return LV_KEY_ESC; + case ',': return LV_KEY_LEFT; + case '/': return LV_KEY_RIGHT; + case ';': return LV_KEY_PREV; + case '.': return LV_KEY_NEXT; + default: return 0; + } + } + return 0; +} + +static error_t cardputer_keyboard_read_key(Device* device, KeyboardKeyData* data) { + auto* internal = static_cast(device_get_driver_data(device)); + + uint32_t new_key = scan_key(internal); + if (new_key != internal->active_key) { + if (internal->active_key != 0) { + push_pending(internal, internal->active_key, false); + } + if (new_key != 0) { + push_pending(internal, new_key, true); + } + internal->active_key = new_key; + } + + CardputerKeyboardPendingEvent event; + if (pop_pending(internal, &event)) { + data->key = event.key; + data->pressed = event.pressed; + data->continue_reading = internal->pending_count > 0; + } else { + data->key = 0; + data->pressed = false; + data->continue_reading = false; + } + + return ERROR_NONE; +} + +// endregion + +static const KeyboardApi cardputer_keyboard_api = { + .read_key = cardputer_keyboard_read_key, +}; + +Driver cardputer_keyboard_driver = { + .name = "cardputer_keyboard", + .compatible = (const char*[]) { "m5stack,cardputer-keyboard", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &cardputer_keyboard_api, + .device_type = &KEYBOARD_TYPE, + .owner = &m5stack_module, + .internal = nullptr +}; diff --git a/Drivers/m5stack-module/source/module.cpp b/Drivers/m5stack-module/source/module.cpp new file mode 100644 index 000000000..f713c9d6c --- /dev/null +++ b/Drivers/m5stack-module/source/module.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver cardputer_keyboard_driver; +extern Driver cardputer_adv_keyboard_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(&cardputer_keyboard_driver) == ERROR_NONE); + check(driver_construct_add(&cardputer_adv_keyboard_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(&cardputer_keyboard_driver) == ERROR_NONE); + check(driver_remove_destruct(&cardputer_adv_keyboard_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module m5stack_module = { + .name = "m5stack", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/py32ioexpander-module/LICENSE-Apache-2.0.md b/Drivers/py32ioexpander-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/py32ioexpander-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/st7789-module/LICENSE-Apache-2.0.md b/Drivers/st7789-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/st7789-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/st7789-module/source/st7789.cpp b/Drivers/st7789-module/source/st7789.cpp index 1f086fdb9..20a5bd8d4 100644 --- a/Drivers/st7789-module/source/st7789.cpp +++ b/Drivers/st7789-module/source/st7789.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include #define TAG "ST7789" @@ -26,8 +28,22 @@ struct St7789Internal { esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_handle_t panel_handle; + // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically + // completes. draw_bitmap() blocks on this so it can honor DisplayApi's synchronous contract + // (see lvgl_display.c: the caller reuses/overwrites the color buffer as soon as draw_bitmap + // returns) - esp_lcd_panel_draw_bitmap() itself only queues the transfer and returns early. + SemaphoreHandle_t draw_done_semaphore; }; +// Fires for every completed SPI transaction on this IO (not just draw_bitmap's color transfers - +// bring-up commands like reset/init/gap go through the same IO), called from ISR context. +static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) { + auto* internal = static_cast(user_ctx); + BaseType_t high_task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken); + return high_task_woken == pdTRUE; +} + static int pin_or_unused(const struct GpioPinSpec& pin) { return pin.gpio_controller == nullptr ? -1 : static_cast(pin.pin); } @@ -52,14 +68,20 @@ static error_t start(Device* device) { return ERROR_OUT_OF_MEMORY; } + internal->draw_done_semaphore = xSemaphoreCreateBinary(); + if (internal->draw_done_semaphore == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + esp_lcd_panel_io_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, + .on_color_trans_done = on_color_trans_done, + .user_ctx = internal, .lcd_cmd_bits = 8, .lcd_param_bits = 8, .cs_ena_pretrans = 0, @@ -79,6 +101,7 @@ static error_t start(Device* device) { 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)); + vSemaphoreDelete(internal->draw_done_semaphore); free(internal); return ERROR_RESOURCE; } @@ -96,6 +119,7 @@ static error_t start(Device* device) { if (ret != ESP_OK) { LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret)); esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); free(internal); return ERROR_RESOURCE; } @@ -122,6 +146,7 @@ static error_t start(Device* device) { LOG_E(TAG, "Failed to bring up panel"); esp_lcd_panel_del(internal->panel_handle); esp_lcd_panel_io_del(internal->io_handle); + vSemaphoreDelete(internal->draw_done_semaphore); free(internal); return ERROR_RESOURCE; } @@ -142,6 +167,7 @@ static error_t stop(Device* device) { return ERROR_RESOURCE; } + vSemaphoreDelete(internal->draw_done_semaphore); free(internal); return ERROR_NONE; } @@ -162,7 +188,22 @@ static error_t st7789_init(Device* device) { 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; + + // Drain any stale signal left over from a prior non-draw transaction (bring-up commands like + // reset/gap also complete through on_color_trans_done), so the take() below can only be + // satisfied by this draw's own transfer completing. + xSemaphoreTake(internal->draw_done_semaphore, 0); + + if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { + return ERROR_RESOURCE; + } + + // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous + // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite + // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the + // transfer and returns once it's handed to the SPI peripheral, not once it's finished. + xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); + return ERROR_NONE; } static error_t st7789_mirror(Device* device, bool x_axis, bool y_axis) { diff --git a/Drivers/xpt2046-module/CMakeLists.txt b/Drivers/xpt2046-module/CMakeLists.txt new file mode 100644 index 000000000..baa571594 --- /dev/null +++ b/Drivers/xpt2046-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(xpt2046-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_touch_xpt2046 esp_lcd driver +) diff --git a/Drivers/xpt2046-module/LICENSE-Apache-2.0.md b/Drivers/xpt2046-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/xpt2046-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/xpt2046-module/README.md b/Drivers/xpt2046-module/README.md new file mode 100644 index 000000000..43245b4a1 --- /dev/null +++ b/Drivers/xpt2046-module/README.md @@ -0,0 +1,9 @@ +# XPT2046 SPI Touch Driver + +A kernel driver for the `XPT2046` resistive touch controller, built on ESP-IDF's `esp_lcd_touch_xpt2046` component over the standard SPI master driver. + +No reset or interrupt pin support (matches the controller's typical wiring: polled over SPI, no dedicated IRQ line wired on most panels using it). + +See https://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml b/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml new file mode 100644 index 000000000..ba460e68e --- /dev/null +++ b/Drivers/xpt2046-module/bindings/xptek,xpt2046.yaml @@ -0,0 +1,27 @@ +description: XPT2046 resistive touch controller + +bus: spi + +compatible: "xptek,xpt2046" + +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 diff --git a/Drivers/xpt2046-module/devicetree.yaml b/Drivers/xpt2046-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/xpt2046-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/xpt2046-module/include/bindings/xpt2046.h b/Drivers/xpt2046-module/include/bindings/xpt2046.h new file mode 100644 index 000000000..418f9e18f --- /dev/null +++ b/Drivers/xpt2046-module/include/bindings/xpt2046.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(xpt2046, struct Xpt2046Config) diff --git a/Drivers/xpt2046-module/include/drivers/xpt2046.h b/Drivers/xpt2046-module/include/drivers/xpt2046.h new file mode 100644 index 000000000..df9e7492d --- /dev/null +++ b/Drivers/xpt2046-module/include/drivers/xpt2046.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +struct Xpt2046Config { + uint16_t x_max; + uint16_t y_max; + bool swap_xy; + bool mirror_x; + bool mirror_y; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/xpt2046-module/include/xpt2046_module.h b/Drivers/xpt2046-module/include/xpt2046_module.h new file mode 100644 index 000000000..9d8584df7 --- /dev/null +++ b/Drivers/xpt2046-module/include/xpt2046_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module xpt2046_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/xpt2046-module/source/module.cpp b/Drivers/xpt2046-module/source/module.cpp new file mode 100644 index 000000000..a22bd803d --- /dev/null +++ b/Drivers/xpt2046-module/source/module.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver xpt2046_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&xpt2046_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&xpt2046_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module xpt2046_module = { + .name = "xpt2046", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/xpt2046-module/source/xpt2046.cpp b/Drivers/xpt2046-module/source/xpt2046.cpp new file mode 100644 index 000000000..a202a9403 --- /dev/null +++ b/Drivers/xpt2046-module/source/xpt2046.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define TAG "XPT2046" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct Xpt2046Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_touch_handle_t touch_handle; +}; + +// 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(Xpt2046Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(cs_pin.pin); + 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; + } + + const esp_lcd_touch_config_t touch_config = { + .x_max = config->x_max, + .y_max = config->y_max, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .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_spi_xpt2046(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)); + + 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"); + 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 ok ? ERROR_NONE : ERROR_RESOURCE; +} + +// endregion + +// region PointerApi + +static error_t xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_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 xpt2046_pointer_api = { + .enter_sleep = xpt2046_enter_sleep, + .exit_sleep = xpt2046_exit_sleep, + .read_data = xpt2046_read_data, + .get_touched_points = xpt2046_get_touched_points, + .set_swap_xy = xpt2046_set_swap_xy, + .get_swap_xy = xpt2046_get_swap_xy, + .set_mirror_x = xpt2046_set_mirror_x, + .get_mirror_x = xpt2046_get_mirror_x, + .set_mirror_y = xpt2046_set_mirror_y, + .get_mirror_y = xpt2046_get_mirror_y, +}; + +Driver xpt2046_driver = { + .name = "xpt2046", + .compatible = (const char*[]) { "xptek,xpt2046", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &xpt2046_pointer_api, + .device_type = &POINTER_TYPE, + .owner = &xpt2046_module, + .internal = nullptr +}; diff --git a/Firmware/Kconfig b/Firmware/Kconfig index 4ddbf8ef5..95fce6704 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -77,6 +77,12 @@ menu "Tactility App" config TT_TDECK_WORKAROUND bool "Temporary work-around until we fix the T-Deck keyboard and trackball settings" default n + config TT_USE_DEPRECATED_HAL + bool "When set to false, it ensures an empty tt::hal::hardwareConfig is made automatically" + default y + help + This allows for migrating projects that don't use the old HAL. + When set to 'n', the project has an option to not depend on Tactility and can depend on TactilityKernel instead. choice TT_USER_DATA_LOCATION prompt "User Data Location" default TT_USER_DATA_LOCATION_INTERNAL diff --git a/Firmware/Source/Main.cpp b/Firmware/Source/Main.cpp index 67ca7b8ad..8dfa43506 100644 --- a/Firmware/Source/Main.cpp +++ b/Firmware/Source/Main.cpp @@ -9,8 +9,13 @@ #include #endif +#ifdef CONFIG_TT_USE_DEPRECATED_HAL // Each board project declares this variable extern const tt::hal::Configuration hardwareConfiguration; +#else +// Legacy placeholder (required until legacy HAL is cleaned up everywhere) +extern const tt::hal::Configuration hardwareConfiguration = {}; +#endif extern "C" { diff --git a/Modules/lvgl-module/source/lvgl_devices.c b/Modules/lvgl-module/source/lvgl_devices.c index 9d4d81c58..1c74846b0 100644 --- a/Modules/lvgl-module/source/lvgl_devices.c +++ b/Modules/lvgl-module/source/lvgl_devices.c @@ -18,36 +18,55 @@ void lvgl_devices_attach() { lvgl_lock(); lv_disp_t* lvgl_display = NULL; - struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE); + struct Device* kernel_display_device = NULL; + device_get_first_by_type(&DISPLAY_TYPE, &kernel_display_device); + // 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) { + device_put(kernel_display_device); kernel_display_device = NULL; } + if (kernel_display_device != NULL) { - struct LvglDisplayConfig lvgl_display_config = {}; + // A full-frame buffer (buffer_height=0) needs one contiguous allocation of + // hres*vres*bpp bytes. Plain ESP32 boards without PSRAM only have ~100-150KB + // contiguous internal RAM, which a 240x320+ panel can already exceed - so + // lvgl_display_add() would fail with ERROR_OUT_OF_MEMORY and never register a + // display. Match the old deprecated-HAL drivers' convention of a partial buffer + // sized to 1/10th of the vertical resolution; it fits comfortably everywhere, + // including boards with PSRAM that could afford full-frame. + uint16_t vres = display_get_resolution_y(kernel_display_device); + struct LvglDisplayConfig lvgl_display_config = { + .buffer_height = vres > 10 ? vres / 10 : vres + }; 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); } + device_put(kernel_display_device); } - struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE); + struct Device* kernel_pointer_device = NULL; + device_get_first_by_type(&POINTER_TYPE, &kernel_pointer_device); // Same placeholder situation as the display above. if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) { + device_put(kernel_pointer_device); kernel_pointer_device = NULL; } - lv_indev_t* lvgl_pointer_device; if (kernel_pointer_device != NULL) { + lv_indev_t* lvgl_pointer_device; 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); } + device_put(kernel_pointer_device); } - struct Device* kernel_keyboard_device = device_find_first_by_type(&KEYBOARD_TYPE); + struct Device* kernel_keyboard_device = NULL; + device_get_first_by_type(&KEYBOARD_TYPE, &kernel_keyboard_device); 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) { @@ -55,6 +74,7 @@ void lvgl_devices_attach() { } else { LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name); } + device_put(kernel_keyboard_device); } lvgl_unlock(); diff --git a/Modules/lvgl-module/source/lvgl_display.c b/Modules/lvgl-module/source/lvgl_display.c index 6e0110b4c..d67fafb43 100644 --- a/Modules/lvgl-module/source/lvgl_display.c +++ b/Modules/lvgl-module/source/lvgl_display.c @@ -21,6 +21,13 @@ struct LvglDisplayCtx { bool base_swap_xy; bool base_mirror_x; bool base_mirror_y; + // True for DISPLAY_COLOR_FORMAT_RGB565_SWAPPED: the panel wants each 16-bit pixel high-byte-first + // over SPI, which this little-endian CPU's native RGB565 buffer isn't. LVGL's own + // LV_COLOR_FORMAT_RGB565_SWAPPED display format looked like the fit but isn't fully supported by + // the SW renderer (produced garbage on real hardware) - esp_lvgl_port's own approach is instead to + // render normal RGB565 and byte-swap the flushed rect in place just before sending it out, via + // lv_draw_sw_rgb565_swap(). Mirrored here. + bool byte_swap; }; static void* lvgl_display_alloc_buffer(size_t size_bytes) { @@ -43,16 +50,21 @@ static void lvgl_display_free_buffer(void* buf) { #endif } -static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_format_t* out) { +// out_byte_swap is set whenever the LVGL-side buffer format doesn't already match the panel's +// wire byte order and needs a post-render swap in the flush callback (see LvglDisplayCtx::byte_swap). +static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_format_t* out, bool* out_byte_swap) { switch (in) { case DISPLAY_COLOR_FORMAT_RGB565: *out = LV_COLOR_FORMAT_RGB565; + *out_byte_swap = false; return true; case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED: - *out = LV_COLOR_FORMAT_RGB565_SWAPPED; + *out = LV_COLOR_FORMAT_RGB565; + *out_byte_swap = true; return true; case DISPLAY_COLOR_FORMAT_RGB888: *out = LV_COLOR_FORMAT_RGB888; + *out_byte_swap = false; return true; default: // DISPLAY_COLOR_FORMAT_BGR565/_SWAPPED: no LVGL equivalent (channel order, not byte order). @@ -103,6 +115,9 @@ static void lvgl_display_rotation_event_cb(lv_event_t* e) { 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); + if (ctx->byte_swap) { + lv_draw_sw_rgb565_swap(color_map, lv_area_get_size(area)); + } // 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. @@ -118,8 +133,9 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* } lv_color_format_t lv_color_format; + bool byte_swap; enum DisplayColorFormat kernel_color_format = display_get_color_format(device); - if (!lvgl_display_map_color_format(kernel_color_format, &lv_color_format)) { + if (!lvgl_display_map_color_format(kernel_color_format, &lv_color_format, &byte_swap)) { LOG_E(TAG, "Unsupported color format %d (no LVGL equivalent)", (int)kernel_color_format); return ERROR_NOT_SUPPORTED; } @@ -134,6 +150,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* return ERROR_OUT_OF_MEMORY; } ctx->device = device; + ctx->byte_swap = byte_swap; 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); diff --git a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp index 7d3542621..711540b3f 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp @@ -126,6 +126,14 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } + // MISO is only actively driven by the selected slave; between commands (and briefly during + // slave selection/response) it floats, which can be read as spurious bits. A weak pull-up + // costs nothing against an actively-driven line and avoids that, e.g. on SD-over-SPI this + // shows up as CMD8/if_cond failing with a garbled/invalid response. + if (data->miso_descriptor != nullptr) { + gpio_descriptor_set_flags(data->miso_descriptor, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP); + } + // Acquire and deselect all CS pins (drive high) for (uint8_t i = 0; i < dts_config->cs_gpios_count; i++) { const GpioPinSpec* cs = &dts_config->cs_gpios[i]; diff --git a/TactilityKernel/CMakeLists.txt b/TactilityKernel/CMakeLists.txt index 101671a02..6b311082d 100644 --- a/TactilityKernel/CMakeLists.txt +++ b/TactilityKernel/CMakeLists.txt @@ -16,5 +16,6 @@ endif () tactility_add_module(TactilityKernel SRCS ${SOURCE_FILES} INCLUDE_DIRS include/ + PRIV_INCLUDE_DIRS private/ REQUIRES ${REQUIRES_LIST} ) diff --git a/TactilityKernel/include/tactility/device.h b/TactilityKernel/include/tactility/device.h index 5943c3187..78eea5645 100644 --- a/TactilityKernel/include/tactility/device.h +++ b/TactilityKernel/include/tactility/device.h @@ -60,7 +60,8 @@ error_t device_construct(struct Device* device); * This fails when a device is busy or has children. * * @param[in,out] device non-null device pointer - * @retval ERROR_INVALID_STATE if the device is busy or has children + * @retval ERROR_INVALID_STATE if the device is started, added, or has children + * @retval ERROR_RESOURCE_BUSY if the device has outstanding device_get() references * @retval ERROR_NONE on success */ error_t device_destruct(struct Device* device); @@ -92,6 +93,9 @@ error_t device_remove(struct Device* device); * Attach the driver. * * @warning must call device_construct() and device_add() first + * @warning the driver's start_device callback must not call device_lock()/device_get()/ + * device_start()/device_stop() on this same device - device_start() holds this + * device's lock for its entire body, so doing so would deadlock. * @param[in,out] device non-null device pointer * @retval ERROR_INVALID_STATE if the device is already started or not added * @retval ERROR_RESOURCE when driver binding fails @@ -102,8 +106,12 @@ error_t device_start(struct Device* device); /** * Detach the driver. * + * @warning the driver's stop_device callback must not call device_lock()/device_get()/ + * device_start()/device_stop() on this same device - device_stop() holds this + * device's lock for its entire body, so doing so would deadlock. * @param[in,out] device non-null device pointer * @retval ERROR_INVALID_STATE if the device is not started + * @retval ERROR_RESOURCE_BUSY if the device has outstanding device_get() references * @retval ERROR_RESOURCE when driver unbinding fails * @retval ERROR_NONE on success */ @@ -226,6 +234,41 @@ bool device_try_lock(struct Device* device, TickType_t timeout); */ void device_unlock(struct Device* device); +/** + * Indicates whether device_construct() has been called and device_destruct() has not + * subsequently been called (or has been followed by another device_construct()). + * + * @param[in] device non-null device pointer + * @return true if the device is currently constructed + */ +bool device_is_constructed(const struct Device* device); + +/** + * Take a reference on a device, guaranteeing its driver data remains valid until a matching + * device_put(). Named after the Linux kernel's get_device()/put_device(). Intended to bracket + * a single hardware operation (one API call or a short sequence of them), not to be held across + * a blocking wait or unrelated later work - device_stop() fails with ERROR_RESOURCE_BUSY while + * any reference is outstanding. + * + * @param[in,out] device non-null device pointer, already known to be valid (e.g. a static + * devicetree pointer, or one captured earlier under a lock) - device_get() does + * not itself protect against a dangling/freed Device*. For a fresh lookup that + * might race a concurrent teardown of a dynamically-allocated Device, use one of + * the device_get_*() lookup functions below instead. + * @retval ERROR_INVALID_STATE if the device is not started (or has been destructed) + * @retval ERROR_NONE on success; caller must call device_put(device) exactly once + */ +error_t device_get(struct Device* device); + +/** + * Release a reference previously taken with a successful device_get() (or a successful + * device_get_by_name()/device_get_first_by_type()/device_get_first_active_by_type()/ + * device_get_first_by_compatible()). + * + * @param[in,out] device non-null device pointer previously passed to a successful device_get() call + */ +void device_put(struct Device* device); + /** * Get the type of a device. * @@ -273,7 +316,7 @@ void device_for_each_of_type(const struct DeviceType* type, void* callback_conte * @param[in] type the type to check * @return true if a device of the specified type exists */ -bool device_exists_of_type(const struct DeviceType* type); +bool device_exists_of_type(const struct DeviceType* type) ; /** * Find a device by its name. @@ -281,7 +324,7 @@ bool device_exists_of_type(const struct DeviceType* type); * @param[in] name non-null device name to look up * @return the device pointer if found, or NULL if not found */ -struct Device* device_find_by_name(const char* name); +struct Device* device_find_by_name(const char* name) __attribute__((deprecated("Use device_get_by_name() and device_put()"))); /** * Find the first started device of the given type. @@ -289,7 +332,7 @@ struct Device* device_find_by_name(const char* name); * @param[in] type non-null device type pointer * @return the first started device of the given type, or NULL if none found */ -struct Device* device_find_first_active_by_type(const struct DeviceType* type); +struct Device* device_find_first_active_by_type(const struct DeviceType* type) __attribute__((deprecated("Use device_get_first_active_by_type() and device_put()"))); /** * Find the first device of the given type. @@ -297,7 +340,7 @@ struct Device* device_find_first_active_by_type(const struct DeviceType* type); * @param[in] type non-null device type pointer * @return the first device of the given type, or NULL if none found */ -struct Device* device_find_first_by_type(const struct DeviceType* type); +struct Device* device_find_first_by_type(const struct DeviceType* type) __attribute__((deprecated("Use device_get_first_by_type() and device_put()"))); /** * Find the first device whose driver matches the given compatible string. @@ -305,7 +348,62 @@ struct Device* device_find_first_by_type(const struct DeviceType* type); * @param[in] compatible non-null compatible string to match * @return the first matching device, or NULL if none found */ -struct Device* device_find_first_by_compatible(const char* compatible); +struct Device* device_find_first_by_compatible(const char* compatible) __attribute__((deprecated("Use device_get_first_by_compatible() and device_put()"))); + +/** + * Find a device by name and atomically take a reference on it (equivalent to a device_find_by_name() + * immediately followed by a successful device_get(), but race-free: the lookup and the reference + * are taken under the same lock, so a device that gets torn down concurrently either isn't found + * or is safely referenced - there is no gap where a caller could be handed a pointer that's about + * to become invalid). Prefer this over device_find_by_name() + device_get() for any device that + * might be dynamically constructed/destructed at runtime (e.g. a hot-pluggable child device), + * rather than a static devicetree-defined one. + * + * @param[in] name non-null device name to look up + * @param[out] out_device receives the found device on success; untouched on failure + * @retval ERROR_NOT_FOUND if no device with that name exists + * @retval ERROR_INVALID_STATE if found but not started (no reference taken) + * @retval ERROR_NONE on success; caller must call device_put(*out_device) exactly once + */ +error_t device_get_by_name(const char* name, struct Device** out_device); + +/** + * Find the first device of the given type and atomically take a reference on it. See + * device_get_by_name() for why this is preferred over device_find_first_by_type() + device_get() + * for dynamically constructed/destructed devices. + * + * @param[in] type non-null device type pointer + * @param[out] out_device receives the found device on success; untouched on failure + * @retval ERROR_NOT_FOUND if no device of that type exists + * @retval ERROR_INVALID_STATE if found but not started (no reference taken) + * @retval ERROR_NONE on success; caller must call device_put(*out_device) exactly once + */ +error_t device_get_first_by_type(const struct DeviceType* type, struct Device** out_device); + +/** + * Find the first started device of the given type and atomically take a reference on it. See + * device_get_by_name() for why this is preferred over device_find_first_active_by_type() + + * device_get() for dynamically constructed/destructed devices. + * + * @param[in] type non-null device type pointer + * @param[out] out_device receives the found device on success; untouched on failure + * @retval ERROR_NOT_FOUND if no started device of that type exists + * @retval ERROR_NONE on success; caller must call device_put(*out_device) exactly once + */ +error_t device_get_first_active_by_type(const struct DeviceType* type, struct Device** out_device); + +/** + * Find the first device whose driver matches the given compatible string and atomically take a + * reference on it. See device_get_by_name() for why this is preferred over + * device_find_first_by_compatible() + device_get() for dynamically constructed/destructed devices. + * + * @param[in] compatible non-null compatible string to match + * @param[out] out_device receives the found device on success; untouched on failure + * @retval ERROR_NOT_FOUND if no matching device exists + * @retval ERROR_INVALID_STATE if found but not started (no reference taken) + * @retval ERROR_NONE on success; caller must call device_put(*out_device) exactly once + */ +error_t device_get_first_by_compatible(const char* compatible, struct Device** out_device); #ifdef __cplusplus } diff --git a/TactilityKernel/include/tactility/device_listener.h b/TactilityKernel/include/tactility/device_listener.h new file mode 100644 index 000000000..bfc912349 --- /dev/null +++ b/TactilityKernel/include/tactility/device_listener.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct Device; + +enum DeviceEvent { + DEVICE_EVENT_STARTED, + DEVICE_EVENT_STOPPED, +}; + +typedef void (*DeviceListenerCallback)( + struct Device *dev, + enum DeviceEvent event, + void* context +); + +struct DeviceEventListener { + DeviceListenerCallback callback; + void* callback_context; +}; + +void device_listener_add(DeviceListenerCallback* callback, void* context); + +void device_listener_remove(DeviceListenerCallback* callback); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/error.h b/TactilityKernel/include/tactility/error.h index 58aa77b64..d7689ba50 100644 --- a/TactilityKernel/include/tactility/error.h +++ b/TactilityKernel/include/tactility/error.h @@ -25,6 +25,7 @@ typedef int error_t; #define ERROR_NOT_ALLOWED 11 #define ERROR_BUFFER_OVERFLOW 12 #define ERROR_OUT_OF_RANGE 13 +#define ERROR_RESOURCE_BUSY 14 // Rejected: device/driver has outstanding references and cannot be stopped/destructed yet /** Convert an error_t to a human-readable text. Useful for logging. */ const char* error_to_string(error_t error); diff --git a/TactilityKernel/private/tactility/device_listener_internal.h b/TactilityKernel/private/tactility/device_listener_internal.h new file mode 100644 index 000000000..8d8f2a248 --- /dev/null +++ b/TactilityKernel/private/tactility/device_listener_internal.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Internal to TactilityKernel: invoked by device lifecycle code (device_start/device_stop) to +// notify all registered listeners of a device state transition. Not part of the public API - +// external code only sees device_listener_add()/device_listener_remove(). +void device_listener_notify(struct Device* dev, enum DeviceEvent event); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/device.cpp b/TactilityKernel/source/device.cpp index 8ecd4e951..8f2aba03a 100644 --- a/TactilityKernel/source/device.cpp +++ b/TactilityKernel/source/device.cpp @@ -2,32 +2,53 @@ #include #include +#include #include #include +#include +#include #include #include #include -#include #include #define TAG "device" struct DeviceInternal { /** Address of the API exposed by the device instance. */ - struct Driver* driver = nullptr; + Driver* driver = nullptr; /** The driver data for this device (e.g. a mutex) */ void* driver_data = nullptr; - /** The mutex for device operations */ - struct Mutex mutex {}; + // The mutex for device operations. Recursive: some drivers (e.g. battery_sense.cpp) + // legitimately call device_add()/device_start() on a CHILD device from within their own + // start_device - device_add_child() then locks the PARENT (this same device) again on the + // same thread. A plain Mutex would deadlock here. + // NOTE: device_start()/device_stop() do NOT hold this across the driver's + // start_device/stop_device callback (see state.starting/state.stopping below) - that callback + // can add/remove child devices, which takes ledger_lock, and lookup helpers + // (device_get_by_name() etc.) take ledger_lock then this mutex via device_get(). Holding both + // at once in opposite orders would ABBA-deadlock against those lookups. + struct RecursiveMutex mutex {}; /** The device state */ struct { int start_result = 0; bool started : 1 = false; bool added : 1 = false; + // Set while driver_bind()/driver_unbind() runs, with `mutex` released. Blocks concurrent + // device_start()/device_stop() calls from racing the same transition; state.stopping is + // also checked by device_get() so no new ref can be acquired while a stop is in flight. + bool starting : 1 = false; + bool stopping : 1 = false; } state; /** Attached child devices */ std::vector children {}; + // Outstanding device_get() holders. Guarded by `mutex`. device_get() refuses new refs once + // state.stopping is set, and device_stop() refuses to set state.stopping while this is > 0 - + // together that guarantees ref_count > 0 implies state.started == true, so by the time + // device_remove()/device_destruct() run (both already require !started), this is always + // already 0. + int32_t ref_count = 0; }; struct DeviceLedger { @@ -55,8 +76,8 @@ extern "C" { #define ledger_lock() mutex_lock(&ledger.mutex) #define ledger_unlock() mutex_unlock(&ledger.mutex) -#define lock_internal(internal) mutex_lock(&internal->mutex) -#define unlock_internal(internal) mutex_unlock(&internal->mutex) +#define lock_internal(internal) recursive_mutex_lock(&internal->mutex) +#define unlock_internal(internal) recursive_mutex_unlock(&internal->mutex) error_t device_construct(Device* device) { device->internal = new(std::nothrow) DeviceInternal; @@ -64,7 +85,7 @@ error_t device_construct(Device* device) { return ERROR_OUT_OF_MEMORY; } LOG_D(TAG, "construct %s", device->name); - mutex_construct(&device->internal->mutex); + recursive_mutex_construct(&device->internal->mutex); return ERROR_NONE; } @@ -81,17 +102,24 @@ error_t device_destruct(Device* device) { unlock_internal(device->internal); return ERROR_INVALID_STATE; } + // Callers are expected to sequence teardown correctly (device_stop() already refuses to + // clear `started` while ref_count > 0, so by the time !started holds above, ref_count is + // already 0) - this is a cheap defense-in-depth check, not a substitute for that discipline. + if (internal->ref_count > 0) { + unlock_internal(device->internal); + return ERROR_RESOURCE_BUSY; + } LOG_D(TAG, "destruct %s", device->name); device->internal = nullptr; - mutex_unlock(&internal->mutex); + recursive_mutex_unlock(&internal->mutex); delete internal; return ERROR_NONE; } /** Add a child to the list of children */ -static void device_add_child(struct Device* device, struct Device* child) { +static void device_add_child(Device* device, Device* child) { device_lock(device); check(device->internal->state.added); device->internal->children.push_back(child); @@ -99,7 +127,7 @@ static void device_add_child(struct Device* device, struct Device* child) { } /** Remove a child from the list of children */ -static void device_remove_child(struct Device* device, struct Device* child) { +static void device_remove_child(Device* device, Device* child) { device_lock(device); const auto iterator = std::ranges::find(device->internal->children, child); if (iterator != device->internal->children.end()) { @@ -168,46 +196,99 @@ error_t device_remove(Device* device) { error_t device_start(Device* device) { LOG_I(TAG, "start %s", device->name); - if (!device->internal->state.added) { - return ERROR_INVALID_STATE; - } + auto* internal = device->internal; + lock_internal(internal); - if (device->internal->driver == nullptr) { + if (!internal->state.added || internal->driver == nullptr) { + unlock_internal(internal); return ERROR_INVALID_STATE; } // Already started - if (device->internal->state.started) { + if (internal->state.started) { + unlock_internal(internal); return ERROR_NONE; } - error_t bind_error = driver_bind(device->internal->driver, device); - device->internal->state.started = (bind_error == ERROR_NONE); - device->internal->state.start_result = bind_error; - return bind_error == ERROR_NONE ? ERROR_NONE : ERROR_RESOURCE; + // Already starting on another thread + if (internal->state.starting) { + unlock_internal(internal); + return ERROR_RESOURCE_BUSY; + } + internal->state.starting = true; + unlock_internal(internal); + + // driver_bind() runs the driver's start_device callback, which may add/start child devices + // (device_add() takes ledger_lock) - `mutex` must stay released across this call. See the + // comment on `mutex` above. + error_t bind_error = driver_bind(internal->driver, device); + + lock_internal(internal); + internal->state.starting = false; + internal->state.started = (bind_error == ERROR_NONE); + internal->state.start_result = bind_error; + unlock_internal(internal); + + if (bind_error != ERROR_NONE) { + return ERROR_RESOURCE; + } + + device_listener_notify(device, DEVICE_EVENT_STARTED); + + return ERROR_NONE; } -error_t device_stop(struct Device* device) { +error_t device_stop(Device* device) { LOG_I(TAG, "stop %s", device->name); - if (!device->internal->state.added) { + auto* internal = device->internal; + lock_internal(internal); + + if (!internal->state.added) { + unlock_internal(internal); return ERROR_INVALID_STATE; } - if (!device->internal->state.started) { + if (!internal->state.started) { + unlock_internal(internal); return ERROR_NONE; } - if (driver_unbind(device->internal->driver, device) != ERROR_NONE) { + if (internal->ref_count > 0) { + unlock_internal(internal); + return ERROR_RESOURCE_BUSY; + } + + // Already stopping on another thread + if (internal->state.stopping) { + unlock_internal(internal); + return ERROR_RESOURCE_BUSY; + } + internal->state.stopping = true; + unlock_internal(internal); + + // driver_unbind() runs the driver's stop_device callback, which may remove/destruct child + // devices (device_remove() takes ledger_lock) - `mutex` must stay released across this call, + // same reasoning as device_start(). state.stopping keeps device_get() from handing out a new + // ref while ref_count is meant to stay at 0 during the unbind. + error_t unbind_error = driver_unbind(internal->driver, device); + + lock_internal(internal); + internal->state.stopping = false; + if (unbind_error != ERROR_NONE) { + unlock_internal(internal); return ERROR_RESOURCE; } + internal->state.started = false; + internal->state.start_result = 0; + unlock_internal(internal); + + device_listener_notify(device, DEVICE_EVENT_STOPPED); - device->internal->state.started = false; - device->internal->state.start_result = 0; return ERROR_NONE; } -error_t device_construct_add(struct Device* device, const char* compatible) { - struct Driver* driver = driver_find_compatible(compatible); +error_t device_construct_add(Device* device, const char* compatible) { + Driver* driver = driver_find_compatible(compatible); if (driver == nullptr) { LOG_E(TAG, "Can't find driver '%s' for device '%s'", compatible, device->name); return ERROR_RESOURCE; @@ -235,7 +316,7 @@ error_t device_construct_add(struct Device* device, const char* compatible) { return error; } -error_t device_construct_add_start(struct Device* device, const char* compatible) { +error_t device_construct_add_start(Device* device, const char* compatible) { error_t error = device_construct_add(device, compatible); if (error != ERROR_NONE) { goto on_construct_add_error; @@ -261,52 +342,76 @@ void device_set_parent(Device* device, Device* parent) { device->parent = parent; } -Device* device_get_parent(struct Device* device) { +Device* device_get_parent(Device* device) { return device->parent; } -void device_set_driver(struct Device* device, struct Driver* driver) { +void device_set_driver(Device* device, Driver* driver) { device->internal->driver = driver; } -struct Driver* device_get_driver(struct Device* device) { +Driver* device_get_driver(Device* device) { return device->internal->driver; } -bool device_is_ready(const struct Device* device) { +bool device_is_ready(const Device* device) { return device->internal->state.started; } -bool device_is_compatible(const struct Device* device, const char* compatible) { +bool device_is_compatible(const Device* device, const char* compatible) { if (device->internal->driver == nullptr) return false; return driver_is_compatible(device->internal->driver, compatible); } -void device_set_driver_data(struct Device* device, void* driver_data) { +void device_set_driver_data(Device* device, void* driver_data) { device->internal->driver_data = driver_data; } -void* device_get_driver_data(struct Device* device) { +void* device_get_driver_data(Device* device) { return device->internal->driver_data; } -bool device_is_added(const struct Device* device) { +bool device_is_added(const Device* device) { return device->internal->state.added; } -void device_lock(struct Device* device) { - mutex_lock(&device->internal->mutex); +void device_lock(Device* device) { + recursive_mutex_lock(&device->internal->mutex); +} + +bool device_try_lock(Device* device, TickType_t timeout) { + return recursive_mutex_try_lock(&device->internal->mutex, timeout); +} + +void device_unlock(Device* device) { + recursive_mutex_unlock(&device->internal->mutex); +} + +bool device_is_constructed(const Device* device) { + return device->internal != nullptr; } -bool device_try_lock(struct Device* device, TickType_t timeout) { - return mutex_try_lock(&device->internal->mutex, timeout); +error_t device_get(Device* device) { + auto* internal = device->internal; + lock_internal(internal); + if (!internal->state.started || internal->state.stopping) { + unlock_internal(internal); + return ERROR_INVALID_STATE; + } + internal->ref_count++; + unlock_internal(internal); + return ERROR_NONE; } -void device_unlock(struct Device* device) { - mutex_unlock(&device->internal->mutex); +void device_put(Device* device) { + auto* internal = device->internal; + lock_internal(internal); + check(internal->ref_count > 0); + internal->ref_count--; + unlock_internal(internal); } -const struct DeviceType* device_get_type(struct Device* device) { +const DeviceType* device_get_type(Device* device) { return device->internal->driver ? device->internal->driver->device_type : NULL; } @@ -320,7 +425,7 @@ void device_for_each(void* callback_context, bool(*on_device)(Device* device, vo ledger_unlock(); } -void device_for_each_child(Device* device, void* callbackContext, bool(*on_device)(struct Device* device, void* context)) { +void device_for_each_child(Device* device, void* callbackContext, bool(*on_device)(Device* device, void* context)) { for (auto* child_device : device->internal->children) { if (!on_device(child_device, callbackContext)) { break; @@ -409,4 +514,96 @@ Device* device_find_first_by_compatible(const char* compatible) { return ctx.found; } +error_t device_get_by_name(const char* name, Device** out_device) { + ledger_lock(); + Device* found = nullptr; + for (auto* device : ledger.devices) { + if (device->name != nullptr && std::strcmp(device->name, name) == 0) { + found = device; + break; + } + } + if (found == nullptr) { + ledger_unlock(); + return ERROR_NOT_FOUND; + } + // device_get() takes internal->mutex while still holding ledger_lock here on purpose: it + // blocks device_remove() (which needs ledger_lock to erase `found`) for the span between + // finding the device and taking a ref on it, so `found` can't be torn down and freed out from + // under us in that window. This is the reverse lock order from device_start()/device_stop(), + // which release internal->mutex before touching ledger_lock - see the comment on + // DeviceInternal::mutex for why that asymmetry is deadlock-free. + error_t error = device_get(found); + ledger_unlock(); + if (error == ERROR_NONE) { + *out_device = found; + } + return error; +} + +error_t device_get_first_by_type(const DeviceType* type, Device** out_device) { + ledger_lock(); + Device* found = nullptr; + for (auto* device : ledger.devices) { + auto* driver = device->internal->driver; + if (driver != nullptr && driver->device_type == type) { + found = device; + break; + } + } + if (found == nullptr) { + ledger_unlock(); + return ERROR_NOT_FOUND; + } + error_t error = device_get(found); + ledger_unlock(); + if (error == ERROR_NONE) { + *out_device = found; + } + return error; +} + +error_t device_get_first_active_by_type(const DeviceType* type, Device** out_device) { + ledger_lock(); + Device* found = nullptr; + for (auto* device : ledger.devices) { + auto* driver = device->internal->driver; + if (driver != nullptr && driver->device_type == type && device->internal->state.started) { + found = device; + break; + } + } + if (found == nullptr) { + ledger_unlock(); + return ERROR_NOT_FOUND; + } + error_t error = device_get(found); + ledger_unlock(); + if (error == ERROR_NONE) { + *out_device = found; + } + return error; +} + +error_t device_get_first_by_compatible(const char* compatible, Device** out_device) { + ledger_lock(); + Device* found = nullptr; + for (auto* device : ledger.devices) { + if (device_is_compatible(device, compatible)) { + found = device; + break; + } + } + if (found == nullptr) { + ledger_unlock(); + return ERROR_NOT_FOUND; + } + error_t error = device_get(found); + ledger_unlock(); + if (error == ERROR_NONE) { + *out_device = found; + } + return error; +} + } // extern "C" diff --git a/TactilityKernel/source/device_listener.cpp b/TactilityKernel/source/device_listener.cpp new file mode 100644 index 000000000..bda49ec43 --- /dev/null +++ b/TactilityKernel/source/device_listener.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include + +struct DeviceListenerLedger { + std::vector listeners; + Mutex mutex {}; + + DeviceListenerLedger() { mutex_construct(&mutex); } + ~DeviceListenerLedger() { mutex_destruct(&mutex); } + + void lock() { mutex_lock(&mutex); } + void unlock() { mutex_unlock(&mutex); } +}; + +static DeviceListenerLedger ledger; + +extern "C" { + +void device_listener_add(DeviceListenerCallback* callback, void* context) { + ledger.lock(); + ledger.listeners.push_back(DeviceEventListener{ *callback, context }); + ledger.unlock(); +} + +void device_listener_remove(DeviceListenerCallback* callback) { + ledger.lock(); + const auto iterator = std::ranges::find_if(ledger.listeners, [callback](const DeviceEventListener& listener) { + return listener.callback == *callback; + }); + if (iterator != ledger.listeners.end()) { + ledger.listeners.erase(iterator); + } + ledger.unlock(); +} + +void device_listener_notify(Device* dev, DeviceEvent event) { + // Copy the listener list under the lock, then invoke callbacks after unlocking: a listener + // calling device_listener_add/remove from within its own callback would otherwise deadlock + // against this same (non-recursive) mutex, and a slow listener would block every other + // thread's add/remove for the duration of this notification. + ledger.lock(); + const std::vector listeners_copy = ledger.listeners; + ledger.unlock(); + + for (const auto& listener : listeners_copy) { + listener.callback(dev, event, listener.callback_context); + } +} + +} // extern "C" diff --git a/TactilityKernel/source/error.cpp b/TactilityKernel/source/error.cpp index e5e149da1..9a253a901 100644 --- a/TactilityKernel/source/error.cpp +++ b/TactilityKernel/source/error.cpp @@ -31,6 +31,8 @@ const char* error_to_string(error_t error) { return "not allowed"; case ERROR_BUFFER_OVERFLOW: return "buffer overflow"; + case ERROR_RESOURCE_BUSY: + return "resource busy"; default: return "unknown"; } diff --git a/Tests/TactilityKernel/Source/DeviceGetPutTest.cpp b/Tests/TactilityKernel/Source/DeviceGetPutTest.cpp new file mode 100644 index 000000000..8fa0e1a0a --- /dev/null +++ b/Tests/TactilityKernel/Source/DeviceGetPutTest.cpp @@ -0,0 +1,141 @@ +#include "doctest.h" + +#include + +#include +#include +#include +#include +#include + +namespace { + +Module module = { + .name = "device_get_put_test_module", + .start = nullptr, + .stop = nullptr +}; + +int start(Device*) { return ERROR_NONE; } +int stop(Device*) { return ERROR_NONE; } + +Driver test_driver = { + .name = "device_get_put_test_driver", + .compatible = (const char*[]) { "device_get_put_test", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &module, + .internal = nullptr, +}; + +} // namespace + +TEST_CASE("device_get should fail with ERROR_INVALID_STATE when the device is not started") { + Device device = { .name = "get_not_started", .config = nullptr, .parent = nullptr }; + + CHECK_EQ(driver_construct_add(&test_driver), ERROR_NONE); + CHECK_EQ(device_construct(&device), ERROR_NONE); + device_set_driver(&device, &test_driver); + CHECK_EQ(device_add(&device), ERROR_NONE); + + CHECK_EQ(device_get(&device), ERROR_INVALID_STATE); + + CHECK_EQ(device_remove(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); + CHECK_EQ(driver_remove_destruct(&test_driver), ERROR_NONE); +} + +TEST_CASE("device_get should succeed once started, and device_put should release it") { + Device device = { .name = "get_started", .config = nullptr, .parent = nullptr }; + + CHECK_EQ(driver_construct_add(&test_driver), ERROR_NONE); + CHECK_EQ(device_construct(&device), ERROR_NONE); + device_set_driver(&device, &test_driver); + CHECK_EQ(device_add(&device), ERROR_NONE); + CHECK_EQ(device_start(&device), ERROR_NONE); + + CHECK_EQ(device_get(&device), ERROR_NONE); + device_put(&device); + + CHECK_EQ(device_stop(&device), ERROR_NONE); + CHECK_EQ(device_remove(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); + CHECK_EQ(driver_remove_destruct(&test_driver), ERROR_NONE); +} + +TEST_CASE("device_stop should fail with ERROR_RESOURCE_BUSY while a reference is held, from a concurrent thread") { + static Device device = { .name = "get_put_concurrent", .config = nullptr, .parent = nullptr }; + static std::atomic acquired { false }; + static std::atomic release { false }; + + CHECK_EQ(driver_construct_add(&test_driver), ERROR_NONE); + CHECK_EQ(device_construct(&device), ERROR_NONE); + device_set_driver(&device, &test_driver); + CHECK_EQ(device_add(&device), ERROR_NONE); + CHECK_EQ(device_start(&device), ERROR_NONE); + + acquired = false; + release = false; + + auto* thread = thread_alloc_full( + "device_get_put_worker", + 4096, + [](void*) -> int32_t { + if (device_get(&device) != ERROR_NONE) { + return 1; + } + acquired = true; + while (!release.load()) { + delay_millis(1); + } + device_put(&device); + return 0; + }, + nullptr, + -1 + ); + + CHECK_EQ(thread_start(thread), ERROR_NONE); + + while (!acquired.load()) { + delay_millis(1); + } + + // Held by the worker thread right now - device_stop() must fail fast, not block. + CHECK_EQ(device_stop(&device), ERROR_RESOURCE_BUSY); + + release = true; + CHECK_EQ(thread_join(thread, 200, 1), ERROR_NONE); + thread_free(thread); + + // Reference released - device_stop() now succeeds. + CHECK_EQ(device_stop(&device), ERROR_NONE); + CHECK_EQ(device_remove(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); + CHECK_EQ(driver_remove_destruct(&test_driver), ERROR_NONE); +} + +TEST_CASE("device_get_by_name should find and reference a started device, or fail if not found/not started") { + Device device = { .name = "get_by_name_device", .config = nullptr, .parent = nullptr }; + + CHECK_EQ(driver_construct_add(&test_driver), ERROR_NONE); + CHECK_EQ(device_construct(&device), ERROR_NONE); + device_set_driver(&device, &test_driver); + CHECK_EQ(device_add(&device), ERROR_NONE); + + Device* out = nullptr; + CHECK_EQ(device_get_by_name("does_not_exist", &out), ERROR_NOT_FOUND); + CHECK_EQ(device_get_by_name("get_by_name_device", &out), ERROR_INVALID_STATE); + + CHECK_EQ(device_start(&device), ERROR_NONE); + CHECK_EQ(device_get_by_name("get_by_name_device", &out), ERROR_NONE); + CHECK_EQ(out, &device); + device_put(out); + + CHECK_EQ(device_stop(&device), ERROR_NONE); + CHECK_EQ(device_remove(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); + CHECK_EQ(driver_remove_destruct(&test_driver), ERROR_NONE); +} diff --git a/Tests/TactilityKernel/Source/DeviceTest.cpp b/Tests/TactilityKernel/Source/DeviceTest.cpp index 897eda9c1..3b5d94e81 100644 --- a/Tests/TactilityKernel/Source/DeviceTest.cpp +++ b/Tests/TactilityKernel/Source/DeviceTest.cpp @@ -12,17 +12,33 @@ static Module module = { .stop = nullptr }; -TEST_CASE("device_construct and device_destruct should set and unset the internal field") { +TEST_CASE("device_construct and device_destruct should set and unset the constructed state") { Device device = { 0 }; error_t error = device_construct(&device); CHECK_EQ(error, ERROR_NONE); - CHECK_NE(device.internal, nullptr); + CHECK_EQ(device_is_constructed(&device), true); CHECK_EQ(device_destruct(&device), ERROR_NONE); - CHECK_EQ(device.internal, nullptr); + CHECK_EQ(device_is_constructed(&device), false); +} + +TEST_CASE("device_construct should be reusable after device_destruct on the same Device") { + Device device = { 0 }; + + CHECK_EQ(device_construct(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); + + // Reconstruct on the same static Device: fresh allocation, behaves like a new device. + CHECK_EQ(device_construct(&device), ERROR_NONE); + CHECK_EQ(device_is_constructed(&device), true); + CHECK_EQ(device_is_added(&device), false); + + CHECK_EQ(device_add(&device), ERROR_NONE); + CHECK_EQ(device_remove(&device), ERROR_NONE); + CHECK_EQ(device_destruct(&device), ERROR_NONE); } TEST_CASE("device_add should add the device to the list of all devices") { diff --git a/device.py b/device.py index df1698d36..570add48a 100644 --- a/device.py +++ b/device.py @@ -170,6 +170,12 @@ def write_core_variables(output_file, device_properties: dict): output_file.write("CONFIG_ESP_WIFI_RX_IRAM_OPT=n\n") output_file.write("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y\n") output_file.write("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=y\n") + # Usage of tt::hal can be disabled to simplify dependency wiring (device depends on TactilityKernel instead of Tactility) + use_deprecated_hal = get_property_or_none(device_properties, "dependencies", "useDeprecatedHal") + if use_deprecated_hal is None or use_deprecated_hal.lower() == "true": + output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=y\n") + else: + output_file.write("CONFIG_TT_USE_DEPRECATED_HAL=n\n") def write_flash_variables(output_file, device_properties: dict): flash_size = get_property_or_exit(device_properties, "hardware", "flashSize")