diff --git a/Data/data/webserver/dashboard.html b/Data/data/webserver/dashboard.html index d964ae23b..69553e720 100644 --- a/Data/data/webserver/dashboard.html +++ b/Data/data/webserver/dashboard.html @@ -613,10 +613,12 @@

Installed Apps

const psramUsed = data.psram.total - data.psram.free; const psramPercent = data.psram.total > 0 ? (psramUsed / data.psram.total) * 100 : 0; const hasPsram = data.psram.total > 0; - const dataUsed = data.storage.data.mounted ? (data.storage.data.total - data.storage.data.free) : 0; - const dataPercent = data.storage.data.mounted && data.storage.data.total > 0 ? (dataUsed / data.storage.data.total) * 100 : 0; - const sdUsed = data.storage.sdcard.mounted ? (data.storage.sdcard.total - data.storage.sdcard.free) : 0; - const sdPercent = data.storage.sdcard.mounted && data.storage.sdcard.total > 0 ? (sdUsed / data.storage.sdcard.total) * 100 : 0; + const dataPartition = data.storage?.data ?? {}; + const sdcard = data.storage?.sdcard ?? {}; + const dataUsed = dataPartition.mounted ? (dataPartition.total - dataPartition.free) : 0; + const dataPercent = dataPartition.mounted && dataPartition.total > 0 ? (dataUsed / dataPartition.total) * 100 : 0; + const sdUsed = sdcard.mounted ? (sdcard.total - sdcard.free) : 0; + const sdPercent = sdcard.mounted && sdcard.total > 0 ? (sdUsed / sdcard.total) * 100 : 0; versionEl.textContent = `Tactility v${data.firmware.version} | ESP-IDF v${data.firmware.idf_version}`; @@ -711,20 +713,20 @@

External Memory (PSRAM)

Storage

