From ad653e0ba7fd51c58ed6d35764df566da4fd29e3 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 18 Mar 2026 10:09:29 +1000 Subject: [PATCH 01/11] M5Stack StickS3 - New Tab5 - driver modules Font size set to 18 for 800x480 displays Fix web server dashboard not rendering when sdcard isn't present Added new driver modules for BM8563 RTC, RX8130CE RTC, MPU6886 IMU, QMI8658 IMU. Applied the above modules to applicable devicetrees. Added driver module for the M5Stack M5PM1 Power Management Chip. Added new device: M5Stack StickS3 Added new M5Stack Tab5 St7123 variant. ButtonControl changed to use interupts and xQueue, added AppClose action. And some bonus symbols of course, the apps are hungry for symbols. --- Data/data/webserver/dashboard.html | 22 +- Devices/btt-panda-touch/device.properties | 3 +- Devices/cyd-8048s043c/device.properties | 1 + .../device.properties | 1 + .../device.properties | 1 + Devices/guition-jc8048w550c/device.properties | 1 + Devices/m5stack-cardputer-adv/devicetree.yaml | 3 +- .../m5stack,cardputer-adv.dts | 6 + Devices/m5stack-core2/devicetree.yaml | 4 +- Devices/m5stack-core2/m5stack,core2.dts | 12 + Devices/m5stack-cores3/devicetree.yaml | 4 +- Devices/m5stack-cores3/m5stack,cores3.dts | 12 + Devices/m5stack-papers3/devicetree.yaml | 4 +- Devices/m5stack-papers3/m5stack,papers3.dts | 12 + Devices/m5stack-stickc-plus/devicetree.yaml | 4 +- .../m5stack,stickc-plus.dts | 12 + Devices/m5stack-stickc-plus2/devicetree.yaml | 4 +- .../m5stack,stickc-plus2.dts | 12 + Devices/m5stack-sticks3/CMakeLists.txt | 7 + .../m5stack-sticks3/Source/Configuration.cpp | 26 ++ .../Source/devices/Display.cpp | 32 ++ .../m5stack-sticks3/Source/devices/Display.h | 18 ++ .../m5stack-sticks3/Source/devices/Power.cpp | 109 +++++++ .../m5stack-sticks3/Source/devices/Power.h | 6 + Devices/m5stack-sticks3/Source/module.cpp | 23 ++ Devices/m5stack-sticks3/device.properties | 25 ++ Devices/m5stack-sticks3/devicetree.yaml | 5 + Devices/m5stack-sticks3/m5stack,sticks3.dts | 71 +++++ Devices/m5stack-tab5/CMakeLists.txt | 2 +- .../m5stack-tab5/Source/devices/Detect.cpp | 35 ++ Devices/m5stack-tab5/Source/devices/Detect.h | 8 + .../m5stack-tab5/Source/devices/Display.cpp | 62 +++- .../Source/devices/Ili9881cDisplay.cpp | 8 +- .../Source/devices/St7123Display.cpp | 144 +++++++++ .../Source/devices/St7123Display.h | 38 +++ .../Source/devices/St7123Touch.cpp | 42 +++ .../m5stack-tab5/Source/devices/St7123Touch.h | 59 ++++ .../{disp_init_data.h => ili9881_init_data.h} | 2 +- .../Source/devices/st7123_init_data.h | 37 +++ Devices/m5stack-tab5/devicetree.yaml | 7 +- Devices/m5stack-tab5/m5stack,tab5.dts | 8 +- Devices/waveshare-s3-lcd-13/devicetree.yaml | 3 +- .../waveshare,s3-lcd-13.dts | 6 + .../devicetree.yaml | 3 +- .../waveshare,s3-touch-lcd-128.dts | 6 + Documentation/ideas.md | 1 - .../ButtonControl/Source/ButtonControl.cpp | 152 ++++++--- Drivers/ButtonControl/Source/ButtonControl.h | 28 +- .../EspLcdCompat/Source/EspLcdDisplayV2.cpp | 6 +- Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h | 2 + Drivers/bm8563-module/CMakeLists.txt | 11 + Drivers/bm8563-module/LICENSE-Apache-2.0.md | 195 ++++++++++++ Drivers/bm8563-module/README.md | 9 + .../bindings/belling,bm8563.yaml | 5 + Drivers/bm8563-module/devicetree.yaml | 3 + .../bm8563-module/include/bindings/bm8563.h | 15 + Drivers/bm8563-module/include/bm8563_module.h | 14 + .../bm8563-module/include/drivers/bm8563.h | 45 +++ Drivers/bm8563-module/source/bm8563.cpp | 115 +++++++ Drivers/bm8563-module/source/module.cpp | 34 ++ Drivers/bm8563-module/source/symbols.c | 9 + Drivers/bmi270-module/source/bmi270.cpp | 14 + Drivers/m5pm1-module/CMakeLists.txt | 11 + Drivers/m5pm1-module/LICENSE-Apache-2.0.md | 195 ++++++++++++ Drivers/m5pm1-module/README.md | 8 + .../m5pm1-module/bindings/m5stack,m5pm1.yaml | 5 + Drivers/m5pm1-module/devicetree.yaml | 3 + Drivers/m5pm1-module/include/bindings/m5pm1.h | 15 + Drivers/m5pm1-module/include/drivers/m5pm1.h | 88 +++++ Drivers/m5pm1-module/include/m5pm1_module.h | 14 + Drivers/m5pm1-module/source/m5pm1.cpp | 300 ++++++++++++++++++ Drivers/m5pm1-module/source/module.cpp | 30 ++ Drivers/m5pm1-module/source/symbols.c | 28 ++ Drivers/mpu6886-module/CMakeLists.txt | 11 + Drivers/mpu6886-module/LICENSE-Apache-2.0.md | 195 ++++++++++++ Drivers/mpu6886-module/README.md | 7 + .../bindings/invensense,mpu6886.yaml | 5 + Drivers/mpu6886-module/devicetree.yaml | 3 + .../mpu6886-module/include/bindings/mpu6886.h | 15 + .../mpu6886-module/include/drivers/mpu6886.h | 33 ++ .../mpu6886-module/include/mpu6886_module.h | 14 + Drivers/mpu6886-module/source/module.cpp | 34 ++ Drivers/mpu6886-module/source/mpu6886.cpp | 160 ++++++++++ Drivers/mpu6886-module/source/symbols.c | 8 + Drivers/qmi8658-module/CMakeLists.txt | 11 + Drivers/qmi8658-module/LICENSE-Apache-2.0.md | 195 ++++++++++++ Drivers/qmi8658-module/README.md | 7 + .../qmi8658-module/bindings/qst,qmi8658.yaml | 5 + Drivers/qmi8658-module/devicetree.yaml | 3 + .../qmi8658-module/include/bindings/qmi8658.h | 15 + .../qmi8658-module/include/drivers/qmi8658.h | 33 ++ .../qmi8658-module/include/qmi8658_module.h | 14 + Drivers/qmi8658-module/source/module.cpp | 30 ++ Drivers/qmi8658-module/source/qmi8658.cpp | 133 ++++++++ Drivers/qmi8658-module/source/symbols.c | 8 + Drivers/rx8130ce-module/CMakeLists.txt | 11 + Drivers/rx8130ce-module/LICENSE-Apache-2.0.md | 195 ++++++++++++ Drivers/rx8130ce-module/README.md | 8 + .../bindings/epson,rx8130ce.yaml | 5 + Drivers/rx8130ce-module/devicetree.yaml | 3 + .../include/bindings/rx8130ce.h | 15 + .../include/drivers/rx8130ce.h | 45 +++ .../rx8130ce-module/include/rx8130ce_module.h | 14 + Drivers/rx8130ce-module/source/module.cpp | 34 ++ Drivers/rx8130ce-module/source/rx8130ce.cpp | 151 +++++++++ Drivers/rx8130ce-module/source/symbols.c | 9 + Firmware/idf_component.yml | 10 + Modules/lvgl-module/source/symbols.c | 12 + .../Source/symbols/gcc_soft_float_p4.cpp | 6 + TactilityC/Source/tt_init.cpp | 1 + 110 files changed, 3626 insertions(+), 97 deletions(-) create mode 100644 Devices/m5stack-sticks3/CMakeLists.txt create mode 100644 Devices/m5stack-sticks3/Source/Configuration.cpp create mode 100644 Devices/m5stack-sticks3/Source/devices/Display.cpp create mode 100644 Devices/m5stack-sticks3/Source/devices/Display.h create mode 100644 Devices/m5stack-sticks3/Source/devices/Power.cpp create mode 100644 Devices/m5stack-sticks3/Source/devices/Power.h create mode 100644 Devices/m5stack-sticks3/Source/module.cpp create mode 100644 Devices/m5stack-sticks3/device.properties create mode 100644 Devices/m5stack-sticks3/devicetree.yaml create mode 100644 Devices/m5stack-sticks3/m5stack,sticks3.dts create mode 100644 Devices/m5stack-tab5/Source/devices/Detect.cpp create mode 100644 Devices/m5stack-tab5/Source/devices/Detect.h create mode 100644 Devices/m5stack-tab5/Source/devices/St7123Display.cpp create mode 100644 Devices/m5stack-tab5/Source/devices/St7123Display.h create mode 100644 Devices/m5stack-tab5/Source/devices/St7123Touch.cpp create mode 100644 Devices/m5stack-tab5/Source/devices/St7123Touch.h rename Devices/m5stack-tab5/Source/devices/{disp_init_data.h => ili9881_init_data.h} (99%) create mode 100644 Devices/m5stack-tab5/Source/devices/st7123_init_data.h create mode 100644 Drivers/bm8563-module/CMakeLists.txt create mode 100644 Drivers/bm8563-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/bm8563-module/README.md create mode 100644 Drivers/bm8563-module/bindings/belling,bm8563.yaml create mode 100644 Drivers/bm8563-module/devicetree.yaml create mode 100644 Drivers/bm8563-module/include/bindings/bm8563.h create mode 100644 Drivers/bm8563-module/include/bm8563_module.h create mode 100644 Drivers/bm8563-module/include/drivers/bm8563.h create mode 100644 Drivers/bm8563-module/source/bm8563.cpp create mode 100644 Drivers/bm8563-module/source/module.cpp create mode 100644 Drivers/bm8563-module/source/symbols.c create mode 100644 Drivers/m5pm1-module/CMakeLists.txt create mode 100644 Drivers/m5pm1-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/m5pm1-module/README.md create mode 100644 Drivers/m5pm1-module/bindings/m5stack,m5pm1.yaml create mode 100644 Drivers/m5pm1-module/devicetree.yaml create mode 100644 Drivers/m5pm1-module/include/bindings/m5pm1.h create mode 100644 Drivers/m5pm1-module/include/drivers/m5pm1.h create mode 100644 Drivers/m5pm1-module/include/m5pm1_module.h create mode 100644 Drivers/m5pm1-module/source/m5pm1.cpp create mode 100644 Drivers/m5pm1-module/source/module.cpp create mode 100644 Drivers/m5pm1-module/source/symbols.c create mode 100644 Drivers/mpu6886-module/CMakeLists.txt create mode 100644 Drivers/mpu6886-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/mpu6886-module/README.md create mode 100644 Drivers/mpu6886-module/bindings/invensense,mpu6886.yaml create mode 100644 Drivers/mpu6886-module/devicetree.yaml create mode 100644 Drivers/mpu6886-module/include/bindings/mpu6886.h create mode 100644 Drivers/mpu6886-module/include/drivers/mpu6886.h create mode 100644 Drivers/mpu6886-module/include/mpu6886_module.h create mode 100644 Drivers/mpu6886-module/source/module.cpp create mode 100644 Drivers/mpu6886-module/source/mpu6886.cpp create mode 100644 Drivers/mpu6886-module/source/symbols.c create mode 100644 Drivers/qmi8658-module/CMakeLists.txt create mode 100644 Drivers/qmi8658-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/qmi8658-module/README.md create mode 100644 Drivers/qmi8658-module/bindings/qst,qmi8658.yaml create mode 100644 Drivers/qmi8658-module/devicetree.yaml create mode 100644 Drivers/qmi8658-module/include/bindings/qmi8658.h create mode 100644 Drivers/qmi8658-module/include/drivers/qmi8658.h create mode 100644 Drivers/qmi8658-module/include/qmi8658_module.h create mode 100644 Drivers/qmi8658-module/source/module.cpp create mode 100644 Drivers/qmi8658-module/source/qmi8658.cpp create mode 100644 Drivers/qmi8658-module/source/symbols.c create mode 100644 Drivers/rx8130ce-module/CMakeLists.txt create mode 100644 Drivers/rx8130ce-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/rx8130ce-module/README.md create mode 100644 Drivers/rx8130ce-module/bindings/epson,rx8130ce.yaml create mode 100644 Drivers/rx8130ce-module/devicetree.yaml create mode 100644 Drivers/rx8130ce-module/include/bindings/rx8130ce.h create mode 100644 Drivers/rx8130ce-module/include/drivers/rx8130ce.h create mode 100644 Drivers/rx8130ce-module/include/rx8130ce_module.h create mode 100644 Drivers/rx8130ce-module/source/module.cpp create mode 100644 Drivers/rx8130ce-module/source/rx8130ce.cpp create mode 100644 Drivers/rx8130ce-module/source/symbols.c 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..ac0756d32 --- /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 +) 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..2420dcb35 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Display.h @@ -0,0 +1,18 @@ +#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; +constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8; + +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..70e7cd1c7 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/Power.cpp @@ -0,0 +1,109 @@ +#include "Power.h" + +#include +#include +#include +#include + +using namespace tt::hal::power; + +static constexpr auto* TAG = "StickS3Power"; + +static constexpr i2c_port_t M5PM1_I2C_PORT = I2C_NUM_0; +static constexpr uint8_t M5PM1_ADDR = 0x6E; + +// Battery voltage: little-endian 16-bit in mV +static constexpr uint8_t REG_BAT_L = 0x22; +// GPIO input register: bit 0 = PM1_G0 (charging status, LOW = charging) +static constexpr uint8_t REG_GPIO_IN = 0x12; +// Power-off: write 0xA1 (high nibble 0xA, low nibble 0x1) to trigger shutdown +static constexpr uint8_t REG_POWEROFF = 0x0C; + +static constexpr float MIN_BATTERY_VOLTAGE_MV = 3300.0f; +static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f; + +class StickS3Power final : public PowerDevice { +public: + 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 (!readBatteryVoltage(mv)) return false; + data.valueAsUint32 = mv; + return true; + } + + case ChargeLevel: { + uint16_t mv = 0; + if (!readBatteryVoltage(mv)) 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: { + uint8_t gpio_in = 0; + if (!tt::hal::i2c::masterReadRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_GPIO_IN, &gpio_in, 1)) { + LOG_W(TAG, "Failed to read GPIO input register"); + return false; + } + // PM1_G0: LOW = charging, HIGH = not charging + data.valueAsBool = (gpio_in & 0x01U) == 0; + return true; + } + + default: + return false; + } + } + + bool supportsPowerOff() const override { return true; } + + void powerOff() override { + LOG_W(TAG, "Powering off via M5PM1"); + // High nibble must be 0xA; low nibble 1 = shutdown + uint8_t value = 0xA1; + if (!tt::hal::i2c::masterWriteRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_POWEROFF, &value, 1)) { + LOG_E(TAG, "Failed to send power-off command"); + } + } + +private: + bool readBatteryVoltage(uint16_t& mv) { + uint8_t buf[2] = {}; + if (!tt::hal::i2c::masterReadRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_BAT_L, buf, sizeof(buf))) { + LOG_W(TAG, "Failed to read battery voltage"); + return false; + } + mv = static_cast(buf[0] | (buf[1] << 8)); + return true; + } +}; + +std::shared_ptr createPower() { + return std::make_shared(); +} 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/devices/Detect.cpp b/Devices/m5stack-tab5/Source/devices/Detect.cpp new file mode 100644 index 000000000..0e6b1022d --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -0,0 +1,35 @@ +#include "Detect.h" + +#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)); + + // 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 (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS) || + tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) { + LOGGER.info("Detected GT911 touch — using ILI9881C display"); + return Tab5Variant::Ili9881c_Gt911; + } + + // Probe for ST7123 touch (new variant) + if (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS)) { + LOGGER.info("Detected ST7123 touch — using ST7123 display"); + return Tab5Variant::St7123; + } + + 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..a8144284e 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; } @@ -115,8 +117,8 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons }; 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..3eb763d5f --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp @@ -0,0 +1,144 @@ +#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"); + 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..039b7917f --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/st7123_init_data.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include + +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..5ac6eee00 --- /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_ARG if year 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..01f0aeca3 --- /dev/null +++ b/Drivers/bm8563-module/source/bm8563.cpp @@ -0,0 +1,115 @@ +// 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 + uint8_t ctrl = 0x00; + if (i2c_controller_write_register(i2c_controller, address, REG_CTRL1, &ctrl, 1, 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_W(TAG, "Clock integrity compromised (VL flag set)"); + } + 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) { + 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..2a7ac09ad 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 MPU6886 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..56532e393 --- /dev/null +++ b/Drivers/m5pm1-module/source/m5pm1.cpp @@ -0,0 +1,300 @@ +// 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)) + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static error_t read16le(Device* i2c, uint8_t addr, uint8_t reg, uint16_t* out) { + uint8_t buf[2] = {}; + error_t err = i2c_controller_read_register(i2c, addr, reg, buf, 2, TIMEOUT); + if (err != ERROR_NONE) return err; + *out = static_cast(buf[0] | (buf[1] << 8)); + return ERROR_NONE; +} + +// --------------------------------------------------------------------------- +// 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 read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_VBAT_L, mv); +} + +error_t m5pm1_get_vin_voltage(Device* device, uint16_t* mv) { + return read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_VIN_L, mv); +} + +error_t m5pm1_get_5vout_voltage(Device* device, uint16_t* mv) { + return read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_5VOUT_L, mv); +} + +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 read16le(i2c, addr, REG_ADC_RES_L, decidegc); +} + +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..fe18b1ce4 --- /dev/null +++ b/Drivers/m5pm1-module/source/module.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver m5pm1_driver; + +static error_t start() { + check(driver_construct_add(&m5pm1_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + 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..0ad39a8af --- /dev/null +++ b/Drivers/mpu6886-module/source/mpu6886.cpp @@ -0,0 +1,160 @@ +// 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, push-pull, latched, cleared on any read + if (i2c_controller_register8_set(i2c_controller, address, REG_INT_PIN_CFG, 0x22, 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; + + vTaskDelay(pdMS_TO_TICKS(100)); + + 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..8b0632938 --- /dev/null +++ b/Drivers/qmi8658-module/source/module.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver qmi8658_driver; + +static error_t start() { + check(driver_construct_add(&qmi8658_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + 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..2f3880e0e --- /dev/null +++ b/Drivers/qmi8658-module/source/qmi8658.cpp @@ -0,0 +1,133 @@ +// 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; + + vTaskDelay(pdMS_TO_TICKS(10)); + + 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..457d35aaa --- /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_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 + * @return ERROR_NONE on success + */ +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..e3f44d380 --- /dev/null +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -0,0 +1,151 @@ +// 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 + uint8_t flag = 0x00; + if (i2c_controller_write_register(i2c_controller, address, REG_FLAG, &flag, 1, 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; + + // 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) { + 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 = 0; + if (dt->year >= 2000 && dt->year <= 2099) { + year_offset = static_cast(dt->year - 2000); + } else if (dt->year > 2099) { + LOG_W(TAG, "Year %u exceeds 2099, clamping to 2099", dt->year); + year_offset = 99; // Clamp to max representable year (2099) + } else { + LOG_W(TAG, "Year %u below 2000, clamping to 2000", dt->year); + } + 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); + i2c_controller_write_register(i2c_controller, address, REG_CTRL0, &ctrl0, 1, I2C_TIMEOUT_TICKS); + + 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..e0920e5d8 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -159,6 +159,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 +251,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), diff --git a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp index 144203cd3..b65521c9c 100644 --- a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp +++ b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp @@ -131,6 +131,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[] = { @@ -253,6 +256,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..1115a6d18 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -99,6 +99,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 From d3e0bca696a2ac254aa33941fbe3dda5eb2bac40 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 18 Mar 2026 11:54:35 +1000 Subject: [PATCH 02/11] Fixes Device detection retry Added check() after each acquire Added VLF read; returns ERROR_INVALID_STATE if set Added month/day/hour/minute/second range checks --- Devices/m5stack-tab5/Source/Configuration.cpp | 6 ++++ .../m5stack-tab5/Source/devices/Detect.cpp | 28 +++++++++++-------- .../Source/devices/Ili9881cDisplay.cpp | 2 +- .../Source/devices/St7123Display.cpp | 2 +- .../bm8563-module/include/drivers/bm8563.h | 2 +- Drivers/bm8563-module/source/bm8563.cpp | 8 ++++-- Drivers/bmi270-module/source/bmi270.cpp | 2 +- Drivers/m5pm1-module/source/module.cpp | 4 +++ Drivers/mpu6886-module/source/mpu6886.cpp | 4 +-- Drivers/qmi8658-module/source/module.cpp | 4 +++ .../include/drivers/rx8130ce.h | 6 ++-- Drivers/rx8130ce-module/source/rx8130ce.cpp | 14 ++++++++++ 12 files changed, 59 insertions(+), 23 deletions(-) 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 index 0e6b1022d..990745291 100644 --- a/Devices/m5stack-tab5/Source/devices/Detect.cpp +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -15,19 +15,23 @@ Tab5Variant detectVariant() { // register reads (read_fw_info) succeed reliably. vTaskDelay(pdMS_TO_TICKS(300)); - // 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 (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS) || - tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) { - LOGGER.info("Detected GT911 touch — using ILI9881C display"); - return Tab5Variant::Ili9881c_Gt911; - } + 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 (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS) || + tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) { + LOGGER.info("Detected GT911 touch — using ILI9881C display"); + return Tab5Variant::Ili9881c_Gt911; + } + + // Probe for ST7123 touch (new variant) + if (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS)) { + LOGGER.info("Detected ST7123 touch — using ST7123 display"); + return Tab5Variant::St7123; + } - // Probe for ST7123 touch (new variant) - if (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS)) { - 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"); diff --git a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp index a8144284e..85540912a 100644 --- a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp +++ b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp @@ -110,7 +110,7 @@ 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, } diff --git a/Devices/m5stack-tab5/Source/devices/St7123Display.cpp b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp index 3eb763d5f..a5dde97d7 100644 --- a/Devices/m5stack-tab5/Source/devices/St7123Display.cpp +++ b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp @@ -106,7 +106,7 @@ bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const .vsync_back_porch = 8, .vsync_front_porch = 220, }, - .flags { + .flags = { .use_dma2d = 1, .disable_lp = 0, } diff --git a/Drivers/bm8563-module/include/drivers/bm8563.h b/Drivers/bm8563-module/include/drivers/bm8563.h index 5ac6eee00..dc5b57748 100644 --- a/Drivers/bm8563-module/include/drivers/bm8563.h +++ b/Drivers/bm8563-module/include/drivers/bm8563.h @@ -36,7 +36,7 @@ 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_ARG if year out of range + * @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); diff --git a/Drivers/bm8563-module/source/bm8563.cpp b/Drivers/bm8563-module/source/bm8563.cpp index 01f0aeca3..7387ea8fa 100644 --- a/Drivers/bm8563-module/source/bm8563.cpp +++ b/Drivers/bm8563-module/source/bm8563.cpp @@ -64,7 +64,8 @@ error_t bm8563_get_datetime(Device* device, Bm8563DateTime* dt) { if (error != ERROR_NONE) return error; if (buf[0] & 0x80u) { - LOG_W(TAG, "Clock integrity compromised (VL flag set)"); + 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); @@ -79,7 +80,10 @@ error_t bm8563_get_datetime(Device* device, Bm8563DateTime* dt) { } error_t bm8563_set_datetime(Device* device, const Bm8563DateTime* dt) { - if (dt->year < 2000 || dt->year > 2199) { + 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; } diff --git a/Drivers/bmi270-module/source/bmi270.cpp b/Drivers/bmi270-module/source/bmi270.cpp index 2a7ac09ad..d4ce55807 100644 --- a/Drivers/bmi270-module/source/bmi270.cpp +++ b/Drivers/bmi270-module/source/bmi270.cpp @@ -141,7 +141,7 @@ static error_t stop(Device* device) { // 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 MPU6886 to sleep"); + LOG_E(TAG, "Failed to put BMI270 to sleep"); return ERROR_RESOURCE; } diff --git a/Drivers/m5pm1-module/source/module.cpp b/Drivers/m5pm1-module/source/module.cpp index fe18b1ce4..1c5fe6fb7 100644 --- a/Drivers/m5pm1-module/source/module.cpp +++ b/Drivers/m5pm1-module/source/module.cpp @@ -8,11 +8,15 @@ 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; } diff --git a/Drivers/mpu6886-module/source/mpu6886.cpp b/Drivers/mpu6886-module/source/mpu6886.cpp index 0ad39a8af..4c37108f0 100644 --- a/Drivers/mpu6886-module/source/mpu6886.cpp +++ b/Drivers/mpu6886-module/source/mpu6886.cpp @@ -89,8 +89,8 @@ static error_t start(Device* device) { // 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, push-pull, latched, cleared on any read - if (i2c_controller_register8_set(i2c_controller, address, REG_INT_PIN_CFG, 0x22, 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; diff --git a/Drivers/qmi8658-module/source/module.cpp b/Drivers/qmi8658-module/source/module.cpp index 8b0632938..5e2308c24 100644 --- a/Drivers/qmi8658-module/source/module.cpp +++ b/Drivers/qmi8658-module/source/module.cpp @@ -8,11 +8,15 @@ 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; } diff --git a/Drivers/rx8130ce-module/include/drivers/rx8130ce.h b/Drivers/rx8130ce-module/include/drivers/rx8130ce.h index 457d35aaa..e348b556d 100644 --- a/Drivers/rx8130ce-module/include/drivers/rx8130ce.h +++ b/Drivers/rx8130ce-module/include/drivers/rx8130ce.h @@ -28,15 +28,15 @@ struct Rx8130ceDateTime { * 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 + * @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 - * @return ERROR_NONE on success + * @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); diff --git a/Drivers/rx8130ce-module/source/rx8130ce.cpp b/Drivers/rx8130ce-module/source/rx8130ce.cpp index e3f44d380..90dda4142 100644 --- a/Drivers/rx8130ce-module/source/rx8130ce.cpp +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -83,6 +83,14 @@ 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] = {}; @@ -101,6 +109,12 @@ error_t rx8130ce_get_datetime(Device* device, Rx8130ceDateTime* dt) { } 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; + } + auto* i2c_controller = device_get_parent(device); auto address = GET_CONFIG(device)->address; From bc8f699877dc2c7e6fb5b101f56b6b3127ac7056 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 18 Mar 2026 12:29:07 +1000 Subject: [PATCH 03/11] and some more --- .../Source/devices/Ili9881cDisplay.cpp | 4 ++++ .../Source/devices/St7123Display.cpp | 4 ++++ Drivers/rx8130ce-module/source/rx8130ce.cpp | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp index 85540912a..0ba634267 100644 --- a/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp +++ b/Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp @@ -69,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; } diff --git a/Devices/m5stack-tab5/Source/devices/St7123Display.cpp b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp index a5dde97d7..f1fd991eb 100644 --- a/Devices/m5stack-tab5/Source/devices/St7123Display.cpp +++ b/Devices/m5stack-tab5/Source/devices/St7123Display.cpp @@ -70,6 +70,10 @@ bool St7123Display::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; } diff --git a/Drivers/rx8130ce-module/source/rx8130ce.cpp b/Drivers/rx8130ce-module/source/rx8130ce.cpp index 90dda4142..ab3f9b7dd 100644 --- a/Drivers/rx8130ce-module/source/rx8130ce.cpp +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -133,20 +133,22 @@ error_t rx8130ce_set_datetime(Device* device, const Rx8130ceDateTime* dt) { buf[4] = dec_to_bcd(dt->day); buf[5] = dec_to_bcd(dt->month); uint8_t year_offset = 0; - if (dt->year >= 2000 && dt->year <= 2099) { - year_offset = static_cast(dt->year - 2000); - } else if (dt->year > 2099) { - LOG_W(TAG, "Year %u exceeds 2099, clamping to 2099", dt->year); - year_offset = 99; // Clamp to max representable year (2099) - } else { - LOG_W(TAG, "Year %u below 2000, clamping to 2000", dt->year); + if (dt->year < 2000 || dt->year > 2099) { + return ERROR_INVALID_ARGUMENT; } + 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); - i2c_controller_write_register(i2c_controller, address, REG_CTRL0, &ctrl0, 1, I2C_TIMEOUT_TICKS); + 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; } From 3011d56abeaec24e62d447990bc56712b2cbd006 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 18 Mar 2026 12:46:39 +1000 Subject: [PATCH 04/11] Update rx8130ce.cpp --- Drivers/rx8130ce-module/source/rx8130ce.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Drivers/rx8130ce-module/source/rx8130ce.cpp b/Drivers/rx8130ce-module/source/rx8130ce.cpp index ab3f9b7dd..ee42daf87 100644 --- a/Drivers/rx8130ce-module/source/rx8130ce.cpp +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -114,6 +114,9 @@ error_t rx8130ce_set_datetime(Device* device, const Rx8130ceDateTime* dt) { 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; @@ -132,10 +135,6 @@ error_t rx8130ce_set_datetime(Device* device, const Rx8130ceDateTime* dt) { buf[3] = 0; // weekday — unused buf[4] = dec_to_bcd(dt->day); buf[5] = dec_to_bcd(dt->month); - uint8_t year_offset = 0; - if (dt->year < 2000 || dt->year > 2099) { - return ERROR_INVALID_ARGUMENT; - } 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); From 879efa6f4c57896f021efcf6f76975ad381e1021 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 18 Mar 2026 14:58:34 +1000 Subject: [PATCH 05/11] quick ninja symbol --- TactilityC/Source/tt_init.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 1115a6d18..676a41654 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -438,6 +438,7 @@ 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), // delimiter ESP_ELFSYM_END }; From a1fa09984d1f6e616cc89c1c1e9814649df9b683 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Thu, 19 Mar 2026 00:32:56 +1000 Subject: [PATCH 06/11] more symbols for p4 app i'm making --- Modules/lvgl-module/source/symbols.c | 3 +++ TactilityC/Source/symbols/gcc_soft_float_p4.cpp | 6 ++++-- TactilityC/Source/tt_init.cpp | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index e0920e5d8..48433c478 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -38,6 +38,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { 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), @@ -391,6 +393,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), diff --git a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp index b65521c9c..6127e2147 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); @@ -184,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), diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 676a41654..e26579b2b 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -439,6 +440,12 @@ const esp_elfsym main_symbols[] { 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_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 }; From a9606802906e7eaded09423bbcb885be599a2552 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Thu, 19 Mar 2026 10:22:18 +1000 Subject: [PATCH 07/11] addressing things... --- Devices/m5stack-sticks3/CMakeLists.txt | 2 +- .../m5stack-sticks3/Source/devices/Display.h | 1 - .../m5stack-sticks3/Source/devices/Power.cpp | 49 +++++++------------ .../m5stack-tab5/Source/devices/Detect.cpp | 17 +++++-- .../Source/devices/st7123_init_data.h | 2 + Drivers/bm8563-module/source/bm8563.cpp | 3 +- Drivers/m5pm1-module/source/m5pm1.cpp | 20 ++------ Drivers/mpu6886-module/source/mpu6886.cpp | 2 - Drivers/qmi8658-module/source/qmi8658.cpp | 2 - Drivers/rx8130ce-module/source/rx8130ce.cpp | 3 +- .../Source/symbols/gcc_soft_float_p4.cpp | 4 +- .../tactility/drivers/i2c_controller.h | 11 +++++ .../source/drivers/i2c_controller.cpp | 8 +++ 13 files changed, 60 insertions(+), 64 deletions(-) diff --git a/Devices/m5stack-sticks3/CMakeLists.txt b/Devices/m5stack-sticks3/CMakeLists.txt index ac0756d32..a6e098d03 100644 --- a/Devices/m5stack-sticks3/CMakeLists.txt +++ b/Devices/m5stack-sticks3/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 ST7789 PwmBacklight ButtonControl + REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl m5pm1-module ) diff --git a/Devices/m5stack-sticks3/Source/devices/Display.h b/Devices/m5stack-sticks3/Source/devices/Display.h index 2420dcb35..65142cb5f 100644 --- a/Devices/m5stack-sticks3/Source/devices/Display.h +++ b/Devices/m5stack-sticks3/Source/devices/Display.h @@ -13,6 +13,5 @@ constexpr auto LCD_HORIZONTAL_RESOLUTION = 135; constexpr auto LCD_VERTICAL_RESOLUTION = 240; constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3; constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; -constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8; std::shared_ptr createDisplay(); diff --git a/Devices/m5stack-sticks3/Source/devices/Power.cpp b/Devices/m5stack-sticks3/Source/devices/Power.cpp index 70e7cd1c7..036b7595f 100644 --- a/Devices/m5stack-sticks3/Source/devices/Power.cpp +++ b/Devices/m5stack-sticks3/Source/devices/Power.cpp @@ -1,29 +1,21 @@ #include "Power.h" -#include #include +#include +#include #include -#include using namespace tt::hal::power; static constexpr auto* TAG = "StickS3Power"; -static constexpr i2c_port_t M5PM1_I2C_PORT = I2C_NUM_0; -static constexpr uint8_t M5PM1_ADDR = 0x6E; - -// Battery voltage: little-endian 16-bit in mV -static constexpr uint8_t REG_BAT_L = 0x22; -// GPIO input register: bit 0 = PM1_G0 (charging status, LOW = charging) -static constexpr uint8_t REG_GPIO_IN = 0x12; -// Power-off: write 0xA1 (high nibble 0xA, low nibble 0x1) to trigger shutdown -static constexpr uint8_t REG_POWEROFF = 0x0C; - 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"; } @@ -45,14 +37,14 @@ class StickS3Power final : public PowerDevice { case BatteryVoltage: { uint16_t mv = 0; - if (!readBatteryVoltage(mv)) return false; + if (m5pm1_get_battery_voltage(m5pm1, &mv) != ERROR_NONE) return false; data.valueAsUint32 = mv; return true; } case ChargeLevel: { uint16_t mv = 0; - if (!readBatteryVoltage(mv)) return false; + 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; @@ -66,13 +58,12 @@ class StickS3Power final : public PowerDevice { } case IsCharging: { - uint8_t gpio_in = 0; - if (!tt::hal::i2c::masterReadRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_GPIO_IN, &gpio_in, 1)) { - LOG_W(TAG, "Failed to read GPIO input register"); + bool charging = false; + if (m5pm1_is_charging(m5pm1, &charging) != ERROR_NONE) { + LOG_W(TAG, "Failed to read charging status"); return false; } - // PM1_G0: LOW = charging, HIGH = not charging - data.valueAsBool = (gpio_in & 0x01U) == 0; + data.valueAsBool = charging; return true; } @@ -85,25 +76,19 @@ class StickS3Power final : public PowerDevice { void powerOff() override { LOG_W(TAG, "Powering off via M5PM1"); - // High nibble must be 0xA; low nibble 1 = shutdown - uint8_t value = 0xA1; - if (!tt::hal::i2c::masterWriteRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_POWEROFF, &value, 1)) { + if (m5pm1_shutdown(m5pm1) != ERROR_NONE) { LOG_E(TAG, "Failed to send power-off command"); } } private: - bool readBatteryVoltage(uint16_t& mv) { - uint8_t buf[2] = {}; - if (!tt::hal::i2c::masterReadRegister(M5PM1_I2C_PORT, M5PM1_ADDR, REG_BAT_L, buf, sizeof(buf))) { - LOG_W(TAG, "Failed to read battery voltage"); - return false; - } - mv = static_cast(buf[0] | (buf[1] << 8)); - return true; - } + ::Device* m5pm1; }; std::shared_ptr createPower() { - return std::make_shared(); + 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-tab5/Source/devices/Detect.cpp b/Devices/m5stack-tab5/Source/devices/Detect.cpp index 990745291..3097720ce 100644 --- a/Devices/m5stack-tab5/Source/devices/Detect.cpp +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -1,7 +1,8 @@ #include "Detect.h" #include -#include +#include +#include #include #include #include @@ -15,18 +16,26 @@ Tab5Variant detectVariant() { // register reads (read_fw_info) succeed reliably. vTaskDelay(pdMS_TO_TICKS(300)); + auto* i2c0 = device_find_by_name("i2c0"); + if (i2c0 == nullptr) { + LOGGER.error("i2c0 not found, defaulting to ST7123"); + return Tab5Variant::St7123; + } + + 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 (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS) || - tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) { + 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 (tt::hal::i2c::masterHasDeviceAtAddress(I2C_NUM_0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS)) { + 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; } diff --git a/Devices/m5stack-tab5/Source/devices/st7123_init_data.h b/Devices/m5stack-tab5/Source/devices/st7123_init_data.h index 039b7917f..90c16a39a 100644 --- a/Devices/m5stack-tab5/Source/devices/st7123_init_data.h +++ b/Devices/m5stack-tab5/Source/devices/st7123_init_data.h @@ -6,6 +6,8 @@ #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}, diff --git a/Drivers/bm8563-module/source/bm8563.cpp b/Drivers/bm8563-module/source/bm8563.cpp index 7387ea8fa..4d3f7804e 100644 --- a/Drivers/bm8563-module/source/bm8563.cpp +++ b/Drivers/bm8563-module/source/bm8563.cpp @@ -34,8 +34,7 @@ static error_t start(Device* device) { auto address = GET_CONFIG(device)->address; // Clear STOP bit — chip may have been stopped after a power cycle - uint8_t ctrl = 0x00; - if (i2c_controller_write_register(i2c_controller, address, REG_CTRL1, &ctrl, 1, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + 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; } diff --git a/Drivers/m5pm1-module/source/m5pm1.cpp b/Drivers/m5pm1-module/source/m5pm1.cpp index 56532e393..c972eb051 100644 --- a/Drivers/m5pm1-module/source/m5pm1.cpp +++ b/Drivers/m5pm1-module/source/m5pm1.cpp @@ -54,18 +54,6 @@ static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); #define GET_CONFIG(device) (static_cast((device)->config)) -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -static error_t read16le(Device* i2c, uint8_t addr, uint8_t reg, uint16_t* out) { - uint8_t buf[2] = {}; - error_t err = i2c_controller_read_register(i2c, addr, reg, buf, 2, TIMEOUT); - if (err != ERROR_NONE) return err; - *out = static_cast(buf[0] | (buf[1] << 8)); - return ERROR_NONE; -} - // --------------------------------------------------------------------------- // Driver lifecycle // --------------------------------------------------------------------------- @@ -130,15 +118,15 @@ static error_t stop(Device* device) { extern "C" { error_t m5pm1_get_battery_voltage(Device* device, uint16_t* mv) { - return read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_VBAT_L, 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 read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_VIN_L, 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 read16le(device_get_parent(device), GET_CONFIG(device)->address, REG_5VOUT_L, 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) { @@ -208,7 +196,7 @@ error_t m5pm1_get_temperature(Device* device, uint16_t* decidegc) { return ERROR_TIMEOUT; } - return read16le(i2c, addr, REG_ADC_RES_L, decidegc); + return i2c_controller_register16le_get(i2c, addr, REG_ADC_RES_L, decidegc, TIMEOUT); } error_t m5pm1_shutdown(Device* device) { diff --git a/Drivers/mpu6886-module/source/mpu6886.cpp b/Drivers/mpu6886-module/source/mpu6886.cpp index 4c37108f0..f55d0eed8 100644 --- a/Drivers/mpu6886-module/source/mpu6886.cpp +++ b/Drivers/mpu6886-module/source/mpu6886.cpp @@ -94,8 +94,6 @@ static error_t start(Device* device) { // Enable DATA_RDY interrupt if (i2c_controller_register8_set(i2c_controller, address, REG_INT_ENABLE, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; - vTaskDelay(pdMS_TO_TICKS(100)); - return ERROR_NONE; } diff --git a/Drivers/qmi8658-module/source/qmi8658.cpp b/Drivers/qmi8658-module/source/qmi8658.cpp index 2f3880e0e..38d47e472 100644 --- a/Drivers/qmi8658-module/source/qmi8658.cpp +++ b/Drivers/qmi8658-module/source/qmi8658.cpp @@ -68,8 +68,6 @@ static error_t start(Device* device) { // Enable accel + gyro if (i2c_controller_register8_set(i2c_controller, address, REG_CTRL7, CTRL7_ENABLE, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; - vTaskDelay(pdMS_TO_TICKS(10)); - return ERROR_NONE; } diff --git a/Drivers/rx8130ce-module/source/rx8130ce.cpp b/Drivers/rx8130ce-module/source/rx8130ce.cpp index ee42daf87..33359b89b 100644 --- a/Drivers/rx8130ce-module/source/rx8130ce.cpp +++ b/Drivers/rx8130ce-module/source/rx8130ce.cpp @@ -46,8 +46,7 @@ static error_t start(Device* device) { auto address = GET_CONFIG(device)->address; // Clear VLF and other status flags - uint8_t flag = 0x00; - if (i2c_controller_write_register(i2c_controller, address, REG_FLAG, &flag, 1, I2C_TIMEOUT_TICKS) != ERROR_NONE) { + 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; } diff --git a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp index 6127e2147..1383679bc 100644 --- a/TactilityC/Source/symbols/gcc_soft_float_p4.cpp +++ b/TactilityC/Source/symbols/gcc_soft_float_p4.cpp @@ -74,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); @@ -200,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), 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" }; From be4c1f83266532a8ee98a6d7f26ebd9c6328fe12 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Thu, 19 Mar 2026 17:46:10 +1000 Subject: [PATCH 08/11] MOAR SYMBOLS! --- TactilityC/Source/tt_init.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index e26579b2b..5a67c9ad0 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -440,6 +440,7 @@ const esp_elfsym main_symbols[] { 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), From 088dc6cddcfaa61aacc20ef384b0e17c80f4fcbb Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Fri, 20 Mar 2026 02:38:58 +1000 Subject: [PATCH 09/11] probably the last symbols for now --- Modules/lvgl-module/source/symbols.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 48433c478..319021f05 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -33,6 +33,7 @@ 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), @@ -452,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 From 9e245ee8869d9d0e7fae75bbbdc1bc0d541d9e9f Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Fri, 20 Mar 2026 11:41:51 +1000 Subject: [PATCH 10/11] Update Detect.cpp --- Devices/m5stack-tab5/Source/devices/Detect.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Devices/m5stack-tab5/Source/devices/Detect.cpp b/Devices/m5stack-tab5/Source/devices/Detect.cpp index 3097720ce..94f859da5 100644 --- a/Devices/m5stack-tab5/Source/devices/Detect.cpp +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -17,10 +17,7 @@ Tab5Variant detectVariant() { vTaskDelay(pdMS_TO_TICKS(300)); auto* i2c0 = device_find_by_name("i2c0"); - if (i2c0 == nullptr) { - LOGGER.error("i2c0 not found, defaulting to ST7123"); - return Tab5Variant::St7123; - } + check("i2c0"); constexpr auto PROBE_TIMEOUT = pdMS_TO_TICKS(50); From 72281f9338efd6c36ff1e7f522c651802d94d7d2 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Fri, 20 Mar 2026 17:49:02 +1000 Subject: [PATCH 11/11] Update Detect.cpp --- Devices/m5stack-tab5/Source/devices/Detect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Devices/m5stack-tab5/Source/devices/Detect.cpp b/Devices/m5stack-tab5/Source/devices/Detect.cpp index 94f859da5..e70f2663b 100644 --- a/Devices/m5stack-tab5/Source/devices/Detect.cpp +++ b/Devices/m5stack-tab5/Source/devices/Detect.cpp @@ -17,7 +17,7 @@ Tab5Variant detectVariant() { vTaskDelay(pdMS_TO_TICKS(300)); auto* i2c0 = device_find_by_name("i2c0"); - check("i2c0"); + check(i2c0); constexpr auto PROBE_TIMEOUT = pdMS_TO_TICKS(50);