From ec0c5ae4966828b128f2223967b1fa37b5a06d99 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Wed, 18 Feb 2026 21:38:34 +0100 Subject: [PATCH 01/25] Implement GPIO interrupts --- .../source/drivers/esp32_gpio.cpp | 61 +++++++++++++++++-- .../tactility/drivers/gpio_controller.h | 60 ++++++++++++++++++ .../source/drivers/gpio_controller.cpp | 28 +++++++++ 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp index d0b39a296..802b945fd 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp @@ -12,7 +12,12 @@ #define TAG "esp32_gpio" +struct Esp32GpioInternal { + uint8_t isr_service_ref_count = 0; +}; + #define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config) +#define GET_INTERNAL(device) ((struct Esp32GpioInternal*)device->internal) extern "C" { @@ -97,15 +102,59 @@ static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_numbe return ERROR_NONE; } +static error_t add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + auto esp_error = gpio_isr_handler_add(static_cast(descriptor->pin), callback, arg); + return esp_err_to_error(esp_error); +} + +static error_t remove_callback(struct GpioDescriptor* descriptor) { + auto esp_error = gpio_isr_handler_remove(static_cast(descriptor->pin)); + return esp_err_to_error(esp_error); +} + +static error_t enable_interrupt(struct GpioDescriptor* descriptor) { + auto* internal = GET_INTERNAL(descriptor->controller); + if (internal->isr_service_ref_count == 0) { + auto esp_error = gpio_install_isr_service(0); + if (esp_error != ESP_OK && esp_error != ESP_ERR_INVALID_STATE) { + return esp_err_to_error(esp_error); + } + } + auto esp_error = gpio_intr_enable(static_cast(descriptor->pin)); + if (esp_error == ESP_OK) { + internal->isr_service_ref_count++; + } + return esp_err_to_error(esp_error); +} + +static error_t disable_interrupt(struct GpioDescriptor* descriptor) { + auto* internal = GET_INTERNAL(descriptor->controller); + auto esp_error = gpio_intr_disable(static_cast(descriptor->pin)); + if (esp_error == ESP_OK && internal->isr_service_ref_count > 0) { + internal->isr_service_ref_count--; + if (internal->isr_service_ref_count == 0) { + gpio_uninstall_isr_service(); + } + } + return esp_err_to_error(esp_error); +} + static error_t start(Device* device) { ESP_LOGI(TAG, "start %s", device->name); - auto pin_count = GET_CONFIG(device)->gpioCount; - return gpio_controller_init_descriptors(device, pin_count, nullptr); + const Esp32GpioConfig* config = GET_CONFIG(device); + device_set_driver_data(device, new Esp32GpioInternal()); + return gpio_controller_init_descriptors(device, config->gpioCount, nullptr); } static error_t stop(Device* device) { ESP_LOGI(TAG, "stop %s", device->name); - return gpio_controller_deinit_descriptors(device); + auto* internal = GET_INTERNAL(device); + if (internal->isr_service_ref_count > 0) { + gpio_uninstall_isr_service(); + } + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); + delete internal; + return ERROR_NONE; } const static GpioControllerApi esp32_gpio_api = { @@ -113,7 +162,11 @@ const static GpioControllerApi esp32_gpio_api = { .get_level = get_level, .set_flags = set_flags, .get_flags = get_flags, - .get_native_pin_number = get_native_pin_number + .get_native_pin_number = get_native_pin_number, + .add_callback = add_callback, + .remove_callback = remove_callback, + .enable_interrupt = enable_interrupt, + .disable_interrupt = disable_interrupt }; extern struct Module platform_esp32_module; diff --git a/TactilityKernel/include/tactility/drivers/gpio_controller.h b/TactilityKernel/include/tactility/drivers/gpio_controller.h index 2251e2f07..796041b54 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_controller.h +++ b/TactilityKernel/include/tactility/drivers/gpio_controller.h @@ -52,6 +52,36 @@ struct GpioControllerApi { * @return ERROR_NONE if successful */ error_t (*get_native_pin_number)(struct GpioDescriptor* descriptor, void* pin_number); + + /** + * @brief Adds a callback to be called when a GPIO interrupt occurs. + * @param[in] descriptor the pin descriptor + * @param[in] callback the callback function + * @param[in] arg the argument to pass to the callback + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*add_callback)(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg); + + /** + * @brief Removes a callback from a GPIO interrupt. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*remove_callback)(struct GpioDescriptor* descriptor); + + /** + * @brief Enables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*enable_interrupt)(struct GpioDescriptor* descriptor); + + /** + * @brief Disables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*disable_interrupt)(struct GpioDescriptor* descriptor); }; struct GpioDescriptor* gpio_descriptor_acquire( @@ -118,6 +148,36 @@ error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_ */ error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number); +/** + * @brief Adds a callback to be called when a GPIO interrupt occurs. + * @param[in] descriptor the pin descriptor + * @param[in] callback the callback function + * @param[in] arg the argument to pass to the callback + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg); + +/** + * @brief Removes a callback from a GPIO interrupt. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_remove_callback(struct GpioDescriptor* descriptor); + +/** + * @brief Enables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_enable_interrupt(struct GpioDescriptor* descriptor); + +/** + * @brief Disables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_disable_interrupt(struct GpioDescriptor* descriptor); + /** * @brief Gets the number of pins supported by the controller. * @param[in] device the GPIO controller device diff --git a/TactilityKernel/source/drivers/gpio_controller.cpp b/TactilityKernel/source/drivers/gpio_controller.cpp index 336183a15..76b0ce1fd 100644 --- a/TactilityKernel/source/drivers/gpio_controller.cpp +++ b/TactilityKernel/source/drivers/gpio_controller.cpp @@ -130,6 +130,34 @@ error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, return GPIO_INTERNAL_API(driver)->get_native_pin_number(descriptor, pin_number); } +error_t gpio_descriptor_add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->add_callback) return ERROR_NOT_SUPPORTED; + return api->add_callback(descriptor, callback, arg); +} + +error_t gpio_descriptor_remove_callback(struct GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->remove_callback) return ERROR_NOT_SUPPORTED; + return api->remove_callback(descriptor); +} + +error_t gpio_descriptor_enable_interrupt(struct GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->enable_interrupt) return ERROR_NOT_SUPPORTED; + return api->enable_interrupt(descriptor); +} + +error_t gpio_descriptor_disable_interrupt(struct GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->disable_interrupt) return ERROR_NOT_SUPPORTED; + return api->disable_interrupt(descriptor); +} + error_t gpio_descriptor_get_owner_type(struct GpioDescriptor* descriptor, GpioOwnerType* owner_type) { *owner_type = descriptor->owner_type; return ERROR_NONE; From dfb0db86c5cd6c231c53e1f216f769fe42caf9da Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 28 Feb 2026 17:00:49 +0100 Subject: [PATCH 02/25] Pi4 expander improvements --- Devices/m5stack-tab5/Source/Configuration.cpp | 159 +++++++++++----- .../include/drivers/pi4ioe5v6408.h | 24 --- Drivers/pi4ioe5v6408-module/source/module.cpp | 3 +- .../source/pi4ioe5v6408.cpp | 171 +++++++++++++++--- Drivers/pi4ioe5v6408-module/source/symbols.c | 15 -- .../source/drivers/esp32_gpio.cpp | 26 +-- .../include/tactility/drivers/gpio.h | 2 +- .../tactility/drivers/gpio_controller.h | 9 + .../tactility/drivers/gpio_descriptor.h | 7 +- .../source/drivers/gpio_controller.cpp | 70 +++---- 10 files changed, 320 insertions(+), 166 deletions(-) delete mode 100644 Drivers/pi4ioe5v6408-module/source/symbols.c diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index d17d47e4b..4566b6db1 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace tt::hal; @@ -19,50 +20,110 @@ static DeviceVector createDevices() { }; } -static error_t initPower(::Device* io_expander0, ::Device* io_expander1) { +/* + PI4IOE5V6408-0 (0x43) + - 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 + */ +constexpr auto GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL = 0; +constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1; +constexpr auto GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE = 2; +constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4; +constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5; +constexpr auto GPIO_EXP0_PIN_CAMERA_RESET = 6; +constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7; + +/* + PI4IOE5V6408-1 (0x44) + - 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 +*/ +constexpr auto GPIO_EXP1_PIN_C6_WLAN_ENABLE = 0; +constexpr auto GPIO_EXP1_PIN_USB_A_5V_ENABLE = 3; +constexpr auto GPIO_EXP1_PIN_DEVICE_POWER = 4; +constexpr auto GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5; +constexpr auto GPIO_EXP1_PIN_IP2326_CHG_STAT_LED = 6; +constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7; + +static void initExpander0(::Device* io_expander0) { constexpr TickType_t i2c_timeout = pdMS_TO_TICKS(10); - /* - PI4IOE5V6408-0 (0x43) - - 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 - */ - - 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); + auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO); + auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO); + auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO); + auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO); + auto* camera_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_CAMERA_RESET, GPIO_OWNER_GPIO); + auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); + + gpio_descriptor_set_flags(rf_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(external_5v_bus_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(lcd_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(touch_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(camera_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT); + + gpio_descriptor_set_level(rf_pin, false); + gpio_descriptor_set_level(speaker_enable_pin, false); + gpio_descriptor_set_level(external_5v_bus_enable_pin, true); + gpio_descriptor_set_level(lcd_reset_pin, false); + gpio_descriptor_set_level(touch_reset_pin, false); + gpio_descriptor_set_level(camera_reset_pin, true); 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: / - - 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 - */ - - 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); + // Enable touch and lcd, but not the camera + gpio_descriptor_set_level(lcd_reset_pin, true); + gpio_descriptor_set_level(touch_reset_pin, true); + + gpio_descriptor_release(rf_pin); + gpio_descriptor_release(speaker_enable_pin); + gpio_descriptor_release(external_5v_bus_enable_pin); + gpio_descriptor_release(lcd_reset_pin); + gpio_descriptor_release(touch_reset_pin); + gpio_descriptor_release(camera_reset_pin); + gpio_descriptor_release(headphone_detect_pin); +} - return ERROR_NONE; +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); + auto* usb_a_5v_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_USB_A_5V_ENABLE, GPIO_OWNER_GPIO); + auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); + auto* ip2326_ncharge_qc_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO); + auto* ip2326_charge_state_led_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_STAT_LED, GPIO_OWNER_GPIO); + auto* ip2326_charge_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO); + + 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); + gpio_descriptor_set_flags(device_power_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_ncharge_qc_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_charge_state_led_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_charge_enable_pin, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP); + + gpio_descriptor_set_level(c6_wlan_enable_pin, true); + gpio_descriptor_set_level(usb_a_5v_enable_pin, true); + gpio_descriptor_set_level(device_power_pin, false); + gpio_descriptor_set_level(ip2326_ncharge_qc_enable_pin, false); + gpio_descriptor_set_level(ip2326_charge_state_led_pin, false); + + gpio_descriptor_release(c6_wlan_enable_pin); + gpio_descriptor_release(usb_a_5v_enable_pin); + gpio_descriptor_release(device_power_pin); + gpio_descriptor_release(ip2326_ncharge_qc_enable_pin); + gpio_descriptor_release(ip2326_charge_state_led_pin); + gpio_descriptor_release(ip2326_charge_enable_pin); } static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = nullptr) { @@ -113,13 +174,11 @@ static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = null return error; } - 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) { + auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + check(speaker_enable_pin, "Failed to acquire speaker enable pin"); + error = gpio_descriptor_set_level(speaker_enable_pin, true); + gpio_descriptor_release(speaker_enable_pin); + if (error != ERROR_NONE) { LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error)); return ERROR_RESOURCE; } @@ -132,10 +191,12 @@ static bool initBoot() { check(i2c0, "i2c0 not found"); auto* io_expander0 = device_find_by_name("io_expander0"); + check(io_expander0, "io_expander0 not found"); auto* io_expander1 = device_find_by_name("io_expander1"); - check(i2c0, "i2c0 not found"); + check(io_expander1, "io_expander1 not found"); - initPower(io_expander0, io_expander1); + initExpander0(io_expander0); + initExpander1(io_expander1); error_t error = initSound(i2c0, io_expander0); if (error != ERROR_NONE) { diff --git a/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h b/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h index 93f982746..8ca85a028 100644 --- a/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h +++ b/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h @@ -2,40 +2,16 @@ #pragma once #include -#include -#include #ifdef __cplusplus extern "C" { #endif -struct Device; - struct Pi4ioe5v6408Config { /** Address on bus */ uint8_t address; }; -error_t pi4ioe5v6408_set_direction(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_output_level(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_output_level(struct Device* device, uint8_t* bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout); - #ifdef __cplusplus } #endif diff --git a/Drivers/pi4ioe5v6408-module/source/module.cpp b/Drivers/pi4ioe5v6408-module/source/module.cpp index d5c31f458..bb744edcb 100644 --- a/Drivers/pi4ioe5v6408-module/source/module.cpp +++ b/Drivers/pi4ioe5v6408-module/source/module.cpp @@ -6,7 +6,6 @@ extern "C" { extern Driver pi4ioe5v6408_driver; -extern const ModuleSymbol pi4ioe5v6408_module_symbols[]; static error_t start() { /* We crash when construct fails, because if a single driver fails to construct, @@ -26,7 +25,7 @@ Module pi4ioe5v6408_module = { .name = "pi4ioe5v6408", .start = start, .stop = stop, - .symbols = pi4ioe5v6408_module_symbols, + .symbols = nullptr, .internal = nullptr }; diff --git a/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp b/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp index fdc1e94aa..82b765c67 100644 --- a/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp +++ b/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -27,72 +29,181 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } LOG_I(TAG, "Started PI4IOE5V6408 device %s", device->name); - return ERROR_NONE; + + return gpio_controller_init_descriptors(device, 8, nullptr); } static error_t stop(Device* device) { + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); return ERROR_NONE; } extern "C" { -error_t pi4ioe5v6408_set_direction(Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_DIRECTION, bits, timeout); -} -error_t pi4ioe5v6408_set_output_level(Device* device, uint8_t bits, TickType_t timeout) { +static error_t set_level(GpioDescriptor* descriptor, bool high) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + + if (high) { + return i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY); + } else { + return i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY); + } } -error_t pi4ioe5v6408_get_output_level(Device* device, uint8_t* bits, TickType_t timeout) { +static error_t get_level(GpioDescriptor* descriptor, bool* high) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bits; + + error_t err = i2c_controller_register8_get(parent, address, PI4_REGISTER_INPUT_LEVEL, &bits, portMAX_DELAY); + if (err != ERROR_NONE) { + return err; + } + + *high = (bits & (1 << descriptor->pin)) != 0; + return ERROR_NONE; } -error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout) { +static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + + error_t err; + + // Direction + if (flags & GPIO_FLAG_DIRECTION_OUTPUT) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // High Impedance + if (flags & GPIO_FLAG_HIGH_IMPEDANCE) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // Pull-up/down + if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN)) { + // Set pull up or pull down + if (flags & GPIO_FLAG_PULL_UP) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // Enable pull-up/down + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY); + } else { + // Disable pull-up/down + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY); + } + + return err; } -error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout) { +static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_DEFAULT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + uint8_t val; + error_t err; + + gpio_flags_t f = GPIO_FLAG_NONE; + + // Direction + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_DIRECTION, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_DIRECTION_OUTPUT; + } else { + f |= GPIO_FLAG_DIRECTION_INPUT; + } + + // Pull-up/down + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_ENABLE, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_SELECT, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_PULL_UP; + } else { + f |= GPIO_FLAG_PULL_DOWN; + } + } + + // High Impedance + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_HIGH_IMPEDANCE; + } + + *flags = f; + return ERROR_NONE; } -error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_ENABLE, bits, timeout); +static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_SELECT, bits, timeout); +static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_LEVEL, bits, timeout); +static error_t remove_callback(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_MASK, bits, timeout); +static error_t enable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_LEVEL, bits, timeout); +static error_t disable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } +const static GpioControllerApi pi4_gpio_api = { + .set_level = set_level, + .get_level = get_level, + .set_flags = set_flags, + .get_flags = get_flags, + .get_native_pin_number = get_native_pin_number, + .add_callback = add_callback, + .remove_callback = remove_callback, + .enable_interrupt = enable_interrupt, + .disable_interrupt = disable_interrupt +}; + Driver pi4ioe5v6408_driver = { .name = "pi4ioe5v6408", .compatible = (const char*[]) { "diodes,pi4ioe5v6408", nullptr}, .start_device = start, .stop_device = stop, - .api = nullptr, - .device_type = nullptr, + .api = static_cast(&pi4_gpio_api), + .device_type = &GPIO_CONTROLLER_TYPE, .owner = &pi4ioe5v6408_module, .internal = nullptr }; diff --git a/Drivers/pi4ioe5v6408-module/source/symbols.c b/Drivers/pi4ioe5v6408-module/source/symbols.c deleted file mode 100644 index ea1dc7a38..000000000 --- a/Drivers/pi4ioe5v6408-module/source/symbols.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -const struct ModuleSymbol pi4ioe5v6408_module_symbols[] = { - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_direction), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_high_impedance), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_input_default_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_enable), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_select), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_input_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_interrupt_mask), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_interrupt_level), - MODULE_SYMBOL_TERMINATOR -}; diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp index 802b945fd..64ae3c631 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp @@ -17,7 +17,7 @@ struct Esp32GpioInternal { }; #define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config) -#define GET_INTERNAL(device) ((struct Esp32GpioInternal*)device->internal) +#define GET_INTERNAL_FROM_DESCRIPTOR(gpio_descriptor) ((struct Esp32GpioInternal*)gpio_descriptor->controller_context) extern "C" { @@ -102,18 +102,18 @@ static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_numbe return ERROR_NONE; } -static error_t add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { +static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { auto esp_error = gpio_isr_handler_add(static_cast(descriptor->pin), callback, arg); return esp_err_to_error(esp_error); } -static error_t remove_callback(struct GpioDescriptor* descriptor) { +static error_t remove_callback(GpioDescriptor* descriptor) { auto esp_error = gpio_isr_handler_remove(static_cast(descriptor->pin)); return esp_err_to_error(esp_error); } -static error_t enable_interrupt(struct GpioDescriptor* descriptor) { - auto* internal = GET_INTERNAL(descriptor->controller); +static error_t enable_interrupt(GpioDescriptor* descriptor) { + auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); if (internal->isr_service_ref_count == 0) { auto esp_error = gpio_install_isr_service(0); if (esp_error != ESP_OK && esp_error != ESP_ERR_INVALID_STATE) { @@ -127,8 +127,8 @@ static error_t enable_interrupt(struct GpioDescriptor* descriptor) { return esp_err_to_error(esp_error); } -static error_t disable_interrupt(struct GpioDescriptor* descriptor) { - auto* internal = GET_INTERNAL(descriptor->controller); +static error_t disable_interrupt(GpioDescriptor* descriptor) { + auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); auto esp_error = gpio_intr_disable(static_cast(descriptor->pin)); if (esp_error == ESP_OK && internal->isr_service_ref_count > 0) { internal->isr_service_ref_count--; @@ -142,18 +142,18 @@ static error_t disable_interrupt(struct GpioDescriptor* descriptor) { static error_t start(Device* device) { ESP_LOGI(TAG, "start %s", device->name); const Esp32GpioConfig* config = GET_CONFIG(device); - device_set_driver_data(device, new Esp32GpioInternal()); - return gpio_controller_init_descriptors(device, config->gpioCount, nullptr); + auto* internal = new Esp32GpioInternal(); + return gpio_controller_init_descriptors(device, config->gpioCount, internal); } static error_t stop(Device* device) { ESP_LOGI(TAG, "stop %s", device->name); - auto* internal = GET_INTERNAL(device); + auto* internal = static_cast(gpio_controller_get_controller_context(device)); if (internal->isr_service_ref_count > 0) { gpio_uninstall_isr_service(); } - check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); delete internal; + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); return ERROR_NONE; } @@ -169,14 +169,14 @@ const static GpioControllerApi esp32_gpio_api = { .disable_interrupt = disable_interrupt }; -extern struct Module platform_esp32_module; +extern Module platform_esp32_module; Driver esp32_gpio_driver = { .name = "esp32_gpio", .compatible = (const char*[]) { "espressif,esp32-gpio", nullptr }, .start_device = start, .stop_device = stop, - .api = (void*)&esp32_gpio_api, + .api = static_cast(&esp32_gpio_api), .device_type = &GPIO_CONTROLLER_TYPE, .owner = &platform_esp32_module, .internal = nullptr diff --git a/TactilityKernel/include/tactility/drivers/gpio.h b/TactilityKernel/include/tactility/drivers/gpio.h index 835ff5736..e7a35771f 100644 --- a/TactilityKernel/include/tactility/drivers/gpio.h +++ b/TactilityKernel/include/tactility/drivers/gpio.h @@ -7,7 +7,6 @@ extern "C" { #endif #include -#include #include #define GPIO_FLAGS_MASK 0x1f @@ -27,6 +26,7 @@ extern "C" { #define GPIO_FLAG_INTERRUPT_BITMASK (0b111 << 5) // 3 bits to hold the values [0, 5] #define GPIO_FLAG_INTERRUPT_FROM_OPTIONS(options) (gpio_int_type_t)((options & GPIO_FLAG_INTERRUPT_BITMASK) >> 5) #define GPIO_FLAG_INTERRUPT_TO_OPTIONS(options, interrupt) (options | (interrupt << 5)) +#define GPIO_FLAG_HIGH_IMPEDANCE (1 << 8) typedef enum { GPIO_INTERRUPT_DISABLE = 0, diff --git a/TactilityKernel/include/tactility/drivers/gpio_controller.h b/TactilityKernel/include/tactility/drivers/gpio_controller.h index 796041b54..9cdd28835 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_controller.h +++ b/TactilityKernel/include/tactility/drivers/gpio_controller.h @@ -201,6 +201,15 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou */ error_t gpio_controller_deinit_descriptors(struct Device* device); +/** + * Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors() + * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded. + * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. + * @param device the GPIO controller device + * @return ERROR_NONE if successful + */ +void* gpio_controller_get_controller_context(struct Device* device); + extern const struct DeviceType GPIO_CONTROLLER_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/drivers/gpio_descriptor.h b/TactilityKernel/include/tactility/drivers/gpio_descriptor.h index 383961aad..80d4d52b3 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_descriptor.h +++ b/TactilityKernel/include/tactility/drivers/gpio_descriptor.h @@ -11,6 +11,11 @@ struct GpioDescriptor { gpio_pin_t pin; /** @brief Current owner */ enum GpioOwnerType owner_type; - /** @brief Implementation-specific context (e.g. from esp32 controller internally) */ + /** + * @brief Implementation-specific context (e.g. from esp32 controller internally) + * Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors() + * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded. + * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. + */ void* controller_context; }; diff --git a/TactilityKernel/source/drivers/gpio_controller.cpp b/TactilityKernel/source/drivers/gpio_controller.cpp index 76b0ce1fd..c2e2bfae7 100644 --- a/TactilityKernel/source/drivers/gpio_controller.cpp +++ b/TactilityKernel/source/drivers/gpio_controller.cpp @@ -14,22 +14,25 @@ extern "C" { struct GpioControllerData { - struct Mutex mutex {}; + Mutex mutex {}; uint32_t pin_count; - struct GpioDescriptor* descriptors = nullptr; + GpioDescriptor* descriptors = nullptr; + void* controller_context; - explicit GpioControllerData(uint32_t pin_count) : pin_count(pin_count) { + explicit GpioControllerData( + uint32_t pin_count, void* controller_context + ) : pin_count(pin_count), controller_context(controller_context) { mutex_construct(&mutex); } - error_t init_descriptors(Device* device, void* controller_context) { - descriptors = (struct GpioDescriptor*)calloc(pin_count, sizeof(struct GpioDescriptor)); + error_t init_descriptors(Device* device) { + descriptors = static_cast(calloc(pin_count, sizeof(GpioDescriptor))); if (!descriptors) return ERROR_OUT_OF_MEMORY; for (uint32_t i = 0; i < pin_count; ++i) { descriptors[i].controller = device; - descriptors[i].pin = (gpio_pin_t)i; + descriptors[i].pin = static_cast(i); descriptors[i].owner_type = GPIO_OWNER_NONE; - descriptors[i].controller_context = controller_context; + descriptors[i].controller_context = this->controller_context; } return ERROR_NONE; } @@ -42,14 +45,14 @@ struct GpioControllerData { } }; -struct GpioDescriptor* gpio_descriptor_acquire( - struct Device* controller, +GpioDescriptor* gpio_descriptor_acquire( + Device* controller, gpio_pin_t pin_number, - enum GpioOwnerType owner + GpioOwnerType owner ) { check(owner != GPIO_OWNER_NONE); - auto* data = (struct GpioControllerData*)device_get_driver_data(controller); + auto* data = static_cast(device_get_driver_data(controller)); mutex_lock(&data->mutex); if (pin_number >= data->pin_count) { @@ -57,7 +60,7 @@ struct GpioDescriptor* gpio_descriptor_acquire( return nullptr; } - struct GpioDescriptor* desc = &data->descriptors[pin_number]; + GpioDescriptor* desc = &data->descriptors[pin_number]; if (desc->owner_type != GPIO_OWNER_NONE) { mutex_unlock(&data->mutex); return nullptr; @@ -69,22 +72,22 @@ struct GpioDescriptor* gpio_descriptor_acquire( return desc; } -error_t gpio_descriptor_release(struct GpioDescriptor* descriptor) { +error_t gpio_descriptor_release(GpioDescriptor* descriptor) { descriptor->owner_type = GPIO_OWNER_NONE; return ERROR_NONE; } -error_t gpio_controller_get_pin_count(struct Device* device, uint32_t* count) { - auto* data = (struct GpioControllerData*)device_get_driver_data(device); +error_t gpio_controller_get_pin_count(Device* device, uint32_t* count) { + auto* data = static_cast(device_get_driver_data(device)); *count = data->pin_count; return ERROR_NONE; } -error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_count, void* controller_context) { - auto* data = new(std::nothrow) GpioControllerData(pin_count); +error_t gpio_controller_init_descriptors(Device* device, uint32_t pin_count, void* controller_context) { + auto* data = new(std::nothrow) GpioControllerData(pin_count, controller_context); if (!data) return ERROR_OUT_OF_MEMORY; - if (data->init_descriptors(device, controller_context) != ERROR_NONE) { + if (data->init_descriptors(device) != ERROR_NONE) { delete data; return ERROR_OUT_OF_MEMORY; } @@ -93,77 +96,82 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou return ERROR_NONE; } -error_t gpio_controller_deinit_descriptors(struct Device* device) { +error_t gpio_controller_deinit_descriptors(Device* device) { auto* data = static_cast(device_get_driver_data(device)); delete data; device_set_driver_data(device, nullptr); return ERROR_NONE; } -error_t gpio_descriptor_set_level(struct GpioDescriptor* descriptor, bool high) { +void* gpio_controller_get_controller_context(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return data->controller_context; +} + +error_t gpio_descriptor_set_level(GpioDescriptor* descriptor, bool high) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->set_level(descriptor, high); } -error_t gpio_descriptor_get_level(struct GpioDescriptor* descriptor, bool* high) { +error_t gpio_descriptor_get_level(GpioDescriptor* descriptor, bool* high) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_level(descriptor, high); } -error_t gpio_descriptor_set_flags(struct GpioDescriptor* descriptor, gpio_flags_t flags) { +error_t gpio_descriptor_set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->set_flags(descriptor, flags); } -error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_t* flags) { +error_t gpio_descriptor_get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_flags(descriptor, flags); } -error_t gpio_descriptor_get_pin_number(struct GpioDescriptor* descriptor, gpio_pin_t* pin) { +error_t gpio_descriptor_get_pin_number(GpioDescriptor* descriptor, gpio_pin_t* pin) { *pin = descriptor->pin; return ERROR_NONE; } -error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number) { +error_t gpio_descriptor_get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_native_pin_number(descriptor, pin_number); } -error_t gpio_descriptor_add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { +error_t gpio_descriptor_add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { const auto* driver = device_get_driver(descriptor->controller); auto* api = GPIO_INTERNAL_API(driver); if (!api->add_callback) return ERROR_NOT_SUPPORTED; return api->add_callback(descriptor, callback, arg); } -error_t gpio_descriptor_remove_callback(struct GpioDescriptor* descriptor) { +error_t gpio_descriptor_remove_callback(GpioDescriptor* descriptor) { const auto* driver = device_get_driver(descriptor->controller); auto* api = GPIO_INTERNAL_API(driver); if (!api->remove_callback) return ERROR_NOT_SUPPORTED; return api->remove_callback(descriptor); } -error_t gpio_descriptor_enable_interrupt(struct GpioDescriptor* descriptor) { +error_t gpio_descriptor_enable_interrupt(GpioDescriptor* descriptor) { const auto* driver = device_get_driver(descriptor->controller); auto* api = GPIO_INTERNAL_API(driver); if (!api->enable_interrupt) return ERROR_NOT_SUPPORTED; return api->enable_interrupt(descriptor); } -error_t gpio_descriptor_disable_interrupt(struct GpioDescriptor* descriptor) { +error_t gpio_descriptor_disable_interrupt(GpioDescriptor* descriptor) { const auto* driver = device_get_driver(descriptor->controller); auto* api = GPIO_INTERNAL_API(driver); if (!api->disable_interrupt) return ERROR_NOT_SUPPORTED; return api->disable_interrupt(descriptor); } -error_t gpio_descriptor_get_owner_type(struct GpioDescriptor* descriptor, GpioOwnerType* owner_type) { +error_t gpio_descriptor_get_owner_type(GpioDescriptor* descriptor, GpioOwnerType* owner_type) { *owner_type = descriptor->owner_type; return ERROR_NONE; } -const struct DeviceType GPIO_CONTROLLER_TYPE { +const DeviceType GPIO_CONTROLLER_TYPE { .name = "gpio-controller" }; From a1470cc87009f45fb2c217adf2d4acd27afa47e9 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 28 Feb 2026 21:44:44 +0100 Subject: [PATCH 03/25] Add basic wifi driver interface --- .../include/tactility/drivers/wifi.h | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 TactilityKernel/include/tactility/drivers/wifi.h diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h new file mode 100644 index 000000000..9ecdcf5c9 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -0,0 +1,186 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Device; + +struct WifiApRecord { + char ssid[32]; + int8_t rssi; + int32_t channel; + wifi_auth_mode_t auth_mode; +}; + +enum WifiRadioState { + WIFI_RADIO_STATE_OFF, + WIFI_RADIO_STATE_ON_PENDING, + WIFI_RADIO_STATE_ON, + WIFI_RADIO_STATE_OFF_PENDING, +}; + +enum WifiStationState { + WIFI_STATION_STATE_DISCONNECTED, + WIFI_STATION_STATE_CONNECTION_PENDING, + WIFI_STATION_STATE_CONNECTED +}; + +enum WifiAccessPointState { + WIFI_ACCESS_POINT_STATE_STARTED, + WIFI_ACCESS_POINT_STATE_STOPPED, +}; + +enum class WifiEventType { + /** Radio state changed */ + WIFI_EVENT_TYPE_RADIO_STATE_CHANGED, + /** WifiStationState changed */ + WIFI_EVENT_TYPE_STATION_STATE_CHANGED, + /** WifiAccessPointState changed */ + WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT, + /** WifiAccessPointState changed */ + WIFI_EVENT_TYPE_ACCESS_POINT_STATE_CHANGED, + /** Started scanning for access points */ + WIFI_EVENT_TYPE_SCAN_STARTED, + /** Finished scanning for access points */ + WIFI_EVENT_TYPE_SCAN_FINISHED, +}; + +enum class WifiStationConnectionError { + WIFI_STATION_CONNECTION_ERROR_NONE, + /** Wrong password */ + WIFI_STATION_CONNECTION_ERROR_WRONG_CREDENTIALS, + /** Failed to connect in a timely manner */ + WIFI_STATION_CONNECTION_ERROR_TIMEOUT, + /** SSID not found */ + WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND, +}; + +struct WifiEvent { + enum WifiEventType type; + union { + enum WifiRadioState radio_state; + enum WifiStationState station_state; + enum WifiAccessPointState access_point_state; + enum WifiStationConnectionError connection_error; + } +}; + +typedef void (*WifiEventCallback)(struct Device* device, void* callback_context, WifiEvent event); + +struct WifiApi { + /** + * Get the radio state of the device. + * @param[in] device the wifi device + * @param[out] state the radio state + * @return ERROR_NONE on success + */ + error_t (*get_radio_state)(struct Device* device, enum WifiRadioState* state); + + /** + * Get the station state of the device. + * @param[in] device the wifi device + * @param[out] state the station state + * @return ERROR_NONE on success + */ + error_t (*get_station_state)(struct Device* device, enum WifiStationState* state); + + /** + * Get the access point state of the device. + * @param[in] device the wifi device + * @param[out] state the access point state + * @return ERROR_NONE on success + */ + error_t (*get_access_point_state)(struct Device* device, enum WifiAccessPointState* state); + + /** + * Check if the device is currently scanning for access points. + * @param[in] device the wifi device + * @return ERROR_NONE on success + */ + error_t (*is_scanning)(struct Device* device); + + /** + * Start a scan for access points. + * @param[in] device the wifi device + * @return ERROR_NONE on success + */ + error_t (*scan)(struct Device* device); + + /** + * Get the scan results of the device. + * @param[in] device the wifi device + * @param[out] results the buffer to store the scan results + * @param[in, out] num_results the number of scan results: it's first used as input to determine the size of the buffer, and then as output to get the actual number of results + * @return ERROR_NONE on success + */ + error_t (*get_scan_results)(struct Device* device, struct WifiApRecord* results, size_t* num_results); + + /** + * Get the IPv4 address of the device. + * @param[in] device the device + * @param[out] ipv4 the buffer to store the IPv4 address (must be at least 16 bytes, will be null-terminated) + * @return ERROR_NONE on success + */ + error_t (*station_get_ipv4_address)(struct Device* device, char* ipv4); + + /** + * Get the IPv6 address of the device. + * @param[in] device the device + * @param[out] ipv6 the buffer to store the IPv6 address (must be at least 33 bytes, will be null-terminated) + * @return ERROR_NONE on success + */ + error_t (*station_get_target_ssid)(struct Device* device, char* ssid); + + /** + * Connect to an access point. + * @param[in] device the wifi device + * @param[in] ssid the SSID of the access point + * @param[in] password the password of the access point (must be at least 33 characters and null-terminated) + * @param[in] channel the Wi-Fi channel to connect to (0 means "any" / no preference) + * @return ERROR_NONE on success + */ + error_t (*station_connect)(struct Device* device, const char* ssid, const char* password, int32_t channel); + + /** + * Disconnect from the current access point. + * @param[in] device the wifi device + * @return ERROR_NONE on success + */ + error_t (*station_disconnect)(struct Device* device); + + /** + * Get the RSSI of the current access point. + * @param[in] device the wifi device + * @param[out] rssi the buffer to store the RSSI + * @return ERROR_NONE on success + */ + error_t (*station_get_rssi)(struct Device* device, int32_t* rssi); + + /** + * Add a WifiEvent callback. + * @param[in] device the wifi device + * @param[in] callback_context the context to pass to the callback + * @param[in] callback the callback function + * @return ERROR_NONE on success + */ + error_t (*add_event_callback)(struct Device* device, void* callback_context, WifiEventCallback callback); + + /** + * Remove a WifiEvent callback. + * @param[in] device the wifi device + * @param[in] callback the callback function + * @return ERROR_NONE on success + */ + error_t (*remove_event_callback)(struct Device* device, WifiEventCallback callback); +}; + +extern const struct DeviceType WIFI_TYPE; + +#ifdef __cplusplus +} +#endif From c2177929bc24f7cbf4907a590ec1fb406ca6ea7f Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 28 Feb 2026 23:29:35 +0100 Subject: [PATCH 04/25] Implement BMI270 driver --- Devices/m5stack-tab5/CMakeLists.txt | 2 +- Devices/m5stack-tab5/Source/Configuration.cpp | 6 +- Devices/m5stack-tab5/devicetree.yaml | 1 + Devices/m5stack-tab5/m5stack,tab5.dts | 6 + Drivers/bmi270-module/CMakeLists.txt | 12 + Drivers/bmi270-module/LICENSE-Apache-2.0.md | 195 ++++++++ Drivers/bmi270-module/README.md | 7 + .../bmi270-module/bindings/bosch,bmi270.yaml | 5 + Drivers/bmi270-module/devicetree.yaml | 3 + .../bmi270-module/include/bindings/bmi270.h | 15 + Drivers/bmi270-module/include/bmi270_module.h | 14 + .../bmi270-module/include/drivers/bmi270.h | 33 ++ .../private/drivers/bmi270_config_data.h | 442 ++++++++++++++++++ Drivers/bmi270-module/source/bmi270.cpp | 183 ++++++++ Drivers/bmi270-module/source/module.cpp | 34 ++ Drivers/bmi270-module/source/symbols.c | 8 + 16 files changed, 960 insertions(+), 6 deletions(-) create mode 100644 Drivers/bmi270-module/CMakeLists.txt create mode 100644 Drivers/bmi270-module/LICENSE-Apache-2.0.md create mode 100644 Drivers/bmi270-module/README.md create mode 100644 Drivers/bmi270-module/bindings/bosch,bmi270.yaml create mode 100644 Drivers/bmi270-module/devicetree.yaml create mode 100644 Drivers/bmi270-module/include/bindings/bmi270.h create mode 100644 Drivers/bmi270-module/include/bmi270_module.h create mode 100644 Drivers/bmi270-module/include/drivers/bmi270.h create mode 100644 Drivers/bmi270-module/private/drivers/bmi270_config_data.h create mode 100644 Drivers/bmi270-module/source/bmi270.cpp create mode 100644 Drivers/bmi270-module/source/module.cpp create mode 100644 Drivers/bmi270-module/source/symbols.c diff --git a/Devices/m5stack-tab5/CMakeLists.txt b/Devices/m5stack-tab5/CMakeLists.txt index 53e5abbeb..c84b1ada3 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 pi4ioe5v6408-module + REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs ) diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index 4566b6db1..0f75154e2 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -1,13 +1,11 @@ #include "devices/Display.h" #include "devices/SdCard.h" -#include +#include #include #include #include -#include -#include using namespace tt::hal; @@ -58,8 +56,6 @@ constexpr auto GPIO_EXP1_PIN_IP2326_CHG_STAT_LED = 6; constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7; static void initExpander0(::Device* io_expander0) { - constexpr TickType_t i2c_timeout = pdMS_TO_TICKS(10); - auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO); auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO); diff --git a/Devices/m5stack-tab5/devicetree.yaml b/Devices/m5stack-tab5/devicetree.yaml index 516236386..bc6b7b156 100644 --- a/Devices/m5stack-tab5/devicetree.yaml +++ b/Devices/m5stack-tab5/devicetree.yaml @@ -1,4 +1,5 @@ dependencies: - Platforms/platform-esp32 - Drivers/pi4ioe5v6408-module + - Drivers/bmi270-module dts: m5stack,tab5.dts diff --git a/Devices/m5stack-tab5/m5stack,tab5.dts b/Devices/m5stack-tab5/m5stack,tab5.dts index 04be1ce2e..2269e690a 100644 --- a/Devices/m5stack-tab5/m5stack,tab5.dts +++ b/Devices/m5stack-tab5/m5stack,tab5.dts @@ -5,6 +5,7 @@ #include #include #include +#include #include / { @@ -52,6 +53,11 @@ compatible = "diodes,pi4ioe5v6408"; reg = <0x44>; }; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; }; i2c_port_a: i2c1 { diff --git a/Drivers/bmi270-module/CMakeLists.txt b/Drivers/bmi270-module/CMakeLists.txt new file mode 100644 index 000000000..3f05a93a6 --- /dev/null +++ b/Drivers/bmi270-module/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(bmi270-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + PRIV_INCLUDE_DIRS private/ + REQUIRES TactilityKernel +) diff --git a/Drivers/bmi270-module/LICENSE-Apache-2.0.md b/Drivers/bmi270-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bmi270-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/bmi270-module/README.md b/Drivers/bmi270-module/README.md new file mode 100644 index 000000000..c447c7b16 --- /dev/null +++ b/Drivers/bmi270-module/README.md @@ -0,0 +1,7 @@ +# BMI270 I2C Driver + +A driver for the `BMI270` 6-axis IMU. + +See https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi270-ds000.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/bmi270-module/bindings/bosch,bmi270.yaml b/Drivers/bmi270-module/bindings/bosch,bmi270.yaml new file mode 100644 index 000000000..188955bb2 --- /dev/null +++ b/Drivers/bmi270-module/bindings/bosch,bmi270.yaml @@ -0,0 +1,5 @@ +description: Bosch BMI270 6-axis IMU + +include: ["i2c-device.yaml"] + +compatible: "bosch,bmi270" diff --git a/Drivers/bmi270-module/devicetree.yaml b/Drivers/bmi270-module/devicetree.yaml new file mode 100644 index 000000000..99f3dfd7e --- /dev/null +++ b/Drivers/bmi270-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings \ No newline at end of file diff --git a/Drivers/bmi270-module/include/bindings/bmi270.h b/Drivers/bmi270-module/include/bindings/bmi270.h new file mode 100644 index 000000000..903958b04 --- /dev/null +++ b/Drivers/bmi270-module/include/bindings/bmi270.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(bmi270, struct Bmi270Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/include/bmi270_module.h b/Drivers/bmi270-module/include/bmi270_module.h new file mode 100644 index 000000000..d1e4f5dfc --- /dev/null +++ b/Drivers/bmi270-module/include/bmi270_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module bmi270_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/include/drivers/bmi270.h b/Drivers/bmi270-module/include/drivers/bmi270.h new file mode 100644 index 000000000..aa8227a60 --- /dev/null +++ b/Drivers/bmi270-module/include/drivers/bmi270.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Bmi270Config { + /** Address on bus */ + uint8_t address; +}; + +struct Bmi270Data { + 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 bmi270 device + * @param[out] data Pointer to Bmi270Data structure to store the data + * @return ERROR_NONE on success + */ +error_t bmi270_read(struct Device* device, struct Bmi270Data* data); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/private/drivers/bmi270_config_data.h b/Drivers/bmi270-module/private/drivers/bmi270_config_data.h new file mode 100644 index 000000000..e77423480 --- /dev/null +++ b/Drivers/bmi270-module/private/drivers/bmi270_config_data.h @@ -0,0 +1,442 @@ +#pragma once + +// BMI270 configuration file +// Copyright (c) 2023 Bosch Sensortec GmbH +// SPDX-License-Identifier: BSD-3-Clause +// Source: https://github.com/boschsensortec/BMI270_SensorAPI +#include + +static const uint8_t bmi270_config_data[] = { + 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x3d, 0xb1, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x91, 0x03, 0x80, 0x2e, 0xbc, + 0xb0, 0x80, 0x2e, 0xa3, 0x03, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x00, 0xb0, 0x50, 0x30, 0x21, 0x2e, 0x59, 0xf5, + 0x10, 0x30, 0x21, 0x2e, 0x6a, 0xf5, 0x80, 0x2e, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x01, 0x00, 0x22, + 0x00, 0x75, 0x00, 0x00, 0x10, 0x00, 0x10, 0xd1, 0x00, 0xb3, 0x43, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xe0, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0xe0, 0xaa, 0x38, 0x05, 0xe0, 0x90, 0x30, 0xfa, 0x00, 0x96, 0x00, 0x4b, 0x09, 0x11, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x2d, 0x01, 0xd4, 0x7b, 0x3b, 0x01, 0xdb, 0x7a, 0x04, 0x00, 0x3f, 0x7b, 0xcd, 0x6c, 0xc3, 0x04, 0x85, 0x09, 0xc3, + 0x04, 0xec, 0xe6, 0x0c, 0x46, 0x01, 0x00, 0x27, 0x00, 0x19, 0x00, 0x96, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x0c, 0x00, + 0xf0, 0x3c, 0x00, 0x01, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x05, 0x00, 0xee, + 0x06, 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa8, 0x05, 0xee, 0x06, 0x00, 0x04, 0xbc, 0x02, 0xb3, 0x00, + 0x85, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x01, 0x00, 0xb9, 0x00, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0xde, + 0x00, 0xeb, 0x00, 0xda, 0x00, 0x00, 0x0c, 0xff, 0x0f, 0x00, 0x04, 0xc0, 0x00, 0x5b, 0xf5, 0xc9, 0x01, 0x1e, 0xf2, + 0x80, 0x00, 0x3f, 0xff, 0x19, 0xf4, 0x58, 0xf5, 0x66, 0xf5, 0x64, 0xf5, 0xc0, 0xf1, 0xf0, 0x00, 0xe0, 0x00, 0xcd, + 0x01, 0xd3, 0x01, 0xdb, 0x01, 0xff, 0x7f, 0xff, 0x01, 0xe4, 0x00, 0x74, 0xf7, 0xf3, 0x00, 0xfa, 0x00, 0xff, 0x3f, + 0xca, 0x03, 0x6c, 0x38, 0x56, 0xfe, 0x44, 0xfd, 0xbc, 0x02, 0xf9, 0x06, 0x00, 0xfc, 0x12, 0x02, 0xae, 0x01, 0x58, + 0xfa, 0x9a, 0xfd, 0x77, 0x05, 0xbb, 0x02, 0x96, 0x01, 0x95, 0x01, 0x7f, 0x01, 0x82, 0x01, 0x89, 0x01, 0x87, 0x01, + 0x88, 0x01, 0x8a, 0x01, 0x8c, 0x01, 0x8f, 0x01, 0x8d, 0x01, 0x92, 0x01, 0x91, 0x01, 0xdd, 0x00, 0x9f, 0x01, 0x7e, + 0x01, 0xdb, 0x00, 0xb6, 0x01, 0x70, 0x69, 0x26, 0xd3, 0x9c, 0x07, 0x1f, 0x05, 0x9d, 0x00, 0x00, 0x08, 0xbc, 0x05, + 0x37, 0xfa, 0xa2, 0x01, 0xaa, 0x01, 0xa1, 0x01, 0xa8, 0x01, 0xa0, 0x01, 0xa8, 0x05, 0xb4, 0x01, 0xb4, 0x01, 0xce, + 0x00, 0xd0, 0x00, 0xfc, 0x00, 0xc5, 0x01, 0xff, 0xfb, 0xb1, 0x00, 0x00, 0x38, 0x00, 0x30, 0xfd, 0xf5, 0xfc, 0xf5, + 0xcd, 0x01, 0xa0, 0x00, 0x5f, 0xff, 0x00, 0x40, 0xff, 0x00, 0x00, 0x80, 0x6d, 0x0f, 0xeb, 0x00, 0x7f, 0xff, 0xc2, + 0xf5, 0x68, 0xf7, 0xb3, 0xf1, 0x67, 0x0f, 0x5b, 0x0f, 0x61, 0x0f, 0x80, 0x0f, 0x58, 0xf7, 0x5b, 0xf7, 0x83, 0x0f, + 0x86, 0x00, 0x72, 0x0f, 0x85, 0x0f, 0xc6, 0xf1, 0x7f, 0x0f, 0x6c, 0xf7, 0x00, 0xe0, 0x00, 0xff, 0xd1, 0xf5, 0x87, + 0x0f, 0x8a, 0x0f, 0xff, 0x03, 0xf0, 0x3f, 0x8b, 0x00, 0x8e, 0x00, 0x90, 0x00, 0xb9, 0x00, 0x2d, 0xf5, 0xca, 0xf5, + 0xcb, 0x01, 0x20, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x50, 0x98, 0x2e, + 0xd7, 0x0e, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, 0xf0, 0x7f, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x00, + 0x2e, 0x01, 0x80, 0x08, 0xa2, 0xfb, 0x2f, 0x98, 0x2e, 0xba, 0x03, 0x21, 0x2e, 0x19, 0x00, 0x01, 0x2e, 0xee, 0x00, + 0x00, 0xb2, 0x07, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x03, 0x2f, 0x01, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x07, + 0xcc, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x27, 0x2f, 0x05, 0x2e, 0x8a, 0x00, 0x05, 0x52, 0x98, 0x2e, 0xc7, 0xc1, + 0x03, 0x2e, 0xe9, 0x00, 0x40, 0xb2, 0xf0, 0x7f, 0x08, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x00, + 0x30, 0x21, 0x2e, 0xe9, 0x00, 0x98, 0x2e, 0xb4, 0xb1, 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x10, 0x2f, 0x05, 0x50, + 0x98, 0x2e, 0x4d, 0xc3, 0x05, 0x50, 0x98, 0x2e, 0x5a, 0xc7, 0x98, 0x2e, 0xf9, 0xb4, 0x98, 0x2e, 0x54, 0xb2, 0x98, + 0x2e, 0x67, 0xb6, 0x98, 0x2e, 0x17, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x2e, 0xef, 0x00, 0x00, 0xb2, + 0x04, 0x2f, 0x98, 0x2e, 0x7a, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xef, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xae, 0x0b, + 0x2f, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x07, 0x2f, 0x05, 0x52, 0x98, 0x2e, 0x8e, 0x0e, 0x00, 0xb2, 0x02, 0x2f, + 0x10, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x01, 0x2e, 0x7d, 0x00, 0x00, 0x90, 0x90, 0x2e, 0xf1, 0x02, 0x01, 0x2e, 0xd7, + 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x2f, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7b, 0x00, + 0x00, 0xb2, 0x12, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x00, 0x90, 0x02, 0x2f, 0x98, 0x2e, 0x1f, 0x0e, 0x09, 0x2d, 0x98, + 0x2e, 0x81, 0x0d, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0x90, 0x02, 0x2f, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, + 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7c, 0x00, 0x00, 0xb2, 0x90, 0x2e, 0x09, 0x03, 0x01, 0x2e, 0x7c, 0x00, 0x01, + 0x31, 0x01, 0x08, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x47, 0xcb, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x81, 0x30, + 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, 0xb2, 0x61, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x98, + 0xbc, 0x98, 0xb8, 0x05, 0xb2, 0x0f, 0x58, 0x23, 0x2f, 0x07, 0x90, 0x09, 0x54, 0x00, 0x30, 0x37, 0x2f, 0x15, 0x41, + 0x04, 0x41, 0xdc, 0xbe, 0x44, 0xbe, 0xdc, 0xba, 0x2c, 0x01, 0x61, 0x00, 0x0f, 0x56, 0x4a, 0x0f, 0x0c, 0x2f, 0xd1, + 0x42, 0x94, 0xb8, 0xc1, 0x42, 0x11, 0x30, 0x05, 0x2e, 0x6a, 0xf7, 0x2c, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x08, 0x22, + 0x98, 0x2e, 0xc3, 0xb7, 0x21, 0x2d, 0x61, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x98, 0x2e, 0xc3, 0xb7, 0x00, 0x30, 0x21, + 0x2e, 0x5a, 0xf5, 0x18, 0x2d, 0xe1, 0x7f, 0x50, 0x30, 0x98, 0x2e, 0xfa, 0x03, 0x0f, 0x52, 0x07, 0x50, 0x50, 0x42, + 0x70, 0x30, 0x0d, 0x54, 0x42, 0x42, 0x7e, 0x82, 0xe2, 0x6f, 0x80, 0xb2, 0x42, 0x42, 0x05, 0x2f, 0x21, 0x2e, 0xd4, + 0x00, 0x10, 0x30, 0x98, 0x2e, 0xc3, 0xb7, 0x03, 0x2d, 0x60, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x01, 0x2e, 0xd4, 0x00, + 0x06, 0x90, 0x18, 0x2f, 0x01, 0x2e, 0x76, 0x00, 0x0b, 0x54, 0x07, 0x52, 0xe0, 0x7f, 0x98, 0x2e, 0x7a, 0xc1, 0xe1, + 0x6f, 0x08, 0x1a, 0x40, 0x30, 0x08, 0x2f, 0x21, 0x2e, 0xd4, 0x00, 0x20, 0x30, 0x98, 0x2e, 0xaf, 0xb7, 0x50, 0x32, + 0x98, 0x2e, 0xfa, 0x03, 0x05, 0x2d, 0x98, 0x2e, 0x38, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x00, 0x30, 0x21, + 0x2e, 0x7c, 0x00, 0x18, 0x2d, 0x01, 0x2e, 0xd4, 0x00, 0x03, 0xaa, 0x01, 0x2f, 0x98, 0x2e, 0x45, 0x0e, 0x01, 0x2e, + 0xd4, 0x00, 0x3f, 0x80, 0x03, 0xa2, 0x01, 0x2f, 0x00, 0x2e, 0x02, 0x2d, 0x98, 0x2e, 0x5b, 0x0e, 0x30, 0x30, 0x98, + 0x2e, 0xce, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x01, 0x2e, 0x77, 0x00, + 0x00, 0xb2, 0x24, 0x2f, 0x98, 0x2e, 0xf5, 0xcb, 0x03, 0x2e, 0xd5, 0x00, 0x11, 0x54, 0x01, 0x0a, 0xbc, 0x84, 0x83, + 0x86, 0x21, 0x2e, 0xc9, 0x01, 0xe0, 0x40, 0x13, 0x52, 0xc4, 0x40, 0x82, 0x40, 0xa8, 0xb9, 0x52, 0x42, 0x43, 0xbe, + 0x53, 0x42, 0x04, 0x0a, 0x50, 0x42, 0xe1, 0x7f, 0xf0, 0x31, 0x41, 0x40, 0xf2, 0x6f, 0x25, 0xbd, 0x08, 0x08, 0x02, + 0x0a, 0xd0, 0x7f, 0x98, 0x2e, 0xa8, 0xcf, 0x06, 0xbc, 0xd1, 0x6f, 0xe2, 0x6f, 0x08, 0x0a, 0x80, 0x42, 0x98, 0x2e, + 0x58, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x21, 0x2e, 0x77, 0x00, 0x21, 0x2e, 0xdd, 0x00, 0x80, 0x2e, 0xf4, + 0x01, 0x1a, 0x24, 0x22, 0x00, 0x80, 0x2e, 0xec, 0x01, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, + 0xfb, 0x6f, 0x01, 0x30, 0x71, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, 0x2f, 0xc0, 0x2e, 0x01, 0x42, 0xf0, 0x5f, 0x80, + 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x01, + 0x34, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x06, 0x32, 0x0f, 0x2e, 0x61, 0xf5, 0xfe, 0x09, 0xc0, 0xb3, 0x04, + 0x2f, 0x17, 0x30, 0x2f, 0x2e, 0xef, 0x00, 0x2d, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, + 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x46, 0x30, 0x0f, 0x2e, 0xa4, 0xf1, 0xbe, 0x09, 0x80, 0xb3, 0x06, 0x2f, 0x0d, + 0x2e, 0xd4, 0x00, 0x84, 0xaf, 0x02, 0x2f, 0x16, 0x30, 0x2d, 0x2e, 0x7b, 0x00, 0x86, 0x30, 0x2d, 0x2e, 0x60, 0xf5, + 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x01, 0x2e, 0x77, 0xf7, 0x09, 0xbc, 0x0f, 0xb8, 0x00, 0xb2, 0x10, + 0x50, 0xfb, 0x7f, 0x10, 0x30, 0x0b, 0x2f, 0x03, 0x2e, 0x8a, 0x00, 0x96, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x05, 0x2f, + 0x03, 0x2e, 0x68, 0xf7, 0x9e, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x07, 0x2f, 0x03, 0x2e, 0x7e, 0x00, 0x41, 0x90, 0x01, + 0x2f, 0x98, 0x2e, 0xdc, 0x03, 0x03, 0x2c, 0x00, 0x30, 0x21, 0x2e, 0x7e, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, + 0x20, 0x50, 0xe0, 0x7f, 0xfb, 0x7f, 0x00, 0x2e, 0x27, 0x50, 0x98, 0x2e, 0x3b, 0xc8, 0x29, 0x50, 0x98, 0x2e, 0xa7, + 0xc8, 0x01, 0x50, 0x98, 0x2e, 0x55, 0xcc, 0xe1, 0x6f, 0x2b, 0x50, 0x98, 0x2e, 0xe0, 0xc9, 0xfb, 0x6f, 0x00, 0x30, + 0xe0, 0x5f, 0x21, 0x2e, 0x7e, 0x00, 0xb8, 0x2e, 0x73, 0x50, 0x01, 0x30, 0x57, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, + 0x2f, 0xb8, 0x2e, 0x21, 0x2e, 0x59, 0xf5, 0x10, 0x30, 0xc0, 0x2e, 0x21, 0x2e, 0x4a, 0xf1, 0x90, 0x50, 0xf7, 0x7f, + 0xe6, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa1, 0x7f, 0x90, 0x7f, 0x82, 0x7f, 0x7b, 0x7f, 0x98, 0x2e, 0x35, + 0xb7, 0x00, 0xb2, 0x90, 0x2e, 0x97, 0xb0, 0x03, 0x2e, 0x8f, 0x00, 0x07, 0x2e, 0x91, 0x00, 0x05, 0x2e, 0xb1, 0x00, + 0x3f, 0xba, 0x9f, 0xb8, 0x01, 0x2e, 0xb1, 0x00, 0xa3, 0xbd, 0x4c, 0x0a, 0x05, 0x2e, 0xb1, 0x00, 0x04, 0xbe, 0xbf, + 0xb9, 0xcb, 0x0a, 0x4f, 0xba, 0x22, 0xbd, 0x01, 0x2e, 0xb3, 0x00, 0xdc, 0x0a, 0x2f, 0xb9, 0x03, 0x2e, 0xb8, 0x00, + 0x0a, 0xbe, 0x9a, 0x0a, 0xcf, 0xb9, 0x9b, 0xbc, 0x01, 0x2e, 0x97, 0x00, 0x9f, 0xb8, 0x93, 0x0a, 0x0f, 0xbc, 0x91, + 0x0a, 0x0f, 0xb8, 0x90, 0x0a, 0x25, 0x2e, 0x18, 0x00, 0x05, 0x2e, 0xc1, 0xf5, 0x2e, 0xbd, 0x2e, 0xb9, 0x01, 0x2e, + 0x19, 0x00, 0x31, 0x30, 0x8a, 0x04, 0x00, 0x90, 0x07, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xa2, 0x03, 0x2f, 0x01, + 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x0c, 0x2f, 0x19, 0x50, 0x05, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x05, 0x2e, 0x78, 0x00, + 0x80, 0x90, 0x10, 0x30, 0x01, 0x2f, 0x21, 0x2e, 0x78, 0x00, 0x25, 0x2e, 0xdd, 0x00, 0x98, 0x2e, 0x3e, 0xb7, 0x00, + 0xb2, 0x02, 0x30, 0x01, 0x30, 0x04, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x00, 0x2f, 0x21, 0x30, 0x01, 0x2e, + 0xea, 0x00, 0x08, 0x1a, 0x0e, 0x2f, 0x23, 0x2e, 0xea, 0x00, 0x33, 0x30, 0x1b, 0x50, 0x0b, 0x09, 0x01, 0x40, 0x17, + 0x56, 0x46, 0xbe, 0x4b, 0x08, 0x4c, 0x0a, 0x01, 0x42, 0x0a, 0x80, 0x15, 0x52, 0x01, 0x42, 0x00, 0x2e, 0x01, 0x2e, + 0x18, 0x00, 0x00, 0xb2, 0x1f, 0x2f, 0x03, 0x2e, 0xc0, 0xf5, 0xf0, 0x30, 0x48, 0x08, 0x47, 0xaa, 0x74, 0x30, 0x07, + 0x2e, 0x7a, 0x00, 0x61, 0x22, 0x4b, 0x1a, 0x05, 0x2f, 0x07, 0x2e, 0x66, 0xf5, 0xbf, 0xbd, 0xbf, 0xb9, 0xc0, 0x90, + 0x0b, 0x2f, 0x1d, 0x56, 0x2b, 0x30, 0xd2, 0x42, 0xdb, 0x42, 0x01, 0x04, 0xc2, 0x42, 0x04, 0xbd, 0xfe, 0x80, 0x81, + 0x84, 0x23, 0x2e, 0x7a, 0x00, 0x02, 0x42, 0x02, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x05, 0x2e, 0xd6, 0x00, 0x81, 0x84, + 0x25, 0x2e, 0xd6, 0x00, 0x02, 0x31, 0x25, 0x2e, 0x60, 0xf5, 0x05, 0x2e, 0x8a, 0x00, 0x0b, 0x50, 0x90, 0x08, 0x80, + 0xb2, 0x0b, 0x2f, 0x05, 0x2e, 0xca, 0xf5, 0xf0, 0x3e, 0x90, 0x08, 0x25, 0x2e, 0xca, 0xf5, 0x05, 0x2e, 0x59, 0xf5, + 0xe0, 0x3f, 0x90, 0x08, 0x25, 0x2e, 0x59, 0xf5, 0x90, 0x6f, 0xa1, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe6, + 0x6f, 0xf7, 0x6f, 0x7b, 0x6f, 0x82, 0x6f, 0x70, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0x90, 0x7f, 0xe5, 0x7f, 0xd4, 0x7f, + 0xc3, 0x7f, 0xb1, 0x7f, 0xa2, 0x7f, 0x87, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x60, 0xf5, 0x60, + 0x7f, 0x98, 0x2e, 0x35, 0xb7, 0x02, 0x30, 0x63, 0x6f, 0x15, 0x52, 0x50, 0x7f, 0x62, 0x7f, 0x5a, 0x2c, 0x02, 0x32, + 0x1a, 0x09, 0x00, 0xb3, 0x14, 0x2f, 0x00, 0xb2, 0x03, 0x2f, 0x09, 0x2e, 0x18, 0x00, 0x00, 0x91, 0x0c, 0x2f, 0x43, + 0x7f, 0x98, 0x2e, 0x97, 0xb7, 0x1f, 0x50, 0x02, 0x8a, 0x02, 0x32, 0x04, 0x30, 0x25, 0x2e, 0x64, 0xf5, 0x15, 0x52, + 0x50, 0x6f, 0x43, 0x6f, 0x44, 0x43, 0x25, 0x2e, 0x60, 0xf5, 0xd9, 0x08, 0xc0, 0xb2, 0x36, 0x2f, 0x98, 0x2e, 0x3e, + 0xb7, 0x00, 0xb2, 0x06, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x02, 0x2f, 0x50, 0x6f, 0x00, 0x90, 0x0a, 0x2f, + 0x01, 0x2e, 0x79, 0x00, 0x00, 0x90, 0x19, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x79, 0x00, 0x00, 0x30, 0x98, 0x2e, 0xdc, + 0x03, 0x13, 0x2d, 0x01, 0x2e, 0xc3, 0xf5, 0x0c, 0xbc, 0x0f, 0xb8, 0x12, 0x30, 0x10, 0x04, 0x03, 0xb0, 0x26, 0x25, + 0x21, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x10, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x02, 0x30, 0x60, 0x7f, 0x25, + 0x2e, 0x79, 0x00, 0x60, 0x6f, 0x00, 0x90, 0x05, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0xea, 0x00, 0x15, 0x50, 0x21, 0x2e, + 0x64, 0xf5, 0x15, 0x52, 0x23, 0x2e, 0x60, 0xf5, 0x02, 0x32, 0x50, 0x6f, 0x00, 0x90, 0x02, 0x2f, 0x03, 0x30, 0x27, + 0x2e, 0x78, 0x00, 0x07, 0x2e, 0x60, 0xf5, 0x1a, 0x09, 0x00, 0x91, 0xa3, 0x2f, 0x19, 0x09, 0x00, 0x91, 0xa0, 0x2f, + 0x90, 0x6f, 0xa2, 0x6f, 0xb1, 0x6f, 0xc3, 0x6f, 0xd4, 0x6f, 0xe5, 0x6f, 0x7b, 0x6f, 0xf6, 0x6f, 0x87, 0x6f, 0x40, + 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x26, 0x30, 0x0f, 0x2e, 0x61, 0xf5, 0x2f, 0x2e, 0x7c, 0x00, + 0x0f, 0x2e, 0x7c, 0x00, 0xbe, 0x09, 0xa2, 0x7f, 0x80, 0x7f, 0x80, 0xb3, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0x91, + 0x7f, 0x7b, 0x7f, 0x0b, 0x2f, 0x23, 0x50, 0x1a, 0x25, 0x12, 0x40, 0x42, 0x7f, 0x74, 0x82, 0x12, 0x40, 0x52, 0x7f, + 0x00, 0x2e, 0x00, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x81, 0x30, 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, + 0xb2, 0x42, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0x89, 0x00, 0x97, 0xbc, 0x06, 0xbc, 0x9f, 0xb8, 0x0f, 0xb8, + 0x00, 0x90, 0x23, 0x2e, 0xd8, 0x00, 0x10, 0x30, 0x01, 0x30, 0x2a, 0x2f, 0x03, 0x2e, 0xd4, 0x00, 0x44, 0xb2, 0x05, + 0x2f, 0x47, 0xb2, 0x00, 0x30, 0x2d, 0x2f, 0x21, 0x2e, 0x7c, 0x00, 0x2b, 0x2d, 0x03, 0x2e, 0xfd, 0xf5, 0x9e, 0xbc, + 0x9f, 0xb8, 0x40, 0x90, 0x14, 0x2f, 0x03, 0x2e, 0xfc, 0xf5, 0x99, 0xbc, 0x9f, 0xb8, 0x40, 0x90, 0x0e, 0x2f, 0x03, + 0x2e, 0x49, 0xf1, 0x25, 0x54, 0x4a, 0x08, 0x40, 0x90, 0x08, 0x2f, 0x98, 0x2e, 0x35, 0xb7, 0x00, 0xb2, 0x10, 0x30, + 0x03, 0x2f, 0x50, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x10, 0x2d, 0x98, 0x2e, 0xaf, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7c, + 0x00, 0x0a, 0x2d, 0x05, 0x2e, 0x69, 0xf7, 0x2d, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x01, 0x2f, 0x21, 0x2e, 0x7d, 0x00, + 0x23, 0x2e, 0x7c, 0x00, 0xe0, 0x31, 0x21, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0x80, 0x6f, 0xa2, 0x6f, 0xb3, + 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0x7b, 0x6f, 0x91, 0x6f, 0x40, 0x5f, 0xc8, 0x2e, 0x60, 0x51, 0x0a, 0x25, 0x36, 0x88, + 0xf4, 0x7f, 0xeb, 0x7f, 0x00, 0x32, 0x31, 0x52, 0x32, 0x30, 0x13, 0x30, 0x98, 0x2e, 0x15, 0xcb, 0x0a, 0x25, 0x33, + 0x84, 0xd2, 0x7f, 0x43, 0x30, 0x05, 0x50, 0x2d, 0x52, 0x98, 0x2e, 0x95, 0xc1, 0xd2, 0x6f, 0x27, 0x52, 0x98, 0x2e, + 0xd7, 0xc7, 0x2a, 0x25, 0xb0, 0x86, 0xc0, 0x7f, 0xd3, 0x7f, 0xaf, 0x84, 0x29, 0x50, 0xf1, 0x6f, 0x98, 0x2e, 0x4d, + 0xc8, 0x2a, 0x25, 0xae, 0x8a, 0xaa, 0x88, 0xf2, 0x6e, 0x2b, 0x50, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x7f, 0x98, 0x2e, + 0xb6, 0xc8, 0xe0, 0x6e, 0x00, 0xb2, 0x32, 0x2f, 0x33, 0x54, 0x83, 0x86, 0xf1, 0x6f, 0xc3, 0x7f, 0x04, 0x30, 0x30, + 0x30, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0xe3, 0x30, 0xc5, 0x6f, 0x56, 0x40, 0x45, 0x41, 0x28, 0x08, 0x03, 0x14, + 0x0e, 0xb4, 0x08, 0xbc, 0x82, 0x40, 0x10, 0x0a, 0x2f, 0x54, 0x26, 0x05, 0x91, 0x7f, 0x44, 0x28, 0xa3, 0x7f, 0x98, + 0x2e, 0xd9, 0xc0, 0x08, 0xb9, 0x33, 0x30, 0x53, 0x09, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x6f, 0x83, 0x17, 0x47, 0x40, + 0x6c, 0x15, 0xb2, 0x6f, 0xbe, 0x09, 0x75, 0x0b, 0x90, 0x42, 0x45, 0x42, 0x51, 0x0e, 0x32, 0xbc, 0x02, 0x89, 0xa1, + 0x6f, 0x7e, 0x86, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0x04, 0x30, 0x91, 0x6f, 0xd6, 0x2f, 0xeb, 0x6f, 0xa0, 0x5e, + 0xb8, 0x2e, 0x03, 0x2e, 0x97, 0x00, 0x1b, 0xbc, 0x60, 0x50, 0x9f, 0xbc, 0x0c, 0xb8, 0xf0, 0x7f, 0x40, 0xb2, 0xeb, + 0x7f, 0x2b, 0x2f, 0x03, 0x2e, 0x7f, 0x00, 0x41, 0x40, 0x01, 0x2e, 0xc8, 0x00, 0x01, 0x1a, 0x11, 0x2f, 0x37, 0x58, + 0x23, 0x2e, 0xc8, 0x00, 0x10, 0x41, 0xa0, 0x7f, 0x38, 0x81, 0x01, 0x41, 0xd0, 0x7f, 0xb1, 0x7f, 0x98, 0x2e, 0x64, + 0xcf, 0xd0, 0x6f, 0x07, 0x80, 0xa1, 0x6f, 0x11, 0x42, 0x00, 0x2e, 0xb1, 0x6f, 0x01, 0x42, 0x11, 0x30, 0x01, 0x2e, + 0xfc, 0x00, 0x00, 0xa8, 0x03, 0x30, 0xcb, 0x22, 0x4a, 0x25, 0x01, 0x2e, 0x7f, 0x00, 0x3c, 0x89, 0x35, 0x52, 0x05, + 0x54, 0x98, 0x2e, 0xc4, 0xce, 0xc1, 0x6f, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x04, 0x2d, 0x01, 0x30, 0xf0, 0x6f, + 0x98, 0x2e, 0x95, 0xcf, 0xeb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0x03, 0x2e, 0xb3, 0x00, 0x02, 0x32, 0xf0, 0x30, 0x03, + 0x31, 0x30, 0x50, 0x8a, 0x08, 0x08, 0x08, 0xcb, 0x08, 0xe0, 0x7f, 0x80, 0xb2, 0xf3, 0x7f, 0xdb, 0x7f, 0x25, 0x2f, + 0x03, 0x2e, 0xca, 0x00, 0x41, 0x90, 0x04, 0x2f, 0x01, 0x30, 0x23, 0x2e, 0xca, 0x00, 0x98, 0x2e, 0x3f, 0x03, 0xc0, + 0xb2, 0x05, 0x2f, 0x03, 0x2e, 0xda, 0x00, 0x00, 0x30, 0x41, 0x04, 0x23, 0x2e, 0xda, 0x00, 0x98, 0x2e, 0x92, 0xb2, + 0x10, 0x25, 0xf0, 0x6f, 0x00, 0xb2, 0x05, 0x2f, 0x01, 0x2e, 0xda, 0x00, 0x02, 0x30, 0x10, 0x04, 0x21, 0x2e, 0xda, + 0x00, 0x40, 0xb2, 0x01, 0x2f, 0x23, 0x2e, 0xc8, 0x01, 0xdb, 0x6f, 0xe0, 0x6f, 0xd0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, + 0x01, 0x30, 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x11, 0x30, 0x23, 0x2e, 0xca, 0x00, 0xdb, 0x6f, 0xd0, 0x5f, 0xb8, + 0x2e, 0xd0, 0x50, 0x0a, 0x25, 0x33, 0x84, 0x55, 0x50, 0xd2, 0x7f, 0xe2, 0x7f, 0x03, 0x8c, 0xc0, 0x7f, 0xbb, 0x7f, + 0x00, 0x30, 0x05, 0x5a, 0x39, 0x54, 0x51, 0x41, 0xa5, 0x7f, 0x96, 0x7f, 0x80, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x05, + 0x30, 0xf5, 0x7f, 0x20, 0x25, 0x91, 0x6f, 0x3b, 0x58, 0x3d, 0x5c, 0x3b, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xc1, 0x6f, + 0xd5, 0x6f, 0x52, 0x40, 0x50, 0x43, 0xc1, 0x7f, 0xd5, 0x7f, 0x10, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, + 0x2e, 0x74, 0xc0, 0x86, 0x6f, 0x30, 0x28, 0x92, 0x6f, 0x82, 0x8c, 0xa5, 0x6f, 0x6f, 0x52, 0x69, 0x0e, 0x39, 0x54, + 0xdb, 0x2f, 0x19, 0xa0, 0x15, 0x30, 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x81, 0x01, 0x0a, 0x2d, 0x01, 0x2e, 0x81, + 0x01, 0x05, 0x28, 0x42, 0x36, 0x21, 0x2e, 0x81, 0x01, 0x02, 0x0e, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, + 0x12, 0x30, 0x01, 0x40, 0x98, 0x2e, 0xfe, 0xc9, 0x51, 0x6f, 0x0b, 0x5c, 0x8e, 0x0e, 0x3b, 0x6f, 0x57, 0x58, 0x02, + 0x30, 0x21, 0x2e, 0x95, 0x01, 0x45, 0x6f, 0x2a, 0x8d, 0xd2, 0x7f, 0xcb, 0x7f, 0x13, 0x2f, 0x02, 0x30, 0x3f, 0x50, + 0xd2, 0x7f, 0xa8, 0x0e, 0x0e, 0x2f, 0xc0, 0x6f, 0x53, 0x54, 0x02, 0x00, 0x51, 0x54, 0x42, 0x0e, 0x10, 0x30, 0x59, + 0x52, 0x02, 0x30, 0x01, 0x2f, 0x00, 0x2e, 0x03, 0x2d, 0x50, 0x42, 0x42, 0x42, 0x12, 0x30, 0xd2, 0x7f, 0x80, 0xb2, + 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x80, 0x01, 0x12, 0x2d, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x80, 0x05, 0x2e, 0x80, + 0x01, 0x11, 0x30, 0x91, 0x28, 0x00, 0x40, 0x25, 0x2e, 0x80, 0x01, 0x10, 0x0e, 0x05, 0x2f, 0x01, 0x2e, 0x7f, 0x01, + 0x01, 0x90, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x00, 0x2e, 0xa0, 0x41, 0x01, 0x90, 0xa6, 0x7f, 0x90, 0x2e, 0xe3, + 0xb4, 0x01, 0x2e, 0x95, 0x01, 0x00, 0xa8, 0x90, 0x2e, 0xe3, 0xb4, 0x5b, 0x54, 0x95, 0x80, 0x82, 0x40, 0x80, 0xb2, + 0x02, 0x40, 0x2d, 0x8c, 0x3f, 0x52, 0x96, 0x7f, 0x90, 0x2e, 0xc2, 0xb3, 0x29, 0x0e, 0x76, 0x2f, 0x01, 0x2e, 0xc9, + 0x00, 0x00, 0x40, 0x81, 0x28, 0x45, 0x52, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, 0x5d, 0x54, 0x80, 0x7f, 0x00, 0x2e, + 0xa1, 0x40, 0x72, 0x7f, 0x82, 0x80, 0x82, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, + 0xc0, 0x62, 0x6f, 0x05, 0x30, 0x87, 0x40, 0xc0, 0x91, 0x04, 0x30, 0x05, 0x2f, 0x05, 0x2e, 0x83, 0x01, 0x80, 0xb2, + 0x14, 0x30, 0x00, 0x2f, 0x04, 0x30, 0x05, 0x2e, 0xc9, 0x00, 0x73, 0x6f, 0x81, 0x40, 0xe2, 0x40, 0x69, 0x04, 0x11, + 0x0f, 0xe1, 0x40, 0x16, 0x30, 0xfe, 0x29, 0xcb, 0x40, 0x02, 0x2f, 0x83, 0x6f, 0x83, 0x0f, 0x22, 0x2f, 0x47, 0x56, + 0x13, 0x0f, 0x12, 0x30, 0x77, 0x2f, 0x49, 0x54, 0x42, 0x0e, 0x12, 0x30, 0x73, 0x2f, 0x00, 0x91, 0x0a, 0x2f, 0x01, + 0x2e, 0x8b, 0x01, 0x19, 0xa8, 0x02, 0x30, 0x6c, 0x2f, 0x63, 0x50, 0x00, 0x2e, 0x17, 0x42, 0x05, 0x42, 0x68, 0x2c, + 0x12, 0x30, 0x0b, 0x25, 0x08, 0x0f, 0x50, 0x30, 0x02, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x03, 0x2d, 0x40, 0x30, 0x21, + 0x2e, 0x83, 0x01, 0x2b, 0x2e, 0x85, 0x01, 0x5a, 0x2c, 0x12, 0x30, 0x00, 0x91, 0x2b, 0x25, 0x04, 0x2f, 0x63, 0x50, + 0x02, 0x30, 0x17, 0x42, 0x17, 0x2c, 0x02, 0x42, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x05, + 0x2e, 0xc9, 0x00, 0x81, 0x84, 0x5b, 0x30, 0x82, 0x40, 0x37, 0x2e, 0x83, 0x01, 0x02, 0x0e, 0x07, 0x2f, 0x5f, 0x52, + 0x40, 0x30, 0x62, 0x40, 0x41, 0x40, 0x91, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x05, 0x30, 0x2b, 0x2e, 0x85, + 0x01, 0x12, 0x30, 0x36, 0x2c, 0x16, 0x30, 0x15, 0x25, 0x81, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, + 0x74, 0xc0, 0x19, 0xa2, 0x16, 0x30, 0x15, 0x2f, 0x05, 0x2e, 0x97, 0x01, 0x80, 0x6f, 0x82, 0x0e, 0x05, 0x2f, 0x01, + 0x2e, 0x86, 0x01, 0x06, 0x28, 0x21, 0x2e, 0x86, 0x01, 0x0b, 0x2d, 0x03, 0x2e, 0x87, 0x01, 0x5f, 0x54, 0x4e, 0x28, + 0x91, 0x42, 0x00, 0x2e, 0x82, 0x40, 0x90, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x88, 0x01, 0x02, 0x30, 0x13, 0x2c, 0x05, + 0x30, 0xc0, 0x6f, 0x08, 0x1c, 0xa8, 0x0f, 0x16, 0x30, 0x05, 0x30, 0x5b, 0x50, 0x09, 0x2f, 0x02, 0x80, 0x2d, 0x2e, + 0x82, 0x01, 0x05, 0x42, 0x05, 0x80, 0x00, 0x2e, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x06, 0x42, 0x02, 0x30, 0x90, + 0x6f, 0x3e, 0x88, 0x01, 0x40, 0x04, 0x41, 0x4c, 0x28, 0x01, 0x42, 0x07, 0x80, 0x10, 0x25, 0x24, 0x40, 0x00, 0x40, + 0x00, 0xa8, 0xf5, 0x22, 0x23, 0x29, 0x44, 0x42, 0x7a, 0x82, 0x7e, 0x88, 0x43, 0x40, 0x04, 0x41, 0x00, 0xab, 0xf5, + 0x23, 0xdf, 0x28, 0x43, 0x42, 0xd9, 0xa0, 0x14, 0x2f, 0x00, 0x90, 0x02, 0x2f, 0xd2, 0x6f, 0x81, 0xb2, 0x05, 0x2f, + 0x63, 0x54, 0x06, 0x28, 0x90, 0x42, 0x85, 0x42, 0x09, 0x2c, 0x02, 0x30, 0x5b, 0x50, 0x03, 0x80, 0x29, 0x2e, 0x7e, + 0x01, 0x2b, 0x2e, 0x82, 0x01, 0x05, 0x42, 0x12, 0x30, 0x2b, 0x2e, 0x83, 0x01, 0x45, 0x82, 0x00, 0x2e, 0x40, 0x40, + 0x7a, 0x82, 0x02, 0xa0, 0x08, 0x2f, 0x63, 0x50, 0x3b, 0x30, 0x15, 0x42, 0x05, 0x42, 0x37, 0x80, 0x37, 0x2e, 0x7e, + 0x01, 0x05, 0x42, 0x12, 0x30, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x8c, 0x40, 0x40, 0x84, 0x41, 0x7a, 0x8c, 0x04, 0x0f, + 0x03, 0x2f, 0x01, 0x2e, 0x8b, 0x01, 0x19, 0xa4, 0x04, 0x2f, 0x2b, 0x2e, 0x82, 0x01, 0x98, 0x2e, 0xf3, 0x03, 0x12, + 0x30, 0x81, 0x90, 0x61, 0x52, 0x08, 0x2f, 0x65, 0x42, 0x65, 0x42, 0x43, 0x80, 0x39, 0x84, 0x82, 0x88, 0x05, 0x42, + 0x45, 0x42, 0x85, 0x42, 0x05, 0x43, 0x00, 0x2e, 0x80, 0x41, 0x00, 0x90, 0x90, 0x2e, 0xe1, 0xb4, 0x65, 0x54, 0xc1, + 0x6f, 0x80, 0x40, 0x00, 0xb2, 0x43, 0x58, 0x69, 0x50, 0x44, 0x2f, 0x55, 0x5c, 0xb7, 0x87, 0x8c, 0x0f, 0x0d, 0x2e, + 0x96, 0x01, 0xc4, 0x40, 0x36, 0x2f, 0x41, 0x56, 0x8b, 0x0e, 0x2a, 0x2f, 0x0b, 0x52, 0xa1, 0x0e, 0x0a, 0x2f, 0x05, + 0x2e, 0x8f, 0x01, 0x14, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x4b, 0x54, 0x02, 0x0f, 0x69, 0x50, 0x05, 0x30, 0x65, 0x54, + 0x15, 0x2f, 0x03, 0x2e, 0x8e, 0x01, 0x4d, 0x5c, 0x8e, 0x0f, 0x3a, 0x2f, 0x05, 0x2e, 0x8f, 0x01, 0x98, 0x2e, 0xfe, + 0xc9, 0x4f, 0x54, 0x82, 0x0f, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x30, 0x2f, 0x6d, 0x52, 0x15, 0x30, 0x42, 0x8c, + 0x45, 0x42, 0x04, 0x30, 0x2b, 0x2c, 0x84, 0x43, 0x6b, 0x52, 0x42, 0x8c, 0x00, 0x2e, 0x85, 0x43, 0x15, 0x30, 0x24, + 0x2c, 0x45, 0x42, 0x8e, 0x0f, 0x20, 0x2f, 0x0d, 0x2e, 0x8e, 0x01, 0xb1, 0x0e, 0x1c, 0x2f, 0x23, 0x2e, 0x8e, 0x01, + 0x1a, 0x2d, 0x0e, 0x0e, 0x17, 0x2f, 0xa1, 0x0f, 0x15, 0x2f, 0x23, 0x2e, 0x8d, 0x01, 0x13, 0x2d, 0x98, 0x2e, 0x74, + 0xc0, 0x43, 0x54, 0xc2, 0x0e, 0x0a, 0x2f, 0x65, 0x50, 0x04, 0x80, 0x0b, 0x30, 0x06, 0x82, 0x0b, 0x42, 0x79, 0x80, + 0x41, 0x40, 0x12, 0x30, 0x25, 0x2e, 0x8c, 0x01, 0x01, 0x42, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x84, 0x82, 0x43, + 0x84, 0xbe, 0x8c, 0x84, 0x40, 0x86, 0x41, 0x26, 0x29, 0x94, 0x42, 0xbe, 0x8e, 0xd5, 0x7f, 0x19, 0xa1, 0x43, 0x40, + 0x0b, 0x2e, 0x8c, 0x01, 0x84, 0x40, 0xc7, 0x41, 0x5d, 0x29, 0x27, 0x29, 0x45, 0x42, 0x84, 0x42, 0xc2, 0x7f, 0x01, + 0x2f, 0xc0, 0xb3, 0x1d, 0x2f, 0x05, 0x2e, 0x94, 0x01, 0x99, 0xa0, 0x01, 0x2f, 0x80, 0xb3, 0x13, 0x2f, 0x80, 0xb3, + 0x18, 0x2f, 0xc0, 0xb3, 0x16, 0x2f, 0x12, 0x40, 0x01, 0x40, 0x92, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x92, 0x6f, 0x10, + 0x0f, 0x20, 0x30, 0x03, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x0a, 0x2d, 0x21, 0x2e, 0x7e, 0x01, 0x07, 0x2d, + 0x20, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x03, 0x2d, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0xc2, 0x6f, 0x01, 0x2e, 0xc9, + 0x00, 0xbc, 0x84, 0x02, 0x80, 0x82, 0x40, 0x00, 0x40, 0x90, 0x0e, 0xd5, 0x6f, 0x02, 0x2f, 0x15, 0x30, 0x98, 0x2e, + 0xf3, 0x03, 0x41, 0x91, 0x05, 0x30, 0x07, 0x2f, 0x67, 0x50, 0x3d, 0x80, 0x2b, 0x2e, 0x8f, 0x01, 0x05, 0x42, 0x04, + 0x80, 0x00, 0x2e, 0x05, 0x42, 0x02, 0x2c, 0x00, 0x30, 0x00, 0x30, 0xa2, 0x6f, 0x98, 0x8a, 0x86, 0x40, 0x80, 0xa7, + 0x05, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0xc0, 0x30, 0x21, 0x2e, 0x95, 0x01, 0x06, 0x25, 0x1a, 0x25, 0xe2, 0x6f, 0x76, + 0x82, 0x96, 0x40, 0x56, 0x43, 0x51, 0x0e, 0xfb, 0x2f, 0xbb, 0x6f, 0x30, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xb8, 0x00, + 0x01, 0x31, 0x41, 0x08, 0x40, 0xb2, 0x20, 0x50, 0xf2, 0x30, 0x02, 0x08, 0xfb, 0x7f, 0x01, 0x30, 0x10, 0x2f, 0x05, + 0x2e, 0xcc, 0x00, 0x81, 0x90, 0xe0, 0x7f, 0x03, 0x2f, 0x23, 0x2e, 0xcc, 0x00, 0x98, 0x2e, 0x55, 0xb6, 0x98, 0x2e, + 0x1d, 0xb5, 0x10, 0x25, 0xfb, 0x6f, 0xe0, 0x6f, 0xe0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, 0x98, 0x2e, 0x95, 0xcf, 0x10, + 0x30, 0x21, 0x2e, 0xcc, 0x00, 0xfb, 0x6f, 0xe0, 0x5f, 0xb8, 0x2e, 0x00, 0x51, 0x05, 0x58, 0xeb, 0x7f, 0x2a, 0x25, + 0x89, 0x52, 0x6f, 0x5a, 0x89, 0x50, 0x13, 0x41, 0x06, 0x40, 0xb3, 0x01, 0x16, 0x42, 0xcb, 0x16, 0x06, 0x40, 0xf3, + 0x02, 0x13, 0x42, 0x65, 0x0e, 0xf5, 0x2f, 0x05, 0x40, 0x14, 0x30, 0x2c, 0x29, 0x04, 0x42, 0x08, 0xa1, 0x00, 0x30, + 0x90, 0x2e, 0x52, 0xb6, 0xb3, 0x88, 0xb0, 0x8a, 0xb6, 0x84, 0xa4, 0x7f, 0xc4, 0x7f, 0xb5, 0x7f, 0xd5, 0x7f, 0x92, + 0x7f, 0x73, 0x30, 0x04, 0x30, 0x55, 0x40, 0x42, 0x40, 0x8a, 0x17, 0xf3, 0x08, 0x6b, 0x01, 0x90, 0x02, 0x53, 0xb8, + 0x4b, 0x82, 0xad, 0xbe, 0x71, 0x7f, 0x45, 0x0a, 0x09, 0x54, 0x84, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0xa3, 0x6f, 0x7b, + 0x54, 0xd0, 0x42, 0xa3, 0x7f, 0xf2, 0x7f, 0x60, 0x7f, 0x20, 0x25, 0x71, 0x6f, 0x75, 0x5a, 0x77, 0x58, 0x79, 0x5c, + 0x75, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xb1, 0x6f, 0x62, 0x6f, 0x50, 0x42, 0xb1, 0x7f, 0xb3, 0x30, 0x10, 0x25, 0x98, + 0x2e, 0x0f, 0xca, 0x84, 0x6f, 0x20, 0x29, 0x71, 0x6f, 0x92, 0x6f, 0xa5, 0x6f, 0x76, 0x82, 0x6a, 0x0e, 0x73, 0x30, + 0x00, 0x30, 0xd0, 0x2f, 0xd2, 0x6f, 0xd1, 0x7f, 0xb4, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x02, + 0x0a, 0xc2, 0x6f, 0xc0, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x42, 0x0a, 0xc0, 0x6f, 0x08, 0x17, + 0x41, 0x18, 0x89, 0x16, 0xe1, 0x18, 0xd0, 0x18, 0xa1, 0x7f, 0x27, 0x25, 0x16, 0x25, 0x98, 0x2e, 0x79, 0xc0, 0x8b, + 0x54, 0x90, 0x7f, 0xb3, 0x30, 0x82, 0x40, 0x80, 0x90, 0x0d, 0x2f, 0x7d, 0x52, 0x92, 0x6f, 0x98, 0x2e, 0x0f, 0xca, + 0xb2, 0x6f, 0x90, 0x0e, 0x06, 0x2f, 0x8b, 0x50, 0x14, 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x14, 0x42, 0x12, 0x42, 0x01, + 0x42, 0x00, 0x2e, 0x31, 0x6f, 0x98, 0x2e, 0x74, 0xc0, 0x41, 0x6f, 0x80, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x82, 0x6f, + 0x10, 0x04, 0x43, 0x52, 0x01, 0x0f, 0x05, 0x2e, 0xcb, 0x00, 0x00, 0x30, 0x04, 0x30, 0x21, 0x2f, 0x51, 0x6f, 0x43, + 0x58, 0x8c, 0x0e, 0x04, 0x30, 0x1c, 0x2f, 0x85, 0x88, 0x41, 0x6f, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, 0x16, 0x2f, + 0x84, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x0f, 0x2f, 0x82, 0x88, 0x31, 0x6f, 0x04, + 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x08, 0x2f, 0x83, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, + 0x02, 0x2f, 0x21, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x00, 0x91, 0x14, 0x2f, 0x03, 0x2e, 0xa1, 0x01, 0x41, 0x90, 0x0e, + 0x2f, 0x03, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x4c, 0x28, 0x23, 0x2e, 0xad, 0x01, 0x46, 0xa0, 0x06, 0x2f, 0x81, 0x84, + 0x8d, 0x52, 0x48, 0x82, 0x82, 0x40, 0x21, 0x2e, 0xa1, 0x01, 0x42, 0x42, 0x5c, 0x2c, 0x02, 0x30, 0x05, 0x2e, 0xaa, + 0x01, 0x80, 0xb2, 0x02, 0x30, 0x55, 0x2f, 0x03, 0x2e, 0xa9, 0x01, 0x92, 0x6f, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, + 0xb2, 0x6f, 0x90, 0x0f, 0x00, 0x30, 0x02, 0x30, 0x4a, 0x2f, 0xa2, 0x6f, 0x87, 0x52, 0x91, 0x00, 0x85, 0x52, 0x51, + 0x0e, 0x02, 0x2f, 0x00, 0x2e, 0x43, 0x2c, 0x02, 0x30, 0xc2, 0x6f, 0x7f, 0x52, 0x91, 0x0e, 0x02, 0x30, 0x3c, 0x2f, + 0x51, 0x6f, 0x81, 0x54, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0xb3, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x32, + 0x6f, 0xc0, 0x7f, 0xb3, 0x30, 0x12, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x42, 0x6f, 0xb0, 0x7f, 0xb3, 0x30, 0x12, 0x25, + 0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x28, 0x83, 0x52, 0x98, 0x2e, 0xfe, 0xc9, 0xc2, 0x6f, 0x90, 0x0f, 0x00, + 0x30, 0x02, 0x30, 0x1d, 0x2f, 0x05, 0x2e, 0xa1, 0x01, 0x80, 0xb2, 0x12, 0x30, 0x0f, 0x2f, 0x42, 0x6f, 0x03, 0x2e, + 0xab, 0x01, 0x91, 0x0e, 0x02, 0x30, 0x12, 0x2f, 0x52, 0x6f, 0x03, 0x2e, 0xac, 0x01, 0x91, 0x0f, 0x02, 0x30, 0x0c, + 0x2f, 0x21, 0x2e, 0xaa, 0x01, 0x0a, 0x2c, 0x12, 0x30, 0x03, 0x2e, 0xcb, 0x00, 0x8d, 0x58, 0x08, 0x89, 0x41, 0x40, + 0x11, 0x43, 0x00, 0x43, 0x25, 0x2e, 0xa1, 0x01, 0xd4, 0x6f, 0x8f, 0x52, 0x00, 0x43, 0x3a, 0x89, 0x00, 0x2e, 0x10, + 0x43, 0x10, 0x43, 0x61, 0x0e, 0xfb, 0x2f, 0x03, 0x2e, 0xa0, 0x01, 0x11, 0x1a, 0x02, 0x2f, 0x02, 0x25, 0x21, 0x2e, + 0xa0, 0x01, 0xeb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x91, 0x52, 0x10, 0x30, 0x02, 0x30, 0x95, 0x56, 0x52, 0x42, 0x4b, + 0x0e, 0xfc, 0x2f, 0x8d, 0x54, 0x88, 0x82, 0x93, 0x56, 0x80, 0x42, 0x53, 0x42, 0x40, 0x42, 0x42, 0x86, 0x83, 0x54, + 0xc0, 0x2e, 0xc2, 0x42, 0x00, 0x2e, 0xa3, 0x52, 0x00, 0x51, 0x52, 0x40, 0x47, 0x40, 0x1a, 0x25, 0x01, 0x2e, 0x97, + 0x00, 0x8f, 0xbe, 0x72, 0x86, 0xfb, 0x7f, 0x0b, 0x30, 0x7c, 0xbf, 0xa5, 0x50, 0x10, 0x08, 0xdf, 0xba, 0x70, 0x88, + 0xf8, 0xbf, 0xcb, 0x42, 0xd3, 0x7f, 0x6c, 0xbb, 0xfc, 0xbb, 0xc5, 0x0a, 0x90, 0x7f, 0x1b, 0x7f, 0x0b, 0x43, 0xc0, + 0xb2, 0xe5, 0x7f, 0xb7, 0x7f, 0xa6, 0x7f, 0xc4, 0x7f, 0x90, 0x2e, 0x1c, 0xb7, 0x07, 0x2e, 0xd2, 0x00, 0xc0, 0xb2, + 0x0b, 0x2f, 0x97, 0x52, 0x01, 0x2e, 0xcd, 0x00, 0x82, 0x7f, 0x98, 0x2e, 0xbb, 0xcc, 0x0b, 0x30, 0x37, 0x2e, 0xd2, + 0x00, 0x82, 0x6f, 0x90, 0x6f, 0x1a, 0x25, 0x00, 0xb2, 0x8b, 0x7f, 0x14, 0x2f, 0xa6, 0xbd, 0x25, 0xbd, 0xb6, 0xb9, + 0x2f, 0xb9, 0x80, 0xb2, 0xd4, 0xb0, 0x0c, 0x2f, 0x99, 0x54, 0x9b, 0x56, 0x0b, 0x30, 0x0b, 0x2e, 0xb1, 0x00, 0xa1, + 0x58, 0x9b, 0x42, 0xdb, 0x42, 0x6c, 0x09, 0x2b, 0x2e, 0xb1, 0x00, 0x8b, 0x42, 0xcb, 0x42, 0x86, 0x7f, 0x73, 0x84, + 0xa7, 0x56, 0xc3, 0x08, 0x39, 0x52, 0x05, 0x50, 0x72, 0x7f, 0x63, 0x7f, 0x98, 0x2e, 0xc2, 0xc0, 0xe1, 0x6f, 0x62, + 0x6f, 0xd1, 0x0a, 0x01, 0x2e, 0xcd, 0x00, 0xd5, 0x6f, 0xc4, 0x6f, 0x72, 0x6f, 0x97, 0x52, 0x9d, 0x5c, 0x98, 0x2e, + 0x06, 0xcd, 0x23, 0x6f, 0x90, 0x6f, 0x99, 0x52, 0xc0, 0xb2, 0x04, 0xbd, 0x54, 0x40, 0xaf, 0xb9, 0x45, 0x40, 0xe1, + 0x7f, 0x02, 0x30, 0x06, 0x2f, 0xc0, 0xb2, 0x02, 0x30, 0x03, 0x2f, 0x9b, 0x5c, 0x12, 0x30, 0x94, 0x43, 0x85, 0x43, + 0x03, 0xbf, 0x6f, 0xbb, 0x80, 0xb3, 0x20, 0x2f, 0x06, 0x6f, 0x26, 0x01, 0x16, 0x6f, 0x6e, 0x03, 0x45, 0x42, 0xc0, + 0x90, 0x29, 0x2e, 0xce, 0x00, 0x9b, 0x52, 0x14, 0x2f, 0x9b, 0x5c, 0x00, 0x2e, 0x93, 0x41, 0x86, 0x41, 0xe3, 0x04, + 0xae, 0x07, 0x80, 0xab, 0x04, 0x2f, 0x80, 0x91, 0x0a, 0x2f, 0x86, 0x6f, 0x73, 0x0f, 0x07, 0x2f, 0x83, 0x6f, 0xc0, + 0xb2, 0x04, 0x2f, 0x54, 0x42, 0x45, 0x42, 0x12, 0x30, 0x04, 0x2c, 0x11, 0x30, 0x02, 0x2c, 0x11, 0x30, 0x11, 0x30, + 0x02, 0xbc, 0x0f, 0xb8, 0xd2, 0x7f, 0x00, 0xb2, 0x0a, 0x2f, 0x01, 0x2e, 0xfc, 0x00, 0x05, 0x2e, 0xc7, 0x01, 0x10, + 0x1a, 0x02, 0x2f, 0x21, 0x2e, 0xc7, 0x01, 0x03, 0x2d, 0x02, 0x2c, 0x01, 0x30, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xd1, 0x6f, 0xa0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xe2, 0x6f, 0x9f, 0x52, 0x01, 0x2e, 0xce, 0x00, 0x82, + 0x40, 0x50, 0x42, 0x0c, 0x2c, 0x42, 0x42, 0x11, 0x30, 0x23, 0x2e, 0xd2, 0x00, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xa0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0x00, 0x2e, 0xfb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x83, + 0x86, 0x01, 0x30, 0x00, 0x30, 0x94, 0x40, 0x24, 0x18, 0x06, 0x00, 0x53, 0x0e, 0x4f, 0x02, 0xf9, 0x2f, 0xb8, 0x2e, + 0xa9, 0x52, 0x00, 0x2e, 0x60, 0x40, 0x41, 0x40, 0x0d, 0xbc, 0x98, 0xbc, 0xc0, 0x2e, 0x01, 0x0a, 0x0f, 0xb8, 0xab, + 0x52, 0x53, 0x3c, 0x52, 0x40, 0x40, 0x40, 0x4b, 0x00, 0x82, 0x16, 0x26, 0xb9, 0x01, 0xb8, 0x41, 0x40, 0x10, 0x08, + 0x97, 0xb8, 0x01, 0x08, 0xc0, 0x2e, 0x11, 0x30, 0x01, 0x08, 0x43, 0x86, 0x25, 0x40, 0x04, 0x40, 0xd8, 0xbe, 0x2c, + 0x0b, 0x22, 0x11, 0x54, 0x42, 0x03, 0x80, 0x4b, 0x0e, 0xf6, 0x2f, 0xb8, 0x2e, 0x9f, 0x50, 0x10, 0x50, 0xad, 0x52, + 0x05, 0x2e, 0xd3, 0x00, 0xfb, 0x7f, 0x00, 0x2e, 0x13, 0x40, 0x93, 0x42, 0x41, 0x0e, 0xfb, 0x2f, 0x98, 0x2e, 0xa5, + 0xb7, 0x98, 0x2e, 0x87, 0xcf, 0x01, 0x2e, 0xd9, 0x00, 0x00, 0xb2, 0xfb, 0x6f, 0x0b, 0x2f, 0x01, 0x2e, 0x69, 0xf7, + 0xb1, 0x3f, 0x01, 0x08, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd9, 0x00, 0x21, 0x2e, 0x69, 0xf7, 0x80, 0x2e, 0x7a, + 0xb7, 0xf0, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xc0, 0xf8, 0x03, 0x2e, 0xfc, 0xf5, 0x15, 0x54, 0xaf, 0x56, 0x82, 0x08, + 0x0b, 0x2e, 0x69, 0xf7, 0xcb, 0x0a, 0xb1, 0x58, 0x80, 0x90, 0xdd, 0xbe, 0x4c, 0x08, 0x5f, 0xb9, 0x59, 0x22, 0x80, + 0x90, 0x07, 0x2f, 0x03, 0x34, 0xc3, 0x08, 0xf2, 0x3a, 0x0a, 0x08, 0x02, 0x35, 0xc0, 0x90, 0x4a, 0x0a, 0x48, 0x22, + 0xc0, 0x2e, 0x23, 0x2e, 0xfc, 0xf5, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x56, 0xc7, 0x98, 0x2e, 0x49, 0xc3, 0x10, + 0x30, 0xfb, 0x6f, 0xf0, 0x5f, 0x21, 0x2e, 0xcc, 0x00, 0x21, 0x2e, 0xca, 0x00, 0xb8, 0x2e, 0x03, 0x2e, 0xd3, 0x00, + 0x16, 0xb8, 0x02, 0x34, 0x4a, 0x0c, 0x21, 0x2e, 0x2d, 0xf5, 0xc0, 0x2e, 0x23, 0x2e, 0xd3, 0x00, 0x03, 0xbc, 0x21, + 0x2e, 0xd5, 0x00, 0x03, 0x2e, 0xd5, 0x00, 0x40, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x30, 0x05, 0x2f, + 0x05, 0x2e, 0xd8, 0x00, 0x80, 0x90, 0x01, 0x2f, 0x23, 0x2e, 0x6f, 0xf5, 0xc0, 0x2e, 0x21, 0x2e, 0xd9, 0x00, 0x11, + 0x30, 0x81, 0x08, 0x01, 0x2e, 0x6a, 0xf7, 0x71, 0x3f, 0x23, 0xbd, 0x01, 0x08, 0x02, 0x0a, 0xc0, 0x2e, 0x21, 0x2e, + 0x6a, 0xf7, 0x30, 0x25, 0x00, 0x30, 0x21, 0x2e, 0x5a, 0xf5, 0x10, 0x50, 0x21, 0x2e, 0x7b, 0x00, 0x21, 0x2e, 0x7c, + 0x00, 0xfb, 0x7f, 0x98, 0x2e, 0xc3, 0xb7, 0x40, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0x03, 0x25, + 0x80, 0x2e, 0xaf, 0xb7, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x01, 0x2e, 0x5d, 0xf7, 0x08, 0xbc, 0x80, 0xac, 0x0e, 0xbb, 0x02, 0x2f, + 0x00, 0x30, 0x41, 0x04, 0x82, 0x06, 0xc0, 0xa4, 0x00, 0x30, 0x11, 0x2f, 0x40, 0xa9, 0x03, 0x2f, 0x40, 0x91, 0x0d, + 0x2f, 0x00, 0xa7, 0x0b, 0x2f, 0x80, 0xb3, 0xb3, 0x58, 0x02, 0x2f, 0x90, 0xa1, 0x26, 0x13, 0x20, 0x23, 0x80, 0x90, + 0x10, 0x30, 0x01, 0x2f, 0xcc, 0x0e, 0x00, 0x2f, 0x00, 0x30, 0xb8, 0x2e, 0xb5, 0x50, 0x18, 0x08, 0x08, 0xbc, 0x88, + 0xb6, 0x0d, 0x17, 0xc6, 0xbd, 0x56, 0xbc, 0xb7, 0x58, 0xda, 0xba, 0x04, 0x01, 0x1d, 0x0a, 0x10, 0x50, 0x05, 0x30, + 0x32, 0x25, 0x45, 0x03, 0xfb, 0x7f, 0xf6, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x16, 0xb5, 0x9a, 0xbc, 0x06, + 0xb8, 0x80, 0xa8, 0x41, 0x0a, 0x0e, 0x2f, 0x80, 0x90, 0x02, 0x2f, 0x2d, 0x50, 0x48, 0x0f, 0x09, 0x2f, 0xbf, 0xa0, + 0x04, 0x2f, 0xbf, 0x90, 0x06, 0x2f, 0xb7, 0x54, 0xca, 0x0f, 0x03, 0x2f, 0x00, 0x2e, 0x02, 0x2c, 0xb7, 0x52, 0x2d, + 0x52, 0xf2, 0x33, 0x98, 0x2e, 0xd9, 0xc0, 0xfb, 0x6f, 0xf1, 0x37, 0xc0, 0x2e, 0x01, 0x08, 0xf0, 0x5f, 0xbf, 0x56, + 0xb9, 0x54, 0xd0, 0x40, 0xc4, 0x40, 0x0b, 0x2e, 0xfd, 0xf3, 0xbf, 0x52, 0x90, 0x42, 0x94, 0x42, 0x95, 0x42, 0x05, + 0x30, 0xc1, 0x50, 0x0f, 0x88, 0x06, 0x40, 0x04, 0x41, 0x96, 0x42, 0xc5, 0x42, 0x48, 0xbe, 0x73, 0x30, 0x0d, 0x2e, + 0xd8, 0x00, 0x4f, 0xba, 0x84, 0x42, 0x03, 0x42, 0x81, 0xb3, 0x02, 0x2f, 0x2b, 0x2e, 0x6f, 0xf5, 0x06, 0x2d, 0x05, + 0x2e, 0x77, 0xf7, 0xbd, 0x56, 0x93, 0x08, 0x25, 0x2e, 0x77, 0xf7, 0xbb, 0x54, 0x25, 0x2e, 0xc2, 0xf5, 0x07, 0x2e, + 0xfd, 0xf3, 0x42, 0x30, 0xb4, 0x33, 0xda, 0x0a, 0x4c, 0x00, 0x27, 0x2e, 0xfd, 0xf3, 0x43, 0x40, 0xd4, 0x3f, 0xdc, + 0x08, 0x43, 0x42, 0x00, 0x2e, 0x00, 0x2e, 0x43, 0x40, 0x24, 0x30, 0xdc, 0x0a, 0x43, 0x42, 0x04, 0x80, 0x03, 0x2e, + 0xfd, 0xf3, 0x4a, 0x0a, 0x23, 0x2e, 0xfd, 0xf3, 0x61, 0x34, 0xc0, 0x2e, 0x01, 0x42, 0x00, 0x2e, 0x60, 0x50, 0x1a, + 0x25, 0x7a, 0x86, 0xe0, 0x7f, 0xf3, 0x7f, 0x03, 0x25, 0xc3, 0x52, 0x41, 0x84, 0xdb, 0x7f, 0x33, 0x30, 0x98, 0x2e, + 0x16, 0xc2, 0x1a, 0x25, 0x7d, 0x82, 0xf0, 0x6f, 0xe2, 0x6f, 0x32, 0x25, 0x16, 0x40, 0x94, 0x40, 0x26, 0x01, 0x85, + 0x40, 0x8e, 0x17, 0xc4, 0x42, 0x6e, 0x03, 0x95, 0x42, 0x41, 0x0e, 0xf4, 0x2f, 0xdb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, + 0xb0, 0x51, 0xfb, 0x7f, 0x98, 0x2e, 0xe8, 0x0d, 0x5a, 0x25, 0x98, 0x2e, 0x0f, 0x0e, 0xcb, 0x58, 0x32, 0x87, 0xc4, + 0x7f, 0x65, 0x89, 0x6b, 0x8d, 0xc5, 0x5a, 0x65, 0x7f, 0xe1, 0x7f, 0x83, 0x7f, 0xa6, 0x7f, 0x74, 0x7f, 0xd0, 0x7f, + 0xb6, 0x7f, 0x94, 0x7f, 0x17, 0x30, 0xc7, 0x52, 0xc9, 0x54, 0x51, 0x7f, 0x00, 0x2e, 0x85, 0x6f, 0x42, 0x7f, 0x00, + 0x2e, 0x51, 0x41, 0x45, 0x81, 0x42, 0x41, 0x13, 0x40, 0x3b, 0x8a, 0x00, 0x40, 0x4b, 0x04, 0xd0, 0x06, 0xc0, 0xac, + 0x85, 0x7f, 0x02, 0x2f, 0x02, 0x30, 0x51, 0x04, 0xd3, 0x06, 0x41, 0x84, 0x05, 0x30, 0x5d, 0x02, 0xc9, 0x16, 0xdf, + 0x08, 0xd3, 0x00, 0x8d, 0x02, 0xaf, 0xbc, 0xb1, 0xb9, 0x59, 0x0a, 0x65, 0x6f, 0x11, 0x43, 0xa1, 0xb4, 0x52, 0x41, + 0x53, 0x41, 0x01, 0x43, 0x34, 0x7f, 0x65, 0x7f, 0x26, 0x31, 0xe5, 0x6f, 0xd4, 0x6f, 0x98, 0x2e, 0x37, 0xca, 0x32, + 0x6f, 0x75, 0x6f, 0x83, 0x40, 0x42, 0x41, 0x23, 0x7f, 0x12, 0x7f, 0xf6, 0x30, 0x40, 0x25, 0x51, 0x25, 0x98, 0x2e, + 0x37, 0xca, 0x14, 0x6f, 0x20, 0x05, 0x70, 0x6f, 0x25, 0x6f, 0x69, 0x07, 0xa2, 0x6f, 0x31, 0x6f, 0x0b, 0x30, 0x04, + 0x42, 0x9b, 0x42, 0x8b, 0x42, 0x55, 0x42, 0x32, 0x7f, 0x40, 0xa9, 0xc3, 0x6f, 0x71, 0x7f, 0x02, 0x30, 0xd0, 0x40, + 0xc3, 0x7f, 0x03, 0x2f, 0x40, 0x91, 0x15, 0x2f, 0x00, 0xa7, 0x13, 0x2f, 0x00, 0xa4, 0x11, 0x2f, 0x84, 0xbd, 0x98, + 0x2e, 0x79, 0xca, 0x55, 0x6f, 0xb7, 0x54, 0x54, 0x41, 0x82, 0x00, 0xf3, 0x3f, 0x45, 0x41, 0xcb, 0x02, 0xf6, 0x30, + 0x98, 0x2e, 0x37, 0xca, 0x35, 0x6f, 0xa4, 0x6f, 0x41, 0x43, 0x03, 0x2c, 0x00, 0x43, 0xa4, 0x6f, 0x35, 0x6f, 0x17, + 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x93, 0x40, 0x42, 0x82, 0x00, 0x41, 0xc3, 0x00, 0x03, 0x43, 0x51, 0x7f, 0x00, 0x2e, + 0x94, 0x40, 0x41, 0x41, 0x4c, 0x02, 0xc4, 0x6f, 0xd1, 0x56, 0x63, 0x0e, 0x74, 0x6f, 0x51, 0x43, 0xa5, 0x7f, 0x8a, + 0x2f, 0x09, 0x2e, 0xd8, 0x00, 0x01, 0xb3, 0x21, 0x2f, 0xcb, 0x58, 0x90, 0x6f, 0x13, 0x41, 0xb6, 0x6f, 0xe4, 0x7f, + 0x00, 0x2e, 0x91, 0x41, 0x14, 0x40, 0x92, 0x41, 0x15, 0x40, 0x17, 0x2e, 0x6f, 0xf5, 0xb6, 0x7f, 0xd0, 0x7f, 0xcb, + 0x7f, 0x98, 0x2e, 0x00, 0x0c, 0x07, 0x15, 0xc2, 0x6f, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0xc3, 0xa3, 0xc1, 0x8f, + 0xe4, 0x6f, 0xd0, 0x6f, 0xe6, 0x2f, 0x14, 0x30, 0x05, 0x2e, 0x6f, 0xf5, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0x18, + 0x2d, 0xcd, 0x56, 0x04, 0x32, 0xb5, 0x6f, 0x1c, 0x01, 0x51, 0x41, 0x52, 0x41, 0xc3, 0x40, 0xb5, 0x7f, 0xe4, 0x7f, + 0x98, 0x2e, 0x1f, 0x0c, 0xe4, 0x6f, 0x21, 0x87, 0x00, 0x43, 0x04, 0x32, 0xcf, 0x54, 0x5a, 0x0e, 0xef, 0x2f, 0x15, + 0x54, 0x09, 0x2e, 0x77, 0xf7, 0x22, 0x0b, 0x29, 0x2e, 0x77, 0xf7, 0xfb, 0x6f, 0x50, 0x5e, 0xb8, 0x2e, 0x10, 0x50, + 0x01, 0x2e, 0xd4, 0x00, 0x00, 0xb2, 0xfb, 0x7f, 0x51, 0x2f, 0x01, 0xb2, 0x48, 0x2f, 0x02, 0xb2, 0x42, 0x2f, 0x03, + 0x90, 0x56, 0x2f, 0xd7, 0x52, 0x79, 0x80, 0x42, 0x40, 0x81, 0x84, 0x00, 0x40, 0x42, 0x42, 0x98, 0x2e, 0x93, 0x0c, + 0xd9, 0x54, 0xd7, 0x50, 0xa1, 0x40, 0x98, 0xbd, 0x82, 0x40, 0x3e, 0x82, 0xda, 0x0a, 0x44, 0x40, 0x8b, 0x16, 0xe3, + 0x00, 0x53, 0x42, 0x00, 0x2e, 0x43, 0x40, 0x9a, 0x02, 0x52, 0x42, 0x00, 0x2e, 0x41, 0x40, 0x15, 0x54, 0x4a, 0x0e, + 0x3a, 0x2f, 0x3a, 0x82, 0x00, 0x30, 0x41, 0x40, 0x21, 0x2e, 0x85, 0x0f, 0x40, 0xb2, 0x0a, 0x2f, 0x98, 0x2e, 0xb1, + 0x0c, 0x98, 0x2e, 0x45, 0x0e, 0x98, 0x2e, 0x5b, 0x0e, 0xfb, 0x6f, 0xf0, 0x5f, 0x00, 0x30, 0x80, 0x2e, 0xce, 0xb7, + 0xdd, 0x52, 0xd3, 0x54, 0x42, 0x42, 0x4f, 0x84, 0x73, 0x30, 0xdb, 0x52, 0x83, 0x42, 0x1b, 0x30, 0x6b, 0x42, 0x23, + 0x30, 0x27, 0x2e, 0xd7, 0x00, 0x37, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0xd6, 0x00, 0x7a, 0x84, 0x17, 0x2c, 0x42, 0x42, + 0x30, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x12, 0x2d, 0x21, 0x30, 0x00, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0x7b, + 0xf7, 0x0b, 0x2d, 0x17, 0x30, 0x98, 0x2e, 0x51, 0x0c, 0xd5, 0x50, 0x0c, 0x82, 0x72, 0x30, 0x2f, 0x2e, 0xd4, 0x00, + 0x25, 0x2e, 0x7b, 0xf7, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, 0x70, 0x50, 0x0a, 0x25, 0x39, + 0x86, 0xfb, 0x7f, 0xe1, 0x32, 0x62, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xb5, 0x56, 0xa5, 0x6f, 0xab, 0x08, 0x91, 0x6f, + 0x4b, 0x08, 0xdf, 0x56, 0xc4, 0x6f, 0x23, 0x09, 0x4d, 0xba, 0x93, 0xbc, 0x8c, 0x0b, 0xd1, 0x6f, 0x0b, 0x09, 0xcb, + 0x52, 0xe1, 0x5e, 0x56, 0x42, 0xaf, 0x09, 0x4d, 0xba, 0x23, 0xbd, 0x94, 0x0a, 0xe5, 0x6f, 0x68, 0xbb, 0xeb, 0x08, + 0xbd, 0xb9, 0x63, 0xbe, 0xfb, 0x6f, 0x52, 0x42, 0xe3, 0x0a, 0xc0, 0x2e, 0x43, 0x42, 0x90, 0x5f, 0xd1, 0x50, 0x03, + 0x2e, 0x25, 0xf3, 0x13, 0x40, 0x00, 0x40, 0x9b, 0xbc, 0x9b, 0xb4, 0x08, 0xbd, 0xb8, 0xb9, 0x98, 0xbc, 0xda, 0x0a, + 0x08, 0xb6, 0x89, 0x16, 0xc0, 0x2e, 0x19, 0x00, 0x62, 0x02, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x81, 0x0d, 0x01, + 0x2e, 0xd4, 0x00, 0x31, 0x30, 0x08, 0x04, 0xfb, 0x6f, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd6, 0x00, 0x21, 0x2e, + 0xd7, 0x00, 0xb8, 0x2e, 0x01, 0x2e, 0xd7, 0x00, 0x03, 0x2e, 0xd6, 0x00, 0x48, 0x0e, 0x01, 0x2f, 0x80, 0x2e, 0x1f, + 0x0e, 0xb8, 0x2e, 0xe3, 0x50, 0x21, 0x34, 0x01, 0x42, 0x82, 0x30, 0xc1, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x01, 0x00, + 0x22, 0x30, 0x01, 0x40, 0x4a, 0x0a, 0x01, 0x42, 0xb8, 0x2e, 0xe3, 0x54, 0xf0, 0x3b, 0x83, 0x40, 0xd8, 0x08, 0xe5, + 0x52, 0x83, 0x42, 0x00, 0x30, 0x83, 0x30, 0x50, 0x42, 0xc4, 0x32, 0x27, 0x2e, 0x64, 0xf5, 0x94, 0x00, 0x50, 0x42, + 0x40, 0x42, 0xd3, 0x3f, 0x84, 0x40, 0x7d, 0x82, 0xe3, 0x08, 0x40, 0x42, 0x83, 0x42, 0xb8, 0x2e, 0xdd, 0x52, 0x00, + 0x30, 0x40, 0x42, 0x7c, 0x86, 0xb9, 0x52, 0x09, 0x2e, 0x70, 0x0f, 0xbf, 0x54, 0xc4, 0x42, 0xd3, 0x86, 0x54, 0x40, + 0x55, 0x40, 0x94, 0x42, 0x85, 0x42, 0x21, 0x2e, 0xd7, 0x00, 0x42, 0x40, 0x25, 0x2e, 0xfd, 0xf3, 0xc0, 0x42, 0x7e, + 0x82, 0x05, 0x2e, 0x7d, 0x00, 0x80, 0xb2, 0x14, 0x2f, 0x05, 0x2e, 0x89, 0x00, 0x27, 0xbd, 0x2f, 0xb9, 0x80, 0x90, + 0x02, 0x2f, 0x21, 0x2e, 0x6f, 0xf5, 0x0c, 0x2d, 0x07, 0x2e, 0x71, 0x0f, 0x14, 0x30, 0x1c, 0x09, 0x05, 0x2e, 0x77, + 0xf7, 0xbd, 0x56, 0x47, 0xbe, 0x93, 0x08, 0x94, 0x0a, 0x25, 0x2e, 0x77, 0xf7, 0xe7, 0x54, 0x50, 0x42, 0x4a, 0x0e, + 0xfc, 0x2f, 0xb8, 0x2e, 0x50, 0x50, 0x02, 0x30, 0x43, 0x86, 0xe5, 0x50, 0xfb, 0x7f, 0xe3, 0x7f, 0xd2, 0x7f, 0xc0, + 0x7f, 0xb1, 0x7f, 0x00, 0x2e, 0x41, 0x40, 0x00, 0x40, 0x48, 0x04, 0x98, 0x2e, 0x74, 0xc0, 0x1e, 0xaa, 0xd3, 0x6f, + 0x14, 0x30, 0xb1, 0x6f, 0xe3, 0x22, 0xc0, 0x6f, 0x52, 0x40, 0xe4, 0x6f, 0x4c, 0x0e, 0x12, 0x42, 0xd3, 0x7f, 0xeb, + 0x2f, 0x03, 0x2e, 0x86, 0x0f, 0x40, 0x90, 0x11, 0x30, 0x03, 0x2f, 0x23, 0x2e, 0x86, 0x0f, 0x02, 0x2c, 0x00, 0x30, + 0xd0, 0x6f, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x40, 0x50, 0xf1, 0x7f, 0x0a, 0x25, 0x3c, 0x86, 0xeb, 0x7f, 0x41, + 0x33, 0x22, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xd3, 0x6f, 0xf4, 0x30, 0xdc, 0x09, 0x47, 0x58, 0xc2, 0x6f, 0x94, 0x09, + 0xeb, 0x58, 0x6a, 0xbb, 0xdc, 0x08, 0xb4, 0xb9, 0xb1, 0xbd, 0xe9, 0x5a, 0x95, 0x08, 0x21, 0xbd, 0xf6, 0xbf, 0x77, + 0x0b, 0x51, 0xbe, 0xf1, 0x6f, 0xeb, 0x6f, 0x52, 0x42, 0x54, 0x42, 0xc0, 0x2e, 0x43, 0x42, 0xc0, 0x5f, 0x50, 0x50, + 0xf5, 0x50, 0x31, 0x30, 0x11, 0x42, 0xfb, 0x7f, 0x7b, 0x30, 0x0b, 0x42, 0x11, 0x30, 0x02, 0x80, 0x23, 0x33, 0x01, + 0x42, 0x03, 0x00, 0x07, 0x2e, 0x80, 0x03, 0x05, 0x2e, 0xd3, 0x00, 0x23, 0x52, 0xe2, 0x7f, 0xd3, 0x7f, 0xc0, 0x7f, + 0x98, 0x2e, 0xb6, 0x0e, 0xd1, 0x6f, 0x08, 0x0a, 0x1a, 0x25, 0x7b, 0x86, 0xd0, 0x7f, 0x01, 0x33, 0x12, 0x30, 0x98, + 0x2e, 0xc2, 0xc4, 0xd1, 0x6f, 0x08, 0x0a, 0x00, 0xb2, 0x0d, 0x2f, 0xe3, 0x6f, 0x01, 0x2e, 0x80, 0x03, 0x51, 0x30, + 0xc7, 0x86, 0x23, 0x2e, 0x21, 0xf2, 0x08, 0xbc, 0xc0, 0x42, 0x98, 0x2e, 0xa5, 0xb7, 0x00, 0x2e, 0x00, 0x2e, 0xd0, + 0x2e, 0xb0, 0x6f, 0x0b, 0xb8, 0x03, 0x2e, 0x1b, 0x00, 0x08, 0x1a, 0xb0, 0x7f, 0x70, 0x30, 0x04, 0x2f, 0x21, 0x2e, + 0x21, 0xf2, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x98, 0x2e, 0x6d, 0xc0, 0x98, 0x2e, 0x5d, 0xc0, 0xed, 0x50, 0x98, + 0x2e, 0x44, 0xcb, 0xef, 0x50, 0x98, 0x2e, 0x46, 0xc3, 0xf1, 0x50, 0x98, 0x2e, 0x53, 0xc7, 0x35, 0x50, 0x98, 0x2e, + 0x64, 0xcf, 0x10, 0x30, 0x98, 0x2e, 0xdc, 0x03, 0x20, 0x26, 0xc0, 0x6f, 0x02, 0x31, 0x12, 0x42, 0xab, 0x33, 0x0b, + 0x42, 0x37, 0x80, 0x01, 0x30, 0x01, 0x42, 0xf3, 0x37, 0xf7, 0x52, 0xfb, 0x50, 0x44, 0x40, 0xa2, 0x0a, 0x42, 0x42, + 0x8b, 0x31, 0x09, 0x2e, 0x5e, 0xf7, 0xf9, 0x54, 0xe3, 0x08, 0x83, 0x42, 0x1b, 0x42, 0x23, 0x33, 0x4b, 0x00, 0xbc, + 0x84, 0x0b, 0x40, 0x33, 0x30, 0x83, 0x42, 0x0b, 0x42, 0xe0, 0x7f, 0xd1, 0x7f, 0x98, 0x2e, 0x58, 0xb7, 0xd1, 0x6f, + 0x80, 0x30, 0x40, 0x42, 0x03, 0x30, 0xe0, 0x6f, 0xf3, 0x54, 0x04, 0x30, 0x00, 0x2e, 0x00, 0x2e, 0x01, 0x89, 0x62, + 0x0e, 0xfa, 0x2f, 0x43, 0x42, 0x11, 0x30, 0xfb, 0x6f, 0xc0, 0x2e, 0x01, 0x42, 0xb0, 0x5f, 0xc1, 0x4a, 0x00, 0x00, + 0x6d, 0x57, 0x00, 0x00, 0x77, 0x8e, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xd3, 0xff, 0xff, 0xff, 0xe5, 0xff, 0xff, + 0xff, 0xee, 0xe1, 0xff, 0xff, 0x7c, 0x13, 0x00, 0x00, 0x46, 0xe6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1 +}; diff --git a/Drivers/bmi270-module/source/bmi270.cpp b/Drivers/bmi270-module/source/bmi270.cpp new file mode 100644 index 000000000..e0f7e5e04 --- /dev/null +++ b/Drivers/bmi270-module/source/bmi270.cpp @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include + +#define TAG "BMI270" + +static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24 +static constexpr uint8_t REG_DATA_ACC = 0x0C; // 6 bytes: acc X/Y/Z LSB/MSB +static constexpr uint8_t REG_DATA_GYR = 0x12; // 6 bytes: gyr X/Y/Z LSB/MSB +static constexpr uint8_t REG_INTERNAL_ST = 0x21; // bit0: init done +static constexpr uint8_t REG_ACC_CONF = 0x40; // ODR + BWP + filter_perf +static constexpr uint8_t REG_ACC_RANGE = 0x41; // range selector +static constexpr uint8_t REG_GYR_CONF = 0x42; // ODR + BWP +static constexpr uint8_t REG_GYR_RANGE = 0x43; // range selector +static constexpr uint8_t REG_INIT_CTRL = 0x59; // 0=start upload, 1=done +static constexpr uint8_t REG_INIT_ADDR_0 = 0x5B; // burst address low nibble +// REG_INIT_ADDR_1 = 0x5C written together with 0x5B (2-byte burst) +static constexpr uint8_t REG_INIT_DATA = 0x5E; // config burst write target +static constexpr uint8_t REG_PWR_CONF = 0x7C; // 0=disable adv. power save +static constexpr uint8_t REG_PWR_CTRL = 0x7D; // bit1=gyr_en, bit2=acc_en +static constexpr uint8_t REG_CMD = 0x7E; // 0xB6 = soft reset + +// ACC_CONF: filter_perf=1, bwp=normal(2), odr=100Hz(8) → 0xA8 +static constexpr uint8_t ACC_CONF_VAL = 0xA8; +static constexpr uint8_t ACC_RANGE_VAL = 0x02; // ±8g +// GYR_CONF: filter_perf=1, noise_perf=1, bwp=normal(2), odr=100Hz(8) → 0xE8 +static constexpr uint8_t GYR_CONF_VAL = 0xE8; +static constexpr uint8_t GYR_RANGE_VAL = 0x00; // ±2000°/s + +// 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) + +// Config upload chunk size (bytes of config data per I2C transaction) +static constexpr size_t CHUNK_SIZE = 64; + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Helpers + +static bool write_register(Device* i2c_controller, uint8_t address, uint8_t reg, uint8_t val) { + return i2c_controller_register8_set(i2c_controller, address, reg, val, I2C_TIMEOUT_TICKS) == ERROR_NONE; +} + +static bool read_register(Device* i2c_controller, uint8_t address, uint8_t reg, uint8_t* buf) { + return i2c_controller_register8_get(i2c_controller, address, reg, buf, I2C_TIMEOUT_TICKS) == ERROR_NONE; +} + +static bool configure(Device* i2c_controller, uint8_t address) { + // Disable advanced power save before uploading + if (!write_register(i2c_controller, address, REG_PWR_CONF, 0x00)) return false; + vTaskDelay(pdMS_TO_TICKS(1)); + + // Signal start of config upload + if (!write_register(i2c_controller, address, REG_INIT_CTRL, 0x00)) return false; + + // Upload config in CHUNK_SIZE-byte bursts + // The half-word address (hwAddr) increments by CHUNK_SIZE/2 per chunk + const size_t totalSize = sizeof(bmi270_config_data); + for (size_t offset = 0; offset < totalSize; offset += CHUNK_SIZE) { + // Set INIT_ADDR_0 and INIT_ADDR_1 (consecutive registers 0x5B/0x5C) + uint16_t hwAddr = static_cast(offset / 2); + uint8_t addrBuf[2] = { + static_cast(hwAddr & 0x0Fu), // INIT_ADDR_0: bits [3:0] + static_cast((hwAddr >> 4) & 0xFFu) // INIT_ADDR_1: bits [11:4] + }; + + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_ADDR_0, addrBuf, 2, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + + // Write chunk to INIT_DATA register. + // Copy to a stack buffer first: the config array may live in DROM (SPI flash) + // which is not DMA-accessible; the I2C driver requires DRAM-backed buffers. + size_t chunkLen = (offset + CHUNK_SIZE <= totalSize) ? CHUNK_SIZE : (totalSize - offset); + uint8_t chunkBuf[CHUNK_SIZE]; + memcpy(chunkBuf, bmi270_config_data + offset, chunkLen); + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_DATA, chunkBuf, static_cast(chunkLen), I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + } + + // Signal end of config upload + if (!write_register(i2c_controller, address, REG_INIT_CTRL, 0x01)) return false; + vTaskDelay(pdMS_TO_TICKS(20)); + + // Verify initialization + uint8_t status = 0; + if (!read_register(i2c_controller, address, REG_INTERNAL_ST, &status)) return false; + return (status & 0x01u) != 0; // bit 0 = init_ok +} + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + + auto address = GET_CONFIG(device)->address; + + // Verify chip ID + uint8_t chip_id= 0; + if (!read_register(i2c_controller, address, REG_CHIP_ID, &chip_id) || chip_id != 0x24) { + return false; + } + + // Soft reset — clears all registers; datasheet specifies 2 ms startup time. + // Use 20 ms to be safe and allow the chip to fully re-initialise before + // any further I2C traffic (a second chip-ID read immediately after reset + // is unreliable and not part of the Bosch SensorAPI init flow). + if (!write_register(i2c_controller, address, REG_CMD, 0xB6)) return false; + vTaskDelay(pdMS_TO_TICKS(20)); + + // Upload 8KB configuration (enables internal feature engine) + if (!configure(i2c_controller, address)) { + return false; + } + + // Configure accelerometer: ODR=100Hz, normal filter, ±8g + if (!write_register(i2c_controller, address, REG_ACC_CONF, ACC_CONF_VAL)) { return false; } + if (!write_register(i2c_controller, address, REG_ACC_RANGE, ACC_RANGE_VAL)) { return false; } + + // Configure gyroscope: ODR=100Hz, normal filter, ±2000°/s + if (!write_register(i2c_controller, address, REG_GYR_CONF, GYR_CONF_VAL)) { return false; } + if (!write_register(i2c_controller, address, REG_GYR_RANGE, GYR_RANGE_VAL)) { return false; } + + // Enable accelerometer and gyroscope + if (!write_register(i2c_controller, address, REG_PWR_CTRL, 0x06)) { return false; } // bit1=gyr_en, bit2=acc_en + vTaskDelay(pdMS_TO_TICKS(2)); + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t bmi270_read(Device* device, Bmi270Data* data) { + auto* i2c_controller = device_get_parent(device); + + auto address = GET_CONFIG(device)->address; + + // Burst-read 12 bytes: acc X/Y/Z (6 bytes) + gyro X/Y/Z (6 bytes) + // Registers: 0x0C–0x17 are contiguous for acc+gyro in I2C mode (no dummy byte) + uint8_t buffer[12] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_DATA_ACC, buffer, sizeof(buffer), 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(buffer[0], buffer[1]) * ACCEL_SCALE; + data->ay = toI16(buffer[2], buffer[3]) * ACCEL_SCALE; + data->az = toI16(buffer[4], buffer[5]) * ACCEL_SCALE; + data->gx = toI16(buffer[6], buffer[7]) * GYRO_SCALE; + data->gy = toI16(buffer[8], buffer[9]) * GYRO_SCALE; + data->gz = toI16(buffer[10], buffer[11]) * GYRO_SCALE; + + return ERROR_NONE; +} + +extern Module bmi270_module; + +Driver bmi270_driver = { + .name = "bmi270", + .compatible = (const char*[]) { "bosch,bmi270", nullptr}, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &bmi270_module, + .internal = nullptr +}; + +} \ No newline at end of file diff --git a/Drivers/bmi270-module/source/module.cpp b/Drivers/bmi270-module/source/module.cpp new file mode 100644 index 000000000..b77e8510b --- /dev/null +++ b/Drivers/bmi270-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver bmi270_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(&bmi270_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(&bmi270_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol bmi270_module_symbols[]; + +Module bmi270_module = { + .name = "bmi270", + .start = start, + .stop = stop, + .symbols = bmi270_module_symbols, + .internal = nullptr +}; + +} diff --git a/Drivers/bmi270-module/source/symbols.c b/Drivers/bmi270-module/source/symbols.c new file mode 100644 index 000000000..68dc957ce --- /dev/null +++ b/Drivers/bmi270-module/source/symbols.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol bmi270_module_symbols[] = { + DEFINE_MODULE_SYMBOL(bmi270_read), + MODULE_SYMBOL_TERMINATOR +}; From 10ab4dded8111620e7ed606f973d9d90525855d2 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 28 Feb 2026 23:43:34 +0100 Subject: [PATCH 05/25] Module bug fixes --- TactilityKernel/include/tactility/module.h | 1 + TactilityKernel/source/module.cpp | 23 ++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/TactilityKernel/include/tactility/module.h b/TactilityKernel/include/tactility/module.h index 345a762c4..910f29b45 100644 --- a/TactilityKernel/include/tactility/module.h +++ b/TactilityKernel/include/tactility/module.h @@ -118,6 +118,7 @@ error_t module_construct_add_start(struct Module* module); /** * @brief Check if the module is started. + * Can be used when module isn't constructed yet. * @param module module to check * @return true if the module is started, false otherwise */ diff --git a/TactilityKernel/source/module.cpp b/TactilityKernel/source/module.cpp index 51200f433..5275709dd 100644 --- a/TactilityKernel/source/module.cpp +++ b/TactilityKernel/source/module.cpp @@ -12,8 +12,8 @@ struct ModuleInternal { }; struct ModuleLedger { - std::vector modules; - struct Mutex mutex {}; + std::vector modules; + Mutex mutex {}; ModuleLedger() { mutex_construct(&mutex); } ~ModuleLedger() { mutex_destruct(&mutex); } @@ -23,36 +23,37 @@ static ModuleLedger ledger; extern "C" { -error_t module_construct(struct Module* module) { +error_t module_construct(Module* module) { module->internal = new (std::nothrow) ModuleInternal(); if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY; return ERROR_NONE; } -error_t module_destruct(struct Module* module) { +error_t module_destruct(Module* module) { delete static_cast(module->internal); module->internal = nullptr; return ERROR_NONE; } -error_t module_add(struct Module* module) { +error_t module_add(Module* module) { mutex_lock(&ledger.mutex); ledger.modules.push_back(module); mutex_unlock(&ledger.mutex); return ERROR_NONE; } -error_t module_remove(struct Module* module) { +error_t module_remove(Module* module) { mutex_lock(&ledger.mutex); ledger.modules.erase(std::remove(ledger.modules.begin(), ledger.modules.end(), module), ledger.modules.end()); mutex_unlock(&ledger.mutex); return ERROR_NONE; } -error_t module_start(struct Module* module) { +error_t module_start(Module* module) { LOG_I(TAG, "start %s", module->name); - auto* internal = static_cast(module->internal); + auto* internal = module->internal; + if (internal == nullptr) return ERROR_INVALID_STATE; if (internal->started) return ERROR_NONE; error_t error = module->start(); @@ -61,13 +62,15 @@ error_t module_start(struct Module* module) { } bool module_is_started(struct Module* module) { - return static_cast(module->internal)->started; + auto* internal = module->internal; + return internal != nullptr && internal->started; } error_t module_stop(struct Module* module) { LOG_I(TAG, "stop %s", module->name); - auto* internal = static_cast(module->internal); + auto* internal = module->internal; + if (internal == nullptr) return ERROR_INVALID_STATE; if (!internal->started) return ERROR_NONE; error_t error = module->stop(); From 4f1d9e5360a55635857eccf0fcb98a1bb4d0fc80 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 1 Mar 2026 12:02:34 +0100 Subject: [PATCH 06/25] Cleanup --- Devices/m5stack-tab5/m5stack,tab5.dts | 20 ----- Drivers/bmi270-module/source/bmi270.cpp | 100 +++++++++++------------- 2 files changed, 47 insertions(+), 73 deletions(-) diff --git a/Devices/m5stack-tab5/m5stack,tab5.dts b/Devices/m5stack-tab5/m5stack,tab5.dts index 2269e690a..620001d98 100644 --- a/Devices/m5stack-tab5/m5stack,tab5.dts +++ b/Devices/m5stack-tab5/m5stack,tab5.dts @@ -24,31 +24,11 @@ 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>; diff --git a/Drivers/bmi270-module/source/bmi270.cpp b/Drivers/bmi270-module/source/bmi270.cpp index e0f7e5e04..fbc6b487b 100644 --- a/Drivers/bmi270-module/source/bmi270.cpp +++ b/Drivers/bmi270-module/source/bmi270.cpp @@ -8,32 +8,32 @@ #define TAG "BMI270" -static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24 -static constexpr uint8_t REG_DATA_ACC = 0x0C; // 6 bytes: acc X/Y/Z LSB/MSB -static constexpr uint8_t REG_DATA_GYR = 0x12; // 6 bytes: gyr X/Y/Z LSB/MSB -static constexpr uint8_t REG_INTERNAL_ST = 0x21; // bit0: init done -static constexpr uint8_t REG_ACC_CONF = 0x40; // ODR + BWP + filter_perf -static constexpr uint8_t REG_ACC_RANGE = 0x41; // range selector -static constexpr uint8_t REG_GYR_CONF = 0x42; // ODR + BWP -static constexpr uint8_t REG_GYR_RANGE = 0x43; // range selector -static constexpr uint8_t REG_INIT_CTRL = 0x59; // 0=start upload, 1=done -static constexpr uint8_t REG_INIT_ADDR_0 = 0x5B; // burst address low nibble +static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24 +static constexpr uint8_t REG_DATA_ACC = 0x0C; // 6 bytes: acc X/Y/Z LSB/MSB +static constexpr uint8_t REG_DATA_GYR = 0x12; // 6 bytes: gyr X/Y/Z LSB/MSB +static constexpr uint8_t REG_INTERNAL_ST = 0x21; // bit0: init done +static constexpr uint8_t REG_ACC_CONF = 0x40; // ODR + BWP + filter_perf +static constexpr uint8_t REG_ACC_RANGE = 0x41; // range selector +static constexpr uint8_t REG_GYR_CONF = 0x42; // ODR + BWP +static constexpr uint8_t REG_GYR_RANGE = 0x43; // range selector +static constexpr uint8_t REG_INIT_CTRL = 0x59; // 0=start upload, 1=done +static constexpr uint8_t REG_INIT_ADDR_0 = 0x5B; // burst address low nibble // REG_INIT_ADDR_1 = 0x5C written together with 0x5B (2-byte burst) -static constexpr uint8_t REG_INIT_DATA = 0x5E; // config burst write target -static constexpr uint8_t REG_PWR_CONF = 0x7C; // 0=disable adv. power save -static constexpr uint8_t REG_PWR_CTRL = 0x7D; // bit1=gyr_en, bit2=acc_en -static constexpr uint8_t REG_CMD = 0x7E; // 0xB6 = soft reset +static constexpr uint8_t REG_INIT_DATA = 0x5E; // config burst write target +static constexpr uint8_t REG_PWR_CONF = 0x7C; // 0=disable adv. power save +static constexpr uint8_t REG_PWR_CTRL = 0x7D; // bit1=gyr_en, bit2=acc_en +static constexpr uint8_t REG_CMD = 0x7E; // 0xB6 = soft reset // ACC_CONF: filter_perf=1, bwp=normal(2), odr=100Hz(8) → 0xA8 -static constexpr uint8_t ACC_CONF_VAL = 0xA8; +static constexpr uint8_t ACC_CONF_VAL = 0xA8; static constexpr uint8_t ACC_RANGE_VAL = 0x02; // ±8g // GYR_CONF: filter_perf=1, noise_perf=1, bwp=normal(2), odr=100Hz(8) → 0xE8 -static constexpr uint8_t GYR_CONF_VAL = 0xE8; +static constexpr uint8_t GYR_CONF_VAL = 0xE8; static constexpr uint8_t GYR_RANGE_VAL = 0x00; // ±2000°/s // 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 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) // Config upload chunk size (bytes of config data per I2C transaction) static constexpr size_t CHUNK_SIZE = 64; @@ -44,51 +44,43 @@ static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10); // region Helpers -static bool write_register(Device* i2c_controller, uint8_t address, uint8_t reg, uint8_t val) { - return i2c_controller_register8_set(i2c_controller, address, reg, val, I2C_TIMEOUT_TICKS) == ERROR_NONE; -} - -static bool read_register(Device* i2c_controller, uint8_t address, uint8_t reg, uint8_t* buf) { - return i2c_controller_register8_get(i2c_controller, address, reg, buf, I2C_TIMEOUT_TICKS) == ERROR_NONE; -} - static bool configure(Device* i2c_controller, uint8_t address) { // Disable advanced power save before uploading - if (!write_register(i2c_controller, address, REG_PWR_CONF, 0x00)) return false; + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CONF, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; vTaskDelay(pdMS_TO_TICKS(1)); // Signal start of config upload - if (!write_register(i2c_controller, address, REG_INIT_CTRL, 0x00)) return false; + if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; // Upload config in CHUNK_SIZE-byte bursts // The half-word address (hwAddr) increments by CHUNK_SIZE/2 per chunk - const size_t totalSize = sizeof(bmi270_config_data); - for (size_t offset = 0; offset < totalSize; offset += CHUNK_SIZE) { + constexpr size_t config_data_size = sizeof(bmi270_config_data); + for (size_t offset = 0; offset < config_data_size; offset += CHUNK_SIZE) { // Set INIT_ADDR_0 and INIT_ADDR_1 (consecutive registers 0x5B/0x5C) - uint16_t hwAddr = static_cast(offset / 2); - uint8_t addrBuf[2] = { - static_cast(hwAddr & 0x0Fu), // INIT_ADDR_0: bits [3:0] - static_cast((hwAddr >> 4) & 0xFFu) // INIT_ADDR_1: bits [11:4] + auto hardware_address = static_cast(offset / 2); + uint8_t address_buffer[2] = { + static_cast(hardware_address & 0x0Fu), // INIT_ADDR_0: bits [3:0] + static_cast((hardware_address >> 4) & 0xFFu) // INIT_ADDR_1: bits [11:4] }; - if (i2c_controller_write_register(i2c_controller, address, REG_INIT_ADDR_0, addrBuf, 2, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_ADDR_0, address_buffer, 2, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; // Write chunk to INIT_DATA register. // Copy to a stack buffer first: the config array may live in DROM (SPI flash) // which is not DMA-accessible; the I2C driver requires DRAM-backed buffers. - size_t chunkLen = (offset + CHUNK_SIZE <= totalSize) ? CHUNK_SIZE : (totalSize - offset); - uint8_t chunkBuf[CHUNK_SIZE]; - memcpy(chunkBuf, bmi270_config_data + offset, chunkLen); - if (i2c_controller_write_register(i2c_controller, address, REG_INIT_DATA, chunkBuf, static_cast(chunkLen), I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + size_t chunk_length = (offset + CHUNK_SIZE <= config_data_size) ? CHUNK_SIZE : (config_data_size - offset); + uint8_t chunk_buffer[CHUNK_SIZE]; + memcpy(chunk_buffer, bmi270_config_data + offset, chunk_length); + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_DATA, chunk_buffer, static_cast(chunk_length), I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; } // Signal end of config upload - if (!write_register(i2c_controller, address, REG_INIT_CTRL, 0x01)) return false; + if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; vTaskDelay(pdMS_TO_TICKS(20)); // Verify initialization uint8_t status = 0; - if (!read_register(i2c_controller, address, REG_INTERNAL_ST, &status)) return false; + if (i2c_controller_register8_get(i2c_controller, address, REG_INTERNAL_ST, &status, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; return (status & 0x01u) != 0; // bit 0 = init_ok } @@ -98,37 +90,41 @@ static bool configure(Device* i2c_controller, uint8_t address) { 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 chip_id= 0; - if (!read_register(i2c_controller, address, REG_CHIP_ID, &chip_id) || chip_id != 0x24) { - return false; + if (i2c_controller_register8_get(i2c_controller, address, REG_CHIP_ID, &chip_id, I2C_TIMEOUT_TICKS) != ERROR_NONE || chip_id != 0x24) { + return ERROR_RESOURCE; } // Soft reset — clears all registers; datasheet specifies 2 ms startup time. // Use 20 ms to be safe and allow the chip to fully re-initialise before // any further I2C traffic (a second chip-ID read immediately after reset // is unreliable and not part of the Bosch SensorAPI init flow). - if (!write_register(i2c_controller, address, REG_CMD, 0xB6)) return false; + if (i2c_controller_register8_set(i2c_controller, address, REG_CMD, 0xB6, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; vTaskDelay(pdMS_TO_TICKS(20)); // Upload 8KB configuration (enables internal feature engine) if (!configure(i2c_controller, address)) { - return false; + return ERROR_RESOURCE; } // Configure accelerometer: ODR=100Hz, normal filter, ±8g - if (!write_register(i2c_controller, address, REG_ACC_CONF, ACC_CONF_VAL)) { return false; } - if (!write_register(i2c_controller, address, REG_ACC_RANGE, ACC_RANGE_VAL)) { return false; } + if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_CONF, ACC_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_RANGE, ACC_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; // Configure gyroscope: ODR=100Hz, normal filter, ±2000°/s - if (!write_register(i2c_controller, address, REG_GYR_CONF, GYR_CONF_VAL)) { return false; } - if (!write_register(i2c_controller, address, REG_GYR_RANGE, GYR_RANGE_VAL)) { return false; } + if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_CONF, GYR_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_RANGE, GYR_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; - // Enable accelerometer and gyroscope - if (!write_register(i2c_controller, address, REG_PWR_CTRL, 0x06)) { return false; } // bit1=gyr_en, bit2=acc_en + // Enable accelerometer and gyroscope (bit1=gyr_en, bit2=acc_en) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CTRL, 0x06, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; vTaskDelay(pdMS_TO_TICKS(2)); return ERROR_NONE; @@ -167,8 +163,6 @@ error_t bmi270_read(Device* device, Bmi270Data* data) { return ERROR_NONE; } -extern Module bmi270_module; - Driver bmi270_driver = { .name = "bmi270", .compatible = (const char*[]) { "bosch,bmi270", nullptr}, From 8c7558dc05da7dca7633ab51c32b1cbf8d5f9033 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 20:53:16 +0100 Subject: [PATCH 07/25] Add new symbols --- Modules/lvgl-module/source/symbols.c | 10 ++++++++++ TactilityC/Source/symbols/stl.cpp | 19 ++++++++++++++++++- TactilityC/Source/tt_init.cpp | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 9ef79ff03..aea82af5b 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -60,6 +60,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_obj_get_group), DEFINE_MODULE_SYMBOL(lv_obj_get_user_data), DEFINE_MODULE_SYMBOL(lv_obj_get_state), + DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_bottom), + DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_y), DEFINE_MODULE_SYMBOL(lv_obj_has_flag), DEFINE_MODULE_SYMBOL(lv_obj_has_flag_any), DEFINE_MODULE_SYMBOL(lv_obj_has_state), @@ -158,7 +160,15 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_obj_set_style_min_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_max_height), // lv_font + DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt), DEFINE_MODULE_SYMBOL(lv_font_get_default), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_bitmap), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc_fmt_txt), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_static_bitmap), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_width), + DEFINE_MODULE_SYMBOL(lv_font_get_line_height), + DEFINE_MODULE_SYMBOL(lv_font_set_kerning), // lv_theme DEFINE_MODULE_SYMBOL(lv_theme_get_color_primary), DEFINE_MODULE_SYMBOL(lv_theme_get_color_secondary), diff --git a/TactilityC/Source/symbols/stl.cpp b/TactilityC/Source/symbols/stl.cpp index 56426854d..7bc96f60d 100644 --- a/TactilityC/Source/symbols/stl.cpp +++ b/TactilityC/Source/symbols/stl.cpp @@ -5,6 +5,19 @@ #include +#include + +extern "C" { + +// std::map / std::set red-black tree non-template helpers. +// We use the mangled names directly (same pattern as string.cpp) to avoid +// ambiguity from the overloaded const/non-const variants in stl_tree.h. +void* _ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base(void*); +void* _ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base(void*); +void _ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_(bool, void*, void*, void*); + +} + const esp_elfsym stl_symbols[] = { // Note: You have to use the mangled names here { "_ZSt17__throw_bad_allocv", (void*)&(std::__throw_bad_alloc) }, @@ -12,7 +25,11 @@ const esp_elfsym stl_symbols[] = { { "_ZSt25__throw_bad_function_callv", (void*)&(std::__throw_bad_function_call) }, { "_ZSt20__throw_length_errorPKc", (void*)&(std::__throw_length_error) }, { "_ZSt19__throw_logic_errorPKc", (void*)&std::__throw_logic_error }, - // { "", (void*)&(std::) }, + { "_ZSt24__throw_out_of_range_fmtPKcz", (void*)&std::__throw_out_of_range_fmt }, + // std::map / std::set (red-black tree internals) + DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base), + DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base), + DEFINE_MODULE_SYMBOL(_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_), // delimiter ESP_ELFSYM_END }; diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index a065045a2..695702b24 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +222,7 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(strtod), ESP_ELFSYM_EXPORT(strrchr), ESP_ELFSYM_EXPORT(strtol), + ESP_ELFSYM_EXPORT(strtoul), ESP_ELFSYM_EXPORT(strcspn), ESP_ELFSYM_EXPORT(strncat), ESP_ELFSYM_EXPORT(strpbrk), @@ -392,6 +395,7 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(gpio_config), ESP_ELFSYM_EXPORT(gpio_get_level), ESP_ELFSYM_EXPORT(gpio_set_level), + ESP_ELFSYM_EXPORT(gpio_reset_pin), // driver/i2s_common.h ESP_ELFSYM_EXPORT(i2s_new_channel), ESP_ELFSYM_EXPORT(i2s_del_channel), @@ -408,6 +412,17 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_clock), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_slot), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_gpio), + // miniz.h + ESP_ELFSYM_EXPORT(tinfl_decompress), + ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback), + ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_heap), + ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem), + // ledc + ESP_ELFSYM_EXPORT(ledc_update_duty), + ESP_ELFSYM_EXPORT(ledc_set_freq), + ESP_ELFSYM_EXPORT(ledc_channel_config), + ESP_ELFSYM_EXPORT(ledc_set_duty), + ESP_ELFSYM_EXPORT(ledc_timer_config), // delimiter ESP_ELFSYM_END }; From 880bae3faec6ab8cdc21c644dd5b96fb26218814 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 21:46:14 +0100 Subject: [PATCH 08/25] Add more symbols --- TactilityC/Source/tt_init.cpp | 5 +++++ TactilityKernel/source/kernel_symbols.c | 15 ++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 695702b24..fb377118b 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -422,7 +422,12 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(ledc_set_freq), ESP_ELFSYM_EXPORT(ledc_channel_config), ESP_ELFSYM_EXPORT(ledc_set_duty), + ESP_ELFSYM_EXPORT(ledc_set_fade), + ESP_ELFSYM_EXPORT(ledc_set_pin), ESP_ELFSYM_EXPORT(ledc_timer_config), + ESP_ELFSYM_EXPORT(ledc_timer_pause), + ESP_ELFSYM_EXPORT(ledc_timer_resume), + ESP_ELFSYM_EXPORT(ledc_timer_rst), // delimiter ESP_ELFSYM_END }; diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 094271247..829ac5c5e 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -1,17 +1,17 @@ +#include +#include +#include +#include #include #include #include +#include #include #include #include #include #include -#include -#include -#include -#include #include -#include #include /** @@ -58,6 +58,11 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(driver_is_compatible), DEFINE_MODULE_SYMBOL(driver_find_compatible), DEFINE_MODULE_SYMBOL(driver_get_device_type), + // file system + DEFINE_MODULE_SYMBOL(file_system_mount), + DEFINE_MODULE_SYMBOL(file_system_unmount), + DEFINE_MODULE_SYMBOL(file_system_is_mounted), + DEFINE_MODULE_SYMBOL(file_system_get_mount_path), // drivers/gpio_controller DEFINE_MODULE_SYMBOL(gpio_descriptor_acquire), DEFINE_MODULE_SYMBOL(gpio_descriptor_release), From 7293ea8928e1db5c9f7030df7854cd336ce73c97 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 21:46:29 +0100 Subject: [PATCH 09/25] Add file_system driver --- .../include/tactility/drivers/file_system.h | 22 +++++++++++++++ .../source/drivers/file_system.cpp | 27 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 TactilityKernel/include/tactility/drivers/file_system.h create mode 100644 TactilityKernel/source/drivers/file_system.cpp diff --git a/TactilityKernel/include/tactility/drivers/file_system.h b/TactilityKernel/include/tactility/drivers/file_system.h new file mode 100644 index 000000000..f1d8dee17 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/file_system.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +struct Device; + +struct FileSystemApi { + error_t (*mount)(struct Device* device); + error_t (*unmount)(struct Device* device); + bool (*is_mounted)(struct Device* device); + error_t (*get_mount_path)(struct Device*, char* out_path); +}; + +extern const struct DeviceType FILE_SYSTEM_TYPE; + +error_t file_system_mount(struct Device* device); + +error_t file_system_unmount(struct Device* device); + +bool file_system_is_mounted(struct Device* device); + +error_t file_system_get_mount_path(struct Device*, char* out_path); diff --git a/TactilityKernel/source/drivers/file_system.cpp b/TactilityKernel/source/drivers/file_system.cpp new file mode 100644 index 000000000..09a9f8ac5 --- /dev/null +++ b/TactilityKernel/source/drivers/file_system.cpp @@ -0,0 +1,27 @@ +#include + +#define INTERNAL_API(driver) ((struct FileSystem*)(driver)->api) + +error_t file_system_mount(struct Device* device) { + const auto* driver = device_get_driver(device); + return INTERNAL_API(driver)->mount(device); +} + +error_t file_system_unmount(struct Device* device) { + const auto* driver = device_get_driver(device); + return INTERNAL_API(driver)->unmount(device); +} + +bool file_system_is_mounted(struct Device* device) { + const auto* driver = device_get_driver(device); + return INTERNAL_API(driver)->is_mounted(device); +} + +error_t file_system_get_mount_path(struct Device*, char* out_path) { + const auto* driver = device_get_driver(device); + return INTERNAL_API(driver)->get_mount_path(device, out_path); +} + +const struct DeviceType FILE_SYSTEM_TYPE { + .name = "file-system" +}; From 17cdec1a1fcb4a8a530367e263e3762f21a689cf Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 21:46:40 +0100 Subject: [PATCH 10/25] Remove unused field --- Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h index dc490678b..32ee8934d 100644 --- a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h +++ b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h @@ -43,7 +43,6 @@ class SdmmcDevice final : public SdCardDevice { busWidth(busWidth) {} - int spiFrequencyKhz; gpio_num_t pinClock; gpio_num_t pinCmd; gpio_num_t pinD0; From b40d595a98b4c0dd48f725687e3d80dd0803136e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 21:46:50 +0100 Subject: [PATCH 11/25] Change default alloc size to 4kB --- Tactility/Source/hal/sdcard/SdmmcDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp index ae74e6ce4..dbd170912 100644 --- a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp @@ -21,7 +21,7 @@ bool SdmmcDevice::mountInternal(const std::string& newMountPath) { esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = config->formatOnMountFailed, .max_files = config->maxOpenFiles, - .allocation_unit_size = config->allocUnitSize, + .allocation_unit_size = 512 * 8, .disk_status_check_enable = config->statusCheckEnabled, .use_one_fat = false }; From 57016baac8971a5663123a22bc2718198eb30fbb Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 21:56:38 +0100 Subject: [PATCH 12/25] Add binfont symbols --- Modules/lvgl-module/source/symbols.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index aea82af5b..0892c0b19 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -424,5 +424,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_anim_path_ease_out), // lv_async DEFINE_MODULE_SYMBOL(lv_async_call), + // lv_binfont + DEFINE_MODULE_SYMBOL(lv_binfont_create), + DEFINE_MODULE_SYMBOL(lv_binfont_destroy), MODULE_SYMBOL_TERMINATOR }; \ No newline at end of file From 1ea2f973054625075b9c540c677c6a9e5ec53ffa Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 23:44:46 +0100 Subject: [PATCH 13/25] SDMMC driver WIP --- .../lilygo-tdongle-s3/lilygo,tdongle-s3.dts | 13 ++ Platforms/platform-esp32/CMakeLists.txt | 2 +- .../bindings/espressif,sdmmc.yaml | 57 +++++++ .../include/tactility/bindings/esp32_sdmmc.h | 15 ++ .../include/tactility/drivers/esp32_sdmmc.h | 33 ++++ .../source/drivers/esp32_sdmmc.cpp | 157 ++++++++++++++++++ .../source/drivers/esp32_sdmmc_fs.cpp | 156 +++++++++++++++++ .../source/drivers/esp32_spi.cpp | 8 +- .../include/tactility/drivers/file_system.h | 15 +- .../source/drivers/file_system.cpp | 17 +- 10 files changed, 460 insertions(+), 13 deletions(-) create mode 100644 Platforms/platform-esp32/bindings/espressif,sdmmc.yaml create mode 100644 Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h create mode 100644 Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h create mode 100644 Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp create mode 100644 Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp diff --git a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts index 50f8a5e53..60b38590b 100644 --- a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts +++ b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,18 @@ pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>; }; + sdmmc0 { + compatible = "espressif,sdmmc"; + pin-clk = <&gpio 12 GPIO_FLAG_NONE>; + pin-cmd = <&gpio 16 GPIO_FLAG_NONE>; + pin-d0 = <&gpio 14 GPIO_FLAG_NONE>; + pin-d1 = <&gpio 17 GPIO_FLAG_NONE>; + pin-d2 = <&gpio 21 GPIO_FLAG_NONE>; + pin-d3 = <&gpio 18 GPIO_FLAG_NONE>; + bus-width = <4>; + max-open-files = <4>; + }; + stemma_qt: uart1 { compatible = "espressif,esp32-uart"; port = ; diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index e0445b4d9..ac049c589 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -6,5 +6,5 @@ idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "include/" PRIV_INCLUDE_DIRS "private/" - REQUIRES TactilityKernel driver + REQUIRES TactilityKernel driver vfs fatfs ) diff --git a/Platforms/platform-esp32/bindings/espressif,sdmmc.yaml b/Platforms/platform-esp32/bindings/espressif,sdmmc.yaml new file mode 100644 index 000000000..9237402d2 --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,sdmmc.yaml @@ -0,0 +1,57 @@ +description: ESP32 SDMMC + +compatible: "espressif,sdmmc" + +properties: + pin-clk: + type: phandle-array + required: true + pin-cmd: + type: phandle-array + required: true + pin-d0: + type: phandle-array + required: true + pin-d1: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d2: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d3: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d4: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d5: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d6: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d7: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-cd: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-wp: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + bus-width: + type: int + required: true + description: Bus width in bits + max-open-files: + type: int + required: true + description: Maximum number of open files + wp-active-high: + type: boolean + default: false + description: Whether the WP pin is active high + enable-uhs: + type: boolean + default: false + description: Enable UHS mode \ No newline at end of file diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h new file mode 100644 index 000000000..6b045aade --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_sdmmc, struct Esp32SdmmcConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h new file mode 100644 index 000000000..e2079c766 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32SdmmcConfig { + struct GpioPinSpec pin_clk; + struct GpioPinSpec pin_cmd; + struct GpioPinSpec pin_d0; + struct GpioPinSpec pin_d1; + struct GpioPinSpec pin_d2; + struct GpioPinSpec pin_d3; + struct GpioPinSpec pin_d4; + struct GpioPinSpec pin_d5; + struct GpioPinSpec pin_d6; + struct GpioPinSpec pin_d7; + struct GpioPinSpec pin_cd; + struct GpioPinSpec pin_wp; + uint8_t bus_width; + uint8_t max_open_files; + bool wp_active_high; + bool enable_uhs; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp new file mode 100644 index 000000000..2222bf48c --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + +#include "tactility/drivers/gpio_descriptor.h" +#include +#include +#include + +#define TAG "esp32_sdmmc" + +#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config) +#define GET_DATA(device) ((struct Esp32SdmmcInternal*)device_get_driver_data(device)) + +extern "C" { + +error_t mount(Device* device) { + return ERROR_NONE; +} + +error_t unmount(Device* device) { + return ERROR_NONE; +} + +bool is_mounted(Device* device) { + return true; +} + +error_t get_mount_path(Device*, char* out_path) { + return ERROR_NONE; +} + +static const FileSystemApi sdmmc_filesystem_api = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_mount_path = get_mount_path +}; + +struct Esp32SdmmcInternal { + RecursiveMutex mutex = {}; + bool initialized = false; + + // Pin descriptors + GpioDescriptor* pin_clk_descriptor = nullptr; + GpioDescriptor* pin_cmd_descriptor = nullptr; + GpioDescriptor* pin_d0_descriptor = nullptr; + GpioDescriptor* pin_d1_descriptor = nullptr; + GpioDescriptor* pin_d2_descriptor = nullptr; + GpioDescriptor* pin_d3_descriptor = nullptr; + GpioDescriptor* pin_d4_descriptor = nullptr; + GpioDescriptor* pin_d5_descriptor = nullptr; + GpioDescriptor* pin_d6_descriptor = nullptr; + GpioDescriptor* pin_d7_descriptor = nullptr; + GpioDescriptor* pin_cd_descriptor = nullptr; + GpioDescriptor* pin_wp_descriptor = nullptr; + + explicit Esp32SdmmcInternal() { + recursive_mutex_construct(&mutex); + } + + ~Esp32SdmmcInternal() { + cleanup_pins(); + recursive_mutex_destruct(&mutex); + } + + void cleanup_pins() { + release_pin(&pin_clk_descriptor); + release_pin(&pin_cmd_descriptor); + release_pin(&pin_d0_descriptor); + release_pin(&pin_d1_descriptor); + release_pin(&pin_d2_descriptor); + release_pin(&pin_d3_descriptor); + release_pin(&pin_d4_descriptor); + release_pin(&pin_d5_descriptor); + release_pin(&pin_d6_descriptor); + release_pin(&pin_d7_descriptor); + release_pin(&pin_cd_descriptor); + release_pin(&pin_wp_descriptor); + } + + void lock() { recursive_mutex_lock(&mutex); } + + error_t try_lock(TickType_t timeout) { return recursive_mutex_try_lock(&mutex, timeout); } + + void unlock() { recursive_mutex_unlock(&mutex); } +}; + + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* data = new (std::nothrow) Esp32SdmmcInternal(); + if (!data) return ERROR_OUT_OF_MEMORY; + + device_set_driver_data(device, data); + + auto* sdmmc_config = GET_CONFIG(device); + + // Acquire pins from the specified GPIO pin specs. Optional pins are allowed. + bool pins_ok = + acquire_pin_or_set_null(sdmmc_config->pin_clk, &data->pin_clk_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_cmd, &data->pin_cmd_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d0, &data->pin_d0_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d1, &data->pin_d1_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d2, &data->pin_d2_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d3, &data->pin_d3_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d4, &data->pin_d4_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d5, &data->pin_d5_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d6, &data->pin_d6_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d7, &data->pin_d7_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor); + + + if (!pins_ok) { + LOG_E(TAG, "Failed to acquire required one or more pins"); + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + delete data; + return ERROR_RESOURCE; + } + + // TODO: filesystem + + data->initialized = true; + return ERROR_NONE; +} + +static error_t stop(Device* device) { + ESP_LOGI(TAG, "stop %s", device->name); + auto* driver_data = GET_DATA(device); + auto* dts_config = GET_CONFIG(device); + + // TODO: filesystem + + driver_data->cleanup_pins(); + device_set_driver_data(device, nullptr); + delete driver_data; + return ERROR_NONE; +} + +extern Module platform_esp32_module; + +Driver esp32_spi_driver = { + .name = "esp32_sdmmc", + .compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &platform_esp32_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp new file mode 100644 index 000000000..e7487e691 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "esp32_sdmmc_fs" + +#define GET_DATA(device) ((struct Esp32SdmmcFsInternal*)device_get_driver_data(device)) +// We re-use the config from the parent device +#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config) + +struct Esp32SdmmcFsInternal { + std::string mount_path {}; + sdmmc_card_t* card = nullptr; +}; + +static gpio_num_t to_native_pin(GpioPinSpec pin_spec) { + if (pin_spec.gpio_controller == nullptr) { return GPIO_NUM_NC; } + return static_cast(pin_spec.pin); +} + +extern "C" { + +error_t mount(Device* device, const char* mount_path) { + LOG_I(TAG, "Mounting %s", mount_path); + + auto* data = GET_DATA(device); + auto* config = GET_CONFIG(device); + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = config->max_open_files, + .allocation_unit_size = 0, // Default is sector size + .disk_status_check_enable = false, + .use_one_fat = false + }; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + + uint32_t slot_config_flags = 0; + if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1; + if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH; + + sdmmc_slot_config_t slot_config = { + .clk = to_native_pin(config->pin_clk), + .cmd = to_native_pin(config->pin_cmd), + .d0 = to_native_pin(config->pin_d0), + .d1 = to_native_pin(config->pin_d1), + .d2 = to_native_pin(config->pin_d2), + .d3 = to_native_pin(config->pin_d3), + .d4 = to_native_pin(config->pin_d4), + .d5 = to_native_pin(config->pin_d5), + .d6 = to_native_pin(config->pin_d6), + .d7 = to_native_pin(config->pin_d7), + .cd = to_native_pin(config->pin_cd), + .wp = to_native_pin(config->pin_wp), + .width = config->bus_width, + .flags = slot_config_flags + }; + + esp_err_t result = esp_vfs_fat_sdmmc_mount(mount_path, &host, &slot_config, &mount_config, &data->card); + + if (result != ESP_OK || data->card == nullptr) { + if (result == ESP_FAIL) { + LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); + } else { + LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result)); + } + return false; + } + + data->mount_path = mount_path; + + return ERROR_NONE; +} + +error_t unmount(Device* device) { + auto* data = GET_DATA(device); + + if (esp_vfs_fat_sdcard_unmount(data->mount_path.c_str(), data->card) != ESP_OK) { + LOG_E(TAG, "Unmount failed for %s", data->mount_path.c_str()); + return ERROR_UNDEFINED; + } + + LOG_I(TAG, "Unmounted %s", data->mount_path.c_str()); + data->mount_path = ""; + data->card = nullptr; + + return ERROR_NONE; +} + +bool is_mounted(Device* device) { + const auto* data = GET_DATA(device); + return data != nullptr && data->card != nullptr; +} + +error_t get_mount_path(Device* device, char* out_path, size_t out_path_size) { + const auto* data = GET_DATA(device); + if (data == nullptr || data->card == nullptr) return ERROR_INVALID_STATE; + if (data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, data->mount_path.c_str(), out_path_size); + return ERROR_NONE; +} + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* data = new (std::nothrow) Esp32SdmmcFsInternal(); + if (!data) return ERROR_OUT_OF_MEMORY; + + device_set_driver_data(device, data); + + // TODO: filesystem + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + ESP_LOGI(TAG, "stop %s", device->name); + auto* driver_data = GET_DATA(device); + + // TODO: filesystem + + device_set_driver_data(device, nullptr); + delete driver_data; + return ERROR_NONE; +} + +extern Module platform_esp32_module; + +static const FileSystemApi sdmmc_fs_api = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_mount_path = get_mount_path +}; + +Driver esp32_sdmmc_fs_driver = { + .name = "esp32_sdmmc_fs", + .compatible = (const char*[]) { "espressif,esp32-sdmmc-fs", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &sdmmc_fs_api, + .device_type = &FILE_SYSTEM_TYPE, + .owner = &platform_esp32_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp index e3f5440cf..14551a27c 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp @@ -2,11 +2,11 @@ #include #include #include +#include #include "tactility/drivers/gpio_descriptor.h" #include #include -#include #include #include @@ -84,7 +84,7 @@ static error_t start(Device* device) { acquire_pin_or_set_null(dts_config->pin_hd, &data->hd_descriptor); if (!pins_ok) { - ESP_LOGE(TAG, "Failed to acquire required SPI pins"); + LOG_E(TAG, "Failed to acquire required SPI pins"); data->cleanup_pins(); device_set_driver_data(device, nullptr); delete data; @@ -113,7 +113,7 @@ static error_t start(Device* device) { data->cleanup_pins(); device_set_driver_data(device, nullptr); delete data; - ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); + LOG_E(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); return ERROR_RESOURCE; } @@ -122,7 +122,7 @@ static error_t start(Device* device) { } static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); + LOG_I(TAG, "stop %s", device->name); auto* driver_data = GET_DATA(device); auto* dts_config = GET_CONFIG(device); diff --git a/TactilityKernel/include/tactility/drivers/file_system.h b/TactilityKernel/include/tactility/drivers/file_system.h index f1d8dee17..86ab008c1 100644 --- a/TactilityKernel/include/tactility/drivers/file_system.h +++ b/TactilityKernel/include/tactility/drivers/file_system.h @@ -1,14 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 #pragma once +#include +#include #include +#ifdef __cplusplus +extern "C" { +#endif + struct Device; struct FileSystemApi { - error_t (*mount)(struct Device* device); + error_t (*mount)(struct Device* device, const char* mount_path); error_t (*unmount)(struct Device* device); bool (*is_mounted)(struct Device* device); - error_t (*get_mount_path)(struct Device*, char* out_path); + error_t (*get_mount_path)(struct Device*, char* out_path, size_t out_path_size); }; extern const struct DeviceType FILE_SYSTEM_TYPE; @@ -20,3 +27,7 @@ error_t file_system_unmount(struct Device* device); bool file_system_is_mounted(struct Device* device); error_t file_system_get_mount_path(struct Device*, char* out_path); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/drivers/file_system.cpp b/TactilityKernel/source/drivers/file_system.cpp index 09a9f8ac5..4b30aa47b 100644 --- a/TactilityKernel/source/drivers/file_system.cpp +++ b/TactilityKernel/source/drivers/file_system.cpp @@ -1,27 +1,32 @@ #include +#include -#define INTERNAL_API(driver) ((struct FileSystem*)(driver)->api) +#define INTERNAL_API(driver) ((FileSystemApi*)(driver)->api) -error_t file_system_mount(struct Device* device) { +extern "C" { + +error_t file_system_mount(Device* device) { const auto* driver = device_get_driver(device); return INTERNAL_API(driver)->mount(device); } -error_t file_system_unmount(struct Device* device) { +error_t file_system_unmount(Device* device) { const auto* driver = device_get_driver(device); return INTERNAL_API(driver)->unmount(device); } -bool file_system_is_mounted(struct Device* device) { +bool file_system_is_mounted(Device* device) { const auto* driver = device_get_driver(device); return INTERNAL_API(driver)->is_mounted(device); } -error_t file_system_get_mount_path(struct Device*, char* out_path) { +error_t file_system_get_mount_path(Device* device, char* out_path) { const auto* driver = device_get_driver(device); return INTERNAL_API(driver)->get_mount_path(device, out_path); } -const struct DeviceType FILE_SYSTEM_TYPE { +const DeviceType FILE_SYSTEM_TYPE { .name = "file-system" }; + +} // extern "C" From d63a401cd50efd7a6a7310e3d1f4b3b33ee9603f Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 2 Mar 2026 23:59:54 +0100 Subject: [PATCH 14/25] SDMMC driver WIP --- .../source/drivers/esp32_sdmmc.cpp | 50 ++++++++----------- .../source/drivers/esp32_sdmmc_fs.cpp | 12 +++-- Platforms/platform-esp32/source/module.cpp | 8 ++- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index 2222bf48c..aa8b55999 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -7,7 +7,7 @@ #include "tactility/drivers/gpio_descriptor.h" #include #include -#include +#include #define TAG "esp32_sdmmc" @@ -16,32 +16,16 @@ extern "C" { -error_t mount(Device* device) { - return ERROR_NONE; -} - -error_t unmount(Device* device) { - return ERROR_NONE; -} - -bool is_mounted(Device* device) { - return true; -} - -error_t get_mount_path(Device*, char* out_path) { - return ERROR_NONE; -} - -static const FileSystemApi sdmmc_filesystem_api = { - .mount = mount, - .unmount = unmount, - .is_mounted = is_mounted, - .get_mount_path = get_mount_path -}; - struct Esp32SdmmcInternal { RecursiveMutex mutex = {}; bool initialized = false; + char fs_device_name[16] = "esp32_sdmmc_fs0"; + Device fs_device = { + .name = fs_device_name, + .config = nullptr, + .parent = nullptr, + .internal = nullptr + }; // Pin descriptors GpioDescriptor* pin_clk_descriptor = nullptr; @@ -122,7 +106,11 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } - // TODO: filesystem + // Create filesystem child device + auto* fs_device = &data->fs_device; + fs_device->parent = device; + fs_device->config = sdmmc_config; + check(device_construct_add_start(fs_device, "espressif,esp32-sdmmc-fs") == ERROR_NONE); data->initialized = true; return ERROR_NONE; @@ -130,14 +118,18 @@ static error_t start(Device* device) { static error_t stop(Device* device) { ESP_LOGI(TAG, "stop %s", device->name); - auto* driver_data = GET_DATA(device); + auto* data = GET_DATA(device); auto* dts_config = GET_CONFIG(device); - // TODO: filesystem + // Create filesystem child device + auto* fs_device = &data->fs_device; + check(device_stop(fs_device) == ERROR_NONE); + check(device_remove(fs_device) == ERROR_NONE); + check(device_destruct(fs_device) == ERROR_NONE); - driver_data->cleanup_pins(); + data->cleanup_pins(); device_set_driver_data(device, nullptr); - delete driver_data; + delete data; return ERROR_NONE; } diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index e7487e691..1e9188d78 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -74,7 +74,7 @@ error_t mount(Device* device, const char* mount_path) { } else { LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result)); } - return false; + return ERROR_UNDEFINED; } data->mount_path = mount_path; @@ -117,7 +117,9 @@ static error_t start(Device* device) { device_set_driver_data(device, data); - // TODO: filesystem + if (mount(device, "/sdcard") != ERROR_NONE) { + LOG_E(TAG, "Failed to mount SD card"); + } return ERROR_NONE; } @@ -126,7 +128,11 @@ static error_t stop(Device* device) { ESP_LOGI(TAG, "stop %s", device->name); auto* driver_data = GET_DATA(device); - // TODO: filesystem + if (is_mounted(device)) { + if (unmount(device) != ERROR_NONE) { + LOG_E(TAG, "Failed to unmount SD card"); + } + } device_set_driver_data(device, nullptr); delete driver_data; diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index f75fbb09e..3d1ce73ea 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -7,6 +7,8 @@ extern "C" { extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2s_driver; +extern Driver esp32_sdmmc_driver; +extern Driver esp32_sdmmc_fs_driver; extern Driver esp32_spi_driver; extern Driver esp32_uart_driver; @@ -16,6 +18,8 @@ static error_t start() { check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE); + check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); + check(driver_construct_add(&esp32_sdmmc_fs_driver) == ERROR_NONE); check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE); check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; @@ -27,12 +31,14 @@ static error_t stop() { check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE); + check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); + check(driver_remove_destruct(&esp32_sdmmc_fs_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; } -struct Module platform_esp32_module = { +Module platform_esp32_module = { .name = "platform-esp32", .start = start, .stop = stop, From e4cf91aa181a7709049a22f7b1c49ce45b5c10b5 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 3 Mar 2026 21:41:48 +0100 Subject: [PATCH 15/25] Fixes and improvements --- .../Source/Configuration.cpp | 4 +- .../Source/devices/Sdcard.cpp | 22 ----------- .../lilygo-tdongle-s3/Source/devices/Sdcard.h | 7 ---- Devices/lilygo-tdongle-s3/device.properties | 1 + .../lilygo-tdongle-s3/lilygo,tdongle-s3.dts | 15 ++++---- ...,sdmmc.yaml => espressif,esp32-sdmmc.yaml} | 6 +-- .../include/tactility/drivers/esp32_sdmmc.h | 1 - .../source/drivers/esp32_sdmmc.cpp | 3 +- .../source/drivers/esp32_sdmmc_fs.cpp | 37 ++++++++++++++++++- .../service/webserver/WebServerService.cpp | 35 ++++++++++++++++++ .../include/tactility/drivers/file_system.h | 4 +- .../source/drivers/file_system.cpp | 8 ++-- 12 files changed, 87 insertions(+), 56 deletions(-) delete mode 100644 Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp delete mode 100644 Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h rename Platforms/platform-esp32/bindings/{espressif,sdmmc.yaml => espressif,esp32-sdmmc.yaml} (89%) diff --git a/Devices/lilygo-tdongle-s3/Source/Configuration.cpp b/Devices/lilygo-tdongle-s3/Source/Configuration.cpp index 8358fd2e9..d7462601c 100644 --- a/Devices/lilygo-tdongle-s3/Source/Configuration.cpp +++ b/Devices/lilygo-tdongle-s3/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Display.h" -#include "devices/Sdcard.h" #include @@ -9,8 +8,7 @@ using namespace tt::hal; static std::vector> createDevices() { return { - createDisplay(), - createSdCard() + createDisplay() }; } diff --git a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp b/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp deleted file mode 100644 index 6315691a9..000000000 --- a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Sdcard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - GPIO_NUM_12, - GPIO_NUM_16, - GPIO_NUM_14, - GPIO_NUM_17, - GPIO_NUM_21, - GPIO_NUM_18, - SdCardDevice::MountBehaviour::AtBoot - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h b/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h deleted file mode 100644 index 5cb65a735..000000000 --- a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -std::shared_ptr createSdCard(); diff --git a/Devices/lilygo-tdongle-s3/device.properties b/Devices/lilygo-tdongle-s3/device.properties index 28622b83d..ec4f21869 100644 --- a/Devices/lilygo-tdongle-s3/device.properties +++ b/Devices/lilygo-tdongle-s3/device.properties @@ -22,3 +22,4 @@ dpi=186 [lvgl] colorDepth=16 uiDensity=compact +fontSize=10 diff --git a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts index 60b38590b..147987a44 100644 --- a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts +++ b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts @@ -32,15 +32,14 @@ }; sdmmc0 { - compatible = "espressif,sdmmc"; - pin-clk = <&gpio 12 GPIO_FLAG_NONE>; - pin-cmd = <&gpio 16 GPIO_FLAG_NONE>; - pin-d0 = <&gpio 14 GPIO_FLAG_NONE>; - pin-d1 = <&gpio 17 GPIO_FLAG_NONE>; - pin-d2 = <&gpio 21 GPIO_FLAG_NONE>; - pin-d3 = <&gpio 18 GPIO_FLAG_NONE>; + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 16 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 14 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 17 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 21 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 18 GPIO_FLAG_NONE>; bus-width = <4>; - max-open-files = <4>; }; stemma_qt: uart1 { diff --git a/Platforms/platform-esp32/bindings/espressif,sdmmc.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml similarity index 89% rename from Platforms/platform-esp32/bindings/espressif,sdmmc.yaml rename to Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml index 9237402d2..7dbdabef9 100644 --- a/Platforms/platform-esp32/bindings/espressif,sdmmc.yaml +++ b/Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml @@ -1,6 +1,6 @@ description: ESP32 SDMMC -compatible: "espressif,sdmmc" +compatible: "espressif,esp32-sdmmc" properties: pin-clk: @@ -43,10 +43,6 @@ properties: type: int required: true description: Bus width in bits - max-open-files: - type: int - required: true - description: Maximum number of open files wp-active-high: type: boolean default: false diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h index e2079c766..ce0b8d686 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -23,7 +23,6 @@ struct Esp32SdmmcConfig { struct GpioPinSpec pin_cd; struct GpioPinSpec pin_wp; uint8_t bus_width; - uint8_t max_open_files; bool wp_active_high; bool enable_uhs; }; diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index aa8b55999..aae486342 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -7,7 +7,6 @@ #include "tactility/drivers/gpio_descriptor.h" #include #include -#include #define TAG "esp32_sdmmc" @@ -135,7 +134,7 @@ static error_t stop(Device* device) { extern Module platform_esp32_module; -Driver esp32_spi_driver = { +Driver esp32_sdmmc_driver = { .name = "esp32_sdmmc", .compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr }, .start_device = start, diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index 1e9188d78..79d5101cf 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -9,7 +10,10 @@ #include #include #include -#include + +#if SOC_SD_PWR_CTRL_SUPPORTED +#include +#endif #define TAG "esp32_sdmmc_fs" @@ -20,6 +24,9 @@ struct Esp32SdmmcFsInternal { std::string mount_path {}; sdmmc_card_t* card = nullptr; +#if SOC_SD_PWR_CTRL_SUPPORTED + sd_pwr_ctrl_handle_t pwr_ctrl_handle = nullptr; +#endif }; static gpio_num_t to_native_pin(GpioPinSpec pin_spec) { @@ -37,7 +44,7 @@ error_t mount(Device* device, const char* mount_path) { esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, - .max_files = config->max_open_files, + .max_files = 4, .allocation_unit_size = 0, // Default is sector size .disk_status_check_enable = false, .use_one_fat = false @@ -45,6 +52,18 @@ error_t mount(Device* device, const char* mount_path) { sdmmc_host_t host = SDMMC_HOST_DEFAULT(); +#if SOC_SD_PWR_CTRL_SUPPORTED + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = 4, // LDO4 is typically used for SDMMC on ESP32-S3 + }; + esp_err_t pwr_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &data->pwr_ctrl_handle); + if (pwr_err != ESP_OK) { + LOG_E(TAG, "Failed to create SD power control driver, err=0x%x", pwr_err); + return ERROR_NOT_SUPPORTED; + } + host.pwr_ctrl_handle = data->pwr_ctrl_handle; +#endif + uint32_t slot_config_flags = 0; if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1; if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH; @@ -74,9 +93,16 @@ error_t mount(Device* device, const char* mount_path) { } else { LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result)); } +#if SOC_SD_PWR_CTRL_SUPPORTED + if (data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle); + data->pwr_ctrl_handle = nullptr; + } +#endif return ERROR_UNDEFINED; } + LOG_I(TAG, "Mounted %s", mount_path); data->mount_path = mount_path; return ERROR_NONE; @@ -94,6 +120,13 @@ error_t unmount(Device* device) { data->mount_path = ""; data->card = nullptr; +#if SOC_SD_PWR_CTRL_SUPPORTED + if (data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle); + data->pwr_ctrl_handle = nullptr; + } +#endif + return ERROR_NONE; } diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 6c34b7c78..90b8bdfe0 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #if TT_FEATURE_SCREENSHOT_ENABLED #include @@ -774,6 +776,16 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) { break; } } + + device_for_each_of_type(&FILE_SYSTEM_TYPE, &json, [] (auto* fs_device, void* context) { + if (file_system_is_mounted(fs_device)) { + auto* json_context_ptr = static_cast(context); + auto& json_context = *json_context_ptr; + json_context << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}"; + } + return true; + }); + json << "]}"; } else { std::vector entries; @@ -1180,6 +1192,29 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) { break; } } + + struct FsIterContext { + std::ostringstream& json; + bool sdcard_found; + }; + FsIterContext fs_iter_context { json, sdcard_found }; + device_for_each_of_type(&FILE_SYSTEM_TYPE, &fs_iter_context, [] (auto* fs_device, void* context) { + if (!file_system_is_mounted(fs_device)) return true; + char mount_path[128]; + if (file_system_get_mount_path(fs_device, mount_path, sizeof(mount_path)) != ESP_OK) return true; + uint64_t storage_total = 0, storage_free = 0; + if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) != ESP_OK) return true; + auto* fs_iter_context = static_cast(context); + auto& json_context = fs_iter_context->json; + json_context << "\"free\":" << storage_free << ","; + json_context << "\"total\":" << storage_total << ","; + json_context << "\"mounted\":true"; + fs_iter_context->sdcard_found = true; + return true; + }); + + if (fs_iter_context.sdcard_found) sdcard_found = true; + if (!sdcard_found) { json << "\"mounted\":false"; } diff --git a/TactilityKernel/include/tactility/drivers/file_system.h b/TactilityKernel/include/tactility/drivers/file_system.h index 86ab008c1..9a487ffe7 100644 --- a/TactilityKernel/include/tactility/drivers/file_system.h +++ b/TactilityKernel/include/tactility/drivers/file_system.h @@ -20,13 +20,13 @@ struct FileSystemApi { extern const struct DeviceType FILE_SYSTEM_TYPE; -error_t file_system_mount(struct Device* device); +error_t file_system_mount(struct Device* device, const char* mount_path); error_t file_system_unmount(struct Device* device); bool file_system_is_mounted(struct Device* device); -error_t file_system_get_mount_path(struct Device*, char* out_path); +error_t file_system_get_mount_path(struct Device*, char* out_path, size_t out_path_size); #ifdef __cplusplus } diff --git a/TactilityKernel/source/drivers/file_system.cpp b/TactilityKernel/source/drivers/file_system.cpp index 4b30aa47b..5bca5bb2f 100644 --- a/TactilityKernel/source/drivers/file_system.cpp +++ b/TactilityKernel/source/drivers/file_system.cpp @@ -5,9 +5,9 @@ extern "C" { -error_t file_system_mount(Device* device) { +error_t file_system_mount(Device* device, const char* mount_path) { const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->mount(device); + return INTERNAL_API(driver)->mount(device, mount_path); } error_t file_system_unmount(Device* device) { @@ -20,9 +20,9 @@ bool file_system_is_mounted(Device* device) { return INTERNAL_API(driver)->is_mounted(device); } -error_t file_system_get_mount_path(Device* device, char* out_path) { +error_t file_system_get_mount_path(Device* device, char* out_path, size_t out_path_size) { const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->get_mount_path(device, out_path); + return INTERNAL_API(driver)->get_mount_path(device, out_path, out_path_size); } const DeviceType FILE_SYSTEM_TYPE { From 6f7ed252ea89cac9c75f09fc8aa8bd1a8cd9775e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 01:12:10 +0100 Subject: [PATCH 16/25] Refactor all SD card path usages --- .../include/tactility/drivers/esp32_sdmmc.h | 7 +- .../tactility/drivers/esp32_sdmmc_fs.h | 22 +++ .../source/drivers/esp32_sdmmc.cpp | 49 ++++--- .../source/drivers/esp32_sdmmc_fs.cpp | 132 ++++++++---------- Platforms/platform-esp32/source/module.cpp | 2 - Tactility/Include/Tactility/MountPoints.h | 2 +- Tactility/Include/Tactility/Paths.h | 9 ++ .../Tactility/hal/sdcard/SdCardDevice.h | 9 +- .../Tactility/hal/sdcard/SdmmcDevice.h | 92 ------------ Tactility/Source/MountPoints.cpp | 53 +++---- Tactility/Source/PartitionsEsp.cpp | 38 +++++ Tactility/Source/Paths.cpp | 51 +++++-- Tactility/Source/Tactility.cpp | 66 ++++----- Tactility/Source/app/files/FilesApp.cpp | 1 - Tactility/Source/app/files/State.cpp | 2 +- Tactility/Source/app/fileselection/State.cpp | 2 +- .../Source/app/screenshot/Screenshot.cpp | 13 +- .../Source/app/systeminfo/SystemInfo.cpp | 24 ++-- Tactility/Source/hal/sdcard/SdCardDevice.cpp | 48 +++++++ Tactility/Source/hal/sdcard/SdmmcDevice.cpp | 123 ---------------- Tactility/Source/hal/usb/Usb.cpp | 35 +++-- Tactility/Source/service/sdcard/Sdcard.cpp | 32 ++--- .../Source/service/statusbar/Statusbar.cpp | 37 ++--- .../service/webserver/WebServerService.cpp | 113 ++++++--------- .../service/wifi/WifiBootSplashInit.cpp | 21 ++- Tactility/Source/settings/BootSettings.cpp | 7 +- .../include/tactility/drivers/file_system.h | 33 ----- .../tactility/filesystem/file_system.h | 37 +++++ .../source/drivers/file_system.cpp | 32 ----- .../source/filesystem/file_system.cpp | 85 +++++++++++ TactilityKernel/source/kernel_symbols.c | 12 +- 31 files changed, 544 insertions(+), 645 deletions(-) create mode 100644 Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h delete mode 100644 Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h create mode 100644 Tactility/Source/hal/sdcard/SdCardDevice.cpp delete mode 100644 Tactility/Source/hal/sdcard/SdmmcDevice.cpp delete mode 100644 TactilityKernel/include/tactility/drivers/file_system.h create mode 100644 TactilityKernel/include/tactility/filesystem/file_system.h delete mode 100644 TactilityKernel/source/drivers/file_system.cpp create mode 100644 TactilityKernel/source/filesystem/file_system.cpp diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h index ce0b8d686..c5cc50f9c 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once -#include -#include +#include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -27,6 +28,8 @@ struct Esp32SdmmcConfig { bool enable_uhs; }; +sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device); + #ifdef __cplusplus } #endif diff --git a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h new file mode 100644 index 000000000..2f5d3f0f7 --- /dev/null +++ b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32SdmmcConfig; +typedef void* Esp32SdmmcHandle; + +extern Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const struct Esp32SdmmcConfig* config, const char* mount_path); + +extern void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle); + +extern sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle); + +extern const FileSystemApi esp32_sdmmc_fs_api; + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index aae486342..3facba295 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -1,12 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 #include #include +#include #include #include #include "tactility/drivers/gpio_descriptor.h" #include #include +#include #define TAG "esp32_sdmmc" @@ -18,13 +20,8 @@ extern "C" { struct Esp32SdmmcInternal { RecursiveMutex mutex = {}; bool initialized = false; - char fs_device_name[16] = "esp32_sdmmc_fs0"; - Device fs_device = { - .name = fs_device_name, - .config = nullptr, - .parent = nullptr, - .internal = nullptr - }; + Esp32SdmmcHandle esp32_sdmmc_fs_handle = nullptr; + FileSystem* file_system = nullptr; // Pin descriptors GpioDescriptor* pin_clk_descriptor = nullptr; @@ -47,6 +44,7 @@ struct Esp32SdmmcInternal { ~Esp32SdmmcInternal() { cleanup_pins(); recursive_mutex_destruct(&mutex); + if (esp32_sdmmc_fs_handle) free(esp32_sdmmc_fs_handle); } void cleanup_pins() { @@ -96,7 +94,6 @@ static error_t start(Device* device) { acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) && acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor); - if (!pins_ok) { LOG_E(TAG, "Failed to acquire required one or more pins"); data->cleanup_pins(); @@ -105,11 +102,13 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } - // Create filesystem child device - auto* fs_device = &data->fs_device; - fs_device->parent = device; - fs_device->config = sdmmc_config; - check(device_construct_add_start(fs_device, "espressif,esp32-sdmmc-fs") == ERROR_NONE); + data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard"); + data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle); + if (file_system_mount(data->file_system) != ERROR_NONE) { + // Error is not recoverable at the time, but it might be recoverable later, + // so we don't return start() failure. + LOG_E(TAG, "Failed to mount SD card filesystem"); + } data->initialized = true; return ERROR_NONE; @@ -120,11 +119,19 @@ static error_t stop(Device* device) { auto* data = GET_DATA(device); auto* dts_config = GET_CONFIG(device); - // Create filesystem child device - auto* fs_device = &data->fs_device; - check(device_stop(fs_device) == ERROR_NONE); - check(device_remove(fs_device) == ERROR_NONE); - check(device_destruct(fs_device) == ERROR_NONE); + if (file_system_is_mounted(data->file_system)) { + if (file_system_unmount(data->file_system) != ERROR_NONE) { + LOG_E(TAG, "Failed to unmount SD card filesystem"); + return ERROR_RESOURCE; + } + } + + // Free file system + file_system_remove(data->file_system); + data->file_system = nullptr; + // Free file system data + esp32_sdmmc_fs_free(data->esp32_sdmmc_fs_handle); + data->esp32_sdmmc_fs_handle = nullptr; data->cleanup_pins(); device_set_driver_data(device, nullptr); @@ -132,6 +139,12 @@ static error_t stop(Device* device) { return ERROR_NONE; } +sdmmc_card_t* esp32_sdmmc_get_card(Device* device) { + if (!device_is_ready(device)) return nullptr; + auto* data = GET_DATA(device); + return esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle); +} + extern Module platform_esp32_module; Driver esp32_sdmmc_driver = { diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index 79d5101cf..456cf3b07 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -1,13 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 #include #include -#include +#include +#include #include #include #include #include -#include #include #include @@ -17,16 +17,24 @@ #define TAG "esp32_sdmmc_fs" -#define GET_DATA(device) ((struct Esp32SdmmcFsInternal*)device_get_driver_data(device)) -// We re-use the config from the parent device -#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config) +#define GET_DATA(data) static_cast(data) -struct Esp32SdmmcFsInternal { - std::string mount_path {}; - sdmmc_card_t* card = nullptr; +struct Esp32SdmmcFsData { + const std::string mount_path; + const Esp32SdmmcConfig* config; + sdmmc_card_t* card; #if SOC_SD_PWR_CTRL_SUPPORTED - sd_pwr_ctrl_handle_t pwr_ctrl_handle = nullptr; + sd_pwr_ctrl_handle_t pwr_ctrl_handle; #endif + + Esp32SdmmcFsData(const Esp32SdmmcConfig* config, const std::string& mount_path) : + mount_path(mount_path), + config(config), + card(nullptr) +#if SOC_SD_PWR_CTRL_SUPPORTED + ,pwr_ctrl_handle(nullptr) +#endif + {} }; static gpio_num_t to_native_pin(GpioPinSpec pin_spec) { @@ -36,11 +44,26 @@ static gpio_num_t to_native_pin(GpioPinSpec pin_spec) { extern "C" { -error_t mount(Device* device, const char* mount_path) { - LOG_I(TAG, "Mounting %s", mount_path); +static error_t get_path(void* data, char* out_path, size_t out_path_size); - auto* data = GET_DATA(device); - auto* config = GET_CONFIG(device); +Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const Esp32SdmmcConfig* config, const char* mount_path) { + return new(std::nothrow) Esp32SdmmcFsData(config, mount_path); +} + +sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle) { + return GET_DATA(handle)->card; +} + +void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle) { + auto* fs_data = GET_DATA(handle); + delete fs_data; +} + +static error_t mount(void* data) { + auto* fs_data = GET_DATA(data); + auto* config = fs_data->config; + + LOG_I(TAG, "Mounting %s", fs_data->mount_path.c_str()); esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, @@ -85,9 +108,9 @@ error_t mount(Device* device, const char* mount_path) { .flags = slot_config_flags }; - esp_err_t result = esp_vfs_fat_sdmmc_mount(mount_path, &host, &slot_config, &mount_config, &data->card); + esp_err_t result = esp_vfs_fat_sdmmc_mount(fs_data->mount_path.c_str(), &host, &slot_config, &mount_config, &fs_data->card); - if (result != ESP_OK || data->card == nullptr) { + if (result != ESP_OK || fs_data->card == nullptr) { if (result == ESP_FAIL) { LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); } else { @@ -102,23 +125,21 @@ error_t mount(Device* device, const char* mount_path) { return ERROR_UNDEFINED; } - LOG_I(TAG, "Mounted %s", mount_path); - data->mount_path = mount_path; + LOG_I(TAG, "Mounted %s", fs_data->mount_path.c_str()); return ERROR_NONE; } -error_t unmount(Device* device) { - auto* data = GET_DATA(device); +static error_t unmount(void* data) { + auto* fs_data = GET_DATA(data); + LOG_I(TAG, "Unmounting %s", fs_data->mount_path.c_str()); - if (esp_vfs_fat_sdcard_unmount(data->mount_path.c_str(), data->card) != ESP_OK) { - LOG_E(TAG, "Unmount failed for %s", data->mount_path.c_str()); + if (esp_vfs_fat_sdcard_unmount(fs_data->mount_path.c_str(), fs_data->card) != ESP_OK) { + LOG_E(TAG, "Unmount failed for %s", fs_data->mount_path.c_str()); return ERROR_UNDEFINED; } - LOG_I(TAG, "Unmounted %s", data->mount_path.c_str()); - data->mount_path = ""; - data->card = nullptr; + fs_data->card = nullptr; #if SOC_SD_PWR_CTRL_SUPPORTED if (data->pwr_ctrl_handle) { @@ -127,69 +148,30 @@ error_t unmount(Device* device) { } #endif - return ERROR_NONE; -} - -bool is_mounted(Device* device) { - const auto* data = GET_DATA(device); - return data != nullptr && data->card != nullptr; -} + LOG_I(TAG, "Unmounted %s", fs_data->mount_path.c_str()); -error_t get_mount_path(Device* device, char* out_path, size_t out_path_size) { - const auto* data = GET_DATA(device); - if (data == nullptr || data->card == nullptr) return ERROR_INVALID_STATE; - if (data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; - strncpy(out_path, data->mount_path.c_str(), out_path_size); return ERROR_NONE; } -static error_t start(Device* device) { - LOG_I(TAG, "start %s", device->name); - auto* data = new (std::nothrow) Esp32SdmmcFsInternal(); - if (!data) return ERROR_OUT_OF_MEMORY; - - device_set_driver_data(device, data); - - if (mount(device, "/sdcard") != ERROR_NONE) { - LOG_E(TAG, "Failed to mount SD card"); - } - - return ERROR_NONE; +static bool is_mounted(void* data) { + const auto* fs_data = GET_DATA(data); + if (fs_data == nullptr || fs_data->card == nullptr) return false; + return sdmmc_get_status(fs_data->card) == ESP_OK; } -static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); - auto* driver_data = GET_DATA(device); - - if (is_mounted(device)) { - if (unmount(device) != ERROR_NONE) { - LOG_E(TAG, "Failed to unmount SD card"); - } - } - - device_set_driver_data(device, nullptr); - delete driver_data; +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + const auto* fs_data = GET_DATA(data); + if (fs_data == nullptr || fs_data->card == nullptr) return ERROR_INVALID_STATE; + if (fs_data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, fs_data->mount_path.c_str(), out_path_size); return ERROR_NONE; } -extern Module platform_esp32_module; - -static const FileSystemApi sdmmc_fs_api = { +const FileSystemApi esp32_sdmmc_fs_api = { .mount = mount, .unmount = unmount, .is_mounted = is_mounted, - .get_mount_path = get_mount_path -}; - -Driver esp32_sdmmc_fs_driver = { - .name = "esp32_sdmmc_fs", - .compatible = (const char*[]) { "espressif,esp32-sdmmc-fs", nullptr }, - .start_device = start, - .stop_device = stop, - .api = &sdmmc_fs_api, - .device_type = &FILE_SYSTEM_TYPE, - .owner = &platform_esp32_module, - .internal = nullptr + .get_path = get_path }; } // extern "C" diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index 3d1ce73ea..6a49f1fa8 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -19,7 +19,6 @@ static error_t start() { check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE); check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); - check(driver_construct_add(&esp32_sdmmc_fs_driver) == ERROR_NONE); check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE); check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; @@ -32,7 +31,6 @@ static error_t stop() { check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); - check(driver_remove_destruct(&esp32_sdmmc_fs_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; diff --git a/Tactility/Include/Tactility/MountPoints.h b/Tactility/Include/Tactility/MountPoints.h index d2a3a9a85..809c8bd46 100644 --- a/Tactility/Include/Tactility/MountPoints.h +++ b/Tactility/Include/Tactility/MountPoints.h @@ -21,6 +21,6 @@ constexpr auto* MOUNT_POINT_DATA = "/data"; constexpr auto* MOUNT_POINT_DATA = "data"; #endif -std::vector getMountPoints(); +std::vector getFileSystemDirents(); } // namespace diff --git a/Tactility/Include/Tactility/Paths.h b/Tactility/Include/Tactility/Paths.h index 005bd6e5e..f12c53e3f 100644 --- a/Tactility/Include/Tactility/Paths.h +++ b/Tactility/Include/Tactility/Paths.h @@ -2,10 +2,19 @@ #include +#include + + namespace tt { bool findFirstMountedSdCardPath(std::string& path); +bool hasMountedSdCard(); + +FileSystem* findFirstMountedSdcardFileSystem(); + +FileSystem* findFirstSdcardFileSystem(); + std::string getSystemRootPath(); std::string getTempPath(); diff --git a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h index a9588e242..d57ffdb73 100644 --- a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h +++ b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h @@ -2,9 +2,11 @@ #include -#include #include +#include + +struct FileSystem; namespace tt::hal::sdcard { /** @@ -31,11 +33,12 @@ class SdCardDevice : public Device { private: MountBehaviour mountBehaviour; + FileSystem* fileSystem; public: - explicit SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {} - ~SdCardDevice() override = default; + explicit SdCardDevice(MountBehaviour mountBehaviour); + ~SdCardDevice() override; Type getType() const final { return Type::SdCard; }; diff --git a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h deleted file mode 100644 index 32ee8934d..000000000 --- a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifdef ESP_PLATFORM - -#pragma once - -#include "SdCardDevice.h" - -#include -#include -#include -#include -#include -#include - -namespace tt::hal::sdcard { - -/** - * SD card interface for the SDMMC interface. - */ -class SdmmcDevice final : public SdCardDevice { - - std::shared_ptr mutex = std::make_shared(); - -public: - - struct Config { - Config( - gpio_num_t pinClock, - gpio_num_t pinCmd, - gpio_num_t pinD0, - gpio_num_t pinD1, - gpio_num_t pinD2, - gpio_num_t pinD3, - MountBehaviour mountBehaviourAtBoot, - uint8_t busWidth = 4 - ) : - pinClock(pinClock), - pinCmd(pinCmd), - pinD0(pinD0), - pinD1(pinD1), - pinD2(pinD2), - pinD3(pinD3), - mountBehaviourAtBoot(mountBehaviourAtBoot), - busWidth(busWidth) - {} - - gpio_num_t pinClock; - gpio_num_t pinCmd; - gpio_num_t pinD0; - gpio_num_t pinD1; - gpio_num_t pinD2; - gpio_num_t pinD3; - MountBehaviour mountBehaviourAtBoot; - uint8_t busWidth; - bool formatOnMountFailed = false; - uint16_t maxOpenFiles = 4; - uint16_t allocUnitSize = 16 * 1024; - bool statusCheckEnabled = false; - }; - -private: - - std::string mountPath; - sdmmc_card_t* card = nullptr; - std::shared_ptr config; - - bool applyGpioWorkAround(); - bool mountInternal(const std::string& mountPath); - -public: - - explicit SdmmcDevice(std::unique_ptr config) : SdCardDevice(config->mountBehaviourAtBoot), - config(std::move(config)) - {} - - std::string getName() const override { return "SDMMC"; } - std::string getDescription() const override { return "SD card via SDMMC interface"; } - - bool mount(const std::string& mountPath) override; - bool unmount() override; - std::string getMountPath() const override { return mountPath; } - - std::shared_ptr getLock() const override { return mutex; } - - State getState(TickType_t timeout) const override; - - /** return card when mounted, otherwise return nullptr */ - sdmmc_card_t* getCard() { return card; } -}; - -} - -#endif diff --git a/Tactility/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp index ac298fbc2..bb9bf098c 100644 --- a/Tactility/Source/MountPoints.cpp +++ b/Tactility/Source/MountPoints.cpp @@ -2,56 +2,37 @@ #include "Tactility/TactilityConfig.h" #include -#include "Tactility/hal/sdcard/SdCardDevice.h" #include #include -#include #include +#include +#include namespace tt::file { -std::vector getMountPoints() { +std::vector getFileSystemDirents() { std::vector dir_entries; dir_entries.clear(); - // Data partition - auto data_dirent = dirent{ - .d_ino = 1, - .d_type = TT_DT_DIR, - .d_name = { 0 } - }; - strcpy(data_dirent.d_name, DATA_PARTITION_NAME); - dir_entries.push_back(data_dirent); - - // SD card partitions - auto sdcards = tt::hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - auto state = sdcard->getState(); - if (state == hal::sdcard::SdCardDevice::State::Mounted) { - auto mount_name = sdcard->getMountPath().substr(1); - auto dir_entry = dirent { - .d_ino = 2, - .d_type = TT_DT_DIR, - .d_name = { 0 } - }; - assert(mount_name.length() < sizeof(dirent::d_name)); - strcpy(dir_entry.d_name, mount_name.c_str()); - dir_entries.push_back(dir_entry); - } - } - - if (config::SHOW_SYSTEM_PARTITION) { - // System partition - auto system_dirent = dirent{ - .d_ino = 0, + file_system_for_each(&dir_entries, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + auto mount_name = std::string(path).substr(1); + if (!config::SHOW_SYSTEM_PARTITION && mount_name.starts_with(SYSTEM_PARTITION_NAME)) return true; + auto dir_entry = dirent { + .d_ino = 2, .d_type = TT_DT_DIR, .d_name = { 0 } }; - strcpy(system_dirent.d_name, SYSTEM_PARTITION_NAME); - dir_entries.push_back(system_dirent); - } + auto& dir_entries = *static_cast*>(context); + strcpy(dir_entry.d_name, mount_name.c_str()); + dir_entries.push_back(dir_entry); + + return true; + }); return dir_entries; } diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index b1b79a5aa..0d6357ad9 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -5,11 +5,47 @@ #include #include +#include +#include namespace tt { static const auto LOGGER = Logger("Partitions"); +// region file_system stub + +struct PartitionFsData { + const char* path; +}; + +static error_t mount(void* data) { + return ERROR_NOT_SUPPORTED; +} + +static error_t unmount(void* data) { + return ERROR_NOT_SUPPORTED; +} + +static bool is_mounted(void* data) { + return true; +} + +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + auto* fs_data = static_cast(data); + if (strlen(fs_data->path) >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, fs_data->path, out_path_size); + return ERROR_NONE; +} + +FileSystemApi partition_fs_api = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_path = get_path +}; + +// endregion file_system stub + static esp_err_t initNvsFlashSafely() { esp_err_t result = nvs_flash_init(); if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { @@ -56,6 +92,7 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result)); } else { LOGGER.info("Mounted /system"); + file_system_add(&partition_fs_api, new PartitionFsData("/system")); } auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle); @@ -63,6 +100,7 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result)); } else { LOGGER.info("Mounted /data"); + file_system_add(&partition_fs_api, new PartitionFsData("/data")); } return system_result == ESP_OK && data_result == ESP_OK; diff --git a/Tactility/Source/Paths.cpp b/Tactility/Source/Paths.cpp index b35a847b2..9698c51de 100644 --- a/Tactility/Source/Paths.cpp +++ b/Tactility/Source/Paths.cpp @@ -2,25 +2,54 @@ #include #include -#include #include +#include namespace tt { bool findFirstMountedSdCardPath(std::string& path) { - // const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - bool is_set = false; - hal::findDevices(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) { - if (device->isMounted()) { - path = device->getMountPath(); - is_set = true; - return false; // stop iterating - } else { - return true; + auto* fs = findFirstMountedSdcardFileSystem(); + if (fs == nullptr) return false; + char found_path[128]; + if (file_system_get_path(fs, found_path, sizeof(found_path)) != ERROR_NONE) return false; + path = found_path; + return true; +} + +bool hasMountedSdCard() { + auto* fs = findFirstMountedSdcardFileSystem(); + return fs != nullptr; +} + +FileSystem* findFirstMountedSdcardFileSystem() { + FileSystem* found = nullptr; + file_system_for_each(&found, [](auto* fs, void* context) { + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + // TODO: Find a better way to identify SD card paths + if (std::string(path).starts_with("/sdcard") && file_system_is_mounted(fs)) { + *static_cast(context) = fs; + return false; + } + return true; + }); + return found; +} + +FileSystem* findFirstSdcardFileSystem() { + FileSystem* found = nullptr; + file_system_for_each(&found, [](auto* fs, void* context) { + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + // TODO: Find a better way to identify SD card paths + if (std::string(path).starts_with("/sdcard")) { + *static_cast(context) = fs; + return false; } + return true; }); - return is_set; + return found; } std::string getSystemRootPath() { diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 85b81afe1..475e2e98b 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -8,19 +8,19 @@ #include #include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include -#include #include #include #include @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -228,22 +229,18 @@ static void registerInstalledApps(const std::string& path) { }); } -static void registerInstalledAppsFromSdCard(const std::shared_ptr& sdcard) { - auto sdcard_root_path = sdcard->getMountPath(); - auto app_path = std::format("{}/app", sdcard_root_path); - if (file::isDirectory(app_path)) { - registerInstalledApps(app_path); - } -} - -static void registerInstalledAppsFromSdCards() { - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - LOGGER.info("Registering apps from {}", sdcard->getMountPath()); - registerInstalledAppsFromSdCard(sdcard); +static void registerInstalledAppsFromFileSystems() { + file_system_for_each(nullptr, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + const auto app_path = std::format("{}/app", path); + if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) { + LOGGER.info("Registering apps from {}", path); + registerInstalledApps(app_path); } - } + return true; + }); } static void registerAndStartSecondaryServices() { @@ -266,7 +263,7 @@ static void registerAndStartSecondaryServices() { static void registerAndStartPrimaryServices() { LOGGER.info("Registering and starting primary system services"); addService(service::gps::manifest); - if (hal::hasDevice(hal::Device::Type::SdCard)) { + if (hasMountedSdCard()) { addService(service::sdcard::manifest); } addService(service::wifi::manifest); @@ -301,26 +298,19 @@ void createTempDirectory(const std::string& rootPath) { } void prepareFileSystems() { - // Temporary directories for SD cards - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - createTempDirectory(sdcard->getMountPath()); - } - } - // Temporary directory for /data - if (file::isDirectory(file::MOUNT_POINT_DATA)) { - createTempDirectory(file::MOUNT_POINT_DATA); - } + file_system_for_each(nullptr, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true; + createTempDirectory(path); + return true; + }); } void registerApps() { registerInternalApps(); - auto data_apps_path = std::format("{}/app", file::MOUNT_POINT_DATA); - if (file::isDirectory(data_apps_path)) { - registerInstalledApps(data_apps_path); - } - registerInstalledAppsFromSdCards(); + registerInstalledAppsFromFileSystems(); } void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]) { diff --git a/Tactility/Source/app/files/FilesApp.cpp b/Tactility/Source/app/files/FilesApp.cpp index ee2637744..5f353cbf7 100644 --- a/Tactility/Source/app/files/FilesApp.cpp +++ b/Tactility/Source/app/files/FilesApp.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/Tactility/Source/app/files/State.cpp b/Tactility/Source/app/files/State.cpp index 8a7bb4853..86f88a594 100644 --- a/Tactility/Source/app/files/State.cpp +++ b/Tactility/Source/app/files/State.cpp @@ -51,7 +51,7 @@ bool State::setEntriesForPath(const std::string& path) { bool get_mount_points = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); if (get_mount_points) { LOGGER.info("Setting custom root"); - dir_entries = file::getMountPoints(); + dir_entries = file::getFileSystemDirents(); current_path = path; selected_child_entry = ""; action = ActionNone; diff --git a/Tactility/Source/app/fileselection/State.cpp b/Tactility/Source/app/fileselection/State.cpp index 78b997477..b2cf4afc5 100644 --- a/Tactility/Source/app/fileselection/State.cpp +++ b/Tactility/Source/app/fileselection/State.cpp @@ -43,7 +43,7 @@ bool State::setEntriesForPath(const std::string& path) { bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); if (show_custom_root) { LOGGER.info("Setting custom root"); - dir_entries = file::getMountPoints(); + dir_entries = file::getFileSystemDirents(); current_path = path; selected_child_entry = ""; return true; diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index 8394b80da..5ec43faec 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -1,18 +1,18 @@ #include #include - #if TT_FEATURE_SCREENSHOT_ENABLED #include #include -#include #include #include #include #include #include #include + +#include #include #include @@ -204,12 +204,9 @@ void ScreenshotApp::createFilePathWidgets(lv_obj_t* parent) { lv_textarea_set_one_line(pathTextArea, true); lv_obj_set_flex_grow(pathTextArea, 1); if (kernel::getPlatform() == kernel::PlatformEsp) { - auto sdcard_devices = tt::hal::findDevices(tt::hal::Device::Type::SdCard); - if (sdcard_devices.size() > 1) { - LOGGER.warn("Found multiple SD card devices - picking first"); - } - if (!sdcard_devices.empty() && sdcard_devices.front()->isMounted()) { - std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_devices.front()->getMountPath(); + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path)) { + std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_path + "/screenshots"; lv_textarea_set_text(pathTextArea, lvgl_mount_path.c_str()); } else { lv_textarea_set_text(pathTextArea, "Error: no SD card"); diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 6d5c3171f..289adb607 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -1,10 +1,10 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -343,14 +343,9 @@ class SystemInfoApp final : public App { } } - if (hasSdcardStorage) { - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - updateMemoryBar(sdcardStorageBar, storage_free, storage_total); - break; // Only update first SD card - } - } + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) { + updateMemoryBar(sdcardStorageBar, storage_free, storage_total); } if (hasSystemStorage) { @@ -624,13 +619,10 @@ class SystemInfoApp final : public App { dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA); } - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - hasSdcardStorage = true; - sdcardStorageBar = createMemoryBar(storage_tab, sdcard->getMountPath().c_str()); - break; // Only show first SD card - } + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) { + hasSdcardStorage = true; + sdcardStorageBar = createMemoryBar(storage_tab, sdcard_path.c_str()); } if (config::SHOW_SYSTEM_PARTITION) { diff --git a/Tactility/Source/hal/sdcard/SdCardDevice.cpp b/Tactility/Source/hal/sdcard/SdCardDevice.cpp new file mode 100644 index 000000000..b370e37b5 --- /dev/null +++ b/Tactility/Source/hal/sdcard/SdCardDevice.cpp @@ -0,0 +1,48 @@ +#include + +#include + +namespace tt::hal::sdcard { + +static error_t mount(void* data) { + auto* device = static_cast(data); + auto path = device->getMountPath(); + if (!device->mount(path)) return ERROR_UNDEFINED; + return ERROR_NONE; +} + +static error_t unmount(void* data) { + auto* device = static_cast(data); + if (!device->unmount()) return ERROR_UNDEFINED; + return ERROR_NONE; +} + +static bool is_mounted(void* data) { + auto* device = static_cast(data); + return device->isMounted(); +} + +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + auto* device = static_cast(data); + if (device->getMountPath().size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, device->getMountPath().c_str(), out_path_size); + return ERROR_NONE; +} + +FileSystemApi sdCardDeviceApi = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_path = get_path +}; + +SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) { + fileSystem = file_system_add(&sdCardDeviceApi, this); + check(fileSystem != nullptr); +} + +SdCardDevice::~SdCardDevice() { + file_system_remove(fileSystem); +} + +} diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp deleted file mode 100644 index dbd170912..000000000 --- a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#ifdef ESP_PLATFORM -#include -#endif - -#if defined(ESP_PLATFORM) && defined(SOC_SDMMC_HOST_SUPPORTED) - -#include -#include - -#include -#include -#include - -namespace tt::hal::sdcard { - -static const auto LOGGER = Logger("SdmmcDevice"); - -bool SdmmcDevice::mountInternal(const std::string& newMountPath) { - LOGGER.info("Mounting {}", newMountPath); - - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = config->formatOnMountFailed, - .max_files = config->maxOpenFiles, - .allocation_unit_size = 512 * 8, - .disk_status_check_enable = config->statusCheckEnabled, - .use_one_fat = false - }; - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - - sdmmc_slot_config_t slot_config = { - .clk = config->pinClock, - .cmd = config->pinCmd, - .d0 = config->pinD0, - .d1 = config->pinD1, - .d2 = config->pinD2, - .d3 = config->pinD3, - .d4 = static_cast(0), - .d5 = static_cast(0), - .d6 = static_cast(0), - .d7 = static_cast(0), - .cd = GPIO_NUM_NC, - .wp = GPIO_NUM_NC, - .width = config->busWidth, - .flags = 0 - }; - - esp_err_t result = esp_vfs_fat_sdmmc_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card); - - if (result != ESP_OK || card == nullptr) { - if (result == ESP_FAIL) { - LOGGER.error("Mounting failed. Ensure the card is formatted with FAT."); - } else { - LOGGER.error("Mounting failed ({})", esp_err_to_name(result)); - } - return false; - } - - mountPath = newMountPath; - - return true; -} - -bool SdmmcDevice::mount(const std::string& newMountPath) { - auto lock = getLock()->asScopedLock(); - lock.lock(); - - if (mountInternal(newMountPath)) { - LOGGER.info("Mounted at {}", newMountPath); - sdmmc_card_print_info(stdout, card); - return true; - } else { - LOGGER.error("Mount failed for {}", newMountPath); - return false; - } -} - -bool SdmmcDevice::unmount() { - auto lock = getLock()->asScopedLock(); - lock.lock(); - - if (card == nullptr) { - LOGGER.error("Can't unmount: not mounted"); - return false; - } - - if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) { - LOGGER.error("Unmount failed for {}", mountPath); - return false; - } - - LOGGER.info("Unmounted {}", mountPath); - mountPath = ""; - card = nullptr; - return true; -} - -SdmmcDevice::State SdmmcDevice::getState(TickType_t timeout) const { - if (card == nullptr) { - return State::Unmounted; - } - - /** - * The SD card and the screen are on the same SPI bus. - * Writing and reading to the bus from 2 devices at the same time causes crashes. - * This work-around ensures that this check is only happening when LVGL isn't rendering. - */ - auto lock = getLock()->asScopedLock(); - bool locked = lock.lock(timeout); - if (!locked) { - return State::Timeout; - } - - if (sdmmc_get_status(card) != ESP_OK) { - return State::Error; - } - - return State::Mounted; -} - -} - -#endif diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index 6138ff3c7..6e7cdc58e 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace tt::hal::usb { @@ -21,29 +22,35 @@ static Mode currentMode = Mode::Default; static RTC_NOINIT_ATTR BootModeData bootModeData; sdmmc_card_t* getCard() { - auto sdcards = findDevices(Device::Type::SdCard); + sdmmc_card_t* sdcard = nullptr; - std::shared_ptr usable_sdcard; - for (auto& sdcard : sdcards) { - auto sdcard_candidate = std::static_pointer_cast(sdcard); - if (sdcard_candidate != nullptr && sdcard_candidate->isMounted() && sdcard_candidate->getCard() != nullptr) { - usable_sdcard = sdcard_candidate; + // Find old HAL SD card device: + auto sdcards = findDevices(Device::Type::SdCard); + for (auto& device : sdcards) { + auto sdcard_device= std::static_pointer_cast(device); + if (sdcard_device != nullptr && sdcard_device->isMounted() && sdcard_device->getCard() != nullptr) { + sdcard = sdcard_device->getCard(); break; } } - if (usable_sdcard == nullptr) { - LOGGER.warn("Couldn't find a mounted SpiSdCard"); - return nullptr; + // Find ESP32 SDMMC device: + if (sdcard == nullptr) { + device_for_each(&sdcard, [](auto* device, void* context) { + if (device_is_ready(device) && device_is_compatible(device, "espressif,esp32-sdmmc")) { + auto** sdcard = static_cast(context); + *sdcard = esp32_sdmmc_get_card(device); + return false; + } + return true; + }); } - auto* sdmmc_card = usable_sdcard->getCard(); - if (sdmmc_card == nullptr) { - LOGGER.warn("SD card has no card object available"); - return nullptr; + if (sdcard == nullptr) { + LOGGER.warn("Couldn't find a mounted SD card"); } - return sdmmc_card; + return sdcard; } static bool canStartNewMode() { diff --git a/Tactility/Source/service/sdcard/Sdcard.cpp b/Tactility/Source/service/sdcard/Sdcard.cpp index 1cbddb821..fad5b1728 100644 --- a/Tactility/Source/service/sdcard/Sdcard.cpp +++ b/Tactility/Source/service/sdcard/Sdcard.cpp @@ -1,11 +1,12 @@ -#include -#include #include +#include #include -#include -#include +#include #include #include +#include +#include +#include namespace tt::service::sdcard { @@ -17,7 +18,7 @@ class SdCardService final : public Service { Mutex mutex; std::unique_ptr updateTimer; - hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted; + bool lastMountedState = false; bool lock(TickType_t timeout) const { return mutex.lock(timeout); @@ -29,23 +30,13 @@ class SdCardService final : public Service { void update() { // TODO: Support multiple SD cards - auto sdcard = hal::findFirstDevice(hal::Device::Type::SdCard); - if (sdcard == nullptr) { - return; - } + auto* file_system = findFirstSdcardFileSystem(); if (lock(50)) { - auto new_state = sdcard->getState(); - - if (new_state == hal::sdcard::SdCardDevice::State::Error) { - LOGGER.error("Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); - sdcard->unmount(); + auto is_mounted = file_system_is_mounted(file_system); + if (is_mounted != lastMountedState) { + lastMountedState = is_mounted; } - - if (new_state != lastState) { - lastState = new_state; - } - unlock(); } else { LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED); @@ -55,7 +46,8 @@ class SdCardService final : public Service { public: bool onStart(ServiceContext& serviceContext) override { - if (hal::findFirstDevice(hal::Device::Type::SdCard) == nullptr) { + auto* sdcard_fs = findFirstSdcardFileSystem(); + if (sdcard_fs == nullptr) { LOGGER.warn("No SD card device found - not starting Service"); return false; } diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 23c440731..f8105c2a9 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include #include @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -56,18 +57,9 @@ static const char* getWifiStatusIcon(wifi::RadioState state) { } } -static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) { - switch (state) { - using enum hal::sdcard::SdCardDevice::State; - case Mounted: - return LVGL_ICON_STATUSBAR_SD_CARD; - case Error: - case Unmounted: - case Timeout: - return LVGL_ICON_STATUSBAR_SD_CARD_ALERT; - default: - check(false, "Unhandled SdCard state"); - } +static const char* getSdCardStatusIcon(bool mounted) { + if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD; + return LVGL_ICON_STATUSBAR_SD_CARD_ALERT; } static const char* getPowerStatusIcon() { @@ -172,18 +164,15 @@ class StatusbarService final : public Service { } void updateSdCardIcon() { - auto sdcards = hal::findDevices(hal::Device::Type::SdCard); + auto* sdcard_fs = findFirstSdcardFileSystem(); // TODO: Support multiple SD cards - auto sdcard = sdcards.empty() ? nullptr : sdcards[0]; - if (sdcard != nullptr) { - auto state = sdcard->getState(50 / portTICK_PERIOD_MS); - if (state != hal::sdcard::SdCardDevice::State::Timeout) { - auto* desired_icon = getSdCardStatusIcon(state); - if (sdcard_last_icon != desired_icon) { - lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon); - lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); - sdcard_last_icon = desired_icon; - } + if (sdcard_fs != nullptr) { + auto mounted = file_system_is_mounted(sdcard_fs); + auto* desired_icon = getSdCardStatusIcon(mounted); + if (sdcard_last_icon != desired_icon) { + lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon); + lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); + sdcard_last_icon = desired_icon; } // TODO: Consider tracking how long the SD card has been in unknown status and then show error } diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 90b8bdfe0..d85a68561 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -26,8 +26,7 @@ #include #include -#include -#include +#include #if TT_FEATURE_SCREENSHOT_ENABLED #include @@ -762,26 +761,20 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) { std::ostringstream json; json << "{\"path\":\"" << norm << "\",\"entries\":["; - + struct FsIterContext { + std::ostringstream& json; + uint16_t count = 0; + }; + FsIterContext fs_iter_context { json }; // Special handling for root: show available mount points if (norm == "/") { - // Always show /data - json << "{\"name\":\"data\",\"type\":\"dir\",\"size\":0}"; - - // Show /sdcard if mounted - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - json << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}"; - break; - } - } - - device_for_each_of_type(&FILE_SYSTEM_TYPE, &json, [] (auto* fs_device, void* context) { - if (file_system_is_mounted(fs_device)) { - auto* json_context_ptr = static_cast(context); - auto& json_context = *json_context_ptr; - json_context << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}"; + file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) { + auto* fs_iter_context = static_cast(context); + char path[128]; + if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ESP_OK && strcmp(path, "/system") != 0) { + fs_iter_context->count++; + if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries + fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}"; } return true; }); @@ -1168,57 +1161,38 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) { json << "\"storage\":{"; uint64_t storage_total = 0, storage_free = 0; - // Data partition - json << "\"data\":{"; - if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) { - json << "\"free\":" << storage_free << ","; - json << "\"total\":" << storage_total << ","; - json << "\"mounted\":true"; - } else { - json << "\"mounted\":false"; - } - json << "},"; - - // SD card - check all sdcard devices - json << "\"sdcard\":{"; - bool sdcard_found = false; - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - json << "\"free\":" << storage_free << ","; - json << "\"total\":" << storage_total << ","; - json << "\"mounted\":true"; - sdcard_found = true; - break; - } - } - struct FsIterContext { std::ostringstream& json; - bool sdcard_found; + uint16_t count = 0; }; - FsIterContext fs_iter_context { json, sdcard_found }; - device_for_each_of_type(&FILE_SYSTEM_TYPE, &fs_iter_context, [] (auto* fs_device, void* context) { - if (!file_system_is_mounted(fs_device)) return true; - char mount_path[128]; - if (file_system_get_mount_path(fs_device, mount_path, sizeof(mount_path)) != ESP_OK) return true; - uint64_t storage_total = 0, storage_free = 0; - if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) != ESP_OK) return true; + FsIterContext fs_iter_context { json }; + file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) { + char mount_path[128] = ""; + if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true; + if (strcmp(mount_path, "/system") == 0) return true; // Hide system partition + + bool mounted = file_system_is_mounted(fs); auto* fs_iter_context = static_cast(context); auto& json_context = fs_iter_context->json; - json_context << "\"free\":" << storage_free << ","; - json_context << "\"total\":" << storage_total << ","; - json_context << "\"mounted\":true"; - fs_iter_context->sdcard_found = true; - return true; - }); + std::string mount_path_cpp = mount_path; - if (fs_iter_context.sdcard_found) sdcard_found = true; + fs_iter_context->count++; + if (fs_iter_context->count != 1) json_context << ","; // add separator between json array entries + json_context << "\"" << mount_path_cpp.substr(1) << "\":{"; - if (!sdcard_found) { - json << "\"mounted\":false"; - } - json << "}"; + uint64_t storage_total = 0, storage_free = 0; + if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) == ESP_OK) { + json_context << "\"free\":" << storage_free << ","; + json_context << "\"total\":" << storage_total << ","; + } else { + json_context << "\"free\":0,"; + json_context << "\"total\":0,"; + } + + json_context << "\"mounted\":" << (mounted ? "true" : "false") << ""; + json_context << "}"; + return true; + }); json << "},"; // end storage @@ -1490,14 +1464,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) { #if TT_FEATURE_SCREENSHOT_ENABLED // Determine save location: prefer SD card root if mounted, otherwise /data std::string save_path; - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - save_path = sdcard->getMountPath(); - break; - } - } - if (save_path.empty()) { + if (!findFirstMountedSdCardPath(save_path)) { save_path = file::MOUNT_POINT_DATA; } @@ -1574,7 +1541,7 @@ esp_err_t WebServerService::handleFsTree(httpd_req_t* request) { std::ostringstream json; json << "{"; // Gather mount points - auto mounts = file::getMountPoints(); + auto mounts = file::getFileSystemDirents(); json << "\"mounts\": ["; bool firstMount = true; for (auto& m : mounts) { diff --git a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp index 04bd0417a..86625684c 100644 --- a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp +++ b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp @@ -6,13 +6,14 @@ #include #include +#include +#include +#include #include #include #include #include #include -#include -#include namespace tt::service::wifi { @@ -118,18 +119,14 @@ static void importWifiApSettingsFromDir(const std::string& path) { void bootSplashInit() { getMainDispatcher().dispatch([] { // First import any provisioning files placed on the system data partition. - const std::string settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings"); - importWifiApSettingsFromDir(settings_path); + const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings"); + importWifiApSettingsFromDir(data_settings_path); // Then scan attached SD cards as before. - const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - if (sdcard->isMounted()) { - const std::string settings_path = file::getChildPath(sdcard->getMountPath(), "settings"); - importWifiApSettingsFromDir(settings_path); - } else { - LOGGER.warn("Skipping unmounted SD card {}", sdcard->getMountPath()); - } + std::string sdcard_path; + if (findFirstMountedSdCardPath((sdcard_path))) { + const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings"); + importWifiApSettingsFromDir(sd_settings_path); } }); } diff --git a/Tactility/Source/settings/BootSettings.cpp b/Tactility/Source/settings/BootSettings.cpp index 1b30ba36f..29cdbcf42 100644 --- a/Tactility/Source/settings/BootSettings.cpp +++ b/Tactility/Source/settings/BootSettings.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -18,9 +19,9 @@ constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId"; constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId"; static std::string getPropertiesFilePath() { - const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard->getMountPath()); + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path)) { + std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path); if (file::isFile(path)) { return path; } diff --git a/TactilityKernel/include/tactility/drivers/file_system.h b/TactilityKernel/include/tactility/drivers/file_system.h deleted file mode 100644 index 9a487ffe7..000000000 --- a/TactilityKernel/include/tactility/drivers/file_system.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct Device; - -struct FileSystemApi { - error_t (*mount)(struct Device* device, const char* mount_path); - error_t (*unmount)(struct Device* device); - bool (*is_mounted)(struct Device* device); - error_t (*get_mount_path)(struct Device*, char* out_path, size_t out_path_size); -}; - -extern const struct DeviceType FILE_SYSTEM_TYPE; - -error_t file_system_mount(struct Device* device, const char* mount_path); - -error_t file_system_unmount(struct Device* device); - -bool file_system_is_mounted(struct Device* device); - -error_t file_system_get_mount_path(struct Device*, char* out_path, size_t out_path_size); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityKernel/include/tactility/filesystem/file_system.h b/TactilityKernel/include/tactility/filesystem/file_system.h new file mode 100644 index 000000000..c928e4b33 --- /dev/null +++ b/TactilityKernel/include/tactility/filesystem/file_system.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct FileSystem; + +struct FileSystemApi { + error_t (*mount)(void* data); + error_t (*unmount)(void* data); + bool (*is_mounted)(void* data); + error_t (*get_path)(void* data, char* out_path, size_t out_path_size); +}; + +struct FileSystem* file_system_add(const struct FileSystemApi* fs_api, void* data); + +void file_system_remove(struct FileSystem* fs); + +void file_system_for_each(void* callback_context, bool (*callback)(struct FileSystem* fs, void* context)); + +error_t file_system_mount(struct FileSystem* fs); + +error_t file_system_unmount(struct FileSystem* fs); + +bool file_system_is_mounted(struct FileSystem* fs); + +error_t file_system_get_path(struct FileSystem* fs, char* out_path, size_t out_path_size); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/drivers/file_system.cpp b/TactilityKernel/source/drivers/file_system.cpp deleted file mode 100644 index 5bca5bb2f..000000000 --- a/TactilityKernel/source/drivers/file_system.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include - -#define INTERNAL_API(driver) ((FileSystemApi*)(driver)->api) - -extern "C" { - -error_t file_system_mount(Device* device, const char* mount_path) { - const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->mount(device, mount_path); -} - -error_t file_system_unmount(Device* device) { - const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->unmount(device); -} - -bool file_system_is_mounted(Device* device) { - const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->is_mounted(device); -} - -error_t file_system_get_mount_path(Device* device, char* out_path, size_t out_path_size) { - const auto* driver = device_get_driver(device); - return INTERNAL_API(driver)->get_mount_path(device, out_path, out_path_size); -} - -const DeviceType FILE_SYSTEM_TYPE { - .name = "file-system" -}; - -} // extern "C" diff --git a/TactilityKernel/source/filesystem/file_system.cpp b/TactilityKernel/source/filesystem/file_system.cpp new file mode 100644 index 000000000..b9e5926a3 --- /dev/null +++ b/TactilityKernel/source/filesystem/file_system.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +// Define the internal FileSystem structure +struct FileSystem { + const FileSystemApi* api; + void* data; +}; + +// Global Mutex and the master list of file systems +static Mutex fs_mutex; +static bool fs_mutex_initialized = false; +static std::vector file_systems; + +static void ensure_mutex_initialized() { + if (!fs_mutex_initialized) { + mutex_construct(&fs_mutex); + fs_mutex_initialized = true; + } +} + +extern "C" { + +FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) { + ensure_mutex_initialized(); + mutex_lock(&fs_mutex); + + auto* fs = new(std::nothrow) struct FileSystem(); + check(fs != nullptr); + fs->api = fs_api; + fs->data = data; + file_systems.push_back(fs); + + mutex_unlock(&fs_mutex); + return fs; +} + +void file_system_remove(FileSystem* fs) { + check(!file_system_is_mounted(fs)); + ensure_mutex_initialized(); + mutex_lock(&fs_mutex); + + auto it = std::ranges::find(file_systems, fs); + if (it != file_systems.end()) { + file_systems.erase(it); + delete fs; + } + + mutex_unlock(&fs_mutex); +} + +void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) { + ensure_mutex_initialized(); + mutex_lock(&fs_mutex); + + for (auto* fs : file_systems) { + if (!callback(fs, callback_context)) break; + } + + mutex_unlock(&fs_mutex); +} + +error_t file_system_mount(FileSystem* fs) { + // Assuming 'device' is accessible or passed via a different mechanism + // as it's required by the FileSystemApi signatures. + return fs->api->mount(fs->data); +} + +error_t file_system_unmount(FileSystem* fs) { + return fs->api->unmount(fs->data); +} + +bool file_system_is_mounted(FileSystem* fs) { + return fs->api->is_mounted(fs->data); +} + +error_t file_system_get_path(FileSystem* fs, char* out_path, size_t out_path_size) { + return fs->api->get_path(fs->data, out_path, out_path_size); +} + +} diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 829ac5c5e..da2b3f777 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -5,13 +5,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include /** @@ -58,11 +58,6 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(driver_is_compatible), DEFINE_MODULE_SYMBOL(driver_find_compatible), DEFINE_MODULE_SYMBOL(driver_get_device_type), - // file system - DEFINE_MODULE_SYMBOL(file_system_mount), - DEFINE_MODULE_SYMBOL(file_system_unmount), - DEFINE_MODULE_SYMBOL(file_system_is_mounted), - DEFINE_MODULE_SYMBOL(file_system_get_mount_path), // drivers/gpio_controller DEFINE_MODULE_SYMBOL(gpio_descriptor_acquire), DEFINE_MODULE_SYMBOL(gpio_descriptor_release), @@ -158,6 +153,11 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(timer_set_callback_priority), // error DEFINE_MODULE_SYMBOL(error_to_string), + // file system + DEFINE_MODULE_SYMBOL(file_system_mount), + DEFINE_MODULE_SYMBOL(file_system_unmount), + DEFINE_MODULE_SYMBOL(file_system_is_mounted), + DEFINE_MODULE_SYMBOL(file_system_get_path), // log #ifndef ESP_PLATFORM DEFINE_MODULE_SYMBOL(log_generic), From cd0e3fc5698683a34df8d4c4c134422f968dc7db Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 01:24:10 +0100 Subject: [PATCH 17/25] Converted SDMMC drivers --- .../lilygo-thmi-s3/Source/Configuration.cpp | 2 -- .../lilygo-thmi-s3/Source/devices/SdCard.cpp | 23 ------------------- .../lilygo-thmi-s3/Source/devices/SdCard.h | 15 ------------ Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts | 9 ++++++++ .../Source/Configuration.cpp | 2 -- .../Source/devices/SdCard.cpp | 22 ------------------ .../Source/devices/SdCard.h | 7 ------ .../waveshare,esp32-s3-geek.dts | 12 ++++++++++ 8 files changed, 21 insertions(+), 71 deletions(-) delete mode 100644 Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp delete mode 100644 Devices/lilygo-thmi-s3/Source/devices/SdCard.h delete mode 100644 Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp delete mode 100644 Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h diff --git a/Devices/lilygo-thmi-s3/Source/Configuration.cpp b/Devices/lilygo-thmi-s3/Source/Configuration.cpp index 8d04e56f0..68aa2d31c 100644 --- a/Devices/lilygo-thmi-s3/Source/Configuration.cpp +++ b/Devices/lilygo-thmi-s3/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Power.h" -#include "devices/SdCard.h" #include "devices/Display.h" #include @@ -11,7 +10,6 @@ using namespace tt::hal; static std::vector> createDevices() { return { - createSdCard(), createDisplay(), std::make_shared(), ButtonControl::createOneButtonControl(0) diff --git a/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp b/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp deleted file mode 100644 index 03137e90b..000000000 --- a/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "SdCard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - SD_DIO_SCLK, //CLK - SD_DIO_CMD, //CMD - SD_DIO_DATA0, //D0 - SD_DIO_NC, //D1 - SD_DIO_NC, //D2 - SD_DIO_NC, //D3 - SdCardDevice::MountBehaviour::AtBoot, - SD_DIO_BUS_WIDTH - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/lilygo-thmi-s3/Source/devices/SdCard.h b/Devices/lilygo-thmi-s3/Source/devices/SdCard.h deleted file mode 100644 index ab4ec439f..000000000 --- a/Devices/lilygo-thmi-s3/Source/devices/SdCard.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -constexpr auto SD_DIO_CMD = GPIO_NUM_11; -constexpr auto SD_DIO_SCLK = GPIO_NUM_12; -constexpr auto SD_DIO_DATA0 = GPIO_NUM_13; -constexpr auto SD_DIO_NC = GPIO_NUM_NC; -constexpr auto SD_DIO_BUS_WIDTH = 1; - -std::shared_ptr createSdCard(); \ No newline at end of file diff --git a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts b/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts index 7392e9676..d6d8fc719 100644 --- a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts +++ b/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts @@ -3,6 +3,7 @@ #include #include #include +#include #include / { @@ -21,4 +22,12 @@ pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 1 GPIO_FLAG_NONE>; }; + + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 11 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 13 GPIO_FLAG_NONE>; + bus-width = <1>; + }; }; diff --git a/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp b/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp index 422ab5a83..5f28b1f3c 100644 --- a/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp +++ b/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Display.h" -#include "devices/SdCard.h" #include #include @@ -10,7 +9,6 @@ using namespace tt::hal; static DeviceVector createDevices() { return { createDisplay(), - createSdCard(), ButtonControl::createOneButtonControl(0) }; } diff --git a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp b/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp deleted file mode 100644 index 542eec0b1..000000000 --- a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "SdCard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - GPIO_NUM_36, //CLK - GPIO_NUM_35, //CMD - GPIO_NUM_37, //D0 - GPIO_NUM_33, //D1 - GPIO_NUM_38, //D2 - GPIO_NUM_34, //D3 - SdCardDevice::MountBehaviour::AtBoot - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h b/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h deleted file mode 100644 index 07d556490..000000000 --- a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -std::shared_ptr createSdCard(); \ No newline at end of file diff --git a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts index 9b98719e6..28b3c9327 100644 --- a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts +++ b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,17 @@ pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>; }; + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 36 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 35 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 37 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 33 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 38 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 34 GPIO_FLAG_NONE>; + bus-width = <4>; + }; + uart0 { compatible = "espressif,esp32-uart"; port = ; From dbefa0a7d434423c1a91a00fb882a16f473b16b8 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 01:29:09 +0100 Subject: [PATCH 18/25] Add comment --- Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp b/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp index 3a08e11ff..9bee001c8 100644 --- a/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp +++ b/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp @@ -18,6 +18,7 @@ static const auto LOGGER = tt::Logger("JcSdCard"); // ESP32-P4 Slot 0 uses IO MUX (fixed pins, not manually configurable) // CLK=43, CMD=44, D0=39, D1=40, D2=41, D3=42 (defined automatically by hardware) +// TODO: Migrate to "espressif,esp32-sdmmc" driver (needs LDO code porting) class SdCardDeviceImpl final : public SdCardDevice { class NoLock final : public tt::Lock { From f12a52ebb96e34e2d557e8d55a2b3d347bdce047 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 01:47:17 +0100 Subject: [PATCH 19/25] Fixes --- .../platform-esp32/include/tactility/drivers/esp32_sdmmc.h | 3 +++ .../private/tactility/drivers/esp32_sdmmc_fs.h | 3 +++ Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp | 2 ++ Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp | 2 ++ Platforms/platform-esp32/source/module.cpp | 7 ++++++- Tactility/Source/hal/sdcard/SdCardDevice.cpp | 2 ++ Tactility/Source/hal/usb/Usb.cpp | 2 ++ 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h index c5cc50f9c..c32c8fd8c 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#if SOC_SDMMC_HOST_SUPPORTED #include #include @@ -33,3 +34,5 @@ sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device); #ifdef __cplusplus } #endif + +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h index 2f5d3f0f7..56284cc45 100644 --- a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h +++ b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#if SOC_SDMMC_HOST_SUPPORTED #include #ifdef __cplusplus @@ -20,3 +21,5 @@ extern const FileSystemApi esp32_sdmmc_fs_api; #ifdef __cplusplus } #endif + +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index 3facba295..b4d19fe8e 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 +#if SOC_SDMMC_HOST_SUPPORTED #include #include #include @@ -159,3 +160,4 @@ Driver esp32_sdmmc_driver = { }; } // extern "C" +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index 456cf3b07..ee77c552d 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 +#if SOC_SDMMC_HOST_SUPPORTED #include #include #include @@ -175,3 +176,4 @@ const FileSystemApi esp32_sdmmc_fs_api = { }; } // extern "C" +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index 6a49f1fa8..f79210917 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -7,8 +7,9 @@ extern "C" { extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2s_driver; +#if SOC_SDMMC_HOST_SUPPORTED extern Driver esp32_sdmmc_driver; -extern Driver esp32_sdmmc_fs_driver; +#endif extern Driver esp32_spi_driver; extern Driver esp32_uart_driver; @@ -18,7 +19,9 @@ static error_t start() { check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_SDMMC_HOST_SUPPORTED check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); +#endif check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE); check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; @@ -30,7 +33,9 @@ static error_t stop() { check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_SDMMC_HOST_SUPPORTED check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); +#endif check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; diff --git a/Tactility/Source/hal/sdcard/SdCardDevice.cpp b/Tactility/Source/hal/sdcard/SdCardDevice.cpp index b370e37b5..15c6388e4 100644 --- a/Tactility/Source/hal/sdcard/SdCardDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdCardDevice.cpp @@ -2,6 +2,8 @@ #include +#include + namespace tt::hal::sdcard { static error_t mount(void* data) { diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index 6e7cdc58e..2fa38a073 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -34,6 +34,7 @@ sdmmc_card_t* getCard() { } } +#if SOC_SDMMC_HOST_SUPPORTED // Find ESP32 SDMMC device: if (sdcard == nullptr) { device_for_each(&sdcard, [](auto* device, void* context) { @@ -45,6 +46,7 @@ sdmmc_card_t* getCard() { return true; }); } +#endif if (sdcard == nullptr) { LOGGER.warn("Couldn't find a mounted SD card"); From c42b43e1c332c1cfe7bed7042dae1328138494bb Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 12:00:58 +0100 Subject: [PATCH 20/25] Compilation fix --- .../platform-esp32/include/tactility/drivers/esp32_sdmmc.h | 1 + .../platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h | 1 + Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp | 2 +- Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp | 1 + Platforms/platform-esp32/source/module.cpp | 2 ++ Tactility/Source/hal/usb/Usb.cpp | 2 ++ TactilityC/Source/tt_init.cpp | 1 - 7 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h index c32c8fd8c..ce2d47dd1 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include #if SOC_SDMMC_HOST_SUPPORTED #include diff --git a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h index 56284cc45..6f0f5bf08 100644 --- a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h +++ b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include #if SOC_SDMMC_HOST_SUPPORTED #include diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index b4d19fe8e..88b779444 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 +#include #if SOC_SDMMC_HOST_SUPPORTED #include #include @@ -118,7 +119,6 @@ static error_t start(Device* device) { static error_t stop(Device* device) { ESP_LOGI(TAG, "stop %s", device->name); auto* data = GET_DATA(device); - auto* dts_config = GET_CONFIG(device); if (file_system_is_mounted(data->file_system)) { if (file_system_unmount(data->file_system) != ERROR_NONE) { diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index ee77c552d..52ef49d10 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 +#include #if SOC_SDMMC_HOST_SUPPORTED #include #include diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index f79210917..84a73aa6c 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -2,6 +2,8 @@ #include #include +#include + extern "C" { extern Driver esp32_gpio_driver; diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index 2fa38a073..8cf803de1 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -1,5 +1,7 @@ #ifdef ESP_PLATFORM +#include + #include #include #include diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 00dfeeb37..796259b22 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -416,7 +416,6 @@ const esp_elfsym main_symbols[] { // miniz.h ESP_ELFSYM_EXPORT(tinfl_decompress), ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback), - ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_heap), ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem), // ledc ESP_ELFSYM_EXPORT(ledc_update_duty), From 9efc7c7f8a66185f09454fdba3025fa379f9a22d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 13:33:19 +0100 Subject: [PATCH 21/25] Fixes --- Devices/m5stack-tab5/Source/Configuration.cpp | 7 ++ .../include/tactility/drivers/esp32_sdmmc.h | 5 ++ .../source/drivers/esp32_gpio.cpp | 36 +++++---- .../source/drivers/esp32_sdmmc.cpp | 18 +++-- .../source/drivers/esp32_sdmmc_fs.cpp | 14 ++-- Tactility/Include/Tactility/Paths.h | 6 +- Tactility/Source/MountPoints.cpp | 1 - Tactility/Source/PartitionsEsp.cpp | 6 +- Tactility/Source/Paths.cpp | 27 ++----- Tactility/Source/Tactility.cpp | 6 +- Tactility/Source/app/fileselection/State.cpp | 9 ++- .../Source/app/systeminfo/SystemInfo.cpp | 2 - Tactility/Source/hal/sdcard/SdCardDevice.cpp | 7 +- Tactility/Source/hal/usb/Usb.cpp | 8 +- Tactility/Source/service/sdcard/Sdcard.cpp | 80 ------------------- .../Source/service/statusbar/Statusbar.cpp | 8 +- .../service/webserver/WebServerService.cpp | 2 +- TactilityC/Source/tt_init.cpp | 4 + .../include/tactility/drivers/gpio.h | 2 +- .../tactility/drivers/gpio_controller.h | 2 +- .../include/tactility/drivers/wifi.h | 39 ++++++--- .../tactility/filesystem/file_system.h | 69 ++++++++++++++++ .../source/drivers/gpio_controller.cpp | 7 +- .../source/filesystem/file_system.cpp | 51 ++++++------ TactilityKernel/source/kernel_symbols.c | 4 + 25 files changed, 229 insertions(+), 191 deletions(-) delete mode 100644 Tactility/Source/service/sdcard/Sdcard.cpp diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index 0f75154e2..6a39ad0ce 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -57,12 +57,19 @@ constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7; static void initExpander0(::Device* io_expander0) { auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO); + check(rf_pin); auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + check(speaker_enable_pin); auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO); + check(external_5v_bus_enable_pin); auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO); + check(lcd_reset_pin); auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO); + check(touch_reset_pin); auto* camera_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_CAMERA_RESET, GPIO_OWNER_GPIO); + check(camera_reset_pin); auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); + check(headphone_detect_pin); gpio_descriptor_set_flags(rf_pin, GPIO_FLAG_DIRECTION_OUTPUT); gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h index ce2d47dd1..152b42559 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -30,6 +30,11 @@ struct Esp32SdmmcConfig { bool enable_uhs; }; +/** + * @brief Get the SD card handle for the given device. + * @param[in] device the device to get the card handle for + * @return the SD card handle, or NULL if the device is not mounted + */ sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device); #ifdef __cplusplus diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp index 64ae3c631..a4b8098ff 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp @@ -103,16 +103,6 @@ static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_numbe } static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { - auto esp_error = gpio_isr_handler_add(static_cast(descriptor->pin), callback, arg); - return esp_err_to_error(esp_error); -} - -static error_t remove_callback(GpioDescriptor* descriptor) { - auto esp_error = gpio_isr_handler_remove(static_cast(descriptor->pin)); - return esp_err_to_error(esp_error); -} - -static error_t enable_interrupt(GpioDescriptor* descriptor) { auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); if (internal->isr_service_ref_count == 0) { auto esp_error = gpio_install_isr_service(0); @@ -120,17 +110,23 @@ static error_t enable_interrupt(GpioDescriptor* descriptor) { return esp_err_to_error(esp_error); } } - auto esp_error = gpio_intr_enable(static_cast(descriptor->pin)); + + auto esp_error = gpio_isr_handler_add(static_cast(descriptor->pin), callback, arg); + if (esp_error == ESP_OK) { internal->isr_service_ref_count++; + } else if (internal->isr_service_ref_count == 0) { + gpio_uninstall_isr_service(); } + return esp_err_to_error(esp_error); } -static error_t disable_interrupt(GpioDescriptor* descriptor) { - auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); - auto esp_error = gpio_intr_disable(static_cast(descriptor->pin)); - if (esp_error == ESP_OK && internal->isr_service_ref_count > 0) { +static error_t remove_callback(GpioDescriptor* descriptor) { + auto esp_error = gpio_isr_handler_remove(static_cast(descriptor->pin)); + if (esp_error == ESP_OK) { + auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); + check(internal->isr_service_ref_count > 0); internal->isr_service_ref_count--; if (internal->isr_service_ref_count == 0) { gpio_uninstall_isr_service(); @@ -139,6 +135,16 @@ static error_t disable_interrupt(GpioDescriptor* descriptor) { return esp_err_to_error(esp_error); } +static error_t enable_interrupt(GpioDescriptor* descriptor) { + auto esp_error = gpio_intr_enable(static_cast(descriptor->pin)); + return esp_err_to_error(esp_error); +} + +static error_t disable_interrupt(GpioDescriptor* descriptor) { + auto esp_error = gpio_intr_disable(static_cast(descriptor->pin)); + return esp_err_to_error(esp_error); +} + static error_t start(Device* device) { ESP_LOGI(TAG, "start %s", device->name); const Esp32GpioConfig* config = GET_CONFIG(device); diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index 88b779444..5955de1d5 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -1,16 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 #include #if SOC_SDMMC_HOST_SUPPORTED -#include -#include -#include -#include -#include -#include "tactility/drivers/gpio_descriptor.h" #include + +#include +#include #include +#include +#include +#include #include +#include #define TAG "esp32_sdmmc" @@ -46,7 +47,7 @@ struct Esp32SdmmcInternal { ~Esp32SdmmcInternal() { cleanup_pins(); recursive_mutex_destruct(&mutex); - if (esp32_sdmmc_fs_handle) free(esp32_sdmmc_fs_handle); + if (esp32_sdmmc_fs_handle) esp32_sdmmc_fs_free(esp32_sdmmc_fs_handle); } void cleanup_pins() { @@ -105,6 +106,7 @@ static error_t start(Device* device) { } data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard"); + check(data->esp32_sdmmc_fs_handle); data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle); if (file_system_mount(data->file_system) != ERROR_NONE) { // Error is not recoverable at the time, but it might be recoverable later, @@ -117,7 +119,7 @@ static error_t start(Device* device) { } static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); + LOG_I(TAG, "stop %s", device->name); auto* data = GET_DATA(device); if (file_system_is_mounted(data->file_system)) { diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index 52ef49d10..cf4b8611c 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -81,12 +81,12 @@ static error_t mount(void* data) { sd_pwr_ctrl_ldo_config_t ldo_config = { .ldo_chan_id = 4, // LDO4 is typically used for SDMMC on ESP32-S3 }; - esp_err_t pwr_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &data->pwr_ctrl_handle); + esp_err_t pwr_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &fs_data->pwr_ctrl_handle); if (pwr_err != ESP_OK) { LOG_E(TAG, "Failed to create SD power control driver, err=0x%x", pwr_err); return ERROR_NOT_SUPPORTED; } - host.pwr_ctrl_handle = data->pwr_ctrl_handle; + host.pwr_ctrl_handle = fs_data->pwr_ctrl_handle; #endif uint32_t slot_config_flags = 0; @@ -144,9 +144,9 @@ static error_t unmount(void* data) { fs_data->card = nullptr; #if SOC_SD_PWR_CTRL_SUPPORTED - if (data->pwr_ctrl_handle) { - sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle); - data->pwr_ctrl_handle = nullptr; + if (fs_data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle); + fs_data->pwr_ctrl_handle = nullptr; } #endif @@ -157,13 +157,13 @@ static error_t unmount(void* data) { static bool is_mounted(void* data) { const auto* fs_data = GET_DATA(data); - if (fs_data == nullptr || fs_data->card == nullptr) return false; + if (fs_data->card == nullptr) return false; return sdmmc_get_status(fs_data->card) == ESP_OK; } static error_t get_path(void* data, char* out_path, size_t out_path_size) { const auto* fs_data = GET_DATA(data); - if (fs_data == nullptr || fs_data->card == nullptr) return ERROR_INVALID_STATE; + if (fs_data->card == nullptr) return ERROR_INVALID_STATE; if (fs_data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; strncpy(out_path, fs_data->mount_path.c_str(), out_path_size); return ERROR_NONE; diff --git a/Tactility/Include/Tactility/Paths.h b/Tactility/Include/Tactility/Paths.h index f12c53e3f..c3930db97 100644 --- a/Tactility/Include/Tactility/Paths.h +++ b/Tactility/Include/Tactility/Paths.h @@ -9,11 +9,7 @@ namespace tt { bool findFirstMountedSdCardPath(std::string& path); -bool hasMountedSdCard(); - -FileSystem* findFirstMountedSdcardFileSystem(); - -FileSystem* findFirstSdcardFileSystem(); +FileSystem* findSdcardFileSystem(bool mustBeMounted); std::string getSystemRootPath(); diff --git a/Tactility/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp index bb9bf098c..32a6866bd 100644 --- a/Tactility/Source/MountPoints.cpp +++ b/Tactility/Source/MountPoints.cpp @@ -14,7 +14,6 @@ namespace tt::file { std::vector getFileSystemDirents() { std::vector dir_entries; - dir_entries.clear(); file_system_for_each(&dir_entries, [](auto* fs, void* context) { if (!file_system_is_mounted(fs)) return true; diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index 0d6357ad9..73f0f11c8 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -92,7 +92,8 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result)); } else { LOGGER.info("Mounted /system"); - file_system_add(&partition_fs_api, new PartitionFsData("/system")); + static auto system_fs_data = PartitionFsData("/system"); + file_system_add(&partition_fs_api, &system_fs_data); } auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle); @@ -100,7 +101,8 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result)); } else { LOGGER.info("Mounted /data"); - file_system_add(&partition_fs_api, new PartitionFsData("/data")); + static auto data_fs_data = PartitionFsData("/data"); + file_system_add(&partition_fs_api, &data_fs_data); } return system_result == ESP_OK && data_result == ESP_OK; diff --git a/Tactility/Source/Paths.cpp b/Tactility/Source/Paths.cpp index 9698c51de..8affa3c0f 100644 --- a/Tactility/Source/Paths.cpp +++ b/Tactility/Source/Paths.cpp @@ -9,7 +9,7 @@ namespace tt { bool findFirstMountedSdCardPath(std::string& path) { - auto* fs = findFirstMountedSdcardFileSystem(); + auto* fs = findSdcardFileSystem(true); if (fs == nullptr) return false; char found_path[128]; if (file_system_get_path(fs, found_path, sizeof(found_path)) != ERROR_NONE) return false; @@ -17,27 +17,7 @@ bool findFirstMountedSdCardPath(std::string& path) { return true; } -bool hasMountedSdCard() { - auto* fs = findFirstMountedSdcardFileSystem(); - return fs != nullptr; -} - -FileSystem* findFirstMountedSdcardFileSystem() { - FileSystem* found = nullptr; - file_system_for_each(&found, [](auto* fs, void* context) { - char path[128]; - if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; - // TODO: Find a better way to identify SD card paths - if (std::string(path).starts_with("/sdcard") && file_system_is_mounted(fs)) { - *static_cast(context) = fs; - return false; - } - return true; - }); - return found; -} - -FileSystem* findFirstSdcardFileSystem() { +FileSystem* findSdcardFileSystem(bool mustBeMounted) { FileSystem* found = nullptr; file_system_for_each(&found, [](auto* fs, void* context) { char path[128]; @@ -49,6 +29,9 @@ FileSystem* findFirstSdcardFileSystem() { } return true; }); + if (found && mustBeMounted && !file_system_is_mounted(found)) { + return nullptr; + } return found; } diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 475e2e98b..52ff1ef4f 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -50,7 +50,6 @@ namespace service { // Primary namespace gps { extern const ServiceManifest manifest; } namespace wifi { extern const ServiceManifest manifest; } - namespace sdcard { extern const ServiceManifest manifest; } #ifdef ESP_PLATFORM namespace development { extern const ServiceManifest manifest; } #endif @@ -236,7 +235,7 @@ static void registerInstalledAppsFromFileSystems() { if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; const auto app_path = std::format("{}/app", path); if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) { - LOGGER.info("Registering apps from {}", path); + LOGGER.info("Registering apps from {}", app_path); registerInstalledApps(app_path); } return true; @@ -263,9 +262,6 @@ static void registerAndStartSecondaryServices() { static void registerAndStartPrimaryServices() { LOGGER.info("Registering and starting primary system services"); addService(service::gps::manifest); - if (hasMountedSdCard()) { - addService(service::sdcard::manifest); - } addService(service::wifi::manifest); #ifdef ESP_PLATFORM addService(service::development::manifest); diff --git a/Tactility/Source/app/fileselection/State.cpp b/Tactility/Source/app/fileselection/State.cpp index b2cf4afc5..7dfa38ca8 100644 --- a/Tactility/Source/app/fileselection/State.cpp +++ b/Tactility/Source/app/fileselection/State.cpp @@ -6,10 +6,11 @@ #include #include +#include #include +#include #include #include -#include namespace tt::app::fileselection { @@ -36,6 +37,12 @@ std::string State::getSelectedChildPath() const { bool State::setEntriesForPath(const std::string& path) { LOGGER.info("Changing path: {} -> {}", current_path, path); + auto lock = mutex.asScopedLock(); + if (!lock.lock(100)) { + LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "setEntriesForPath"); + return false; + } + /** * ESP32 does not have a root directory, so we have to create it manually. * We'll add the NVS Flash partitions and the binding for the sdcard. diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 289adb607..8e7f25d39 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -321,7 +321,6 @@ class SystemInfoApp final : public App { bool hasExternalMem = false; bool hasDataStorage = false; - bool hasSdcardStorage = false; bool hasSystemStorage = false; void updateMemory() { @@ -621,7 +620,6 @@ class SystemInfoApp final : public App { std::string sdcard_path; if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) { - hasSdcardStorage = true; sdcardStorageBar = createMemoryBar(storage_tab, sdcard_path.c_str()); } diff --git a/Tactility/Source/hal/sdcard/SdCardDevice.cpp b/Tactility/Source/hal/sdcard/SdCardDevice.cpp index 15c6388e4..1c147f5b4 100644 --- a/Tactility/Source/hal/sdcard/SdCardDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdCardDevice.cpp @@ -26,8 +26,10 @@ static bool is_mounted(void* data) { static error_t get_path(void* data, char* out_path, size_t out_path_size) { auto* device = static_cast(data); - if (device->getMountPath().size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; - strncpy(out_path, device->getMountPath().c_str(), out_path_size); + const auto mount_path = device->getMountPath(); + if (mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + if (mount_path.empty()) return ERROR_INVALID_STATE; + strncpy(out_path, mount_path.c_str(), out_path_size); return ERROR_NONE; } @@ -44,6 +46,7 @@ SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mount } SdCardDevice::~SdCardDevice() { + check(!isMounted()); file_system_remove(fileSystem); } diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index 8cf803de1..c52a0c52a 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -42,8 +42,12 @@ sdmmc_card_t* getCard() { device_for_each(&sdcard, [](auto* device, void* context) { if (device_is_ready(device) && device_is_compatible(device, "espressif,esp32-sdmmc")) { auto** sdcard = static_cast(context); - *sdcard = esp32_sdmmc_get_card(device); - return false; + auto* sdmmc_card = esp32_sdmmc_get_card(device); + if (sdmmc_card) { + *sdcard = sdmmc_card; + return false; + } + return true; } return true; }); diff --git a/Tactility/Source/service/sdcard/Sdcard.cpp b/Tactility/Source/service/sdcard/Sdcard.cpp deleted file mode 100644 index fad5b1728..000000000 --- a/Tactility/Source/service/sdcard/Sdcard.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace tt::service::sdcard { - -static const auto LOGGER = Logger("SdcardService"); - -extern const ServiceManifest manifest; - -class SdCardService final : public Service { - - Mutex mutex; - std::unique_ptr updateTimer; - bool lastMountedState = false; - - bool lock(TickType_t timeout) const { - return mutex.lock(timeout); - } - - void unlock() const { - mutex.unlock(); - } - - void update() { - // TODO: Support multiple SD cards - auto* file_system = findFirstSdcardFileSystem(); - - if (lock(50)) { - auto is_mounted = file_system_is_mounted(file_system); - if (is_mounted != lastMountedState) { - lastMountedState = is_mounted; - } - unlock(); - } else { - LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED); - } - } - -public: - - bool onStart(ServiceContext& serviceContext) override { - auto* sdcard_fs = findFirstSdcardFileSystem(); - if (sdcard_fs == nullptr) { - LOGGER.warn("No SD card device found - not starting Service"); - return false; - } - - auto service = findServiceById(manifest.id); - updateTimer = std::make_unique(Timer::Type::Periodic, 1000, [service] { - service->update(); - }); - - // We want to try and scan more often in case of startup or scan lock failure - updateTimer->start(); - - return true; - } - - void onStop(ServiceContext& serviceContext) override { - if (updateTimer != nullptr) { - // Stop thread - updateTimer->stop(); - updateTimer = nullptr; - } - } -}; - -extern const ServiceManifest manifest = { - .id = "sdcard", - .createService = create -}; - -} // namespace diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index f8105c2a9..0af4c2007 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -164,7 +164,7 @@ class StatusbarService final : public Service { } void updateSdCardIcon() { - auto* sdcard_fs = findFirstSdcardFileSystem(); + auto* sdcard_fs = findSdcardFileSystem(false); // TODO: Support multiple SD cards if (sdcard_fs != nullptr) { auto mounted = file_system_is_mounted(sdcard_fs); @@ -174,7 +174,11 @@ class StatusbarService final : public Service { lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); sdcard_last_icon = desired_icon; } - // TODO: Consider tracking how long the SD card has been in unknown status and then show error + } else { + if (sdcard_last_icon != nullptr) { + lvgl::statusbar_icon_set_visibility(sdcard_icon_id, false); + sdcard_last_icon = nullptr; + } } } diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index c3d9190a8..5a2f5523b 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -775,7 +775,7 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) { file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) { auto* fs_iter_context = static_cast(context); char path[128]; - if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ESP_OK && strcmp(path, "/system") != 0) { + if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ERROR_NONE && strcmp(path, "/system") != 0) { fs_iter_context->count++; if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}"; diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 796259b22..04072a2f8 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -423,6 +423,10 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(ledc_channel_config), ESP_ELFSYM_EXPORT(ledc_set_duty), ESP_ELFSYM_EXPORT(ledc_set_fade), + ESP_ELFSYM_EXPORT(ledc_set_fade_with_step), + ESP_ELFSYM_EXPORT(ledc_set_fade_with_time), + ESP_ELFSYM_EXPORT(ledc_set_fade_step_and_start), + ESP_ELFSYM_EXPORT(ledc_set_fade_time_and_start), ESP_ELFSYM_EXPORT(ledc_set_pin), ESP_ELFSYM_EXPORT(ledc_timer_config), ESP_ELFSYM_EXPORT(ledc_timer_pause), diff --git a/TactilityKernel/include/tactility/drivers/gpio.h b/TactilityKernel/include/tactility/drivers/gpio.h index e7a35771f..2250725fc 100644 --- a/TactilityKernel/include/tactility/drivers/gpio.h +++ b/TactilityKernel/include/tactility/drivers/gpio.h @@ -9,7 +9,7 @@ extern "C" { #include #include -#define GPIO_FLAGS_MASK 0x1f +#define GPIO_FLAGS_MASK 0xff #define GPIO_PIN_NONE -1 diff --git a/TactilityKernel/include/tactility/drivers/gpio_controller.h b/TactilityKernel/include/tactility/drivers/gpio_controller.h index 9cdd28835..82a2552ea 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_controller.h +++ b/TactilityKernel/include/tactility/drivers/gpio_controller.h @@ -206,7 +206,7 @@ error_t gpio_controller_deinit_descriptors(struct Device* device); * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded. * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. * @param device the GPIO controller device - * @return ERROR_NONE if successful + * @return the context void pointer */ void* gpio_controller_get_controller_context(struct Device* device); diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h index 9ecdcf5c9..331ee03b5 100644 --- a/TactilityKernel/include/tactility/drivers/wifi.h +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -4,17 +4,36 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif struct Device; +enum WifiAuthenticationType { + WIFI_AUTHENTICATION_TYPE_OPEN = 0, + WIFI_AUTHENTICATION_TYPE_WEP, + WIFI_AUTHENTICATION_TYPE_WPA_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_PSK, + WIFI_AUTHENTICATION_TYPE_WPA_WPA2_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_ENTERPRISE, + WIFI_AUTHENTICATION_TYPE_WPA3_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_WPA3_PSK, + WIFI_AUTHENTICATION_TYPE_WAPI_PSK, + WIFI_AUTHENTICATION_TYPE_OWE, + WIFI_AUTHENTICATION_TYPE_WPA3_ENT_192, + WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK, + WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE, + WIFI_AUTHENTICATION_TYPE_MAX +} wifi_auth_mode_t; + struct WifiApRecord { char ssid[32]; int8_t rssi; int32_t channel; - wifi_auth_mode_t auth_mode; + enum WifiAuthenticationType authentication_type; }; enum WifiRadioState { @@ -35,7 +54,7 @@ enum WifiAccessPointState { WIFI_ACCESS_POINT_STATE_STOPPED, }; -enum class WifiEventType { +enum WifiEventType { /** Radio state changed */ WIFI_EVENT_TYPE_RADIO_STATE_CHANGED, /** WifiStationState changed */ @@ -50,7 +69,7 @@ enum class WifiEventType { WIFI_EVENT_TYPE_SCAN_FINISHED, }; -enum class WifiStationConnectionError { +enum WifiStationConnectionError { WIFI_STATION_CONNECTION_ERROR_NONE, /** Wrong password */ WIFI_STATION_CONNECTION_ERROR_WRONG_CREDENTIALS, @@ -67,10 +86,10 @@ struct WifiEvent { enum WifiStationState station_state; enum WifiAccessPointState access_point_state; enum WifiStationConnectionError connection_error; - } + }; }; -typedef void (*WifiEventCallback)(struct Device* device, void* callback_context, WifiEvent event); +typedef void (*WifiEventCallback)(struct Device* device, void* callback_context, struct WifiEvent event); struct WifiApi { /** @@ -100,9 +119,9 @@ struct WifiApi { /** * Check if the device is currently scanning for access points. * @param[in] device the wifi device - * @return ERROR_NONE on success + * @return true when scanning */ - error_t (*is_scanning)(struct Device* device); + bool (*is_scanning)(struct Device* device); /** * Start a scan for access points. @@ -129,9 +148,9 @@ struct WifiApi { error_t (*station_get_ipv4_address)(struct Device* device, char* ipv4); /** - * Get the IPv6 address of the device. + * Get the IPv4 address of the device. * @param[in] device the device - * @param[out] ipv6 the buffer to store the IPv6 address (must be at least 33 bytes, will be null-terminated) + * @param[out] ipv4 the buffer to store the IPv4 address (must be at least 33 bytes, will be null-terminated) * @return ERROR_NONE on success */ error_t (*station_get_target_ssid)(struct Device* device, char* ssid); @@ -140,7 +159,7 @@ struct WifiApi { * Connect to an access point. * @param[in] device the wifi device * @param[in] ssid the SSID of the access point - * @param[in] password the password of the access point (must be at least 33 characters and null-terminated) + * @param[in] password the password of the access point (33 characters at most, including null-termination) * @param[in] channel the Wi-Fi channel to connect to (0 means "any" / no preference) * @return ERROR_NONE on success */ diff --git a/TactilityKernel/include/tactility/filesystem/file_system.h b/TactilityKernel/include/tactility/filesystem/file_system.h index c928e4b33..40a6bec75 100644 --- a/TactilityKernel/include/tactility/filesystem/file_system.h +++ b/TactilityKernel/include/tactility/filesystem/file_system.h @@ -11,25 +11,94 @@ extern "C" { struct FileSystem; +/** + * @brief File system API. + * + * Provides a set of function pointers to interact with a specific file system implementation. + */ struct FileSystemApi { + /** + * @brief Mounts the file system. + * @param[in] data file system private data + * @return ERROR_NONE on success, or an error code + */ error_t (*mount)(void* data); + + /** + * @brief Unmounts the file system. + * @param[in] data file system private data + * @return ERROR_NONE on success, or an error code + */ error_t (*unmount)(void* data); + + /** + * @brief Checks if the file system is mounted. + * @param[in] data file system private data + * @return true if mounted, false otherwise + */ bool (*is_mounted)(void* data); + + /** + * @brief Gets the mount path. + * @param[in] data file system private data + * @param[out] out_path buffer to store the path + * @param[in] out_path_size size of the output buffer + * @return ERROR_NONE on success, or an error code + */ error_t (*get_path)(void* data, char* out_path, size_t out_path_size); }; +/** + * @brief Registers a new file system. + * @param[in] fs_api the file system API implementation + * @param[in] data private data for the file system + * @return the registered FileSystem object + */ struct FileSystem* file_system_add(const struct FileSystemApi* fs_api, void* data); +/** + * @brief Removes a registered file system. + * @note The file system must be unmounted before removal. + * @param[in] fs the FileSystem object to remove + */ void file_system_remove(struct FileSystem* fs); +/** + * @brief Iterates over all registered file systems. + * @param[in] callback_context context passed to the callback + * @param[in] callback function called for each file system. Return true to continue, false to stop. + */ void file_system_for_each(void* callback_context, bool (*callback)(struct FileSystem* fs, void* context)); +/** + * @brief Mounts the file system. + * @param[in] fs the FileSystem object + * @return ERROR_NONE on success, or an error code + */ error_t file_system_mount(struct FileSystem* fs); +/** + * @warning Unmounting can fail (e.g. when the device is busy), so you might need to retry it. + * @brief Unmounts the file system. + * @param[in] fs the FileSystem object + * @return ERROR_NONE on success, or an error code + */ error_t file_system_unmount(struct FileSystem* fs); +/** + * @brief Checks if the file system is mounted. + * @param[in] fs the FileSystem object + * @return true if mounted, false otherwise + */ bool file_system_is_mounted(struct FileSystem* fs); +/** + * @brief Gets the mount path. + * @param[in] fs the FileSystem object + * @param[out] out_path buffer to store the path + * @param[in] out_path_size size of the output buffer + * @return ERROR_NONE on success, or an error code + */ error_t file_system_get_path(struct FileSystem* fs, char* out_path, size_t out_path_size); #ifdef __cplusplus diff --git a/TactilityKernel/source/drivers/gpio_controller.cpp b/TactilityKernel/source/drivers/gpio_controller.cpp index c2e2bfae7..922aad6ac 100644 --- a/TactilityKernel/source/drivers/gpio_controller.cpp +++ b/TactilityKernel/source/drivers/gpio_controller.cpp @@ -73,13 +73,18 @@ GpioDescriptor* gpio_descriptor_acquire( } error_t gpio_descriptor_release(GpioDescriptor* descriptor) { + auto* data = static_cast(device_get_driver_data(descriptor->controller)); + mutex_lock(&data->mutex); descriptor->owner_type = GPIO_OWNER_NONE; + mutex_unlock(&data->mutex); return ERROR_NONE; } error_t gpio_controller_get_pin_count(Device* device, uint32_t* count) { auto* data = static_cast(device_get_driver_data(device)); + mutex_lock(&data->mutex); *count = data->pin_count; + mutex_unlock(&data->mutex); return ERROR_NONE; } @@ -98,8 +103,8 @@ error_t gpio_controller_init_descriptors(Device* device, uint32_t pin_count, voi error_t gpio_controller_deinit_descriptors(Device* device) { auto* data = static_cast(device_get_driver_data(device)); - delete data; device_set_driver_data(device, nullptr); + delete data; return ERROR_NONE; } diff --git a/TactilityKernel/source/filesystem/file_system.cpp b/TactilityKernel/source/filesystem/file_system.cpp index b9e5926a3..e144ab0ac 100644 --- a/TactilityKernel/source/filesystem/file_system.cpp +++ b/TactilityKernel/source/filesystem/file_system.cpp @@ -11,57 +11,62 @@ struct FileSystem { void* data; }; -// Global Mutex and the master list of file systems -static Mutex fs_mutex; -static bool fs_mutex_initialized = false; -static std::vector file_systems; +// Global list of file systems and its mutex +struct FileSystemsLedger { + std::vector file_systems; + Mutex mutex {}; -static void ensure_mutex_initialized() { - if (!fs_mutex_initialized) { - mutex_construct(&fs_mutex); - fs_mutex_initialized = true; - } + FileSystemsLedger() { mutex_construct(&mutex); } + ~FileSystemsLedger() { mutex_destruct(&mutex); } + + void lock() { mutex_lock(&mutex); } + void unlock() { mutex_unlock(&mutex); } +}; + +static FileSystemsLedger& get_ledger() { + static FileSystemsLedger ledger; + return ledger; } extern "C" { FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) { - ensure_mutex_initialized(); - mutex_lock(&fs_mutex); + auto& ledger = get_ledger(); + ledger.lock(); auto* fs = new(std::nothrow) struct FileSystem(); check(fs != nullptr); fs->api = fs_api; fs->data = data; - file_systems.push_back(fs); + ledger.file_systems.push_back(fs); - mutex_unlock(&fs_mutex); + ledger.unlock(); return fs; } void file_system_remove(FileSystem* fs) { check(!file_system_is_mounted(fs)); - ensure_mutex_initialized(); - mutex_lock(&fs_mutex); + auto& ledger = get_ledger(); + ledger.lock(); - auto it = std::ranges::find(file_systems, fs); - if (it != file_systems.end()) { - file_systems.erase(it); + auto it = std::ranges::find(ledger.file_systems, fs); + if (it != ledger.file_systems.end()) { + ledger.file_systems.erase(it); delete fs; } - mutex_unlock(&fs_mutex); + ledger.unlock(); } void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) { - ensure_mutex_initialized(); - mutex_lock(&fs_mutex); + auto& ledger = get_ledger(); + ledger.lock(); - for (auto* fs : file_systems) { + for (auto* fs : ledger.file_systems) { if (!callback(fs, callback_context)) break; } - mutex_unlock(&fs_mutex); + ledger.unlock(); } error_t file_system_mount(FileSystem* fs) { diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index da2b3f777..7ed2a8eb4 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -14,6 +14,10 @@ #include #include +#ifndef ESP_PLATFORM +#include +#endif + /** * This file is a C file instead of C++, so we can import all headers as C code. * The intent is to catch errors that only show up when compiling as C and not as C++. From 4b48ec874680880c6850cf10b678372fd2412f01 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 14:46:42 +0100 Subject: [PATCH 22/25] Fixes --- .../source/drivers/esp32_gpio.cpp | 17 +++++++++-- .../source/drivers/esp32_sdmmc.cpp | 28 ++++++++++++++++--- .../source/drivers/esp32_sdmmc_fs.cpp | 1 - .../Include/Tactility/service/wifi/Wifi.h | 2 +- Tactility/Source/hal/sdcard/SdCardDevice.cpp | 1 - .../tactility/drivers/gpio_controller.h | 2 +- .../include/tactility/drivers/wifi.h | 10 +++---- .../source/filesystem/file_system.cpp | 18 ++++++------ 8 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp index a4b8098ff..d6d5137a9 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp @@ -146,20 +146,31 @@ static error_t disable_interrupt(GpioDescriptor* descriptor) { } static error_t start(Device* device) { - ESP_LOGI(TAG, "start %s", device->name); + LOG_I(TAG, "start %s", device->name); const Esp32GpioConfig* config = GET_CONFIG(device); auto* internal = new Esp32GpioInternal(); return gpio_controller_init_descriptors(device, config->gpioCount, internal); } static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); + LOG_I(TAG, "stop %s", device->name); + const Esp32GpioConfig* config = GET_CONFIG(device); auto* internal = static_cast(gpio_controller_get_controller_context(device)); + + // First, remove all ISR handlers to prevent callbacks during cleanup + for (uint8_t i = 0; i < config->gpioCount; ++i) { + gpio_isr_handler_remove(static_cast(i)); + } + + // Then uninstall ISR service if (internal->isr_service_ref_count > 0) { gpio_uninstall_isr_service(); } - delete internal; + + // Now safe to deinit descriptors and delete internal check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); + delete internal; + return ERROR_NONE; } diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp index 5955de1d5..d36fe5aeb 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -67,8 +67,6 @@ struct Esp32SdmmcInternal { void lock() { recursive_mutex_lock(&mutex); } - error_t try_lock(TickType_t timeout) { return recursive_mutex_try_lock(&mutex, timeout); } - void unlock() { recursive_mutex_unlock(&mutex); } }; @@ -78,6 +76,7 @@ static error_t start(Device* device) { auto* data = new (std::nothrow) Esp32SdmmcInternal(); if (!data) return ERROR_OUT_OF_MEMORY; + data->lock(); device_set_driver_data(device, data); auto* sdmmc_config = GET_CONFIG(device); @@ -101,12 +100,20 @@ static error_t start(Device* device) { LOG_E(TAG, "Failed to acquire required one or more pins"); data->cleanup_pins(); device_set_driver_data(device, nullptr); + data->unlock(); delete data; return ERROR_RESOURCE; } data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard"); - check(data->esp32_sdmmc_fs_handle); + if (!data->esp32_sdmmc_fs_handle) { + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + data->unlock(); + delete data; + return ERROR_OUT_OF_MEMORY; + } + data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle); if (file_system_mount(data->file_system) != ERROR_NONE) { // Error is not recoverable at the time, but it might be recoverable later, @@ -115,16 +122,21 @@ static error_t start(Device* device) { } data->initialized = true; + data->unlock(); return ERROR_NONE; } static error_t stop(Device* device) { LOG_I(TAG, "stop %s", device->name); auto* data = GET_DATA(device); + if (!data) return ERROR_NONE; + + data->lock(); if (file_system_is_mounted(data->file_system)) { if (file_system_unmount(data->file_system) != ERROR_NONE) { LOG_E(TAG, "Failed to unmount SD card filesystem"); + data->unlock(); return ERROR_RESOURCE; } } @@ -138,6 +150,8 @@ static error_t stop(Device* device) { data->cleanup_pins(); device_set_driver_data(device, nullptr); + + data->unlock(); delete data; return ERROR_NONE; } @@ -145,7 +159,13 @@ static error_t stop(Device* device) { sdmmc_card_t* esp32_sdmmc_get_card(Device* device) { if (!device_is_ready(device)) return nullptr; auto* data = GET_DATA(device); - return esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle); + if (!data) return nullptr; + + data->lock(); + auto* card = esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle); + data->unlock(); + + return card; } extern Module platform_esp32_module; diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index cf4b8611c..131c58291 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -163,7 +163,6 @@ static bool is_mounted(void* data) { static error_t get_path(void* data, char* out_path, size_t out_path_size) { const auto* fs_data = GET_DATA(data); - if (fs_data->card == nullptr) return ERROR_INVALID_STATE; if (fs_data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; strncpy(out_path, fs_data->mount_path.c_str(), out_path_size); return ERROR_NONE; diff --git a/Tactility/Include/Tactility/service/wifi/Wifi.h b/Tactility/Include/Tactility/service/wifi/Wifi.h index 86a9a61ea..2fdfbe6bd 100644 --- a/Tactility/Include/Tactility/service/wifi/Wifi.h +++ b/Tactility/Include/Tactility/service/wifi/Wifi.h @@ -29,7 +29,7 @@ typedef enum { WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */ WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */ WIFI_AUTH_MAX -} wifi_auth_mode_t; +}; #endif namespace tt::service::wifi { diff --git a/Tactility/Source/hal/sdcard/SdCardDevice.cpp b/Tactility/Source/hal/sdcard/SdCardDevice.cpp index 1c147f5b4..44fbd2843 100644 --- a/Tactility/Source/hal/sdcard/SdCardDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdCardDevice.cpp @@ -46,7 +46,6 @@ SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mount } SdCardDevice::~SdCardDevice() { - check(!isMounted()); file_system_remove(fileSystem); } diff --git a/TactilityKernel/include/tactility/drivers/gpio_controller.h b/TactilityKernel/include/tactility/drivers/gpio_controller.h index 82a2552ea..44365a968 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_controller.h +++ b/TactilityKernel/include/tactility/drivers/gpio_controller.h @@ -203,7 +203,7 @@ error_t gpio_controller_deinit_descriptors(struct Device* device); /** * Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors() - * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded. + * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. * @param device the GPIO controller device * @return the context void pointer diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h index 331ee03b5..cd6155458 100644 --- a/TactilityKernel/include/tactility/drivers/wifi.h +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -30,7 +30,7 @@ enum WifiAuthenticationType { } wifi_auth_mode_t; struct WifiApRecord { - char ssid[32]; + char ssid[33]; // 32 bytes + null terminator int8_t rssi; int32_t channel; enum WifiAuthenticationType authentication_type; @@ -148,9 +148,9 @@ struct WifiApi { error_t (*station_get_ipv4_address)(struct Device* device, char* ipv4); /** - * Get the IPv4 address of the device. - * @param[in] device the device - * @param[out] ipv4 the buffer to store the IPv4 address (must be at least 33 bytes, will be null-terminated) + * Get the SSID of the access point the device is currently connected to. + * @param[in] device the wifi device + * @param[out] ssid the buffer to store the SSID (must be at least 33 bytes, will be null-terminated) * @return ERROR_NONE on success */ error_t (*station_get_target_ssid)(struct Device* device, char* ssid); @@ -159,7 +159,7 @@ struct WifiApi { * Connect to an access point. * @param[in] device the wifi device * @param[in] ssid the SSID of the access point - * @param[in] password the password of the access point (33 characters at most, including null-termination) + * @param[in] password the password of the access point * @param[in] channel the Wi-Fi channel to connect to (0 means "any" / no preference) * @return ERROR_NONE on success */ diff --git a/TactilityKernel/source/filesystem/file_system.cpp b/TactilityKernel/source/filesystem/file_system.cpp index e144ab0ac..876e113f1 100644 --- a/TactilityKernel/source/filesystem/file_system.cpp +++ b/TactilityKernel/source/filesystem/file_system.cpp @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 -#include +#include #include +#include +#include #include -#include // Define the internal FileSystem structure struct FileSystem { @@ -14,13 +15,14 @@ struct FileSystem { // Global list of file systems and its mutex struct FileSystemsLedger { std::vector file_systems; - Mutex mutex {}; + // Use recursive mutex so that file_system_for_each() can lock within the callback + RecursiveMutex mutex {}; - FileSystemsLedger() { mutex_construct(&mutex); } - ~FileSystemsLedger() { mutex_destruct(&mutex); } + FileSystemsLedger() { recursive_mutex_construct(&mutex); } + ~FileSystemsLedger() { recursive_mutex_destruct(&mutex); } - void lock() { mutex_lock(&mutex); } - void unlock() { mutex_unlock(&mutex); } + void lock() { recursive_mutex_lock(&mutex); } + void unlock() { recursive_mutex_unlock(&mutex); } }; static FileSystemsLedger& get_ledger() { @@ -61,11 +63,9 @@ void file_system_remove(FileSystem* fs) { void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) { auto& ledger = get_ledger(); ledger.lock(); - for (auto* fs : ledger.file_systems) { if (!callback(fs, callback_context)) break; } - ledger.unlock(); } From f8f01edc485234314e7e13d6099485d2eb444786 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 14:58:56 +0100 Subject: [PATCH 23/25] Build fix --- Tactility/Include/Tactility/service/wifi/Wifi.h | 2 +- TactilityKernel/include/tactility/drivers/wifi.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tactility/Include/Tactility/service/wifi/Wifi.h b/Tactility/Include/Tactility/service/wifi/Wifi.h index 2fdfbe6bd..86a9a61ea 100644 --- a/Tactility/Include/Tactility/service/wifi/Wifi.h +++ b/Tactility/Include/Tactility/service/wifi/Wifi.h @@ -29,7 +29,7 @@ typedef enum { WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */ WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */ WIFI_AUTH_MAX -}; +} wifi_auth_mode_t; #endif namespace tt::service::wifi { diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h index cd6155458..dafc3e84b 100644 --- a/TactilityKernel/include/tactility/drivers/wifi.h +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -27,7 +27,7 @@ enum WifiAuthenticationType { WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK, WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE, WIFI_AUTHENTICATION_TYPE_MAX -} wifi_auth_mode_t; +}; struct WifiApRecord { char ssid[33]; // 32 bytes + null terminator From dab89218e1a88c4fbb2d4cef90b79a269ffc7ebc Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 15:05:55 +0100 Subject: [PATCH 24/25] Fixes --- Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp | 6 +++--- TactilityKernel/source/filesystem/file_system.cpp | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp index 131c58291..9387d7568 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -119,9 +119,9 @@ static error_t mount(void* data) { LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result)); } #if SOC_SD_PWR_CTRL_SUPPORTED - if (data->pwr_ctrl_handle) { - sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle); - data->pwr_ctrl_handle = nullptr; + if (fs_data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle); + fs_data->pwr_ctrl_handle = nullptr; } #endif return ERROR_UNDEFINED; diff --git a/TactilityKernel/source/filesystem/file_system.cpp b/TactilityKernel/source/filesystem/file_system.cpp index 876e113f1..d22dd31a7 100644 --- a/TactilityKernel/source/filesystem/file_system.cpp +++ b/TactilityKernel/source/filesystem/file_system.cpp @@ -22,6 +22,7 @@ struct FileSystemsLedger { ~FileSystemsLedger() { recursive_mutex_destruct(&mutex); } void lock() { recursive_mutex_lock(&mutex); } + bool is_locked() { return recursive_mutex_is_locked(&mutex); } void unlock() { recursive_mutex_unlock(&mutex); } }; @@ -34,6 +35,7 @@ extern "C" { FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) { auto& ledger = get_ledger(); + check(!ledger.is_locked()); // ensure file_system_for_each() doesn't add a filesystem while iterating ledger.lock(); auto* fs = new(std::nothrow) struct FileSystem(); @@ -49,6 +51,7 @@ FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) { void file_system_remove(FileSystem* fs) { check(!file_system_is_mounted(fs)); auto& ledger = get_ledger(); + check(!ledger.is_locked()); // ensure file_system_for_each() doesn't remove a filesystem while iterating ledger.lock(); auto it = std::ranges::find(ledger.file_systems, fs); From 54be5ff53a77b95d054a544846288d3e7aa6c299 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 7 Mar 2026 15:36:03 +0100 Subject: [PATCH 25/25] Rename T-HMI S3 to T-HMI --- .github/workflows/build.yml | 2 +- Devices/{lilygo-thmi-s3 => lilygo-thmi}/CMakeLists.txt | 0 .../Source/Configuration.cpp | 0 Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/Init.cpp | 8 ++++---- .../Source/devices/Display.cpp | 0 .../Source/devices/Display.h | 0 .../Source/devices/Power.cpp | 0 .../Source/devices/Power.h | 8 ++++---- Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/module.cpp | 2 +- Devices/{lilygo-thmi-s3 => lilygo-thmi}/device.properties | 2 +- Devices/{lilygo-thmi-s3 => lilygo-thmi}/devicetree.yaml | 2 +- .../lilygo,thmi-s3.dts => lilygo-thmi/lilygo,thmi.dts} | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/CMakeLists.txt (100%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/Configuration.cpp (100%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/Init.cpp (79%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/devices/Display.cpp (100%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/devices/Display.h (100%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/devices/Power.cpp (100%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/devices/Power.h (81%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/Source/module.cpp (91%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/device.properties (94%) rename Devices/{lilygo-thmi-s3 => lilygo-thmi}/devicetree.yaml (64%) rename Devices/{lilygo-thmi-s3/lilygo,thmi-s3.dts => lilygo-thmi/lilygo,thmi.dts} (96%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b5fdc376..d0c6cfe3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: { id: guition-jc8048w550c, arch: esp32s3 }, { id: heltec-wifi-lora-32-v3, arch: esp32s3 }, { id: lilygo-tdeck, arch: esp32s3 }, - { id: lilygo-thmi-s3, arch: esp32s3 }, + { id: lilygo-thmi, arch: esp32s3 }, { id: lilygo-tdongle-s3, arch: esp32s3 }, { id: lilygo-tdisplay-s3, arch: esp32s3 }, { id: lilygo-tlora-pager, arch: esp32s3 }, diff --git a/Devices/lilygo-thmi-s3/CMakeLists.txt b/Devices/lilygo-thmi/CMakeLists.txt similarity index 100% rename from Devices/lilygo-thmi-s3/CMakeLists.txt rename to Devices/lilygo-thmi/CMakeLists.txt diff --git a/Devices/lilygo-thmi-s3/Source/Configuration.cpp b/Devices/lilygo-thmi/Source/Configuration.cpp similarity index 100% rename from Devices/lilygo-thmi-s3/Source/Configuration.cpp rename to Devices/lilygo-thmi/Source/Configuration.cpp diff --git a/Devices/lilygo-thmi-s3/Source/Init.cpp b/Devices/lilygo-thmi/Source/Init.cpp similarity index 79% rename from Devices/lilygo-thmi-s3/Source/Init.cpp rename to Devices/lilygo-thmi/Source/Init.cpp index f64d64c37..0a44f1fb6 100644 --- a/Devices/lilygo-thmi-s3/Source/Init.cpp +++ b/Devices/lilygo-thmi/Source/Init.cpp @@ -5,11 +5,11 @@ #include "Tactility/kernel/SystemEvents.h" #include -#define TAG "thmi-s3" +#define TAG "thmi" static bool powerOn() { gpio_config_t power_signal_config = { - .pin_bit_mask = (1ULL << THMI_S3_POWERON_GPIO) | (1ULL << THMI_S3_POWEREN_GPIO), + .pin_bit_mask = (1ULL << THMI_POWERON_GPIO) | (1ULL << THMI_POWEREN_GPIO), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, @@ -20,11 +20,11 @@ static bool powerOn() { return false; } - if (gpio_set_level(THMI_S3_POWERON_GPIO, 1) != ESP_OK) { + if (gpio_set_level(THMI_POWERON_GPIO, 1) != ESP_OK) { return false; } - if (gpio_set_level(THMI_S3_POWEREN_GPIO, 1) != ESP_OK) { + if (gpio_set_level(THMI_POWEREN_GPIO, 1) != ESP_OK) { return false; } diff --git a/Devices/lilygo-thmi-s3/Source/devices/Display.cpp b/Devices/lilygo-thmi/Source/devices/Display.cpp similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Display.cpp rename to Devices/lilygo-thmi/Source/devices/Display.cpp diff --git a/Devices/lilygo-thmi-s3/Source/devices/Display.h b/Devices/lilygo-thmi/Source/devices/Display.h similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Display.h rename to Devices/lilygo-thmi/Source/devices/Display.h diff --git a/Devices/lilygo-thmi-s3/Source/devices/Power.cpp b/Devices/lilygo-thmi/Source/devices/Power.cpp similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Power.cpp rename to Devices/lilygo-thmi/Source/devices/Power.cpp diff --git a/Devices/lilygo-thmi-s3/Source/devices/Power.h b/Devices/lilygo-thmi/Source/devices/Power.h similarity index 81% rename from Devices/lilygo-thmi-s3/Source/devices/Power.h rename to Devices/lilygo-thmi/Source/devices/Power.h index ed7be56fa..c831083c4 100644 --- a/Devices/lilygo-thmi-s3/Source/devices/Power.h +++ b/Devices/lilygo-thmi/Source/devices/Power.h @@ -6,16 +6,16 @@ #include #include -constexpr auto THMI_S3_POWEREN_GPIO = GPIO_NUM_10; -constexpr auto THMI_S3_POWERON_GPIO = GPIO_NUM_14; +constexpr auto THMI_POWEREN_GPIO = GPIO_NUM_10; +constexpr auto THMI_POWERON_GPIO = GPIO_NUM_14; using tt::hal::power::PowerDevice; class Power final : public PowerDevice { ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); - bool initialized = false; esp_adc_cal_characteristics_t adcCharacteristics; + bool initialized = false; bool calibrated = false; bool adcInitCalibration(); @@ -25,7 +25,7 @@ class Power final : public PowerDevice { public: - std::string getName() const override { return "T-hmi Power"; } + std::string getName() const override { return "T-HMI Power"; } std::string getDescription() const override { return "Power measurement via ADC"; } bool supportsMetric(MetricType type) const override; diff --git a/Devices/lilygo-thmi-s3/Source/module.cpp b/Devices/lilygo-thmi/Source/module.cpp similarity index 91% rename from Devices/lilygo-thmi-s3/Source/module.cpp rename to Devices/lilygo-thmi/Source/module.cpp index 9d5b1ef07..799711a1a 100644 --- a/Devices/lilygo-thmi-s3/Source/module.cpp +++ b/Devices/lilygo-thmi/Source/module.cpp @@ -13,7 +13,7 @@ static error_t stop() { } struct Module lilygo_thmi_s3_module = { - .name = "lilygo-thmi-s3", + .name = "lilygo-thmi", .start = start, .stop = stop, .symbols = nullptr, diff --git a/Devices/lilygo-thmi-s3/device.properties b/Devices/lilygo-thmi/device.properties similarity index 94% rename from Devices/lilygo-thmi-s3/device.properties rename to Devices/lilygo-thmi/device.properties index 9e70d045c..787069cbf 100644 --- a/Devices/lilygo-thmi-s3/device.properties +++ b/Devices/lilygo-thmi/device.properties @@ -1,6 +1,6 @@ [general] vendor=LilyGO -name=T-HMI S3 +name=T-HMI [apps] launcherAppId=Launcher diff --git a/Devices/lilygo-thmi-s3/devicetree.yaml b/Devices/lilygo-thmi/devicetree.yaml similarity index 64% rename from Devices/lilygo-thmi-s3/devicetree.yaml rename to Devices/lilygo-thmi/devicetree.yaml index 0474b6950..68d5c934f 100644 --- a/Devices/lilygo-thmi-s3/devicetree.yaml +++ b/Devices/lilygo-thmi/devicetree.yaml @@ -1,3 +1,3 @@ dependencies: - Platforms/platform-esp32 -dts: lilygo,thmi-s3.dts +dts: lilygo,thmi.dts diff --git a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts b/Devices/lilygo-thmi/lilygo,thmi.dts similarity index 96% rename from Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts rename to Devices/lilygo-thmi/lilygo,thmi.dts index d6d8fc719..a83a31db2 100644 --- a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -8,7 +8,7 @@ / { compatible = "root"; - model = "LilyGO T-HMI S3"; + model = "LilyGO T-HMI"; gpio0 { compatible = "espressif,esp32-gpio";