Data Partition - ${ - data.storage.data.mounted ? formatBytes(data.storage.data.free) + ' free' : 'Not mounted' + ${ + dataPartition.mounted ? formatBytes(dataPartition.free) + ' free' : 'Not mounted' }
- ${data.storage.data.mounted ? `
+ ${dataPartition.mounted ? `
` : ''}
SD Card - ${ - data.storage.sdcard.mounted ? formatBytes(data.storage.sdcard.free) + ' free' : 'Not mounted' + ${ + sdcard.mounted ? formatBytes(sdcard.free) + ' free' : 'Not mounted' }
- ${data.storage.sdcard.mounted ? `
+ ${sdcard.mounted ? `
` : ''}
diff --git a/Devices/btt-panda-touch/device.properties b/Devices/btt-panda-touch/device.properties index 1f34fcb6a..a1d965ae6 100644 --- a/Devices/btt-panda-touch/device.properties +++ b/Devices/btt-panda-touch/device.properties @@ -19,4 +19,5 @@ shape=rectangle dpi=187 [lvgl] -colorDepth=16 \ No newline at end of file +colorDepth=16 +fontSize=18 diff --git a/Devices/cyd-8048s043c/device.properties b/Devices/cyd-8048s043c/device.properties index 6c6a231ee..80114bee8 100644 --- a/Devices/cyd-8048s043c/device.properties +++ b/Devices/cyd-8048s043c/device.properties @@ -26,3 +26,4 @@ warningMessage= [lvgl] theme=DefaultDark colorDepth=16 +fontSize=18 diff --git a/Devices/elecrow-crowpanel-advance-50/device.properties b/Devices/elecrow-crowpanel-advance-50/device.properties index 00780ad28..f12aba6f0 100644 --- a/Devices/elecrow-crowpanel-advance-50/device.properties +++ b/Devices/elecrow-crowpanel-advance-50/device.properties @@ -21,3 +21,4 @@ dpi=187 [lvgl] colorDepth=16 +fontSize=18 diff --git a/Devices/elecrow-crowpanel-basic-50/device.properties b/Devices/elecrow-crowpanel-basic-50/device.properties index 4ccf0fdaf..6372dc6b6 100644 --- a/Devices/elecrow-crowpanel-basic-50/device.properties +++ b/Devices/elecrow-crowpanel-basic-50/device.properties @@ -21,3 +21,4 @@ dpi=187 [lvgl] colorDepth=16 +fontSize=18 diff --git a/Devices/guition-jc8048w550c/device.properties b/Devices/guition-jc8048w550c/device.properties index 0a50b3f0c..e405ed7b6 100644 --- a/Devices/guition-jc8048w550c/device.properties +++ b/Devices/guition-jc8048w550c/device.properties @@ -20,3 +20,4 @@ dpi=187 [lvgl] colorDepth=16 +fontSize=18 diff --git a/Devices/m5stack-cardputer-adv/devicetree.yaml b/Devices/m5stack-cardputer-adv/devicetree.yaml index 2548e656e..d6b380731 100644 --- a/Devices/m5stack-cardputer-adv/devicetree.yaml +++ b/Devices/m5stack-cardputer-adv/devicetree.yaml @@ -1,3 +1,4 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/bmi270-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 b51c37b16..421f3436a 100644 --- a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts +++ b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts @@ -6,6 +6,7 @@ #include #include #include +#include // Reference: https://docs.m5stack.com/en/core/Cardputer-Adv / { @@ -23,6 +24,11 @@ clock-frequency = <400000>; pin-sda = <&gpio0 8 GPIO_FLAG_NONE>; pin-scl = <&gpio0 9 GPIO_FLAG_NONE>; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; }; i2c_port_a { diff --git a/Devices/m5stack-core2/devicetree.yaml b/Devices/m5stack-core2/devicetree.yaml index 3016b01ab..d1c990164 100644 --- a/Devices/m5stack-core2/devicetree.yaml +++ b/Devices/m5stack-core2/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/mpu6886-module +- Drivers/bm8563-module dts: m5stack,core2.dts diff --git a/Devices/m5stack-core2/m5stack,core2.dts b/Devices/m5stack-core2/m5stack,core2.dts index f18fd6e24..8092e39cd 100644 --- a/Devices/m5stack-core2/m5stack,core2.dts +++ b/Devices/m5stack-core2/m5stack,core2.dts @@ -6,6 +6,8 @@ #include #include #include +#include +#include // Reference: https://docs.m5stack.com/en/core/Core2 / { @@ -23,6 +25,16 @@ clock-frequency = <400000>; pin-sda = <&gpio0 21 GPIO_FLAG_NONE>; pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; + + mpu6886 { + compatible = "invensense,mpu6886"; + reg = <0x68>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; }; i2c_port_a { diff --git a/Devices/m5stack-cores3/devicetree.yaml b/Devices/m5stack-cores3/devicetree.yaml index daa64904a..b65b1d7e5 100644 --- a/Devices/m5stack-cores3/devicetree.yaml +++ b/Devices/m5stack-cores3/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/bmi270-module +- Drivers/bm8563-module dts: m5stack,cores3.dts diff --git a/Devices/m5stack-cores3/m5stack,cores3.dts b/Devices/m5stack-cores3/m5stack,cores3.dts index 55adccc13..08b17fe96 100644 --- a/Devices/m5stack-cores3/m5stack,cores3.dts +++ b/Devices/m5stack-cores3/m5stack,cores3.dts @@ -6,6 +6,8 @@ #include #include #include +#include +#include // Reference: https://docs.m5stack.com/en/core/CoreS3 / { @@ -23,6 +25,16 @@ clock-frequency = <400000>; pin-sda = <&gpio0 12 GPIO_FLAG_NONE>; pin-scl = <&gpio0 11 GPIO_FLAG_NONE>; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; }; i2c_port_a { diff --git a/Devices/m5stack-papers3/devicetree.yaml b/Devices/m5stack-papers3/devicetree.yaml index e1585b29d..855cc0553 100644 --- a/Devices/m5stack-papers3/devicetree.yaml +++ b/Devices/m5stack-papers3/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/bmi270-module +- Drivers/bm8563-module dts: m5stack,papers3.dts diff --git a/Devices/m5stack-papers3/m5stack,papers3.dts b/Devices/m5stack-papers3/m5stack,papers3.dts index 3d8b69549..207b351c2 100644 --- a/Devices/m5stack-papers3/m5stack,papers3.dts +++ b/Devices/m5stack-papers3/m5stack,papers3.dts @@ -4,6 +4,8 @@ #include #include #include +#include +#include / { compatible = "root"; @@ -20,6 +22,16 @@ clock-frequency = <400000>; pin-sda = <&gpio0 41 GPIO_FLAG_NONE>; pin-scl = <&gpio0 42 GPIO_FLAG_NONE>; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; }; spi0 { diff --git a/Devices/m5stack-stickc-plus/devicetree.yaml b/Devices/m5stack-stickc-plus/devicetree.yaml index dcb567e86..287869e9c 100644 --- a/Devices/m5stack-stickc-plus/devicetree.yaml +++ b/Devices/m5stack-stickc-plus/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/mpu6886-module +- Drivers/bm8563-module dts: m5stack,stickc-plus.dts diff --git a/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts b/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts index 88d618218..55bdbf6e7 100644 --- a/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts +++ b/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts @@ -5,6 +5,8 @@ #include #include #include +#include +#include / { compatible = "root"; @@ -21,6 +23,16 @@ clock-frequency = <400000>; pin-sda = <&gpio0 21 GPIO_FLAG_NONE>; pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; + + mpu6886 { + compatible = "invensense,mpu6886"; + reg = <0x68>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; }; i2c_grove { diff --git a/Devices/m5stack-stickc-plus2/devicetree.yaml b/Devices/m5stack-stickc-plus2/devicetree.yaml index 7d3045f09..13b717386 100644 --- a/Devices/m5stack-stickc-plus2/devicetree.yaml +++ b/Devices/m5stack-stickc-plus2/devicetree.yaml @@ -1,3 +1,5 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/mpu6886-module +- Drivers/bm8563-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 96524067d..9f86c6800 100644 --- a/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts +++ b/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts @@ -4,6 +4,8 @@ #include #include #include +#include +#include / { compatible = "root"; @@ -20,6 +22,16 @@ clock-frequency = <400000>; pin-sda = <&gpio0 21 GPIO_FLAG_NONE>; pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; + + mpu6886 { + compatible = "invensense,mpu6886"; + reg = <0x68>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; }; i2c_grove { diff --git a/Devices/m5stack-sticks3/CMakeLists.txt b/Devices/m5stack-sticks3/CMakeLists.txt new file mode 100644 index 000000000..a6e098d03 --- /dev/null +++ b/Devices/m5stack-sticks3/CMakeLists.txt @@ -0,0 +1,7 @@ +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 m5pm1-module +) diff --git a/Devices/m5stack-sticks3/Source/Configuration.cpp b/Devices/m5stack-sticks3/Source/Configuration.cpp new file mode 100644 index 000000000..4c3619234 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/Configuration.cpp @@ -0,0 +1,26 @@ +#include "devices/Display.h" +#include "devices/Power.h" +#include + +#include +#include +#include + +using namespace tt::hal; + +bool initBoot() { + return driver::pwmbacklight::init(GPIO_NUM_38, 512); +} + +static DeviceVector createDevices() { + return { + createPower(), + ButtonControl::createTwoButtonControl(11, 12), // top button, side button + createDisplay() + }; +} + +extern const Configuration hardwareConfiguration = { + .initBoot = initBoot, + .createDevices = createDevices +}; diff --git a/Devices/m5stack-sticks3/Source/devices/Display.cpp b/Devices/m5stack-sticks3/Source/devices/Display.cpp new file mode 100644 index 000000000..5b732b068 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Display.cpp @@ -0,0 +1,32 @@ +#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-sticks3/Source/devices/Display.h b/Devices/m5stack-sticks3/Source/devices/Display.h new file mode 100644 index 000000000..65142cb5f --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Display.h @@ -0,0 +1,17 @@ +#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_41; +constexpr auto LCD_PIN_DC = GPIO_NUM_45; +constexpr auto LCD_PIN_RESET = GPIO_NUM_21; +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; + +std::shared_ptr createDisplay(); diff --git a/Devices/m5stack-sticks3/Source/devices/Power.cpp b/Devices/m5stack-sticks3/Source/devices/Power.cpp new file mode 100644 index 000000000..036b7595f --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Power.cpp @@ -0,0 +1,94 @@ +#include "Power.h" + +#include +#include +#include +#include + +using namespace tt::hal::power; + +static constexpr auto* TAG = "StickS3Power"; + +static constexpr float MIN_BATTERY_VOLTAGE_MV = 3300.0f; +static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f; + +class StickS3Power final : public PowerDevice { +public: + explicit StickS3Power(::Device* m5pm1Device) : m5pm1(m5pm1Device) {} + + std::string getName() const override { return "M5Stack StickS3 Power"; } + std::string getDescription() const override { return "Battery monitoring via M5PM1 over I2C"; } + + bool supportsMetric(MetricType type) const override { + switch (type) { + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + case IsCharging: + return true; + default: + return false; + } + } + + bool getMetric(MetricType type, MetricData& data) override { + switch (type) { + using enum MetricType; + + case BatteryVoltage: { + uint16_t mv = 0; + if (m5pm1_get_battery_voltage(m5pm1, &mv) != ERROR_NONE) return false; + data.valueAsUint32 = mv; + return true; + } + + case ChargeLevel: { + uint16_t mv = 0; + if (m5pm1_get_battery_voltage(m5pm1, &mv) != ERROR_NONE) return false; + float voltage = static_cast(mv); + if (voltage >= MAX_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 100; + } else if (voltage <= MIN_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 0; + } else { + float factor = (voltage - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV); + data.valueAsUint8 = static_cast(factor * 100.0f); + } + return true; + } + + case IsCharging: { + bool charging = false; + if (m5pm1_is_charging(m5pm1, &charging) != ERROR_NONE) { + LOG_W(TAG, "Failed to read charging status"); + return false; + } + data.valueAsBool = charging; + return true; + } + + default: + return false; + } + } + + bool supportsPowerOff() const override { return true; } + + void powerOff() override { + LOG_W(TAG, "Powering off via M5PM1"); + if (m5pm1_shutdown(m5pm1) != ERROR_NONE) { + LOG_E(TAG, "Failed to send power-off command"); + } + } + +private: + ::Device* m5pm1; +}; + +std::shared_ptr createPower() { + auto* m5pm1 = device_find_by_name("m5pm1"); + if (m5pm1 == nullptr) { + LOG_E(TAG, "m5pm1 device not found"); + } + return std::make_shared(m5pm1); +} diff --git a/Devices/m5stack-sticks3/Source/devices/Power.h b/Devices/m5stack-sticks3/Source/devices/Power.h new file mode 100644 index 000000000..7598ded6f --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Power.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::shared_ptr createPower(); diff --git a/Devices/m5stack-sticks3/Source/module.cpp b/Devices/m5stack-sticks3/Source/module.cpp new file mode 100644 index 000000000..77b30f3c2 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/module.cpp @@ -0,0 +1,23 @@ +#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_sticks3_module = { + .name = "m5stack-sticks3", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/m5stack-sticks3/device.properties b/Devices/m5stack-sticks3/device.properties new file mode 100644 index 000000000..4bcc3b9e3 --- /dev/null +++ b/Devices/m5stack-sticks3/device.properties @@ -0,0 +1,25 @@ +[general] +vendor=M5Stack +name=StickS3 + +[apps] +launcherAppId=Launcher +autoStartAppId=ApWebServer + +[hardware] +target=ESP32S3 +flashSize=8MB +spiRam=true +spiRamMode=OCT +spiRamSpeed=80M +esptoolFlashFreq=80M +tinyUsb=true + +[display] +size=1.14" +shape=rectangle +dpi=242 + +[lvgl] +colorDepth=16 +uiDensity=compact diff --git a/Devices/m5stack-sticks3/devicetree.yaml b/Devices/m5stack-sticks3/devicetree.yaml new file mode 100644 index 000000000..1022f4de9 --- /dev/null +++ b/Devices/m5stack-sticks3/devicetree.yaml @@ -0,0 +1,5 @@ +dependencies: +- Platforms/platform-esp32 +- Drivers/bmi270-module +- Drivers/m5pm1-module +dts: m5stack,sticks3.dts diff --git a/Devices/m5stack-sticks3/m5stack,sticks3.dts b/Devices/m5stack-sticks3/m5stack,sticks3.dts new file mode 100644 index 000000000..37ae19fa2 --- /dev/null +++ b/Devices/m5stack-sticks3/m5stack,sticks3.dts @@ -0,0 +1,71 @@ +/dts-v1/; +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + compatible = "root"; + model = "M5Stack StickS3"; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + i2c_internal { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <100000>; + pin-sda = <&gpio0 47 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 48 GPIO_FLAG_NONE>; + + m5pm1 { + compatible = "m5stack,m5pm1"; + reg = <0x6E>; + }; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; + }; + + i2c_grove { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 9 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 10 GPIO_FLAG_NONE>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + pin-mosi = <&gpio0 39 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>; + }; + + // Speaker and microphone (ES8311) + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 17 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 15 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 14 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 16 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 18 GPIO_FLAG_NONE>; + }; + + uart_grove: uart1 { + compatible = "espressif,esp32-uart"; + status = "disabled"; + port = ; + pin-tx = <&gpio0 9 GPIO_FLAG_NONE>; + pin-rx = <&gpio0 10 GPIO_FLAG_NONE>; + }; +}; diff --git a/Devices/m5stack-tab5/CMakeLists.txt b/Devices/m5stack-tab5/CMakeLists.txt index c84b1ada3..13e48e7e8 100644 --- a/Devices/m5stack-tab5/CMakeLists.txt +++ b/Devices/m5stack-tab5/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs + REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver vfs fatfs ) diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index 6a39ad0ce..cf8c79153 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -102,11 +102,17 @@ static void initExpander0(::Device* io_expander0) { static void initExpander1(::Device* io_expander1) { auto* c6_wlan_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_C6_WLAN_ENABLE, GPIO_OWNER_GPIO); + check(c6_wlan_enable_pin); auto* usb_a_5v_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_USB_A_5V_ENABLE, GPIO_OWNER_GPIO); + check(usb_a_5v_enable_pin); auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); + check(device_power_pin); auto* ip2326_ncharge_qc_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO); + check(ip2326_ncharge_qc_enable_pin); auto* ip2326_charge_state_led_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_STAT_LED, GPIO_OWNER_GPIO); + check(ip2326_charge_state_led_pin); auto* ip2326_charge_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO); + check(ip2326_charge_enable_pin); gpio_descriptor_set_flags(c6_wlan_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); gpio_descriptor_set_flags(usb_a_5v_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); diff --git a/Devices/m5stack-tab5/Source/devices/Detect.cpp b/Devices/m5stack-tab5/Source/devices/Detect.cpp new file mode 100644 index 000000000..e70f2663b --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -0,0 +1,45 @@ +#include "Detect.h" + +#include +#include +#include +#include +#include +#include +#include + +static const auto LOGGER = tt::Logger("Tab5Detect"); + +Tab5Variant detectVariant() { + // Allow time for touch IC to fully boot after expander reset in initBoot(). + // 100ms is enough for I2C ACK (probe) but cold power-on needs ~300ms before + // register reads (read_fw_info) succeed reliably. + vTaskDelay(pdMS_TO_TICKS(300)); + + auto* i2c0 = device_find_by_name("i2c0"); + check(i2c0); + + constexpr auto PROBE_TIMEOUT = pdMS_TO_TICKS(50); + + for (int attempt = 0; attempt < 3; ++attempt) { + // GT911 address depends on INT pin state during reset: + // GPIO 23 has a pull-up resistor to 3V3, so INT is high at reset → GT911 uses 0x5D (primary) + // It may also appear at 0x14 (backup) if the pin happened to be driven low + if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE || + i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, PROBE_TIMEOUT) == ERROR_NONE) { + LOGGER.info("Detected GT911 touch — using ILI9881C display"); + return Tab5Variant::Ili9881c_Gt911; + } + + // Probe for ST7123 touch (new variant) + if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE) { + LOGGER.info("Detected ST7123 touch — using ST7123 display"); + return Tab5Variant::St7123; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + } + + LOGGER.warn("No known touch controller detected, defaulting to ST7123"); + return Tab5Variant::St7123; +} diff --git a/Devices/m5stack-tab5/Source/devices/Detect.h b/Devices/m5stack-tab5/Source/devices/Detect.h new file mode 100644 index 000000000..96d159f45 --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/Detect.h @@ -0,0 +1,8 @@ +#pragma once + +enum class Tab5Variant { + Ili9881c_Gt911, // Older variant + St7123, // Newer variant (default) +}; + +[[nodiscard]] Tab5Variant detectVariant(); diff --git a/Devices/m5stack-tab5/Source/devices/Display.cpp b/Devices/m5stack-tab5/Source/devices/Display.cpp index 3d74e1a04..b7516976f 100644 --- a/Devices/m5stack-tab5/Source/devices/Display.cpp +++ b/Devices/m5stack-tab5/Source/devices/Display.cpp @@ -1,16 +1,22 @@ +#include "Detect.h" #include "Display.h" #include "Ili9881cDisplay.h" +#include "St7123Display.h" +#include "St7123Touch.h" #include #include #include -#include #include +#include +#include + +static const auto LOGGER = tt::Logger("Tab5Display"); constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22; -static std::shared_ptr createTouch() { +static std::shared_ptr createGt911Touch() { auto configuration = std::make_unique( I2C_NUM_0, 720, @@ -19,25 +25,46 @@ static std::shared_ptr createTouch() { false, // mirrorX false, // mirrorY GPIO_NUM_NC, // reset pin - GPIO_NUM_NC // "GPIO_NUM_23 cannot be used due to resistor to 3V3" https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L76234 + GPIO_NUM_NC // "GPIO_NUM_23 cannot be used due to resistor to 3V3" + // https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L76234 ); - return std::make_shared(std::move(configuration)); } +static std::shared_ptr createSt7123Touch() { + auto configuration = std::make_unique( + I2C_NUM_0, + 720, + 1280, + false, // swapXY + false, // mirrorX + false, // mirrorY + GPIO_NUM_23 // interrupt pin + ); + return std::make_shared(std::move(configuration)); +} + std::shared_ptr createDisplay() { // Initialize PWM backlight if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 5000, LEDC_TIMER_1, LEDC_CHANNEL_0)) { - tt::Logger("Tab5").warn("Failed to initialize backlight"); + LOGGER.warn("Failed to initialize backlight"); } - auto touch = createTouch(); + Tab5Variant variant = detectVariant(); + + std::shared_ptr touch; - // Work-around to init touch : interrupt pin must be set to low - // Note: There is a resistor to 3V3 on interrupt pin which is blocking GT911 touch - // See https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L777 - tt::hal::gpio::configure(23, tt::hal::gpio::Mode::Output, true, false); - tt::hal::gpio::setLevel(23, false); + if (variant == Tab5Variant::St7123) { + touch = createSt7123Touch(); + } else { + touch = createGt911Touch(); + + // Work-around to init GT911 touch: interrupt pin must be set to low + // Note: There is a resistor to 3V3 on interrupt pin which is blocking GT911 touch + // See https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L777 + tt::hal::gpio::configure(23, tt::hal::gpio::Mode::Output, true, false); + tt::hal::gpio::setLevel(23, false); + } auto configuration = std::make_shared(EspLcdConfiguration { .horizontalResolution = 720, @@ -50,6 +77,8 @@ std::shared_ptr createDisplay() { .mirrorY = false, .invertColor = false, .bufferSize = 0, // 0 = default (1/10 of screen) + .swRotate = (variant == Tab5Variant::St7123), // ST7123 MIPI-DSI panel does not support hardware swap_xy + .buffSpiram = (variant == Tab5Variant::St7123), // sw_rotate needs a 3rd buffer; use PSRAM to avoid OOM in internal SRAM .touch = touch, .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, .resetPin = LCD_PIN_RESET, @@ -59,6 +88,13 @@ std::shared_ptr createDisplay() { .bitsPerPixel = 16 }); - const auto display = std::make_shared(configuration); - return std::static_pointer_cast(display); + if (variant == Tab5Variant::St7123) { + return std::static_pointer_cast( + std::make_shared(configuration) + ); + } else { + return std::static_pointer_cast( + std::make_shared(configuration) + ); + } } diff --git a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp index b088d8842..0ba634267 100644 --- a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp +++ b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp @@ -1,5 +1,5 @@ #include "Ili9881cDisplay.h" -#include "disp_init_data.h" +#include "ili9881_init_data.h" #include #include @@ -47,6 +47,8 @@ bool Ili9881cDisplay::createMipiDsiBus() { if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) { LOGGER.error("Failed to create bus"); + esp_ldo_release_channel(ldoChannel); + ldoChannel = nullptr; return false; } @@ -67,6 +69,10 @@ bool Ili9881cDisplay::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) { if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) { LOGGER.error("Failed to create panel IO"); + esp_lcd_del_dsi_bus(mipiDsiBus); + mipiDsiBus = nullptr; + esp_ldo_release_channel(ldoChannel); + ldoChannel = nullptr; return false; } @@ -108,15 +114,15 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons .vsync_back_porch = 20, .vsync_front_porch = 20, }, - .flags { + .flags = { .use_dma2d = 1, // TODO: true? .disable_lp = 0, } }; ili9881c_vendor_config_t vendor_config = { - .init_cmds = disp_init_data, - .init_cmds_size = std::size(disp_init_data), + .init_cmds = ili9881_init_data, + .init_cmds_size = std::size(ili9881_init_data), .mipi_config = { .dsi_bus = mipiDsiBus, .dpi_config = &dpi_config, diff --git a/Devices/m5stack-tab5/Source/devices/St7123Display.cpp b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp new file mode 100644 index 000000000..f1fd991eb --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp @@ -0,0 +1,148 @@ +#include "St7123Display.h" +#include "st7123_init_data.h" + +#include +#include + +static const auto LOGGER = tt::Logger("St7123"); + +St7123Display::~St7123Display() { + // TODO: This should happen during ::stop(), but this isn't currently exposed + if (mipiDsiBus != nullptr) { + esp_lcd_del_dsi_bus(mipiDsiBus); + mipiDsiBus = nullptr; + } + if (ldoChannel != nullptr) { + esp_ldo_release_channel(ldoChannel); + ldoChannel = nullptr; + } +} + +bool St7123Display::createMipiDsiBus() { + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = 3, + .voltage_mv = 2500, + .flags = { + .adjustable = 0, + .owned_by_hw = 0, + .bypass = 0 + } + }; + + if (esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldoChannel) != ESP_OK) { + LOGGER.error("Failed to acquire LDO channel for MIPI DSI PHY"); + return false; + } + + LOGGER.info("Powered on"); + + const esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = 2, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = 965 // ST7123 lane bitrate per M5Stack BSP + }; + + if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) { + LOGGER.error("Failed to create bus"); + esp_ldo_release_channel(ldoChannel); + ldoChannel = nullptr; + return false; + } + + LOGGER.info("Bus created"); + return true; +} + +bool St7123Display::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) { + if (mipiDsiBus == nullptr) { + if (!createMipiDsiBus()) { + return false; + } + } + + // DBI interface for LCD commands/parameters (8-bit cmd/param per ST7123 spec) + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + + if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) { + LOGGER.error("Failed to create panel IO"); + esp_lcd_del_dsi_bus(mipiDsiBus); + mipiDsiBus = nullptr; + esp_ldo_release_channel(ldoChannel); + ldoChannel = nullptr; + return false; + } + + return true; +} + +esp_lcd_panel_dev_config_t St7123Display::createPanelConfig(std::shared_ptr espLcdConfiguration, gpio_num_t resetPin) { + return { + .reset_gpio_num = resetPin, + .rgb_ele_order = espLcdConfiguration->rgbElementOrder, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = 16, + .flags = { + .reset_active_high = 0 + }, + .vendor_config = nullptr + }; +} + +bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) { + esp_lcd_dpi_panel_config_t dpi_config = { + .virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 70, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = { + .h_size = 720, + .v_size = 1280, + .hsync_pulse_width = 2, + .hsync_back_porch = 40, + .hsync_front_porch = 40, + .vsync_pulse_width = 2, + .vsync_back_porch = 8, + .vsync_front_porch = 220, + }, + .flags = { + .use_dma2d = 1, + .disable_lp = 0, + } + }; + + st7123_vendor_config_t vendor_config = { + .init_cmds = st7123_init_data, + .init_cmds_size = std::size(st7123_init_data), + .mipi_config = { + .dsi_bus = mipiDsiBus, + .dpi_config = &dpi_config, + }, + }; + + // Create a mutable copy of panelConfig to set vendor_config + esp_lcd_panel_dev_config_t mutable_panel_config = panelConfig; + mutable_panel_config.vendor_config = &vendor_config; + + if (esp_lcd_new_panel_st7123(ioHandle, &mutable_panel_config, &panelHandle) != ESP_OK) { + LOGGER.error("Failed to create panel"); + return false; + } + + LOGGER.info("Panel created successfully"); + return true; +} + +lvgl_port_display_dsi_cfg_t St7123Display::getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) { + // Disable avoid_tearing to prevent stalls/blank flashes when other tasks (e.g. flash writes) block timing + return lvgl_port_display_dsi_cfg_t{ + .flags = { + .avoid_tearing = 0, + }, + }; +} diff --git a/Devices/m5stack-tab5/Source/devices/St7123Display.h b/Devices/m5stack-tab5/Source/devices/St7123Display.h new file mode 100644 index 000000000..adb9a28ab --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/St7123Display.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include +#include + +class St7123Display final : public EspLcdDisplayV2 { + + esp_lcd_dsi_bus_handle_t mipiDsiBus = nullptr; + esp_ldo_channel_handle_t ldoChannel = nullptr; + + bool createMipiDsiBus(); + +protected: + + bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override; + + esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr espLcdConfiguration, gpio_num_t resetPin) override; + + bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override; + + bool useDsiPanel() const override { return true; } + + lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) override; + +public: + + St7123Display( + const std::shared_ptr& configuration + ) : EspLcdDisplayV2(configuration) {} + + ~St7123Display() override; + + std::string getName() const override { return "St7123"; } + + std::string getDescription() const override { return "St7123 MIPI-DSI display"; } +}; diff --git a/Devices/m5stack-tab5/Source/devices/St7123Touch.cpp b/Devices/m5stack-tab5/Source/devices/St7123Touch.cpp new file mode 100644 index 000000000..1b2aa0c92 --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/St7123Touch.cpp @@ -0,0 +1,42 @@ +#include "St7123Touch.h" + +#include +#include +#include + +static const auto LOGGER = tt::Logger("ST7123Touch"); + +bool St7123Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_ST7123_CONFIG(); + return esp_lcd_new_panel_io_i2c( + static_cast(configuration->port), + &io_config, + &outHandle + ) == ESP_OK; +} + +bool St7123Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& touchHandle) { + return esp_lcd_touch_new_i2c_st7123(ioHandle, &config, &touchHandle) == ESP_OK; +} + +esp_lcd_touch_config_t St7123Touch::createEspLcdTouchConfig() { + return { + .x_max = configuration->xMax, + .y_max = configuration->yMax, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = configuration->pinInterrupt, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = configuration->swapXy, + .mirror_x = configuration->mirrorX, + .mirror_y = configuration->mirrorY, + }, + .process_coordinates = nullptr, + .interrupt_callback = nullptr, + .user_data = nullptr, + .driver_data = nullptr + }; +} diff --git a/Devices/m5stack-tab5/Source/devices/St7123Touch.h b/Devices/m5stack-tab5/Source/devices/St7123Touch.h new file mode 100644 index 000000000..2c3ddb4d4 --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/St7123Touch.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +class St7123Touch final : public EspLcdTouch { + +public: + + class Configuration { + public: + + Configuration( + i2c_port_t port, + uint16_t xMax, + uint16_t yMax, + bool swapXy = false, + bool mirrorX = false, + bool mirrorY = false, + gpio_num_t pinInterrupt = GPIO_NUM_NC + ) : port(port), + xMax(xMax), + yMax(yMax), + swapXy(swapXy), + mirrorX(mirrorX), + mirrorY(mirrorY), + pinInterrupt(pinInterrupt) + {} + + i2c_port_t port; + uint16_t xMax; + uint16_t yMax; + bool swapXy; + bool mirrorX; + bool mirrorY; + gpio_num_t pinInterrupt; + }; + +private: + + std::unique_ptr configuration; + + bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override; + + bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& touchHandle) override; + + esp_lcd_touch_config_t createEspLcdTouchConfig() override; + +public: + + explicit St7123Touch(std::unique_ptr inConfiguration) : configuration(std::move(inConfiguration)) { + assert(configuration != nullptr); + } + + std::string getName() const override { return "ST7123Touch"; } + + std::string getDescription() const override { return "ST7123 I2C touch driver"; } +}; diff --git a/Devices/m5stack-tab5/Source/devices/disp_init_data.h b/Devices/m5stack-tab5/Source/devices/ili9881_init_data.h similarity index 99% rename from Devices/m5stack-tab5/Source/devices/disp_init_data.h rename to Devices/m5stack-tab5/Source/devices/ili9881_init_data.h index 2ddb42641..8e72baf08 100644 --- a/Devices/m5stack-tab5/Source/devices/disp_init_data.h +++ b/Devices/m5stack-tab5/Source/devices/ili9881_init_data.h @@ -6,7 +6,7 @@ #pragma once #include -static const ili9881c_lcd_init_cmd_t disp_init_data[] = { +static const ili9881c_lcd_init_cmd_t ili9881_init_data[] = { // {cmd, { data }, data_size, delay} /**** CMD_Page 1 ****/ diff --git a/Devices/m5stack-tab5/Source/devices/st7123_init_data.h b/Devices/m5stack-tab5/Source/devices/st7123_init_data.h new file mode 100644 index 000000000..90c16a39a --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/st7123_init_data.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include + +//Refer to https://github.com/m5stack/M5Tab5-UserDemo +//https://github.com/m5stack/M5Tab5-UserDemo/blob/main/LICENSE +static const st7123_lcd_init_cmd_t st7123_init_data[] = { + {0x60, (uint8_t[]){0x71, 0x23, 0xa2}, 3, 0}, + {0x60, (uint8_t[]){0x71, 0x23, 0xa3}, 3, 0}, + {0x60, (uint8_t[]){0x71, 0x23, 0xa4}, 3, 0}, + {0xA4, (uint8_t[]){0x31}, 1, 0}, + {0xD7, (uint8_t[]){0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80}, 6, 0}, + {0x90, (uint8_t[]){0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09}, 7, 0}, + {0xA3, (uint8_t[]){0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF}, 40, 0}, + {0xA6, (uint8_t[]){0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E, 0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00}, 55, 0}, + {0xA7, (uint8_t[]){0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44}, 60, 0}, + {0xAC, (uint8_t[]){0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C, 0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12, 0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01}, 44, 0}, + {0xAD, (uint8_t[]){0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01, 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, 25, 0}, + {0xAE, (uint8_t[]){0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00}, 7, 0}, + {0xB2, (uint8_t[]){0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, 0xC0, 0x00}, 17, 0}, + {0xE8, (uint8_t[]){0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E}, 15, 0}, + {0x75, (uint8_t[]){0x03, 0x04}, 2, 0}, + {0xE7, (uint8_t[]){0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00, 0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45, 0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77}, 36, 0}, + {0xEA, (uint8_t[]){0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C}, 8, 0}, + {0xB0, (uint8_t[]){0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43}, 7, 0}, + {0xB7, (uint8_t[]){0x00, 0x00, 0x73, 0x73}, 0x04, 0}, + {0xBF, (uint8_t[]){0xA6, 0xAA}, 2, 0}, + {0xA9, (uint8_t[]){0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03}, 10, 0}, + {0xC8, (uint8_t[]){0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF}, 37, 0}, + {0xC9, (uint8_t[]){0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF}, 37, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 1, 100}, + {0x29, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 100}, +}; diff --git a/Devices/m5stack-tab5/devicetree.yaml b/Devices/m5stack-tab5/devicetree.yaml index bc6b7b156..d6cb798ab 100644 --- a/Devices/m5stack-tab5/devicetree.yaml +++ b/Devices/m5stack-tab5/devicetree.yaml @@ -1,5 +1,6 @@ dependencies: - - Platforms/platform-esp32 - - Drivers/pi4ioe5v6408-module - - Drivers/bmi270-module +- Platforms/platform-esp32 +- Drivers/pi4ioe5v6408-module +- Drivers/bmi270-module +- Drivers/rx8130ce-module dts: m5stack,tab5.dts diff --git a/Devices/m5stack-tab5/m5stack,tab5.dts b/Devices/m5stack-tab5/m5stack,tab5.dts index 620001d98..93fc843dc 100644 --- a/Devices/m5stack-tab5/m5stack,tab5.dts +++ b/Devices/m5stack-tab5/m5stack,tab5.dts @@ -7,6 +7,7 @@ #include #include #include +#include / { compatible = "root"; @@ -20,7 +21,7 @@ i2c_internal: i2c0 { compatible = "espressif,esp32-i2c"; port = ; - clock-frequency = <400000>; + clock-frequency = <100000>; pin-sda = <&gpio0 31 GPIO_FLAG_NONE>; pin-scl = <&gpio0 32 GPIO_FLAG_NONE>; @@ -38,6 +39,11 @@ compatible = "bosch,bmi270"; reg = <0x68>; }; + + rx8130ce { + compatible = "epson,rx8130ce"; + reg = <0x32>; + }; }; i2c_port_a: i2c1 { diff --git a/Devices/waveshare-s3-lcd-13/devicetree.yaml b/Devices/waveshare-s3-lcd-13/devicetree.yaml index 9321802ee..1b96c2dd5 100644 --- a/Devices/waveshare-s3-lcd-13/devicetree.yaml +++ b/Devices/waveshare-s3-lcd-13/devicetree.yaml @@ -1,3 +1,4 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/qmi8658-module dts: waveshare,s3-lcd-13.dts diff --git a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts index 97d815782..0acee4dfe 100644 --- a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts +++ b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts @@ -4,6 +4,7 @@ #include #include #include +#include // Reference: https://www.waveshare.com/wiki/ESP32-S3-LCD-1.3 / { @@ -21,6 +22,11 @@ clock-frequency = <400000>; pin-sda = <&gpio0 47 GPIO_FLAG_NONE>; pin-scl = <&gpio0 48 GPIO_FLAG_NONE>; + + qmi8658 { + compatible = "qst,qmi8658"; + reg = <0x6B>; + }; }; spi0 { diff --git a/Devices/waveshare-s3-touch-lcd-128/devicetree.yaml b/Devices/waveshare-s3-touch-lcd-128/devicetree.yaml index e51ea4a19..117dac1af 100644 --- a/Devices/waveshare-s3-touch-lcd-128/devicetree.yaml +++ b/Devices/waveshare-s3-touch-lcd-128/devicetree.yaml @@ -1,3 +1,4 @@ dependencies: - - Platforms/platform-esp32 +- Platforms/platform-esp32 +- Drivers/qmi8658-module dts: waveshare,s3-touch-lcd-128.dts diff --git a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts index d5f3c955a..5a9ca28a7 100644 --- a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts +++ b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts @@ -4,6 +4,7 @@ #include #include #include +#include // Reference: https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-1.28 / { @@ -21,6 +22,11 @@ clock-frequency = <400000>; pin-sda = <&gpio0 6 GPIO_FLAG_NONE>; pin-scl = <&gpio0 7 GPIO_FLAG_NONE>; + + qmi8658 { + compatible = "qst,qmi8658"; + reg = <0x6B>; + }; }; display_spi: spi0 { diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 625d4388f..664221e09 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -29,7 +29,6 @@ - Fix glitches when installing app via App Hub with 4.3" Waveshare - TCA9534 keyboards should use interrupts - GT911 drivers should use interrupts if it's stable -- Change ButtonControl to work with interrupts and xQueue - Fix Cardputer (original): use LV_KEY_NEXT and _PREV in keyboard mapping instead of encoder driver hack (and check GPIO app if it then hangs too) - Logging with a function that uses std::format - Expose http::download() and main dispatcher to TactiltyC. diff --git a/Drivers/ButtonControl/Source/ButtonControl.cpp b/Drivers/ButtonControl/Source/ButtonControl.cpp index e7607b5b3..d9f4b558f 100644 --- a/Drivers/ButtonControl/Source/ButtonControl.cpp +++ b/Drivers/ButtonControl/Source/ButtonControl.cpp @@ -1,22 +1,52 @@ #include "ButtonControl.h" +#include #include #include static const auto LOGGER = tt::Logger("ButtonControl"); -ButtonControl::ButtonControl(const std::vector& pinConfigurations) : pinConfigurations(pinConfigurations) { +ButtonControl::ButtonControl(const std::vector& pinConfigurations) + : buttonQueue(20, sizeof(ButtonEvent)), + pinConfigurations(pinConfigurations) { + pinStates.resize(pinConfigurations.size()); - for (const auto& pinConfiguration : pinConfigurations) { - tt::hal::gpio::configure(pinConfiguration.pin, tt::hal::gpio::Mode::Input, false, false); + + // Build isrArgs with one entry per unique physical pin, then configure GPIO. + isrArgs.reserve(pinConfigurations.size()); + for (size_t i = 0; i < pinConfigurations.size(); i++) { + const auto pin = static_cast(pinConfigurations[i].pin); + + // Skip if this physical pin was already seen. + bool seen = false; + for (const auto& arg : isrArgs) { + if (arg.pin == pin) { seen = true; break; } + } + if (seen) continue; + + gpio_config_t io_conf = { + .pin_bit_mask = 1ULL << pin, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_ANYEDGE, + }; + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + LOGGER.error("Failed to configure GPIO {}: {}", static_cast(pin), esp_err_to_name(err)); + continue; + } + + // isrArgs is reserved upfront; push_back will not reallocate, keeping addresses stable + // for gpio_isr_handler_add() called later in startThread(). + isrArgs.push_back({ .self = this, .pin = pin }); } } ButtonControl::~ButtonControl() { if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) { - interruptDriverThread = true; - driverThread->join(); + stopThread(); } } @@ -48,7 +78,7 @@ void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { data->state = LV_INDEV_STATE_PRESSED; break; case Action::AppClose: - // TODO: implement + tt::app::stop(); break; } } @@ -57,57 +87,86 @@ void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { } } -void ButtonControl::updatePin(std::vector::const_reference configuration, std::vector::reference state) { - if (tt::hal::gpio::getLevel(configuration.pin)) { // if pressed - if (state.pressState) { - // check time for long press trigger - auto time_passed = tt::kernel::getMillis() - state.pressStartTime; - if (time_passed > 500) { - // state.triggerLongPress = true; - } - } else { - state.pressStartTime = tt::kernel::getMillis(); - state.pressState = true; - } +void ButtonControl::updatePin(std::vector::const_reference configuration, std::vector::reference state, bool pressed) { + auto now = tt::kernel::getMillis(); + + // Software debounce: ignore edges within 20ms of the last state change. + if ((now - state.lastChangeTime) < 20) { + return; + } + state.lastChangeTime = now; + + if (pressed) { + state.pressStartTime = now; + state.pressState = true; } else { // released if (state.pressState) { - auto time_passed = tt::kernel::getMillis() - state.pressStartTime; + auto time_passed = now - state.pressStartTime; if (time_passed < 500) { - LOGGER.debug("Trigger short press"); + LOGGER.info("Short press ({}ms)", time_passed); state.triggerShortPress = true; + } else { + LOGGER.info("Long press ({}ms)", time_passed); + state.triggerLongPress = true; } state.pressState = false; } } } +void IRAM_ATTR ButtonControl::gpioIsrHandler(void* arg) { + auto* isrArg = static_cast(arg); + ButtonEvent event { + .pin = isrArg->pin, + .pressed = gpio_get_level(isrArg->pin) == 0, // active-low: LOW = pressed + }; + // tt::MessageQueue::put() is ISR-safe with timeout=0: it detects ISR context via + // xPortInIsrContext() and uses xQueueSendFromISR() + portYIELD_FROM_ISR() internally. + isrArg->self->buttonQueue.put(&event, 0); +} + void ButtonControl::driverThreadMain() { - while (!shouldInterruptDriverThread()) { - if (mutex.lock(100)) { - for (int i = 0; i < pinConfigurations.size(); i++) { - updatePin(pinConfigurations[i], pinStates[i]); + ButtonEvent event; + while (buttonQueue.get(&event, portMAX_DELAY)) { + if (event.pin == GPIO_NUM_NC) { + break; // shutdown sentinel + } + LOGGER.info("Pin {} {}", static_cast(event.pin), event.pressed ? "down" : "up"); + if (mutex.lock(portMAX_DELAY)) { + // Update ALL PinConfiguration entries that share this physical pin. + for (size_t i = 0; i < pinConfigurations.size(); i++) { + if (static_cast(pinConfigurations[i].pin) == event.pin) { + updatePin(pinConfigurations[i], pinStates[i], event.pressed); + } } mutex.unlock(); } - tt::kernel::delayMillis(5); } } -bool ButtonControl::shouldInterruptDriverThread() const { - bool interrupt = false; - if (mutex.lock(50 / portTICK_PERIOD_MS)) { - interrupt = interruptDriverThread; - mutex.unlock(); - } - return interrupt; -} - -void ButtonControl::startThread() { +bool ButtonControl::startThread() { LOGGER.info("Start"); - mutex.lock(); + esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + LOGGER.error("Failed to install GPIO ISR service: {}", esp_err_to_name(err)); + return false; + } - interruptDriverThread = false; + // isrArgs has one entry per unique physical pin — no duplicate registrations. + // Addresses are stable: vector was reserved in constructor and is not modified after that. + int handlersAdded = 0; + for (auto& arg : isrArgs) { + err = gpio_isr_handler_add(arg.pin, gpioIsrHandler, &arg); + if (err != ESP_OK) { + LOGGER.error("Failed to add ISR for GPIO {}: {}", static_cast(arg.pin), esp_err_to_name(err)); + for (int i = 0; i < handlersAdded; i++) { + gpio_isr_handler_remove(isrArgs[i].pin); + } + return false; + } + handlersAdded++; + } driverThread = std::make_shared("ButtonControl", 4096, [this] { driverThreadMain(); @@ -115,22 +174,21 @@ void ButtonControl::startThread() { }); driverThread->start(); - - mutex.unlock(); + return true; } void ButtonControl::stopThread() { LOGGER.info("Stop"); - mutex.lock(); - interruptDriverThread = true; - mutex.unlock(); + for (const auto& arg : isrArgs) { + gpio_isr_handler_remove(arg.pin); + } - driverThread->join(); + ButtonEvent sentinel { .pin = GPIO_NUM_NC, .pressed = false }; + buttonQueue.put(&sentinel, portMAX_DELAY); - mutex.lock(); + driverThread->join(); driverThread = nullptr; - mutex.unlock(); } bool ButtonControl::startLvgl(lv_display_t* display) { @@ -138,7 +196,9 @@ bool ButtonControl::startLvgl(lv_display_t* display) { return false; } - startThread(); + if (!startThread()) { + return false; + } deviceHandle = lv_indev_create(); lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_ENCODER); diff --git a/Drivers/ButtonControl/Source/ButtonControl.h b/Drivers/ButtonControl/Source/ButtonControl.h index 146830904..5aab931b9 100644 --- a/Drivers/ButtonControl/Source/ButtonControl.h +++ b/Drivers/ButtonControl/Source/ButtonControl.h @@ -2,9 +2,12 @@ #include #include +#include #include #include +#include + class ButtonControl final : public tt::hal::encoder::EncoderDevice { public: @@ -31,28 +34,41 @@ class ButtonControl final : public tt::hal::encoder::EncoderDevice { struct PinState { long pressStartTime = 0; - long pressReleaseTime = 0; + long lastChangeTime = 0; bool pressState = false; bool triggerShortPress = false; bool triggerLongPress = false; }; + /** Queued from ISR to worker thread. pin == GPIO_NUM_NC is a shutdown sentinel. */ + struct ButtonEvent { + gpio_num_t pin; + bool pressed; + }; + + /** One entry per unique physical pin; addresses must remain stable after construction. */ + struct IsrArg { + ButtonControl* self; + gpio_num_t pin; + }; + lv_indev_t* deviceHandle = nullptr; std::shared_ptr driverThread; - bool interruptDriverThread = false; tt::Mutex mutex; + tt::MessageQueue buttonQueue; std::vector pinConfigurations; std::vector pinStates; + std::vector isrArgs; // one entry per unique physical pin - bool shouldInterruptDriverThread() const; - - static void updatePin(std::vector::const_reference value, std::vector::reference pin_state); + static void updatePin(std::vector::const_reference config, std::vector::reference state, bool pressed); void driverThreadMain(); static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); - void startThread(); + static void IRAM_ATTR gpioIsrHandler(void* arg); + + bool startThread(); void stopThread(); public: diff --git a/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.cpp b/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.cpp index 27061170f..6559939c8 100644 --- a/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.cpp +++ b/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.cpp @@ -180,9 +180,9 @@ lvgl_port_display_cfg_t EspLcdDisplayV2::getLvglPortDisplayConfig(std::shared_pt }, .color_format = configuration->lvglColorFormat, .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, + .buff_dma = configuration->buffSpiram ? 0u : 1u, + .buff_spiram = configuration->buffSpiram ? 1u : 0u, + .sw_rotate = configuration->swRotate ? 1u : 0u, .swap_bytes = configuration->lvglSwapBytes, .full_refresh = 0, .direct_mode = 0 diff --git a/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h b/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h index 5d843c4bd..9f919171c 100644 --- a/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h +++ b/Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h @@ -20,6 +20,8 @@ struct EspLcdConfiguration { bool mirrorY; bool invertColor; uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size + bool swRotate = false; // Use LVGL software rotation instead of hardware swap_xy (required for MIPI-DSI panels that don't support swap_xy) + bool buffSpiram = false; // Allocate LVGL draw buffers from PSRAM instead of DMA-capable internal SRAM (required when sw_rotate needs a 3rd buffer that won't fit in internal SRAM) std::shared_ptr touch; std::function _Nullable backlightDutyFunction; gpio_num_t resetPin; diff --git a/Drivers/bm8563-module/CMakeLists.txt b/Drivers/bm8563-module/CMakeLists.txt new file mode 100644 index 000000000..4c4f20b18 --- /dev/null +++ b/Drivers/bm8563-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(bm8563-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/bm8563-module/LICENSE-Apache-2.0.md b/Drivers/bm8563-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bm8563-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/bm8563-module/README.md b/Drivers/bm8563-module/README.md new file mode 100644 index 000000000..80e9816fe --- /dev/null +++ b/Drivers/bm8563-module/README.md @@ -0,0 +1,9 @@ +# BM8563 I2C Driver + +A driver for the `BM8563` Realtime Clock from BELLING [SHANGHAI BELLING CO., LTD.] +Drop-in functional clone — same init, same registers, same I2C protocol as the NXP PCF8563. + +See https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf +And: https://www.alldatasheet.com/datasheet-pdf/pdf/1768247/BELLING/BM8563.html + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/bm8563-module/bindings/belling,bm8563.yaml b/Drivers/bm8563-module/bindings/belling,bm8563.yaml new file mode 100644 index 000000000..c9aea5ce7 --- /dev/null +++ b/Drivers/bm8563-module/bindings/belling,bm8563.yaml @@ -0,0 +1,5 @@ +description: BM8563 RTC (PCF8563-compatible) + +include: [ "i2c-device.yaml" ] + +compatible: "belling,bm8563" diff --git a/Drivers/bm8563-module/devicetree.yaml b/Drivers/bm8563-module/devicetree.yaml new file mode 100644 index 000000000..99f3dfd7e --- /dev/null +++ b/Drivers/bm8563-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings \ No newline at end of file diff --git a/Drivers/bm8563-module/include/bindings/bm8563.h b/Drivers/bm8563-module/include/bindings/bm8563.h new file mode 100644 index 000000000..bebbf677a --- /dev/null +++ b/Drivers/bm8563-module/include/bindings/bm8563.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(bm8563, struct Bm8563Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bm8563-module/include/bm8563_module.h b/Drivers/bm8563-module/include/bm8563_module.h new file mode 100644 index 000000000..c7d174056 --- /dev/null +++ b/Drivers/bm8563-module/include/bm8563_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module bm8563_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bm8563-module/include/drivers/bm8563.h b/Drivers/bm8563-module/include/drivers/bm8563.h new file mode 100644 index 000000000..dc5b57748 --- /dev/null +++ b/Drivers/bm8563-module/include/drivers/bm8563.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Bm8563Config { + /** Address on bus */ + uint8_t address; +}; + +struct Bm8563DateTime { + uint16_t year; // 2000–2199 + uint8_t month; // 1–12 + uint8_t day; // 1–31 + uint8_t hour; // 0–23 + uint8_t minute; // 0–59 + uint8_t second; // 0–59 +}; + +/** + * Read the current date and time from the RTC. + * @param[in] device bm8563 device + * @param[out] dt Pointer to Bm8563DateTime to populate + * @return ERROR_NONE on success + */ +error_t bm8563_get_datetime(struct Device* device, struct Bm8563DateTime* dt); + +/** + * Write the date and time to the RTC. + * @param[in] device bm8563 device + * @param[in] dt Pointer to Bm8563DateTime to write (year must be 2000–2199) + * @return ERROR_NONE on success, ERROR_INVALID_ARGUMENT if any field is out of range + */ +error_t bm8563_set_datetime(struct Device* device, const struct Bm8563DateTime* dt); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bm8563-module/source/bm8563.cpp b/Drivers/bm8563-module/source/bm8563.cpp new file mode 100644 index 000000000..4d3f7804e --- /dev/null +++ b/Drivers/bm8563-module/source/bm8563.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define TAG "BM8563" + +static constexpr uint8_t REG_CTRL1 = 0x00; // Control status 1 +static constexpr uint8_t REG_SECONDS = 0x02; // Seconds BCD, bit 7 = VL (clock integrity) +// Registers 0x02–0x08: seconds, minutes, hours, days, weekdays, months, years + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(50); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Helpers + +static uint8_t bcd_to_dec(uint8_t bcd) { return static_cast((bcd >> 4) * 10 + (bcd & 0x0F)); } +static uint8_t dec_to_bcd(uint8_t dec) { return static_cast(((dec / 10) << 4) | (dec % 10)); } + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Clear STOP bit — chip may have been stopped after a power cycle + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL1, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + LOG_E(TAG, "Failed to clear STOP bit at 0x%02X", address); + return ERROR_RESOURCE; + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + // RTC oscillator should continue running on battery backup + // No action needed on driver stop + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t bm8563_get_datetime(Device* device, Bm8563DateTime* dt) { + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // Burst-read 7 registers starting at 0x02: + // [0]=seconds [1]=minutes [2]=hours [3]=days [4]=weekdays [5]=months [6]=years + uint8_t buf[7] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_SECONDS, buf, sizeof(buf), I2C_TIMEOUT_TICKS); + if (error != ERROR_NONE) return error; + + if (buf[0] & 0x80u) { + LOG_E(TAG, "Clock integrity compromised (VL flag set) — data unreliable"); + return ERROR_INVALID_STATE; + } + dt->second = bcd_to_dec(buf[0] & 0x7Fu); // mask VL flag + dt->minute = bcd_to_dec(buf[1] & 0x7Fu); + dt->hour = bcd_to_dec(buf[2] & 0x3Fu); + dt->day = bcd_to_dec(buf[3] & 0x3Fu); + // buf[4] = weekday — ignored + dt->month = bcd_to_dec(buf[5] & 0x1Fu); + bool century = (buf[5] & 0x80u) != 0; + dt->year = static_cast(2000 + bcd_to_dec(buf[6]) + (century ? 100 : 0)); + + return ERROR_NONE; +} + +error_t bm8563_set_datetime(Device* device, const Bm8563DateTime* dt) { + if (dt->year < 2000 || dt->year > 2199 || + dt->month < 1 || dt->month > 12 || + dt->day < 1 || dt->day > 31 || + dt->hour > 23 || dt->minute > 59 || dt->second > 59) { + return ERROR_INVALID_ARGUMENT; + } + + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + bool century = (dt->year >= 2100); + uint8_t y = static_cast(century ? dt->year - 2100 : dt->year - 2000); + + uint8_t buf[7] = {}; + buf[0] = dec_to_bcd(dt->second); + buf[1] = dec_to_bcd(dt->minute); + buf[2] = dec_to_bcd(dt->hour); + buf[3] = dec_to_bcd(dt->day); + buf[4] = 0; // weekday — leave as Sunday (unused) + buf[5] = static_cast(dec_to_bcd(dt->month) | (century ? 0x80u : 0x00u)); + buf[6] = dec_to_bcd(y); + + return i2c_controller_write_register(i2c_controller, address, REG_SECONDS, buf, sizeof(buf), I2C_TIMEOUT_TICKS); +} + +Driver bm8563_driver = { + .name = "bm8563", + .compatible = (const char*[]) { "belling,bm8563", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &bm8563_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/bm8563-module/source/module.cpp b/Drivers/bm8563-module/source/module.cpp new file mode 100644 index 000000000..259e029da --- /dev/null +++ b/Drivers/bm8563-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver bm8563_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(&bm8563_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(&bm8563_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol bm8563_module_symbols[]; + +Module bm8563_module = { + .name = "bm8563", + .start = start, + .stop = stop, + .symbols = bm8563_module_symbols, + .internal = nullptr +}; + +} diff --git a/Drivers/bm8563-module/source/symbols.c b/Drivers/bm8563-module/source/symbols.c new file mode 100644 index 000000000..43312b41b --- /dev/null +++ b/Drivers/bm8563-module/source/symbols.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol bm8563_module_symbols[] = { + DEFINE_MODULE_SYMBOL(bm8563_get_datetime), + DEFINE_MODULE_SYMBOL(bm8563_set_datetime), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/bmi270-module/source/bmi270.cpp b/Drivers/bmi270-module/source/bmi270.cpp index fbc6b487b..d4ce55807 100644 --- a/Drivers/bmi270-module/source/bmi270.cpp +++ b/Drivers/bmi270-module/source/bmi270.cpp @@ -131,6 +131,20 @@ static error_t start(Device* device) { } static error_t stop(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Disable accelerometer and gyroscope (clear bit1=gyr_en, bit2=acc_en) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CTRL, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + LOG_E(TAG, "Failed to put BMI270 to sleep"); + return ERROR_RESOURCE; + } + return ERROR_NONE; } diff --git a/Drivers/m5pm1-module/CMakeLists.txt b/Drivers/m5pm1-module/CMakeLists.txt new file mode 100644 index 000000000..1dfa2ac68 --- /dev/null +++ b/Drivers/m5pm1-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(m5pm1-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/m5pm1-module/LICENSE-Apache-2.0.md b/Drivers/m5pm1-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/m5pm1-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/m5pm1-module/README.md b/Drivers/m5pm1-module/README.md new file mode 100644 index 000000000..b1e5fece1 --- /dev/null +++ b/Drivers/m5pm1-module/README.md @@ -0,0 +1,8 @@ +# M5PM1 I2C Driver + +A driver for the `M5PM1` power management chip. + +See https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1207/M5PM1_Datasheet_EN.pdf +And https://github.com/m5stack/M5PM1 + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/m5pm1-module/bindings/m5stack,m5pm1.yaml b/Drivers/m5pm1-module/bindings/m5stack,m5pm1.yaml new file mode 100644 index 000000000..b8957ec4d --- /dev/null +++ b/Drivers/m5pm1-module/bindings/m5stack,m5pm1.yaml @@ -0,0 +1,5 @@ +description: M5Stack M5PM1 Power Management IC + +include: ["i2c-device.yaml"] + +compatible: "m5stack,m5pm1" diff --git a/Drivers/m5pm1-module/devicetree.yaml b/Drivers/m5pm1-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/m5pm1-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/m5pm1-module/include/bindings/m5pm1.h b/Drivers/m5pm1-module/include/bindings/m5pm1.h new file mode 100644 index 000000000..a63d58bca --- /dev/null +++ b/Drivers/m5pm1-module/include/bindings/m5pm1.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(m5pm1, struct M5pm1Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5pm1-module/include/drivers/m5pm1.h b/Drivers/m5pm1-module/include/drivers/m5pm1.h new file mode 100644 index 000000000..32c6a2752 --- /dev/null +++ b/Drivers/m5pm1-module/include/drivers/m5pm1.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct M5pm1Config { + uint8_t address; +}; + +// --------------------------------------------------------------------------- +// Power source (REG_PWR_SRC 0x04) +// --------------------------------------------------------------------------- +typedef enum { + M5PM1_PWR_SRC_5VIN = 0, + M5PM1_PWR_SRC_5VINOUT = 1, + M5PM1_PWR_SRC_BAT = 2, + M5PM1_PWR_SRC_UNKNOWN = 3, +} M5pm1PowerSource; + +// --------------------------------------------------------------------------- +// Voltage readings +// --------------------------------------------------------------------------- +error_t m5pm1_get_battery_voltage(struct Device* device, uint16_t* mv); +error_t m5pm1_get_vin_voltage(struct Device* device, uint16_t* mv); +error_t m5pm1_get_5vout_voltage(struct Device* device, uint16_t* mv); +error_t m5pm1_get_power_source(struct Device* device, M5pm1PowerSource* source); + +// --------------------------------------------------------------------------- +// Charging & power rails +// --------------------------------------------------------------------------- +/** PM1_G0 low = charging (connected to charge-status pin of the charge IC) */ +error_t m5pm1_is_charging(struct Device* device, bool* charging); +error_t m5pm1_set_charge_enable(struct Device* device, bool enable); +error_t m5pm1_set_boost_enable(struct Device* device, bool enable); ///< 5V BOOST / Grove power +error_t m5pm1_set_ldo_enable(struct Device* device, bool enable); ///< 3.3V LDO + +// --------------------------------------------------------------------------- +// Temperature (internal chip sensor) +// --------------------------------------------------------------------------- +/** Returns temperature in units of 0.1 °C */ +error_t m5pm1_get_temperature(struct Device* device, uint16_t* decidegc); + +// --------------------------------------------------------------------------- +// System commands +// --------------------------------------------------------------------------- +error_t m5pm1_shutdown(struct Device* device); +error_t m5pm1_reboot(struct Device* device); + +// --------------------------------------------------------------------------- +// Power button (M5PM1 internal button, not ESP32 GPIO) +// --------------------------------------------------------------------------- +/** Current instantaneous state of the power button */ +error_t m5pm1_btn_get_state(struct Device* device, bool* pressed); +/** Edge-triggered flag — auto-clears on read */ +error_t m5pm1_btn_get_flag(struct Device* device, bool* was_pressed); + +// --------------------------------------------------------------------------- +// Watchdog timer +// --------------------------------------------------------------------------- +/** timeout_sec: 0 = disabled, 1–255 = timeout in seconds */ +error_t m5pm1_wdt_set(struct Device* device, uint8_t timeout_sec); +error_t m5pm1_wdt_feed(struct Device* device); + +// --------------------------------------------------------------------------- +// RTC RAM (32 bytes, retained across sleep / power-off) +// --------------------------------------------------------------------------- +error_t m5pm1_read_rtc_ram(struct Device* device, uint8_t offset, uint8_t* data, uint8_t len); +error_t m5pm1_write_rtc_ram(struct Device* device, uint8_t offset, const uint8_t* data, uint8_t len); + +// --------------------------------------------------------------------------- +// NeoPixel LED (via M5PM1 LED controller, max 32 LEDs) +// --------------------------------------------------------------------------- +error_t m5pm1_set_led_count(struct Device* device, uint8_t count); +error_t m5pm1_set_led_color(struct Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b); +error_t m5pm1_refresh_leds(struct Device* device); +error_t m5pm1_disable_leds(struct Device* device); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5pm1-module/include/m5pm1_module.h b/Drivers/m5pm1-module/include/m5pm1_module.h new file mode 100644 index 000000000..9e5f3e73e --- /dev/null +++ b/Drivers/m5pm1-module/include/m5pm1_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module m5pm1_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/m5pm1-module/source/m5pm1.cpp b/Drivers/m5pm1-module/source/m5pm1.cpp new file mode 100644 index 000000000..c972eb051 --- /dev/null +++ b/Drivers/m5pm1-module/source/m5pm1.cpp @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define TAG "M5PM1" + +// --------------------------------------------------------------------------- +// Register map +// --------------------------------------------------------------------------- +static constexpr uint8_t REG_DEVICE_ID = 0x00; ///< R - Device ID (0x50) +static constexpr uint8_t REG_PWR_SRC = 0x04; ///< R - Power source (0=5VIN, 1=5VINOUT, 2=BAT) +static constexpr uint8_t REG_PWR_CFG = 0x06; ///< RW - [3]=BOOST_EN [2]=LDO_EN [1]=DCDC_EN [0]=CHG_EN +static constexpr uint8_t REG_I2C_CFG = 0x09; ///< RW - [4]=SPD(400kHz) [3:0]=SLP_TO(0=off) +static constexpr uint8_t REG_WDT_CNT = 0x0A; ///< RW - Watchdog countdown (0=disabled, 1–255=seconds) +static constexpr uint8_t REG_WDT_KEY = 0x0B; ///< W - Write 0xA5 to feed watchdog +static constexpr uint8_t REG_SYS_CMD = 0x0C; ///< W - High nibble=0xA; low: 1=shutdown 2=reboot 3=download +static constexpr uint8_t REG_GPIO_MODE = 0x10; ///< RW - GPIO direction [4:0] (1=output, 0=input) +static constexpr uint8_t REG_GPIO_OUT = 0x11; ///< RW - GPIO output level [4:0] +static constexpr uint8_t REG_GPIO_IN = 0x12; ///< R - GPIO input state [4:0] +static constexpr uint8_t REG_GPIO_DRV = 0x13; ///< RW - Drive mode [4:0] (0=push-pull, 1=open-drain) +static constexpr uint8_t REG_GPIO_FUNC0 = 0x16; ///< RW - GPIO0–3 function (2 bits each: 00=GPIO) +static constexpr uint8_t REG_VBAT_L = 0x22; ///< R - Battery voltage low byte (mV, 16-bit LE) +static constexpr uint8_t REG_VIN_L = 0x24; ///< R - VIN voltage low byte (mV, 16-bit LE) +static constexpr uint8_t REG_5VOUT_L = 0x26; ///< R - 5V output voltage low byte (mV, 16-bit LE) +static constexpr uint8_t REG_ADC_RES_L = 0x28; ///< R - ADC result low byte (mV, 16-bit LE) +static constexpr uint8_t REG_ADC_CTRL = 0x2A; ///< RW - [3:1]=channel [0]=START +static constexpr uint8_t REG_BTN_STATUS = 0x48; ///< R - [7]=BTN_FLAG(auto-clear) [0]=BTN_STATE +static constexpr uint8_t REG_NEO_CFG = 0x50; ///< RW - [6]=REFRESH [5:0]=LED_CNT +static constexpr uint8_t REG_NEO_DATA = 0x60; ///< RW - NeoPixel RGB565 data, 2 bytes per LED (max 32) +static constexpr uint8_t REG_RTC_RAM = 0xA0; ///< RW - 32 bytes of RTC RAM + +// PWR_CFG bit masks +static constexpr uint8_t PWR_CFG_CHG_EN = (1U << 0U); +static constexpr uint8_t PWR_CFG_DCDC_EN = (1U << 1U); +static constexpr uint8_t PWR_CFG_LDO_EN = (1U << 2U); +static constexpr uint8_t PWR_CFG_BOOST_EN = (1U << 3U); + +// System command values (high nibble must be 0xA) +static constexpr uint8_t SYS_CMD_SHUTDOWN = 0xA1; +static constexpr uint8_t SYS_CMD_REBOOT = 0xA2; + +// ADC channel for temperature +static constexpr uint8_t ADC_CH_TEMP = 6; + +// PM1_G2: LCD power enable on M5Stack StickS3 +static constexpr uint8_t LCD_POWER_BIT = (1U << 2U); + +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// --------------------------------------------------------------------------- +// Driver lifecycle +// --------------------------------------------------------------------------- + +static error_t start(Device* device) { + Device* i2c = device_get_parent(device); + if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + const uint8_t addr = GET_CONFIG(device)->address; + + // M5PM1 enters I2C sleep after inactivity. The first transaction after sleep + // is ignored as the chip wakes up. Retry with increasing delays until ACK. + bool awake = false; + for (int attempt = 0; attempt < 5; attempt++) { + uint8_t chip_id = 0; + if (i2c_controller_register8_get(i2c, addr, REG_DEVICE_ID, &chip_id, TIMEOUT) == ERROR_NONE) { + LOG_I(TAG, "M5PM1 online (chip_id=0x%02X)", chip_id); + awake = true; + break; + } + vTaskDelay(pdMS_TO_TICKS(20 * (attempt + 1))); + } + + if (!awake) { + LOG_E(TAG, "M5PM1 not responding — LCD power will not be enabled"); + return ERROR_NONE; // non-fatal: don't crash the kernel + } + + // Disable I2C idle sleep so the PMIC stays reachable on battery power + if (i2c_controller_register8_set(i2c, addr, REG_I2C_CFG, 0x00, TIMEOUT) != ERROR_NONE) { + LOG_W(TAG, "Failed to disable I2C sleep (non-fatal)"); + } + + // PM1_G2 → LCD power enable (L3B rail on StickS3) + // Sequence matches M5GFX: clear FUNC0 bit2, set MODE bit2 output, clear DRV bit2 push-pull, set OUT bit2 high + bool lcd_ok = + i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_FUNC0, LCD_POWER_BIT, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_set_bits (i2c, addr, REG_GPIO_MODE, LCD_POWER_BIT, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_DRV, LCD_POWER_BIT, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_set_bits (i2c, addr, REG_GPIO_OUT, LCD_POWER_BIT, TIMEOUT) == ERROR_NONE; + + if (lcd_ok) { + LOG_I(TAG, "LCD power enabled via PM1_G2"); + } else { + LOG_E(TAG, "Failed to enable LCD power via PM1_G2"); + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + return ERROR_NONE; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +extern "C" { + +error_t m5pm1_get_battery_voltage(Device* device, uint16_t* mv) { + return i2c_controller_register16le_get(device_get_parent(device), GET_CONFIG(device)->address, REG_VBAT_L, mv, TIMEOUT); +} + +error_t m5pm1_get_vin_voltage(Device* device, uint16_t* mv) { + return i2c_controller_register16le_get(device_get_parent(device), GET_CONFIG(device)->address, REG_VIN_L, mv, TIMEOUT); +} + +error_t m5pm1_get_5vout_voltage(Device* device, uint16_t* mv) { + return i2c_controller_register16le_get(device_get_parent(device), GET_CONFIG(device)->address, REG_5VOUT_L, mv, TIMEOUT); +} + +error_t m5pm1_get_power_source(Device* device, M5pm1PowerSource* source) { + uint8_t val = 0; + error_t err = i2c_controller_register8_get(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_SRC, &val, TIMEOUT); + if (err != ERROR_NONE) return err; + *source = static_cast(val & 0x03U); + return ERROR_NONE; +} + +error_t m5pm1_is_charging(Device* device, bool* charging) { + // PM1_G0 is wired to the charge IC's charge-status output: LOW = charging + uint8_t gpio_in = 0; + error_t err = i2c_controller_register8_get(device_get_parent(device), GET_CONFIG(device)->address, REG_GPIO_IN, &gpio_in, TIMEOUT); + if (err != ERROR_NONE) return err; + *charging = (gpio_in & 0x01U) == 0; + return ERROR_NONE; +} + +error_t m5pm1_set_charge_enable(Device* device, bool enable) { + if (enable) { + return i2c_controller_register8_set_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_CHG_EN, TIMEOUT); + } else { + return i2c_controller_register8_reset_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_CHG_EN, TIMEOUT); + } +} + +error_t m5pm1_set_boost_enable(Device* device, bool enable) { + if (enable) { + return i2c_controller_register8_set_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_BOOST_EN, TIMEOUT); + } else { + return i2c_controller_register8_reset_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_BOOST_EN, TIMEOUT); + } +} + +error_t m5pm1_set_ldo_enable(Device* device, bool enable) { + if (enable) { + return i2c_controller_register8_set_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_LDO_EN, TIMEOUT); + } else { + return i2c_controller_register8_reset_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_PWR_CFG, PWR_CFG_LDO_EN, TIMEOUT); + } +} + +error_t m5pm1_get_temperature(Device* device, uint16_t* decidegc) { + Device* i2c = device_get_parent(device); + uint8_t addr = GET_CONFIG(device)->address; + + // Select temperature channel and start conversion + uint8_t ctrl = static_cast((ADC_CH_TEMP << 1U) | 0x01U); + error_t err = i2c_controller_register8_set(i2c, addr, REG_ADC_CTRL, ctrl, TIMEOUT); + if (err != ERROR_NONE) return err; + + // Poll until conversion complete (START bit clears) + bool conversion_done = false; + for (int i = 0; i < 10; i++) { + vTaskDelay(pdMS_TO_TICKS(5)); + uint8_t status = 0; + if (i2c_controller_register8_get(i2c, addr, REG_ADC_CTRL, &status, TIMEOUT) == ERROR_NONE) { + if ((status & 0x01U) == 0) { + conversion_done = true; + break; + } + } + } + + if (!conversion_done) { + return ERROR_TIMEOUT; + } + + return i2c_controller_register16le_get(i2c, addr, REG_ADC_RES_L, decidegc, TIMEOUT); +} + +error_t m5pm1_shutdown(Device* device) { + uint8_t cmd = SYS_CMD_SHUTDOWN; + return i2c_controller_write_register(device_get_parent(device), GET_CONFIG(device)->address, REG_SYS_CMD, &cmd, 1, TIMEOUT); +} + +error_t m5pm1_reboot(Device* device) { + uint8_t cmd = SYS_CMD_REBOOT; + return i2c_controller_write_register(device_get_parent(device), GET_CONFIG(device)->address, REG_SYS_CMD, &cmd, 1, TIMEOUT); +} + +error_t m5pm1_btn_get_state(Device* device, bool* pressed) { + uint8_t val = 0; + error_t err = i2c_controller_register8_get(device_get_parent(device), GET_CONFIG(device)->address, REG_BTN_STATUS, &val, TIMEOUT); + if (err != ERROR_NONE) return err; + *pressed = (val & 0x01U) != 0; + return ERROR_NONE; +} + +error_t m5pm1_btn_get_flag(Device* device, bool* was_pressed) { + uint8_t val = 0; + error_t err = i2c_controller_register8_get(device_get_parent(device), GET_CONFIG(device)->address, REG_BTN_STATUS, &val, TIMEOUT); + if (err != ERROR_NONE) return err; + *was_pressed = (val & 0x80U) != 0; // BTN_FLAG auto-clears on read + return ERROR_NONE; +} + +error_t m5pm1_wdt_set(Device* device, uint8_t timeout_sec) { + return i2c_controller_register8_set(device_get_parent(device), GET_CONFIG(device)->address, REG_WDT_CNT, timeout_sec, TIMEOUT); +} + +error_t m5pm1_wdt_feed(Device* device) { + return i2c_controller_register8_set(device_get_parent(device), GET_CONFIG(device)->address, REG_WDT_KEY, 0xA5, TIMEOUT); +} + +error_t m5pm1_read_rtc_ram(Device* device, uint8_t offset, uint8_t* data, uint8_t len) { + if (offset + len > 32) return ERROR_INVALID_ARGUMENT; + return i2c_controller_read_register(device_get_parent(device), GET_CONFIG(device)->address, static_cast(REG_RTC_RAM + offset), data, len, TIMEOUT); +} + +error_t m5pm1_write_rtc_ram(Device* device, uint8_t offset, const uint8_t* data, uint8_t len) { + if (offset + len > 32) return ERROR_INVALID_ARGUMENT; + return i2c_controller_write_register(device_get_parent(device), GET_CONFIG(device)->address, static_cast(REG_RTC_RAM + offset), data, len, TIMEOUT); +} + +error_t m5pm1_set_led_count(Device* device, uint8_t count) { + if (count == 0 || count > 32) return ERROR_INVALID_ARGUMENT; + uint8_t val = count & 0x3FU; + return i2c_controller_register8_set(device_get_parent(device), GET_CONFIG(device)->address, REG_NEO_CFG, val, TIMEOUT); +} + +error_t m5pm1_set_led_color(Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + if (index >= 32) return ERROR_INVALID_ARGUMENT; + Device* i2c = device_get_parent(device); + uint8_t addr = GET_CONFIG(device)->address; + // Store as RGB565: [15:11]=R5, [10:5]=G6, [4:0]=B5 + uint16_t rgb565 = static_cast(((r >> 3U) << 11U) | ((g >> 2U) << 5U) | (b >> 3U)); + uint8_t buf[2] = { static_cast(rgb565 & 0xFFU), static_cast(rgb565 >> 8U) }; + return i2c_controller_write_register(i2c, addr, static_cast(REG_NEO_DATA + index * 2U), buf, 2, TIMEOUT); +} + +error_t m5pm1_refresh_leds(Device* device) { + return i2c_controller_register8_set_bits(device_get_parent(device), GET_CONFIG(device)->address, REG_NEO_CFG, 0x40U, TIMEOUT); +} + +error_t m5pm1_disable_leds(Device* device) { + // Set count to 1 and write black, then refresh + Device* i2c = device_get_parent(device); + uint8_t addr = GET_CONFIG(device)->address; + uint8_t black[2] = { 0, 0 }; + error_t err = i2c_controller_write_register(i2c, addr, REG_NEO_DATA, black, 2, TIMEOUT); + if (err != ERROR_NONE) return err; + uint8_t cfg = 0x41U; // REFRESH | count=1 + return i2c_controller_register8_set(i2c, addr, REG_NEO_CFG, cfg, TIMEOUT); +} + +Driver m5pm1_driver = { + .name = "m5pm1", + .compatible = (const char*[]) { "m5stack,m5pm1", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &m5pm1_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/m5pm1-module/source/module.cpp b/Drivers/m5pm1-module/source/module.cpp new file mode 100644 index 000000000..1c5fe6fb7 --- /dev/null +++ b/Drivers/m5pm1-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver m5pm1_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(&m5pm1_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(&m5pm1_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol m5pm1_module_symbols[]; + +Module m5pm1_module = { + .name = "m5pm1", + .start = start, + .stop = stop, + .symbols = m5pm1_module_symbols, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/m5pm1-module/source/symbols.c b/Drivers/m5pm1-module/source/symbols.c new file mode 100644 index 000000000..76ed259ea --- /dev/null +++ b/Drivers/m5pm1-module/source/symbols.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol m5pm1_module_symbols[] = { + DEFINE_MODULE_SYMBOL(m5pm1_get_battery_voltage), + DEFINE_MODULE_SYMBOL(m5pm1_get_vin_voltage), + DEFINE_MODULE_SYMBOL(m5pm1_get_5vout_voltage), + DEFINE_MODULE_SYMBOL(m5pm1_get_power_source), + DEFINE_MODULE_SYMBOL(m5pm1_is_charging), + DEFINE_MODULE_SYMBOL(m5pm1_set_charge_enable), + DEFINE_MODULE_SYMBOL(m5pm1_set_boost_enable), + DEFINE_MODULE_SYMBOL(m5pm1_set_ldo_enable), + DEFINE_MODULE_SYMBOL(m5pm1_get_temperature), + DEFINE_MODULE_SYMBOL(m5pm1_shutdown), + DEFINE_MODULE_SYMBOL(m5pm1_reboot), + DEFINE_MODULE_SYMBOL(m5pm1_btn_get_state), + DEFINE_MODULE_SYMBOL(m5pm1_btn_get_flag), + DEFINE_MODULE_SYMBOL(m5pm1_wdt_set), + DEFINE_MODULE_SYMBOL(m5pm1_wdt_feed), + DEFINE_MODULE_SYMBOL(m5pm1_read_rtc_ram), + DEFINE_MODULE_SYMBOL(m5pm1_write_rtc_ram), + DEFINE_MODULE_SYMBOL(m5pm1_set_led_count), + DEFINE_MODULE_SYMBOL(m5pm1_set_led_color), + DEFINE_MODULE_SYMBOL(m5pm1_refresh_leds), + DEFINE_MODULE_SYMBOL(m5pm1_disable_leds), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/mpu6886-module/CMakeLists.txt b/Drivers/mpu6886-module/CMakeLists.txt new file mode 100644 index 000000000..cdbd590fd --- /dev/null +++ b/Drivers/mpu6886-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(mpu6886-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/mpu6886-module/LICENSE-Apache-2.0.md b/Drivers/mpu6886-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/mpu6886-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/mpu6886-module/README.md b/Drivers/mpu6886-module/README.md new file mode 100644 index 000000000..82d6721a3 --- /dev/null +++ b/Drivers/mpu6886-module/README.md @@ -0,0 +1,7 @@ +# MPU6886 I2C Driver + +A driver for the `MPU6886` 6-axis IMU. + +See https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/MPU-6886-000193%2Bv1.1_GHIC_en.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/mpu6886-module/bindings/invensense,mpu6886.yaml b/Drivers/mpu6886-module/bindings/invensense,mpu6886.yaml new file mode 100644 index 000000000..9d9a700c2 --- /dev/null +++ b/Drivers/mpu6886-module/bindings/invensense,mpu6886.yaml @@ -0,0 +1,5 @@ +description: InvenSense (TDK) MPU-6886 6-axis IMU + +include: ["i2c-device.yaml"] + +compatible: "invensense,mpu6886" diff --git a/Drivers/mpu6886-module/devicetree.yaml b/Drivers/mpu6886-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/mpu6886-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/mpu6886-module/include/bindings/mpu6886.h b/Drivers/mpu6886-module/include/bindings/mpu6886.h new file mode 100644 index 000000000..dbbf93a15 --- /dev/null +++ b/Drivers/mpu6886-module/include/bindings/mpu6886.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(mpu6886, struct Mpu6886Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/mpu6886-module/include/drivers/mpu6886.h b/Drivers/mpu6886-module/include/drivers/mpu6886.h new file mode 100644 index 000000000..7de0a60ff --- /dev/null +++ b/Drivers/mpu6886-module/include/drivers/mpu6886.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Mpu6886Config { + /** Address on bus */ + uint8_t address; +}; + +struct Mpu6886Data { + float ax, ay, az; // acceleration in g (±8g range) + float gx, gy, gz; // angular rate in °/s (±2000°/s range) +}; + +/** + * Read accelerometer and gyroscope data. + * @param[in] device mpu6886 device + * @param[out] data Pointer to Mpu6886Data to populate + * @return ERROR_NONE on success + */ +error_t mpu6886_read(struct Device* device, struct Mpu6886Data* data); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/mpu6886-module/include/mpu6886_module.h b/Drivers/mpu6886-module/include/mpu6886_module.h new file mode 100644 index 000000000..5d05f2ae8 --- /dev/null +++ b/Drivers/mpu6886-module/include/mpu6886_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module mpu6886_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/mpu6886-module/source/module.cpp b/Drivers/mpu6886-module/source/module.cpp new file mode 100644 index 000000000..044ea12db --- /dev/null +++ b/Drivers/mpu6886-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver mpu6886_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(&mpu6886_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(&mpu6886_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol mpu6886_module_symbols[]; + +Module mpu6886_module = { + .name = "mpu6886", + .start = start, + .stop = stop, + .symbols = mpu6886_module_symbols, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/mpu6886-module/source/mpu6886.cpp b/Drivers/mpu6886-module/source/mpu6886.cpp new file mode 100644 index 000000000..f55d0eed8 --- /dev/null +++ b/Drivers/mpu6886-module/source/mpu6886.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define TAG "MPU6886" + +// Register map +static constexpr uint8_t REG_SMPLRT_DIV = 0x19; // sample rate divider +static constexpr uint8_t REG_CONFIG = 0x1A; // DLPF config +static constexpr uint8_t REG_GYRO_CONFIG = 0x1B; // gyro full-scale select +static constexpr uint8_t REG_ACCEL_CONFIG = 0x1C; // accel full-scale select +static constexpr uint8_t REG_ACCEL_CONFIG2 = 0x1D; // accel low-pass filter +static constexpr uint8_t REG_INT_PIN_CFG = 0x37; // interrupt pin config +static constexpr uint8_t REG_INT_ENABLE = 0x38; // interrupt enable +static constexpr uint8_t REG_ACCEL_XOUT_H = 0x3B; // first accel output register +static constexpr uint8_t REG_GYRO_XOUT_H = 0x43; // first gyro output register +static constexpr uint8_t REG_USER_CTRL = 0x6A; // user control (DMP, FIFO, I2C) +static constexpr uint8_t REG_PWR_MGMT_1 = 0x6B; // power management 1 +static constexpr uint8_t REG_PWR_MGMT_2 = 0x6C; // power management 2 +static constexpr uint8_t REG_FIFO_EN = 0x23; // FIFO enable +static constexpr uint8_t REG_WHO_AM_I = 0x75; // chip ID — expect 0x19 + +static constexpr uint8_t WHO_AM_I_VALUE = 0x19; + +// Configuration values +// GYRO_CONFIG: FS_SEL=3 (±2000°/s), FCHOICE_B=00 → 0x18 +static constexpr uint8_t GYRO_CONFIG_VAL = 0x18; +// ACCEL_CONFIG: AFS_SEL=2 (±8g) → 0x10 +static constexpr uint8_t ACCEL_CONFIG_VAL = 0x10; +// CONFIG: DLPF_CFG=1 → gyro BW=176Hz, temp BW=188Hz → 0x01 +static constexpr uint8_t CONFIG_VAL = 0x01; +// SMPLRT_DIV: sample rate = 1kHz / (1 + 5) = 166Hz → 0x05 +static constexpr uint8_t SMPLRT_DIV_VAL = 0x05; + +// Scaling: full-scale / 2^15 +static constexpr float ACCEL_SCALE = 8.0f / 32768.0f; // g per LSB (±8g) +static constexpr float GYRO_SCALE = 2000.0f / 32768.0f; // °/s per LSB (±2000°/s) + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Verify chip ID + uint8_t who_am_i = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_WHO_AM_I, &who_am_i, I2C_TIMEOUT_TICKS) != ERROR_NONE + || who_am_i != WHO_AM_I_VALUE) { + LOG_E(TAG, "WHO_AM_I mismatch: got 0x%02X, expected 0x%02X", who_am_i, WHO_AM_I_VALUE); + return ERROR_RESOURCE; + } + + // Wake from sleep (clear all PWR_MGMT_1 bits) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_MGMT_1, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + vTaskDelay(pdMS_TO_TICKS(10)); + + // Device reset (bit 7) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_MGMT_1, 0x80, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + vTaskDelay(pdMS_TO_TICKS(10)); + + // Select auto clock (CLKSEL=1: PLL with gyro reference when available) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_MGMT_1, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + vTaskDelay(pdMS_TO_TICKS(10)); + + // Configure accel: ±8g + if (i2c_controller_register8_set(i2c_controller, address, REG_ACCEL_CONFIG, ACCEL_CONFIG_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Configure gyro: ±2000°/s + if (i2c_controller_register8_set(i2c_controller, address, REG_GYRO_CONFIG, GYRO_CONFIG_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // DLPF: gyro BW=176Hz + if (i2c_controller_register8_set(i2c_controller, address, REG_CONFIG, CONFIG_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Sample rate: 1kHz / (1+5) = 166Hz + if (i2c_controller_register8_set(i2c_controller, address, REG_SMPLRT_DIV, SMPLRT_DIV_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Clear interrupt enables before reconfiguring + if (i2c_controller_register8_set(i2c_controller, address, REG_INT_ENABLE, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Accel low-pass filter: ACCEL_FCHOICE_B=0, A_DLPF_CFG=0 + if (i2c_controller_register8_set(i2c_controller, address, REG_ACCEL_CONFIG2, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Disable DMP and FIFO + if (i2c_controller_register8_set(i2c_controller, address, REG_USER_CTRL, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (i2c_controller_register8_set(i2c_controller, address, REG_FIFO_EN, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Interrupt: active-high (ACTL=0), push-pull (OPEN=0), latched (LATCH_INT_EN=1), cleared on any read (INT_ANYRD_2CLEAR=1) → 0x30 + if (i2c_controller_register8_set(i2c_controller, address, REG_INT_PIN_CFG, 0x30, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + // Enable DATA_RDY interrupt + if (i2c_controller_register8_set(i2c_controller, address, REG_INT_ENABLE, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Put device to sleep (set SLEEP bit in PWR_MGMT_1) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_MGMT_1, 0x40, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + LOG_E(TAG, "Failed to put MPU6886 to sleep"); + return ERROR_RESOURCE; + } + + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t mpu6886_read(Device* device, Mpu6886Data* data) { + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // MPU6886 is big-endian (MSB first), unlike BMI270 + auto toI16 = [](uint8_t hi, uint8_t lo) -> int16_t { + return static_cast(static_cast(hi) << 8 | lo); + }; + + // Burst read: accel (6) + temp (2) + gyro (6) = 14 bytes at 0x3B + uint8_t buf[14] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_ACCEL_XOUT_H, buf, sizeof(buf), I2C_TIMEOUT_TICKS); + if (error != ERROR_NONE) return error; + + data->ax = toI16(buf[0], buf[1]) * ACCEL_SCALE; + data->ay = toI16(buf[2], buf[3]) * ACCEL_SCALE; + data->az = toI16(buf[4], buf[5]) * ACCEL_SCALE; + // buf[6..7] = temperature (skipped) + data->gx = toI16(buf[8], buf[9]) * GYRO_SCALE; + data->gy = toI16(buf[10], buf[11]) * GYRO_SCALE; + data->gz = toI16(buf[12], buf[13]) * GYRO_SCALE; + + return ERROR_NONE; +} + +Driver mpu6886_driver = { + .name = "mpu6886", + .compatible = (const char*[]) { "invensense,mpu6886", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &mpu6886_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/mpu6886-module/source/symbols.c b/Drivers/mpu6886-module/source/symbols.c new file mode 100644 index 000000000..c94694fb9 --- /dev/null +++ b/Drivers/mpu6886-module/source/symbols.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol mpu6886_module_symbols[] = { + DEFINE_MODULE_SYMBOL(mpu6886_read), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/qmi8658-module/CMakeLists.txt b/Drivers/qmi8658-module/CMakeLists.txt new file mode 100644 index 000000000..bad70971d --- /dev/null +++ b/Drivers/qmi8658-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(qmi8658-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/qmi8658-module/LICENSE-Apache-2.0.md b/Drivers/qmi8658-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/qmi8658-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/qmi8658-module/README.md b/Drivers/qmi8658-module/README.md new file mode 100644 index 000000000..38fa3d6ca --- /dev/null +++ b/Drivers/qmi8658-module/README.md @@ -0,0 +1,7 @@ +# QMI8658 I2C Driver + +A driver for the `QMI8658` 6-axis IMU. + +See https://www.waveshare.net/w/upload/5/5f/QMI8658C.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/qmi8658-module/bindings/qst,qmi8658.yaml b/Drivers/qmi8658-module/bindings/qst,qmi8658.yaml new file mode 100644 index 000000000..6182201af --- /dev/null +++ b/Drivers/qmi8658-module/bindings/qst,qmi8658.yaml @@ -0,0 +1,5 @@ +description: QST QMI8658 6-axis IMU + +include: [ "i2c-device.yaml" ] + +compatible: "qst,qmi8658" diff --git a/Drivers/qmi8658-module/devicetree.yaml b/Drivers/qmi8658-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/qmi8658-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/qmi8658-module/include/bindings/qmi8658.h b/Drivers/qmi8658-module/include/bindings/qmi8658.h new file mode 100644 index 000000000..1e083f70d --- /dev/null +++ b/Drivers/qmi8658-module/include/bindings/qmi8658.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(qmi8658, struct Qmi8658Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/qmi8658-module/include/drivers/qmi8658.h b/Drivers/qmi8658-module/include/drivers/qmi8658.h new file mode 100644 index 000000000..ff1ba67a6 --- /dev/null +++ b/Drivers/qmi8658-module/include/drivers/qmi8658.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Qmi8658Config { + /** I2C address (0x6A when SA0=low, 0x6B when SA0=high) */ + uint8_t address; +}; + +struct Qmi8658Data { + float ax, ay, az; // acceleration in g (±8g range) + float gx, gy, gz; // angular rate in °/s (±2048°/s range) +}; + +/** + * Read accelerometer and gyroscope data. + * @param[in] device qmi8658 device + * @param[out] data Pointer to Qmi8658Data to populate + * @return ERROR_NONE on success + */ +error_t qmi8658_read(struct Device* device, struct Qmi8658Data* data); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/qmi8658-module/include/qmi8658_module.h b/Drivers/qmi8658-module/include/qmi8658_module.h new file mode 100644 index 000000000..95a715721 --- /dev/null +++ b/Drivers/qmi8658-module/include/qmi8658_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module qmi8658_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/qmi8658-module/source/module.cpp b/Drivers/qmi8658-module/source/module.cpp new file mode 100644 index 000000000..5e2308c24 --- /dev/null +++ b/Drivers/qmi8658-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver qmi8658_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(&qmi8658_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(&qmi8658_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol qmi8658_module_symbols[]; + +Module qmi8658_module = { + .name = "qmi8658", + .start = start, + .stop = stop, + .symbols = qmi8658_module_symbols, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/qmi8658-module/source/qmi8658.cpp b/Drivers/qmi8658-module/source/qmi8658.cpp new file mode 100644 index 000000000..38d47e472 --- /dev/null +++ b/Drivers/qmi8658-module/source/qmi8658.cpp @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define TAG "QMI8658" + +// Register map (QMI8658 datasheet) +static constexpr uint8_t REG_WHO_AM_I = 0x00; // chip ID — expect 0x05 +static constexpr uint8_t REG_CTRL1 = 0x02; // serial interface config +static constexpr uint8_t REG_CTRL2 = 0x03; // accel: range[6:4] + ODR[3:0] +static constexpr uint8_t REG_CTRL3 = 0x04; // gyro: range[6:4] + ODR[3:0] +static constexpr uint8_t REG_CTRL7 = 0x08; // sensor enable: bit0=accel, bit1=gyro +static constexpr uint8_t REG_AX_L = 0x35; // first data register (accel X LSB) + +static constexpr uint8_t WHO_AM_I_VALUE = 0x05; + +// CTRL1: auto address increment (bit 6), little-endian (bit 5 = 0) +static constexpr uint8_t CTRL1_VAL = 0x40; +// CTRL2: accel ±8G (aFS=0x02 → bits[6:4]=010) + 1000Hz ODR (0x03 → bits[3:0]=0011) = 0x23 +static constexpr uint8_t CTRL2_VAL = 0x23; +// CTRL3: gyro ±2048DPS (gFS=0x06 → bits[6:4]=110) + 1000Hz ODR (0x03 → bits[3:0]=0011) = 0x63 +static constexpr uint8_t CTRL3_VAL = 0x63; +// CTRL7: enable accel (bit0) + gyro (bit1) +static constexpr uint8_t CTRL7_ENABLE = 0x03; + +// Scaling: full-scale / 2^15 +static constexpr float ACCEL_SCALE = 8.0f / 32768.0f; // g per LSB (±8g) → 1/4096 +static constexpr float GYRO_SCALE = 2048.0f / 32768.0f; // °/s per LSB (±2048°/s) → 1/16 + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Verify chip ID + uint8_t who_am_i = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_WHO_AM_I, &who_am_i, I2C_TIMEOUT_TICKS) != ERROR_NONE + || who_am_i != WHO_AM_I_VALUE) { + LOG_E(TAG, "WHO_AM_I mismatch: got 0x%02X, expected 0x%02X", who_am_i, WHO_AM_I_VALUE); + return ERROR_RESOURCE; + } + + // Disable all sensors during configuration + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL7, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Serial interface: auto address increment, little-endian + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL1, CTRL1_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Accel: ±8g, 1000Hz ODR + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL2, CTRL2_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Gyro: ±2048°/s, 1000Hz ODR + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL3, CTRL3_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Enable accel + gyro + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL7, CTRL7_ENABLE, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Put device to sleep + if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL7, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + LOG_E(TAG, "Failed to put QMI8658 to sleep"); + return ERROR_RESOURCE; + } + + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t qmi8658_read(Device* device, Qmi8658Data* data) { + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // Burst-read 12 bytes starting at AX_L (0x35): accel X/Y/Z then gyro X/Y/Z + // QMI8658 is little-endian (LSB first), same as BMI270 + uint8_t buf[12] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_AX_L, buf, sizeof(buf), I2C_TIMEOUT_TICKS); + if (error != ERROR_NONE) return error; + + auto toI16 = [](uint8_t lo, uint8_t hi) -> int16_t { + return static_cast(static_cast(hi) << 8 | lo); + }; + + data->ax = toI16(buf[0], buf[1]) * ACCEL_SCALE; + data->ay = toI16(buf[2], buf[3]) * ACCEL_SCALE; + data->az = toI16(buf[4], buf[5]) * ACCEL_SCALE; + data->gx = toI16(buf[6], buf[7]) * GYRO_SCALE; + data->gy = toI16(buf[8], buf[9]) * GYRO_SCALE; + data->gz = toI16(buf[10], buf[11]) * GYRO_SCALE; + + return ERROR_NONE; +} + +Driver qmi8658_driver = { + .name = "qmi8658", + .compatible = (const char*[]) { "qst,qmi8658", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &qmi8658_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/qmi8658-module/source/symbols.c b/Drivers/qmi8658-module/source/symbols.c new file mode 100644 index 000000000..47ba5e540 --- /dev/null +++ b/Drivers/qmi8658-module/source/symbols.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol qmi8658_module_symbols[] = { + DEFINE_MODULE_SYMBOL(qmi8658_read), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/rx8130ce-module/CMakeLists.txt b/Drivers/rx8130ce-module/CMakeLists.txt new file mode 100644 index 000000000..229a4a09d --- /dev/null +++ b/Drivers/rx8130ce-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(rx8130ce-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/rx8130ce-module/LICENSE-Apache-2.0.md b/Drivers/rx8130ce-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/rx8130ce-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/rx8130ce-module/README.md b/Drivers/rx8130ce-module/README.md new file mode 100644 index 000000000..df37e8bc9 --- /dev/null +++ b/Drivers/rx8130ce-module/README.md @@ -0,0 +1,8 @@ +# RX8130CE I2C Driver + +A driver for the `RX8130CE` Realtime Clock. + +See https://download.epsondevice.com/td/pdf/brief/RX8130CE_en.pdf +And: https://download.epsondevice.com/td/pdf/app/RX8130CE_en.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/rx8130ce-module/bindings/epson,rx8130ce.yaml b/Drivers/rx8130ce-module/bindings/epson,rx8130ce.yaml new file mode 100644 index 000000000..5b832890e --- /dev/null +++ b/Drivers/rx8130ce-module/bindings/epson,rx8130ce.yaml @@ -0,0 +1,5 @@ +description: Epson RX8130CE RTC + +include: [ "i2c-device.yaml" ] + +compatible: "epson,rx8130ce" diff --git a/Drivers/rx8130ce-module/devicetree.yaml b/Drivers/rx8130ce-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/rx8130ce-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/rx8130ce-module/include/bindings/rx8130ce.h b/Drivers/rx8130ce-module/include/bindings/rx8130ce.h new file mode 100644 index 000000000..682ba35cd --- /dev/null +++ b/Drivers/rx8130ce-module/include/bindings/rx8130ce.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(rx8130ce, struct Rx8130ceConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/rx8130ce-module/include/drivers/rx8130ce.h b/Drivers/rx8130ce-module/include/drivers/rx8130ce.h new file mode 100644 index 000000000..e348b556d --- /dev/null +++ b/Drivers/rx8130ce-module/include/drivers/rx8130ce.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Rx8130ceConfig { + /** Address on bus */ + uint8_t address; +}; + +struct Rx8130ceDateTime { + uint16_t year; // 2000–2099 + uint8_t month; // 1–12 + uint8_t day; // 1–31 + uint8_t hour; // 0–23 + uint8_t minute; // 0–59 + uint8_t second; // 0–59 +}; + +/** + * Read the current date and time from the RTC. + * @param[in] device rx8130ce device + * @param[out] dt Pointer to Rx8130ceDateTime to populate + * @return ERROR_NONE on success, ERROR_INVALID_STATE if VLF is set (clock data unreliable) + */ +error_t rx8130ce_get_datetime(struct Device* device, struct Rx8130ceDateTime* dt); + +/** + * Write the date and time to the RTC. + * @param[in] device rx8130ce device + * @param[in] dt Pointer to Rx8130ceDateTime to write (year must be 2000–2099) + * @return ERROR_NONE on success, ERROR_INVALID_ARGUMENT if any field is out of range + */ +error_t rx8130ce_set_datetime(struct Device* device, const struct Rx8130ceDateTime* dt); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/rx8130ce-module/include/rx8130ce_module.h b/Drivers/rx8130ce-module/include/rx8130ce_module.h new file mode 100644 index 000000000..05a2b430c --- /dev/null +++ b/Drivers/rx8130ce-module/include/rx8130ce_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module rx8130ce_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/rx8130ce-module/source/module.cpp b/Drivers/rx8130ce-module/source/module.cpp new file mode 100644 index 000000000..dcce39ce4 --- /dev/null +++ b/Drivers/rx8130ce-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver rx8130ce_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(&rx8130ce_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(&rx8130ce_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol rx8130ce_module_symbols[]; + +Module rx8130ce_module = { + .name = "rx8130ce", + .start = start, + .stop = stop, + .symbols = rx8130ce_module_symbols, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/rx8130ce-module/source/rx8130ce.cpp b/Drivers/rx8130ce-module/source/rx8130ce.cpp new file mode 100644 index 000000000..33359b89b --- /dev/null +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define TAG "RX8130CE" + +// Register map (RX8130CE datasheet, section 8) +static constexpr uint8_t REG_SECONDS = 0x10; // BCD seconds +static constexpr uint8_t REG_MINUTES = 0x11; // BCD minutes +static constexpr uint8_t REG_HOURS = 0x12; // BCD hours (24h) +static constexpr uint8_t REG_WEEKDAY = 0x13; // weekday (ignored) +static constexpr uint8_t REG_DAY = 0x14; // BCD day +static constexpr uint8_t REG_MONTH = 0x15; // BCD month +static constexpr uint8_t REG_YEAR = 0x16; // BCD year (00–99, base 2000) +// 0x17–0x1B: alarm / timer registers (not used here) +static constexpr uint8_t REG_FLAG = 0x1D; // status flags (VLF bit 1) +static constexpr uint8_t REG_CTRL0 = 0x1E; // Control 0 — STOP bit (bit 6) +static constexpr uint8_t REG_CTRL1 = 0x1F; // Control 1 — battery backup bits + +static constexpr uint8_t CTRL0_STOP = 0x40; // bit 6: stop the clock oscillator (RX8130_BIT_CTRL_STOP) +static constexpr uint8_t FLAG_VLF = 0x02; // bit 1: voltage-low flag (time unreliable) + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(50); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Helpers + +static uint8_t bcd_to_dec(uint8_t bcd) { return static_cast((bcd >> 4) * 10 + (bcd & 0x0F)); } +static uint8_t dec_to_bcd(uint8_t dec) { return static_cast(((dec / 10) << 4) | (dec % 10)); } + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Clear VLF and other status flags + if (i2c_controller_register8_set(i2c_controller, address, REG_FLAG, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + LOG_E(TAG, "Failed to clear FLAG register at 0x%02X", address); + return ERROR_RESOURCE; + } + + // Enable battery backup: set bits 4 and 5 of CTRL1 (0x1F) + uint8_t ctrl1 = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_CTRL1, &ctrl1, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + ctrl1 |= 0x30; // bits 4+5: BSTP=1, BPWD=1 + if (i2c_controller_write_register(i2c_controller, address, REG_CTRL1, &ctrl1, 1, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Ensure oscillator is running (STOP=0) + uint8_t ctrl0 = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_CTRL0, &ctrl0, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (ctrl0 & CTRL0_STOP) { + ctrl0 &= static_cast(~CTRL0_STOP); + if (i2c_controller_write_register(i2c_controller, address, REG_CTRL0, &ctrl0, 1, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + // RTC oscillator should continue running on battery backup + // No action needed on driver stop + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t rx8130ce_get_datetime(Device* device, Rx8130ceDateTime* dt) { + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // Check VLF (voltage-low flag) before reading time data + uint8_t flag = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_FLAG, &flag, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (flag & FLAG_VLF) { + LOG_E(TAG, "Clock integrity compromised (VLF flag set) — data unreliable"); + return ERROR_INVALID_STATE; + } + + // Burst-read 7 registers starting at 0x10: + // [0]=sec [1]=min [2]=hours [3]=weekday [4]=day [5]=month [6]=year + uint8_t buf[7] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_SECONDS, buf, sizeof(buf), I2C_TIMEOUT_TICKS); + if (error != ERROR_NONE) return error; + + dt->second = bcd_to_dec(buf[0] & 0x7Fu); + dt->minute = bcd_to_dec(buf[1] & 0x7Fu); + dt->hour = bcd_to_dec(buf[2] & 0x3Fu); + // buf[3] = weekday — ignored + dt->day = bcd_to_dec(buf[4] & 0x3Fu); + dt->month = bcd_to_dec(buf[5] & 0x1Fu); + dt->year = static_cast(2000 + bcd_to_dec(buf[6])); + + return ERROR_NONE; +} + +error_t rx8130ce_set_datetime(Device* device, const Rx8130ceDateTime* dt) { + if (dt->month < 1 || dt->month > 12 || + dt->day < 1 || dt->day > 31 || + dt->hour > 23 || dt->minute > 59 || dt->second > 59) { + return ERROR_INVALID_ARGUMENT; + } + if (dt->year < 2000 || dt->year > 2099) { + return ERROR_INVALID_ARGUMENT; + } + + auto* i2c_controller = device_get_parent(device); + auto address = GET_CONFIG(device)->address; + + // Set STOP bit to freeze the oscillator during the write (prevents tearing) + uint8_t ctrl0 = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_CTRL0, &ctrl0, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + uint8_t ctrl0_stop = ctrl0 | CTRL0_STOP; + if (i2c_controller_write_register(i2c_controller, address, REG_CTRL0, &ctrl0_stop, 1, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Write 7 time registers in one burst + uint8_t buf[7] = {}; + buf[0] = dec_to_bcd(dt->second); + buf[1] = dec_to_bcd(dt->minute); + buf[2] = dec_to_bcd(dt->hour); + buf[3] = 0; // weekday — unused + buf[4] = dec_to_bcd(dt->day); + buf[5] = dec_to_bcd(dt->month); + uint8_t year_offset = static_cast(dt->year - 2000); + buf[6] = dec_to_bcd(year_offset); + error_t error = i2c_controller_write_register(i2c_controller, address, REG_SECONDS, buf, sizeof(buf), I2C_TIMEOUT_TICKS); + + // Clear STOP bit to resume — do this even if the write failed + ctrl0 &= static_cast(~CTRL0_STOP); + error_t resume_error = i2c_controller_write_register(i2c_controller, address, REG_CTRL0, &ctrl0, 1, I2C_TIMEOUT_TICKS); + if (resume_error != ERROR_NONE) { + LOG_E(TAG, "Failed to clear STOP bit after datetime write"); + if (error == ERROR_NONE) { + return resume_error; + } + } + + return error; +} + +Driver rx8130ce_driver = { + .name = "rx8130ce", + .compatible = (const char*[]) { "epson,rx8130ce", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &rx8130ce_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/rx8130ce-module/source/symbols.c b/Drivers/rx8130ce-module/source/symbols.c new file mode 100644 index 000000000..21183d30d --- /dev/null +++ b/Drivers/rx8130ce-module/source/symbols.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol rx8130ce_module_symbols[] = { + DEFINE_MODULE_SYMBOL(rx8130ce_get_datetime), + DEFINE_MODULE_SYMBOL(rx8130ce_set_datetime), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 4221d0c4b..ac03ba56a 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -49,6 +49,16 @@ dependencies: rules: # More hardware seems to be supported - enable as needed - if: "target in [esp32p4]" + espressif/esp_lcd_st7123: + version: "1.0.2" + rules: + # More hardware seems to be supported - enable as needed + - if: "target in [esp32p4]" + espressif/esp_lcd_touch_st7123: + version: "1.0.1" + rules: + # More hardware seems to be supported - enable as needed + - if: "target in [esp32p4]" espressif/esp_lcd_panel_io_additions: "1.0.1" espressif/esp_tinyusb: version: "1.7.6~1" diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 43f273446..319021f05 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -33,11 +33,14 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_event_get_current_target_obj), DEFINE_MODULE_SYMBOL(lv_event_get_draw_task), DEFINE_MODULE_SYMBOL(lv_event_stop_bubbling), + DEFINE_MODULE_SYMBOL(lv_event_get_layer), // lv_obj DEFINE_MODULE_SYMBOL(lv_color_hex), DEFINE_MODULE_SYMBOL(lv_color_make), DEFINE_MODULE_SYMBOL(lv_color_black), DEFINE_MODULE_SYMBOL(lv_color_white), + DEFINE_MODULE_SYMBOL(lv_color_lighten), + DEFINE_MODULE_SYMBOL(lv_color_darken), DEFINE_MODULE_SYMBOL(lv_obj_center), DEFINE_MODULE_SYMBOL(lv_obj_clean), DEFINE_MODULE_SYMBOL(lv_obj_create), @@ -159,6 +162,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_obj_move_to_index), DEFINE_MODULE_SYMBOL(lv_obj_set_style_min_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_max_height), + DEFINE_MODULE_SYMBOL(lv_obj_set_style_width), + DEFINE_MODULE_SYMBOL(lv_obj_set_style_height), // lv_font DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt), DEFINE_MODULE_SYMBOL(lv_font_get_default), @@ -249,6 +254,16 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_arc_get_knob_offset), DEFINE_MODULE_SYMBOL(lv_arc_align_obj_to_angle), DEFINE_MODULE_SYMBOL(lv_arc_rotate_obj_to_angle), + // lv_chart + DEFINE_MODULE_SYMBOL(lv_chart_set_div_line_count), + DEFINE_MODULE_SYMBOL(lv_chart_set_all_values), + DEFINE_MODULE_SYMBOL(lv_chart_create), + DEFINE_MODULE_SYMBOL(lv_chart_set_type), + DEFINE_MODULE_SYMBOL(lv_chart_set_next_value), + DEFINE_MODULE_SYMBOL(lv_chart_add_series), + DEFINE_MODULE_SYMBOL(lv_chart_set_axis_range), + DEFINE_MODULE_SYMBOL(lv_chart_set_update_mode), + DEFINE_MODULE_SYMBOL(lv_chart_set_point_count), // lv_dropdown DEFINE_MODULE_SYMBOL(lv_dropdown_create), DEFINE_MODULE_SYMBOL(lv_dropdown_add_option), @@ -379,6 +394,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_slider_get_value), DEFINE_MODULE_SYMBOL(lv_slider_set_range), DEFINE_MODULE_SYMBOL(lv_slider_set_value), + DEFINE_MODULE_SYMBOL(lv_slider_set_mode), // lv_tabview DEFINE_MODULE_SYMBOL(lv_tabview_add_tab), DEFINE_MODULE_SYMBOL(lv_tabview_create), @@ -437,5 +453,11 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_binfont_destroy), // lv_style_gen DEFINE_MODULE_SYMBOL(lv_style_set_text_font), + // lv_draw_line + DEFINE_MODULE_SYMBOL(lv_draw_line), + DEFINE_MODULE_SYMBOL(lv_draw_line_dsc_init), + // lv_area + DEFINE_MODULE_SYMBOL(lv_area_get_width), + DEFINE_MODULE_SYMBOL(lv_area_get_height), MODULE_SYMBOL_TERMINATOR }; \ No newline at end of file diff --git a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp index 144203cd3..1383679bc 100644 --- a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp +++ b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp @@ -59,10 +59,11 @@ extern long __fixdfdi(double a); // extern long long __fixxfti(long double a); // extern unsigned int __fixunssfsi(float a); -// extern unsigned int __fixunsdfsi(double a); +extern unsigned int __fixunsdfsi(double a); // extern unsigned int __fixunstfsi(long double a); // extern unsigned int __fixunsxfsi(long double a); +extern unsigned long __fixunssfdi(float a); extern unsigned long __fixunsdfdi(double a); // extern unsigned long __fixunstfdi(long double a); // extern unsigned long __fixunsxfdi(long double a); @@ -73,7 +74,7 @@ extern unsigned long __fixunsdfdi(double a); // extern unsigned long long __fixunsxfti(long double a); // extern float __floatsisf(int i); -// extern double __floatsidf(int i); +extern double __floatsidf(int i); // extern long double __floatsitf(int i); // extern long double __floatsixf(int i); @@ -131,6 +132,9 @@ int __ledf2(double a, double b); int __gtdf2(double a, double b); // int __gttf2(long double a, long double b); +// GCC integer/bitwise helpers (compiler-rt) +int __clzsi2(unsigned int x); + } // extern "C" const esp_elfsym gcc_soft_float_symbols[] = { @@ -181,10 +185,11 @@ const esp_elfsym gcc_soft_float_symbols[] = { // ESP_ELFSYM_EXPORT(__fixxfti), // ESP_ELFSYM_EXPORT(__fixunssfsi), - // ESP_ELFSYM_EXPORT(__fixunsdfsi), + ESP_ELFSYM_EXPORT(__fixunsdfsi), // ESP_ELFSYM_EXPORT(__fixunstfsi), // ESP_ELFSYM_EXPORT(__fixunsxfsi), + ESP_ELFSYM_EXPORT(__fixunssfdi), ESP_ELFSYM_EXPORT(__fixunsdfdi), // ESP_ELFSYM_EXPORT(__fixunstfdi), // ESP_ELFSYM_EXPORT(__fixunsxfdi), @@ -195,7 +200,7 @@ const esp_elfsym gcc_soft_float_symbols[] = { // ESP_ELFSYM_EXPORT(__fixunsxfti), // ESP_ELFSYM_EXPORT(__floatsisf), - // ESP_ELFSYM_EXPORT(__floatsidf), + ESP_ELFSYM_EXPORT(__floatsidf), // ESP_ELFSYM_EXPORT(__floatsitf), // ESP_ELFSYM_EXPORT(__floatsixf), @@ -253,6 +258,9 @@ const esp_elfsym gcc_soft_float_symbols[] = { ESP_ELFSYM_EXPORT(__gtdf2), // ESP_ELFSYM_EXPORT(__gttf2), + // GCC integer/bitwise helpers + ESP_ELFSYM_EXPORT(__clzsi2), + ESP_ELFSYM_END }; diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 04072a2f8..5a67c9ad0 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,7 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(difftime), ESP_ELFSYM_EXPORT(localtime_r), ESP_ELFSYM_EXPORT(localtime), + ESP_ELFSYM_EXPORT(mktime), // esp_sntp.h ESP_ELFSYM_EXPORT(sntp_get_sync_status), // math.h @@ -437,6 +439,14 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size), ESP_ELFSYM_EXPORT(heap_caps_get_free_size), ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block), + ESP_ELFSYM_EXPORT(heap_caps_malloc), + ESP_ELFSYM_EXPORT(heap_caps_calloc), + ESP_ELFSYM_EXPORT(heap_caps_free), + // esp_timer.h + ESP_ELFSYM_EXPORT(esp_timer_create), + ESP_ELFSYM_EXPORT(esp_timer_stop), + ESP_ELFSYM_EXPORT(esp_timer_delete), + ESP_ELFSYM_EXPORT(esp_timer_start_periodic), // delimiter ESP_ELFSYM_END }; diff --git a/TactilityKernel/include/tactility/drivers/i2c_controller.h b/TactilityKernel/include/tactility/drivers/i2c_controller.h index 6a50d9142..1cd8da057 100644 --- a/TactilityKernel/include/tactility/drivers/i2c_controller.h +++ b/TactilityKernel/include/tactility/drivers/i2c_controller.h @@ -207,6 +207,17 @@ error_t i2c_controller_register8_set_bits(struct Device* device, uint8_t address */ error_t i2c_controller_register8_reset_bits(struct Device* device, uint8_t address, uint8_t reg, uint8_t bits_to_reset, TickType_t timeout); +/** + * @brief Reads a little-endian 16-bit register value from an I2C device. + * @param[in] device the I2C controller device + * @param[in] address the 7-bit I2C address of the slave device + * @param[in] reg the register address of the low byte + * @param[out] value a pointer to the variable to store the 16-bit result + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the read operation was successful + */ +error_t i2c_controller_register16le_get(struct Device* device, uint8_t address, uint8_t reg, uint16_t* value, TickType_t timeout); + extern const struct DeviceType I2C_CONTROLLER_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/source/drivers/i2c_controller.cpp b/TactilityKernel/source/drivers/i2c_controller.cpp index c82a9f2d4..cba7437dd 100644 --- a/TactilityKernel/source/drivers/i2c_controller.cpp +++ b/TactilityKernel/source/drivers/i2c_controller.cpp @@ -77,6 +77,14 @@ error_t i2c_controller_has_device_at_address(Device* device, uint8_t address, Ti return I2C_DRIVER_API(driver)->write(device, address, message, 2, timeout); } +error_t i2c_controller_register16le_get(Device* device, uint8_t address, uint8_t reg, uint16_t* value, TickType_t timeout) { + uint8_t buf[2] = {}; + error_t err = i2c_controller_read_register(device, address, reg, buf, 2, timeout); + if (err != ERROR_NONE) return err; + *value = static_cast(buf[0] | (buf[1] << 8)); + return ERROR_NONE; +} + const struct DeviceType I2C_CONTROLLER_TYPE { .name = "i2c-controller" };