Tab5 power expander driver and devicetree parsing improvements (#507)

* **New Features**
  * PI4IOE5V6408 I2C I/O expander driver with public GPIO APIs
  * CLI tool to list devicetree dependencies

* **Device Tree Updates**
  * M5Stack Tab5 configured with two I2C IO expanders; PI4IOE5V6408 binding added

* **Build / Tooling**
  * Devicetree code generation integrated into build; generated artifacts and dynamic dependency resolution exposed

* **Refactor**
  * Kernel/run APIs updated to accept a null‑terminated devicetree modules array; many module symbols renamed

* **Documentation**
  * Added README and Apache‑2.0 license for new driver module
This commit is contained in:
Ken Van Hoeylandt
2026-02-17 22:59:30 +01:00
committed by GitHub
parent f0f764baff
commit d2048e01b6
82 changed files with 749 additions and 253 deletions
+1 -1
View File
@@ -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 GT911 PwmBacklight driver vfs fatfs pi4ioe5v6408-module
)
+38 -61
View File
@@ -6,6 +6,7 @@
#include <Tactility/hal/Configuration.h>
#include <Tactility/hal/i2c/I2c.h>
#include <drivers/pi4ioe5v6408.h>
using namespace tt::hal;
@@ -18,9 +19,11 @@ static DeviceVector createDevices() {
};
}
static error_t initPower(::Device* i2c_controller) {
static error_t initPower(::Device* io_expander0, ::Device* io_expander1) {
constexpr TickType_t i2c_timeout = pdMS_TO_TICKS(10);
/*
PI4IOE5V6408-1 (0x43)
PI4IOE5V6408-0 (0x43)
- Bit 0: RF internal/external switch
- Bit 1: Speaker enable
- Bit 2: External 5V bus enable
@@ -29,8 +32,18 @@ static error_t initPower(::Device* i2c_controller) {
- Bit 5: Touch reset
- Bit 6: Camera reset
- Bit 7: Headphone detect
*/
PI4IOE5V6408-2 (0x44)
check(pi4ioe5v6408_set_direction(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_output_level(io_expander0, 0b01000110, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_output_high_impedance(io_expander0, 0b00000000, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_select(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_enable(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE);
vTaskDelay(pdMS_TO_TICKS(10));
check(pi4ioe5v6408_set_output_level(io_expander0, 0b01110110, i2c_timeout) == ERROR_NONE);
/*
PI4IOE5V6408-1 (0x44)
- Bit 0: C6 WLAN enable
- Bit 1: /
- Bit 2: /
@@ -41,57 +54,18 @@ static error_t initPower(::Device* i2c_controller) {
- Bit 7: IP2326: CHG_EN
*/
// Init byte arrays adapted from https://github.com/m5stack/M5GFX/blob/03565ccc96cb0b73c8b157f5ec3fbde439b034ad/src/M5GFX.cpp
static constexpr uint8_t reg_data_io1_1[] = {
0x03, 0b01111111, // PI4IO_REG_IO_DIR
0x05, 0b01000110, // PI4IO_REG_OUT_SET (bit4=LCD Reset, bit5=GT911 TouchReset -> LOW)
0x07, 0b00000000, // PI4IO_REG_OUT_H_IM
0x0D, 0b01111111, // PI4IO_REG_PULL_SEL
0x0B, 0b01111111, // PI4IO_REG_PULL_EN
};
static constexpr uint8_t reg_data_io1_2[] = {
0x05, 0b01110110, // PI4IO_REG_OUT_SET (bit4=LCD Reset, bit5=GT911 TouchReset -> HIGH)
};
static constexpr uint8_t reg_data_io2[] = {
0x03, 0b10111001, // PI4IO_REG_IO_DIR
0x07, 0b00000110, // PI4IO_REG_OUT_H_IM
0x0D, 0b10111001, // PI4IO_REG_PULL_SEL
0x0B, 0b11111001, // PI4IO_REG_PULL_EN
0x09, 0b01000000, // PI4IO_REG_IN_DEF_STA
0x11, 0b10111111, // PI4IO_REG_INT_MASK
0x05, 0b10001001, // PI4IO_REG_OUT_SET (enable WiFi, USB-A 5V and CHG_EN)
};
constexpr auto IO_EXPANDER1_ADDRESS = 0x43;
auto error = i2c_controller_write_register_array(i2c_controller, IO_EXPANDER1_ADDRESS, reg_data_io1_1, sizeof(reg_data_io1_1), pdMS_TO_TICKS(100));
if (error != ERROR_NONE) {
LOG_E(TAG, "IO expander 1 init failed in phase 1");
return ERROR_UNDEFINED;
}
constexpr auto IO_EXPANDER2_ADDRESS = 0x44;
error = i2c_controller_write_register_array(i2c_controller, IO_EXPANDER2_ADDRESS, reg_data_io2, sizeof(reg_data_io2), pdMS_TO_TICKS(100));
if (error != ERROR_NONE) {
LOG_E(TAG, "IO expander 2 init failed");
return ERROR_UNDEFINED;
}
// The M5Stack code applies this, but it's not known why
// TODO: Remove and test it extensively
tt::kernel::delayTicks(10);
error = i2c_controller_write_register_array(i2c_controller, IO_EXPANDER1_ADDRESS, reg_data_io1_2, sizeof(reg_data_io1_2), pdMS_TO_TICKS(100));
if (error != ERROR_NONE) {
LOG_E(TAG, "IO expander 1 init failed in phase 2");
return ERROR_UNDEFINED;
}
check(pi4ioe5v6408_set_direction(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_output_high_impedance(io_expander1, 0b00000110, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_select(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_enable(io_expander1, 0b11111001, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_input_default_level(io_expander1, 0b01000000, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_interrupt_mask(io_expander1, 0b10111111, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_output_level(io_expander1, 0b10001001, i2c_timeout) == ERROR_NONE);
return ERROR_NONE;
}
static error_t initSound(::Device* i2c_controller) {
static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = nullptr) {
// Init data from M5Unified:
// https://github.com/m5stack/M5Unified/blob/master/src/M5Unified.cpp
static constexpr uint8_t ES8388_I2C_ADDR = 0x10;
@@ -139,13 +113,15 @@ static error_t initSound(::Device* i2c_controller) {
return error;
}
constexpr auto IO_EXPANDER1_ADDRESS = 0x43;
constexpr auto AMP_REGISTER = 0x05;
// Note: to disable the amplifier, reset the bits
error = i2c_controller_register8_set_bits(i2c_controller, IO_EXPANDER1_ADDRESS, AMP_REGISTER, 0b00000010, pdMS_TO_TICKS(100));
if (error != ERROR_NONE) {
uint8_t output_level = 0;
if (pi4ioe5v6408_get_output_level(io_expander0, &output_level, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "Failed to read power level: %s", error_to_string(error));
return ERROR_RESOURCE;
}
if (pi4ioe5v6408_set_output_level(io_expander0, output_level | 0b00000010, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error));
return error;
return ERROR_RESOURCE;
}
return ERROR_NONE;
@@ -155,12 +131,13 @@ static bool initBoot() {
auto* i2c0 = device_find_by_name("i2c0");
check(i2c0, "i2c0 not found");
auto error = initPower(i2c0);
if (error != ERROR_NONE) {
return false;
}
auto* io_expander0 = device_find_by_name("io_expander0");
auto* io_expander1 = device_find_by_name("io_expander1");
check(i2c0, "i2c0 not found");
error = initSound(i2c0);
initPower(io_expander0, io_expander1);
error_t error = initSound(i2c0, io_expander0);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to enable ES8388");
}
+1 -2
View File
@@ -12,8 +12,7 @@ static error_t stop() {
return ERROR_NONE;
}
/** @warning The variable name must be exactly "device_module" */
struct Module device_module = {
Module m5stack_tab5_module = {
.name = "m5stack-tab5",
.start = start,
.stop = stop,
+1
View File
@@ -1,3 +1,4 @@
dependencies:
- Platforms/platform-esp32
- Drivers/pi4ioe5v6408-module
dts: m5stack,tab5.dts
+31
View File
@@ -5,6 +5,7 @@
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
#include <bindings/pi4ioe5v6408.h>
/ {
compatible = "root";
@@ -21,6 +22,36 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 31 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
/*
- Bit 0: RF internal/external switch
- Bit 1: Speaker enable
- Bit 2: External 5V bus enable
- Bit 3: /
- Bit 4: LCD reset
- Bit 5: Touch reset
- Bit 6: Camera reset
- Bit 7: Headphone detect
*/
io_expander0 {
compatible = "diodes,pi4ioe5v6408";
reg = <0x43>;
};
/*
- Bit 0: C6 WLAN enable
- Bit 1: /
- Bit 2: /
- Bit 3: USB-A 5V enable
- Bit 4: Device power: PWROFF_PLUSE
- Bit 5: IP2326: nCHG_QC_EN
- Bit 6: IP2326: CHG_STAT_LED
- Bit 7: IP2326: CHG_EN
*/
io_expander1 {
compatible = "diodes,pi4ioe5v6408";
reg = <0x44>;
};
};
i2c_port_a: i2c1 {