diff --git a/Devices/xteink-x4/CMakeLists.txt b/Devices/xteink-x4/CMakeLists.txt new file mode 100644 index 000000000..38c007833 --- /dev/null +++ b/Devices/xteink-x4/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "source" + REQUIRES TactilityKernel driver +) diff --git a/Devices/xteink-x4/bindings/xteink,x4-power.yaml b/Devices/xteink-x4/bindings/xteink,x4-power.yaml new file mode 100644 index 000000000..ae054d3d3 --- /dev/null +++ b/Devices/xteink-x4/bindings/xteink,x4-power.yaml @@ -0,0 +1,36 @@ +description: > + Xteink X4 charge/power control: USB VBUS detection, battery-latch power off and the + GPIO3 power button (deep-sleep wake source and long-press sleep, mirroring the + M5Paper's power button in the reference firmware). + Battery voltage/capacity are handled separately by a generic battery-sense node. + +compatible: "xteink,x4-power" + +properties: + pin-usb-detect: + type: phandles + required: true + description: USB VBUS detect pin (1 = USB connected). Doubles as the charge indicator as the charge IC exposes no status pin. + pin-power-off: + type: phandles + required: true + description: Battery MOSFET latch pin, is driven HIGH at boot to keep the rail on, driven LOW and held to power off on battery + pin-power-button: + type: phandles + required: true + description: Power button pin (active-low, pulled up). Arms the deep-sleep GPIO wake source and triggers sleep when held. + power-button-hold-ms: + type: int + default: 400 + description: How long the power button must be held (ms) before the device sleeps. Matches the reference firmware's default long-press duration. + wake-hold-ms: + type: int + default: 0 + description: > + Battery cold-boot wake verification. On battery the only boot path is the power + button re-engaging the latch, so the driver polls the button for this many ms after + it starts: a boot that never shows a press is treated as a stray tap and the device + returns to sleep. USB-powered boots (flash, plug-in) and deep-sleep GPIO wakes are + always accepted regardless of this value. 0 (default) disables the check - enable + only once the firmware is confirmed stable, since a boot that fails verification + powers back off immediately. diff --git a/Devices/xteink-x4/device.properties b/Devices/xteink-x4/device.properties new file mode 100644 index 000000000..3b83141eb --- /dev/null +++ b/Devices/xteink-x4/device.properties @@ -0,0 +1,16 @@ +general.vendor=Xteink +general.name=X4 + +apps.launcherAppId=Launcher + +hardware.target=ESP32C3 +hardware.flashSize=16MB +hardware.flashMode=DIO +hardware.spiRam=false +hardware.tinyUsb=true +hardware.wifi=true +hardware.bluetooth=true + +storage.userDataLocation=SD + +sdkconfig.CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y diff --git a/Devices/xteink-x4/devicetree.yaml b/Devices/xteink-x4/devicetree.yaml new file mode 100644 index 000000000..8f6a04185 --- /dev/null +++ b/Devices/xteink-x4/devicetree.yaml @@ -0,0 +1,5 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/button-adc-control-module +bindings: bindings +dts: xteink,x4.dts diff --git a/Devices/xteink-x4/source/bindings/xteink_x4_power.h b/Devices/xteink-x4/source/bindings/xteink_x4_power.h new file mode 100644 index 000000000..66029d127 --- /dev/null +++ b/Devices/xteink-x4/source/bindings/xteink_x4_power.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +DEFINE_DEVICETREE(x4_power, struct XteinkX4PowerConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Devices/xteink-x4/source/drivers/xteink_x4_power.cpp b/Devices/xteink-x4/source/drivers/xteink_x4_power.cpp new file mode 100644 index 000000000..301bb206b --- /dev/null +++ b/Devices/xteink-x4/source/drivers/xteink_x4_power.cpp @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "xteink_x4_power.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +constexpr auto* TAG = "XteinkX4Power"; +#define GET_CONFIG(device) (static_cast((device)->config)) + +// How long to wait after dropping the battery latch before concluding the device +// is still alive (e.g. because USB VBUS is keeping the rail up). +static constexpr TickType_t POWER_OFF_WAIT = pdMS_TO_TICKS(1000); + +static constexpr uint32_t POWER_BUTTON_POLL_MS = 20; +static constexpr uint32_t POWER_BUTTON_DEBOUNCE_MS = 30; +static constexpr configSTACK_DEPTH_TYPE POWER_BUTTON_THREAD_STACK_SIZE = 4096; + +extern "C" { + +extern Module xteink_x4_module; + +struct XteinkX4PowerInternal { + GpioDescriptor* usb_detect_descriptor = nullptr; + GpioDescriptor* power_off_descriptor = nullptr; + GpioDescriptor* power_button_descriptor = nullptr; + gpio_num_t power_off_native_pin = GPIO_NUM_NC; + gpio_num_t power_button_native_pin = GPIO_NUM_NC; + Device* power_supply_device = nullptr; + Device* device = nullptr; + uint32_t power_button_hold_ms = 0; + uint32_t wake_hold_ms = 0; + Thread* power_button_thread = nullptr; + bool stop_requested = false; +}; + +error_t xteink_x4_power_is_usb_connected(Device* device, bool* connected) { + auto* internal = static_cast(device_get_driver_data(device)); + return gpio_descriptor_get_level(internal->usb_detect_descriptor, connected); +} + +error_t xteink_x4_power_is_power_button_pressed(Device* device, bool* pressed) { + auto* internal = static_cast(device_get_driver_data(device)); + return gpio_descriptor_get_level(internal->power_button_descriptor, pressed); +} + +error_t xteink_x4_power_off(Device* device) { + LOG_W(TAG, "Power-off requested"); + // Note: callers are responsible for stopping the display (e.g. EPD refresh) before calling + // this. GPIO13 gates the battery MOSFET; pulling it LOW and holding it powers the MCU off on + // battery (mirrors the reference firmware's deep-sleep path). On self-latching units the pull + // re-engages after a button press; the hold guarantees the pin stays LOW through the loss of + // the digital domain so the latch doesn't float back on. + + auto* internal = static_cast(device_get_driver_data(device)); + + gpio_descriptor_set_level(internal->power_off_descriptor, false); + if (gpio_hold_en(internal->power_off_native_pin) != ESP_OK) { + LOG_E(TAG, "Failed to hold power-off pin low"); + return ERROR_RESOURCE; + } + // Retain the held state across deep sleep so an RTC wake can't re-engage the + // battery latch before the driver re-asserts it on boot. + gpio_deep_sleep_hold_en(); + + LOG_W(TAG, "Battery latch released. Waiting for power-off..."); + vTaskDelay(POWER_OFF_WAIT); + LOG_W(TAG, "Device did not power off as expected (USB power present?)"); + return ERROR_NONE; +} + +// region Power button (deep-sleep wake + long-press sleep) + +/** + * @brief Waits for the power button to be released, dropping the battery latch and + * entering deep sleep once it is. + * @note On battery the latch cut powers the MCU off before the deep sleep completes and + * the button physically re-engages the latch to boot again. While USB VBUS is present + * the MCU stays powered and the GPIO wake source below is what actually resumes it. + */ +error_t xteink_x4_power_enter_sleep(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + // Wait for the button to be released so the armed wake source doesn't fire + // immediately and wake the device right back up. + bool pressed = true; + while (pressed) { + if (gpio_descriptor_get_level(internal->power_button_descriptor, &pressed) != ERROR_NONE) { + vTaskDelay(pdMS_TO_TICKS(POWER_BUTTON_POLL_MS)); + continue; + } + if (pressed) { + vTaskDelay(pdMS_TO_TICKS(POWER_BUTTON_POLL_MS)); + } + } + + gpio_descriptor_set_level(internal->power_off_descriptor, false); + if (gpio_hold_en(internal->power_off_native_pin) != ESP_OK) { + LOG_E(TAG, "Failed to hold power-off pin low"); + gpio_descriptor_set_level(internal->power_off_descriptor, true); + return ERROR_RESOURCE; + } + + if (esp_deep_sleep_enable_gpio_wakeup(1ULL << internal->power_button_native_pin, ESP_GPIO_WAKEUP_GPIO_LOW) != ESP_OK) { + LOG_E(TAG, "Failed to arm power-button wakeup"); + gpio_hold_dis(internal->power_off_native_pin); + gpio_descriptor_set_level(internal->power_off_descriptor, true); + return ERROR_RESOURCE; + } + + // Keep the latch cut and the power-button pull-up through deep sleep. + gpio_deep_sleep_hold_en(); + esp_sleep_config_gpio_isolate(); + + LOG_W(TAG, "Entering deep sleep"); + esp_deep_sleep_start(); + + // Only reached if the deep sleep was aborted. + gpio_hold_dis(internal->power_off_native_pin); + gpio_descriptor_set_level(internal->power_off_descriptor, true); + return ERROR_NONE; +} + +// Whether this boot was caused by the power button: a GPIO wake from a USB-powered deep +// sleep, or a battery cold boot (with the latch dropped, the button is the only way to +// re-engage the battery rail). USB-powered cold boots (flash, plug-in) are excluded. +static bool boot_was_power_button_initiated(const XteinkX4PowerInternal* internal) { + if (esp_reset_reason() == ESP_RST_DEEPSLEEP) { + return esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO; + } + if (esp_reset_reason() == ESP_RST_POWERON) { + bool usb_connected = true; + return gpio_descriptor_get_level(internal->usb_detect_descriptor, &usb_connected) == ERROR_NONE && !usb_connected; + } + return false; +} + +// Battery cold boots are verified so a stray tap (e.g. in a bag) doesn't power the device +// on and drain the battery: the button must register as pressed within wake_hold_ms of the +// driver starting, or the device returns to sleep. Deliberate power-on holds (typically a +// second or more) are still down by the time the driver starts. Deep-sleep GPIO wakes are +// always accepted - the button was physically pressed to wake, and a glitch merely boots a +// USB-powered device once. +static void verify_boot_wake(XteinkX4PowerInternal* internal) { + if (internal->wake_hold_ms == 0 || !boot_was_power_button_initiated(internal)) { + return; + } + + const uint32_t start = get_millis(); + do { + bool pressed = false; + if (gpio_descriptor_get_level(internal->power_button_descriptor, &pressed) == ERROR_NONE && pressed) { + return; + } + vTaskDelay(pdMS_TO_TICKS(POWER_BUTTON_POLL_MS)); + } while ((get_millis() - start) < internal->wake_hold_ms); + + LOG_W(TAG, "Power button not held within %lu ms of boot; returning to sleep", static_cast(internal->wake_hold_ms)); + if (xteink_x4_power_enter_sleep(internal->device) != ERROR_NONE) { + LOG_E(TAG, "Failed to return to sleep"); + } +} + +static int32_t power_button_monitor(void* context) { + auto* internal = static_cast(context); + + // The press that powered the device on is still held; ignore it and wait for release + // so it doesn't count towards the long-press sleep trigger. + while (!internal->stop_requested) { + bool pressed = false; + if (gpio_descriptor_get_level(internal->power_button_descriptor, &pressed) != ERROR_NONE) { + vTaskDelay(pdMS_TO_TICKS(50)); + continue; + } + if (!pressed) { + break; + } + vTaskDelay(pdMS_TO_TICKS(POWER_BUTTON_POLL_MS)); + } + + bool debounced_pressed = false; + uint32_t last_change_time = 0; + uint32_t press_start_ticks = 0; + + while (!internal->stop_requested) { + bool raw_pressed = false; + if (gpio_descriptor_get_level(internal->power_button_descriptor, &raw_pressed) != ERROR_NONE) { + vTaskDelay(pdMS_TO_TICKS(50)); + continue; + } + + const uint32_t now = get_millis(); + if ((now - last_change_time) >= POWER_BUTTON_DEBOUNCE_MS && raw_pressed != debounced_pressed) { + last_change_time = now; + debounced_pressed = raw_pressed; + if (debounced_pressed) { + press_start_ticks = xTaskGetTickCount(); + } + } + + if (debounced_pressed && + (xTaskGetTickCount() - press_start_ticks) >= pdMS_TO_TICKS(internal->power_button_hold_ms)) { + LOG_I(TAG, "Power button held %lu ms, entering sleep", static_cast(internal->power_button_hold_ms)); + xteink_x4_power_enter_sleep(internal->device); + // Reached only if the deep sleep was aborted; require a fresh release before + // re-arming the trigger. + debounced_pressed = false; + last_change_time = get_millis(); + } + + vTaskDelay(pdMS_TO_TICKS(POWER_BUTTON_POLL_MS)); + } + + return 0; +} + +// endregion + +// region Power supply child device + +static bool ps_supports_property(Device*, PowerSupplyProperty property) { + return property == POWER_SUPPLY_PROP_IS_CHARGING; +} + +static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) { + if (property != POWER_SUPPLY_PROP_IS_CHARGING) { + return ERROR_NOT_SUPPORTED; + } + // The X4's charge IC has no status pin; "charging" is inferred from VBUS presence, matching + // the reference firmware (see xteink_x4_power_is_usb_connected()). + bool connected; + error_t error = xteink_x4_power_is_usb_connected(device_get_parent(device), &connected); + if (error != ERROR_NONE) { + return error; + } + out_value->int_value = connected ? 1 : 0; + return ERROR_NONE; +} + +static bool ps_supports_charge_control(Device*) { return false; } +static bool ps_is_allowed_to_charge(Device*) { return false; } +static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_quick_charge(Device*) { return false; } +static bool ps_is_quick_charge_enabled(Device*) { return false; } +static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; } +static bool ps_supports_power_off(Device*) { return true; } +static error_t ps_power_off(Device* device) { return xteink_x4_power_off(device_get_parent(device)); } + +static constexpr PowerSupplyApi XTEINK_X4_POWER_SUPPLY_API = { + .supports_property = ps_supports_property, + .get_property = ps_get_property, + .supports_charge_control = ps_supports_charge_control, + .is_allowed_to_charge = ps_is_allowed_to_charge, + .set_allowed_to_charge = ps_set_allowed_to_charge, + .supports_quick_charge = ps_supports_quick_charge, + .is_quick_charge_enabled = ps_is_quick_charge_enabled, + .set_quick_charge_enabled = ps_set_quick_charge_enabled, + .supports_power_off = ps_supports_power_off, + .power_off = ps_power_off, +}; + +// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal, but +// never matched against a devicetree node: xteink_x4_power_driver wires it up directly by pointer. +Driver xteink_x4_power_supply_driver = { + .name = "xteink-x4-power-supply", + .compatible = (const char*[]) { "xteink-x4-power-supply", nullptr }, + .start_device = nullptr, + .stop_device = nullptr, + .api = &XTEINK_X4_POWER_SUPPLY_API, + .device_type = &POWER_SUPPLY_TYPE, + .owner = &xteink_x4_module, + .internal = nullptr +}; + +static error_t create_power_supply_child(Device* parent, Device*& out_child) { + auto* child = new(std::nothrow) Device { .address = 0, .name = "xteink-x4-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr }; + if (child == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + error_t error = device_construct(child); + if (error != ERROR_NONE) { + delete child; + return error; + } + + device_set_parent(child, parent); + device_set_driver(child, &xteink_x4_power_supply_driver); + + error = device_add(child); + if (error != ERROR_NONE) { + device_destruct(child); + delete child; + return error; + } + + error = device_start(child); + if (error != ERROR_NONE) { + device_remove(child); + device_destruct(child); + delete child; + return error; + } + + out_child = child; + return ERROR_NONE; +} + +static void destroy_power_supply_child(Device* child) { + check(device_stop(child) == ERROR_NONE); + check(device_remove(child) == ERROR_NONE); + check(device_destruct(child) == ERROR_NONE); + delete child; +} + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = new(std::nothrow) XteinkX4PowerInternal(); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->usb_detect_descriptor = gpio_descriptor_acquire(config->pin_usb_detect.gpio_controller, config->pin_usb_detect.pin, config->pin_usb_detect.flags | GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO); + if (internal->usb_detect_descriptor == nullptr) { + LOG_E(TAG, "Failed to configure usb-detect pin"); + delete internal; + return ERROR_RESOURCE; + } + + internal->power_off_descriptor = gpio_descriptor_acquire(config->pin_power_off.gpio_controller, config->pin_power_off.pin, config->pin_power_off.flags | GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO); + if (internal->power_off_descriptor == nullptr) { + LOG_E(TAG, "Failed to configure power-off pin"); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return ERROR_RESOURCE; + } + + if (gpio_descriptor_get_native_pin_number(internal->power_off_descriptor, &internal->power_off_native_pin) != ERROR_NONE) { + LOG_E(TAG, "Power-off pin has no native pin number"); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return ERROR_NOT_SUPPORTED; + } + + internal->power_button_descriptor = gpio_descriptor_acquire(config->pin_power_button.gpio_controller, config->pin_power_button.pin, config->pin_power_button.flags | GPIO_FLAG_DIRECTION_INPUT, GPIO_OWNER_GPIO); + if (internal->power_button_descriptor == nullptr) { + LOG_E(TAG, "Failed to configure power-button pin"); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return ERROR_RESOURCE; + } + + if (gpio_descriptor_get_native_pin_number(internal->power_button_descriptor, &internal->power_button_native_pin) != ERROR_NONE) { + LOG_E(TAG, "Power-button pin has no native pin number"); + gpio_descriptor_release(internal->power_button_descriptor); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return ERROR_NOT_SUPPORTED; + } + + // A previous power-off held this pin LOW; that state survives a reset, so release it + // before asserting the latch or the battery rail stays disconnected on non-self-latching + // units (see the reference firmware's holdPowerRails()). + gpio_hold_dis(internal->power_off_native_pin); + gpio_descriptor_set_level(internal->power_off_descriptor, true); + + error_t error = create_power_supply_child(device, internal->power_supply_device); + if (error != ERROR_NONE) { + gpio_descriptor_release(internal->power_button_descriptor); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return error; + } + + internal->device = device; + internal->power_button_hold_ms = config->power_button_hold_ms; + internal->wake_hold_ms = config->wake_hold_ms; + device_set_driver_data(device, internal); + + // On a battery cold boot a stray tap would otherwise leave the device running on + // battery until the next power-off; reject it before anything is started. + verify_boot_wake(internal); + + if (internal->power_button_hold_ms > 0) { + internal->power_button_thread = thread_alloc_full( + "x4_power_button", + POWER_BUTTON_THREAD_STACK_SIZE, + power_button_monitor, + internal, + tskNO_AFFINITY + ); + if (internal->power_button_thread == nullptr || thread_start(internal->power_button_thread) != ERROR_NONE) { + LOG_E(TAG, "Failed to start power-button monitor"); + if (internal->power_button_thread != nullptr) { + thread_free(internal->power_button_thread); + internal->power_button_thread = nullptr; + } + device_set_driver_data(device, nullptr); + destroy_power_supply_child(internal->power_supply_device); + gpio_descriptor_release(internal->power_button_descriptor); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + delete internal; + return ERROR_RESOURCE; + } + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + if (internal->power_button_thread != nullptr) { + internal->stop_requested = true; + thread_join(internal->power_button_thread, portMAX_DELAY, POWER_BUTTON_POLL_MS); + thread_free(internal->power_button_thread); + internal->power_button_thread = nullptr; + } + destroy_power_supply_child(internal->power_supply_device); + gpio_descriptor_release(internal->power_button_descriptor); + gpio_descriptor_release(internal->power_off_descriptor); + gpio_descriptor_release(internal->usb_detect_descriptor); + device_set_driver_data(device, nullptr); + delete internal; + return ERROR_NONE; +} + +// endregion + +Driver xteink_x4_power_driver = { + .name = "xteink-x4-power", + .compatible = (const char*[]) { "xteink,x4-power", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &xteink_x4_module, + .internal = nullptr +}; + +} diff --git a/Devices/xteink-x4/source/drivers/xteink_x4_power.h b/Devices/xteink-x4/source/drivers/xteink_x4_power.h new file mode 100644 index 000000000..67ca01ca8 --- /dev/null +++ b/Devices/xteink-x4/source/drivers/xteink_x4_power.h @@ -0,0 +1,60 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include + +struct XteinkX4PowerConfig { + /** USB VBUS detect pin: 1 = USB connected (charging), 0 = battery only */ + struct GpioPinSpec pin_usb_detect; + /** Battery MOSFET latch pin: driven HIGH to keep the battery rail on, LOW to power off */ + struct GpioPinSpec pin_power_off; + /** Power button pin (active-low, pulled up) */ + struct GpioPinSpec pin_power_button; + /** Hold duration (ms) of the power button before the device enters sleep */ + uint32_t power_button_hold_ms; + /** Minimum boot hold (ms); 0 disables battery cold-boot wake verification */ + uint32_t wake_hold_ms; +}; + +/** + * @brief Whether USB VBUS is currently present. + * @note This is the reference firmware's charge indicator as well: the X4's charge + * IC exposes no status pin, so "charging" is inferred from VBUS presence (see the + * sunwoods schematic thread: U0RXD reads ~1.0V idle and ~3.3V with USB plugged in). + */ +error_t xteink_x4_power_is_usb_connected(struct Device* device, bool* connected); + +/** + * @brief Whether the power button is currently pressed. + */ +error_t xteink_x4_power_is_power_button_pressed(struct Device* device, bool* pressed); + +/** + * @brief Drives the battery latch LOW and holds it, powering the board off on battery. + * @warning This only cuts the battery rail. While USB VBUS is present the board stays + * powered from USB (the LDO is disabled but the MCU keeps running), so callers should + * gate this on the USB-detect state when a full shutdown is required. + * @note Does not return on a successful battery power-off. + */ +error_t xteink_x4_power_off(struct Device* device); + +/** + * @brief Enters deep sleep. Drops the battery latch first (powering the MCU off on + * battery, so the power button physically re-engages the latch to boot again) and arms + * the power button as the deep-sleep GPIO wake source for the USB-powered case. + * @warning Callers are responsible for parking the display before calling this. + * @note Does not return on a successful deep sleep. + */ +error_t xteink_x4_power_enter_sleep(struct Device* device); + +#ifdef __cplusplus +} +#endif diff --git a/Devices/xteink-x4/source/module.cpp b/Devices/xteink-x4/source/module.cpp new file mode 100644 index 000000000..4979785b7 --- /dev/null +++ b/Devices/xteink-x4/source/module.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver xteink_x4_power_driver; +extern Driver xteink_x4_power_supply_driver; + +static Driver* const xteink_x4_drivers[] = { + &xteink_x4_power_driver, + &xteink_x4_power_supply_driver, + nullptr +}; + +Module xteink_x4_module = { + .name = "xteink-x4", + .drivers = xteink_x4_drivers +}; + +} diff --git a/Devices/xteink-x4/xteink,x4.dts b/Devices/xteink-x4/xteink,x4.dts new file mode 100644 index 000000000..9134ae8ba --- /dev/null +++ b/Devices/xteink-x4/xteink,x4.dts @@ -0,0 +1,77 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/ { + compatible = "root"; + model = "Xteink X4"; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + channels = , + , + ; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <22>; + }; + + buttons { + compatible = "tactility,button-adc-control"; + debounce-ms = <20>; + buttons = <&adc0 1 3803 3103 LV_KEY_ESC>, + <&adc0 1 3103 2093 LV_KEY_ENTER>, + <&adc0 1 2093 749 LV_KEY_PREV>, + <&adc0 1 749 -2147483648 LV_KEY_NEXT>, + <&adc0 2 3168 1123 LV_KEY_UP>, + <&adc0 2 1123 -2147483648 LV_KEY_DOWN>; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + cs-gpios = <&gpio0 12 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 10 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 7 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 8 GPIO_FLAG_NONE>; + max-transfer-size = <4096>; + + sdcard@0 { + compatible = "espressif,esp32-sdspi"; + frequency-khz = <20000>; + }; + }; + + power { + compatible = "xteink,x4-power"; + pin-usb-detect = <&gpio0 20 GPIO_FLAG_NONE>; + pin-power-off = <&gpio0 13 GPIO_FLAG_NONE>; + pin-power-button = <&gpio0 3 GPIO_FLAG_PULL_UP>; + }; +}; diff --git a/Drivers/button-adc-control-module/CMakeLists.txt b/Drivers/button-adc-control-module/CMakeLists.txt new file mode 100644 index 000000000..61ab99b7a --- /dev/null +++ b/Drivers/button-adc-control-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(button-adc-control-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 lvgl driver +) diff --git a/Drivers/button-adc-control-module/LICENSE-Apache-2.0.md b/Drivers/button-adc-control-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/button-adc-control-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/button-adc-control-module/README.md b/Drivers/button-adc-control-module/README.md new file mode 100644 index 000000000..95ef085c5 --- /dev/null +++ b/Drivers/button-adc-control-module/README.md @@ -0,0 +1,36 @@ +# Button ADC Control + +Kernel driver for buttons behind an ADC resistor ladder (e.g. the Xteink X4's six side +buttons on two ADC pins). Exposes a `KEYBOARD_TYPE` device (`tactility/drivers/keyboard.h`) +that translates presses into LVGL navigation keys. + +Devicetree binding: `tactility,button-adc-control` (see `bindings/tactility,button-adc-control.yaml`). + +Each button is defined by an ADC channel and the raw-value band it occupies; a button is +pressed while `band_low < raw <= band_high`. Each entry is +``; the bottom-most band uses `-2147483648` +(INT32_MIN) as its `band_low` so the ladder's near-ground rung is still inside the band. +Bands come from the values recorded on real hardware (e.g. the X4's Back 3512 / +Confirm 2694 / Left 1493 / Right ~5 and Up 2242 / Down ~5 readings), split at the +midpoints between neighbouring readings. The ADC must be configured with the same +attenuation the ranges were recorded at (X4: 11 dB, `ADC_ATTEN_DB_12` on this IDF, its +non-deprecated alias). + +## Example + +```dts +buttons { + compatible = "tactility,button-adc-control"; + debounce-ms = <20>; + buttons = <&adc0 1 3803 3103 LV_KEY_ESC>, // Back, presses at 3512 + <&adc0 1 3103 2093 LV_KEY_ENTER>, // Confirm, presses at 2694 + <&adc0 1 2093 749 LV_KEY_LEFT>, // Left, presses at 1493 + <&adc0 1 749 -2147483648 LV_KEY_RIGHT>, // Right, presses at ~5 + <&adc0 2 3168 1123 LV_KEY_UP>, // Up, presses at 2242 + <&adc0 2 1123 -2147483648 LV_KEY_DOWN>; // Down, presses at ~5 +}; +``` + +## License + +[Apache License Version 2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/button-adc-control-module/bindings/tactility,button-adc-control.yaml b/Drivers/button-adc-control-module/bindings/tactility,button-adc-control.yaml new file mode 100644 index 000000000..33f1742f3 --- /dev/null +++ b/Drivers/button-adc-control-module/bindings/tactility,button-adc-control.yaml @@ -0,0 +1,22 @@ +description: > + Buttons behind an ADC resistor ladder, where each button occupies a distinct raw-value + band on a shared ADC channel. Exposes a KEYBOARD_TYPE device: presses and releases + translate to LVGL navigation keys, so apps can be driven without a touchscreen. + +compatible: "tactility,button-adc-control" + +properties: + buttons: + type: phandle-array + required: true + element-type: "struct AdcButtonControlConfig" + description: > + One entry per button: . A button is + pressed while band_low < raw <= band_high, where raw is the 12-bit ADC reading on + the referenced channel. band_high/band_low are the exclusive/inclusive edges of the + button's band as recorded on the hardware ladder; the bottom-most band uses INT32_MIN + as its band_low. The key is an LVGL key code (e.g. LV_KEY_ESC). + debounce-ms: + type: int + default: 20 + description: Minimum time between recognized state changes, for software debouncing diff --git a/Drivers/button-adc-control-module/devicetree.yaml b/Drivers/button-adc-control-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/button-adc-control-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/button-adc-control-module/include/bindings/button_adc_control.h b/Drivers/button-adc-control-module/include/bindings/button_adc_control.h new file mode 100644 index 000000000..a023cb7f5 --- /dev/null +++ b/Drivers/button-adc-control-module/include/bindings/button_adc_control.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(button_adc_control, struct ButtonAdcControlConfig) diff --git a/Drivers/button-adc-control-module/include/button_adc_control_module.h b/Drivers/button-adc-control-module/include/button_adc_control_module.h new file mode 100644 index 000000000..f7583a508 --- /dev/null +++ b/Drivers/button-adc-control-module/include/button_adc_control_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module button_adc_control_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/button-adc-control-module/include/drivers/button_adc_control.h b/Drivers/button-adc-control-module/include/drivers/button_adc_control.h new file mode 100644 index 000000000..4bc23e86e --- /dev/null +++ b/Drivers/button-adc-control-module/include/drivers/button_adc_control.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +/** + * @brief A single ladder button: an ADC channel plus the raw-value band that selects it. + */ +struct AdcButtonControlConfig { + /** ADC device controlling the channel */ + struct Device* adc_controller; + /** The channel's index on the device */ + uint8_t channel; + /** Upper (inclusive) edge of the raw-value band; no button above this value */ + int band_high; + /** Lower (exclusive) edge of the raw-value band; INT32_MIN for the bottom band */ + int band_low; + /** LVGL key code emitted when the button is pressed */ + uint32_t key; +}; + +struct ButtonAdcControlConfig { + /** One entry per button, in declared order */ + struct AdcButtonControlConfig* buttons; + /** The item count of buttons */ + uint32_t buttons_count; + /** Minimum time between recognized state changes, for software debouncing */ + uint32_t debounce_ms; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/button-adc-control-module/source/button_adc_control.cpp b/Drivers/button-adc-control-module/source/button_adc_control.cpp new file mode 100644 index 000000000..62cdbf4b1 --- /dev/null +++ b/Drivers/button-adc-control-module/source/button_adc_control.cpp @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "ButtonAdcControl" +#define GET_CONFIG(device) (static_cast((device)->config)) + +// Worst case: every button on the ladder completes a press+release pair within one +// read_key() poll interval. Ladders cap at a handful of buttons, so 16 never binds. +constexpr auto BUTTON_ADC_PENDING_CAPACITY = 16; + +struct ButtonAdcPendingEvent { + uint32_t key; + bool pressed; +}; + +struct ButtonAdcButtonState { + bool in_use; + bool debounced_pressed; + uint32_t last_change_time; +}; + +struct ButtonAdcInternal { + ButtonAdcButtonState* button_states; + ButtonAdcPendingEvent pending[BUTTON_ADC_PENDING_CAPACITY]; + uint8_t pending_head; + uint8_t pending_count; +}; + +static void push_pending(ButtonAdcInternal* internal, uint32_t key, bool pressed) { + if (internal->pending_count >= BUTTON_ADC_PENDING_CAPACITY) { + LOG_W(TAG, "Pending event queue full, dropping event"); + return; + } + uint8_t tail = (internal->pending_head + internal->pending_count) % BUTTON_ADC_PENDING_CAPACITY; + internal->pending[tail] = { .key = key, .pressed = pressed }; + internal->pending_count++; +} + +static bool pop_pending(ButtonAdcInternal* internal, ButtonAdcPendingEvent* out_event) { + if (internal->pending_count == 0) { + return false; + } + *out_event = internal->pending[internal->pending_head]; + internal->pending_head = (internal->pending_head + 1) % BUTTON_ADC_PENDING_CAPACITY; + internal->pending_count--; + return true; +} + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(ButtonAdcInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + *internal = {}; + + internal->button_states = static_cast(calloc(config->buttons_count, sizeof(ButtonAdcButtonState))); + if (internal->button_states == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + for (size_t i = 0; i < config->buttons_count; ++i) { + internal->button_states[i].in_use = config->buttons[i].adc_controller != nullptr; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + free(internal->button_states); + free(internal); + return ERROR_NONE; +} + +// endregion + +// region KeyboardApi + +static void poll_button(const ButtonAdcControlConfig* config, ButtonAdcInternal* internal, size_t button_index) { + auto& state = internal->button_states[button_index]; + if (!state.in_use) { + return; + } + + const auto& button = config->buttons[button_index]; + + int raw; + AdcChannelSpec channel_spec = { button.adc_controller, button.channel }; + if (adc_channel_read_raw(&channel_spec, &raw, portMAX_DELAY) != ERROR_NONE) { + return; + } + + bool raw_pressed = raw > button.band_low && raw <= button.band_high; + + uint32_t now = get_millis(); + if ((now - state.last_change_time) < config->debounce_ms) { + return; + } + + if (raw_pressed == state.debounced_pressed) { + return; + } + state.last_change_time = now; + state.debounced_pressed = raw_pressed; + + push_pending(internal, button.key, raw_pressed); +} + +static error_t button_adc_control_read_key(Device* device, KeyboardKeyData* data) { + const auto* config = GET_CONFIG(device); + auto* internal = static_cast(device_get_driver_data(device)); + + for (size_t i = 0; i < config->buttons_count; ++i) { + poll_button(config, internal, i); + } + + ButtonAdcPendingEvent event; + if (pop_pending(internal, &event)) { + data->key = event.key; + data->pressed = event.pressed; + data->continue_reading = internal->pending_count > 0; + } else { + data->key = 0; + data->pressed = false; + data->continue_reading = false; + } + + return ERROR_NONE; +} + +// endregion + +static constexpr KeyboardApi button_adc_control_api = { + .read_key = button_adc_control_read_key, +}; + +Driver button_adc_control_driver = { + .name = "button_adc_control", + .compatible = (const char*[]) { "tactility,button-adc-control", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &button_adc_control_api, + .device_type = &KEYBOARD_TYPE, + .owner = &button_adc_control_module, + .internal = nullptr +}; diff --git a/Drivers/button-adc-control-module/source/module.cpp b/Drivers/button-adc-control-module/source/module.cpp new file mode 100644 index 000000000..38cf40954 --- /dev/null +++ b/Drivers/button-adc-control-module/source/module.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver button_adc_control_driver; + +static Driver* const button_adc_control_drivers[] = { + &button_adc_control_driver, + nullptr +}; + +Module button_adc_control_module = { + .name = "button-adc-control", + .drivers = button_adc_control_drivers +}; + +} // extern "C" diff --git a/Libraries/elf_loader/Kconfig b/Libraries/elf_loader/Kconfig index be8f32b73..df72e828e 100644 --- a/Libraries/elf_loader/Kconfig +++ b/Libraries/elf_loader/Kconfig @@ -1,15 +1,15 @@ menu "Espressif ELF Loader Configuration" - visible if (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) + visible if (IDF_TARGET_ESP32 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) config ELF_LOADER_BUS_ADDRESS_MIRROR bool default y if (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3) - default n if (IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) + default n if (IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) config ELF_LOADER bool "Enable Espressif ELF Loader" default y - depends on (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) + depends on (IDF_TARGET_ESP32 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) help Select this option to enable ELF Loader and show the submenu with ELF Loader configuration choices. diff --git a/Libraries/elf_loader/idf_component.yml b/Libraries/elf_loader/idf_component.yml index 65795807b..b7efdc824 100644 --- a/Libraries/elf_loader/idf_component.yml +++ b/Libraries/elf_loader/idf_component.yml @@ -1,6 +1,7 @@ version: "1.1.1" targets: - esp32 + - esp32c3 - esp32s2 - esp32s3 - esp32c6 diff --git a/Libraries/esp_epaper b/Libraries/esp_epaper index 0bf4f144b..abd736a74 160000 --- a/Libraries/esp_epaper +++ b/Libraries/esp_epaper @@ -1 +1 @@ -Subproject commit 0bf4f144b543048f10bdadd49f0578858bdc2a39 +Subproject commit abd736a74a169d7c11077374a13df3a073aa77bf diff --git a/device.py b/device.py index 448908258..b0d9c914c 100644 --- a/device.py +++ b/device.py @@ -12,6 +12,19 @@ SHELL_COLOR_ORANGE = "\033[93m" SHELL_COLOR_RESET = "\033[m" +# Maximum core clock (MHz) per target +CPU_FREQUENCIES_MHZ = { + "esp32": 240, + "esp32s2": 240, + "esp32s3": 240, + "esp32c3": 160, + "esp32c6": 160, + "esp32h2": 96, + "esp32p4": 360, + "esp32c2": 120, + "esp32c5": 240, +} + DEVICES_DIRECTORY = "Devices" def print_warning(message): @@ -148,13 +161,13 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s def write_core_variables(output_file, device_properties: dict): idf_target = get_property_or_exit(device_properties, "hardware.target").lower() + cpu_frequency = get_property_or_default(device_properties, "hardware.cpuFreq", + CPU_FREQUENCIES_MHZ.get(idf_target, 240)) output_file.write("# Target\n") output_file.write(f"CONFIG_IDF_TARGET=\"{idf_target}\"\n") output_file.write("# CPU\n") - output_file.write("CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y\n") - output_file.write("CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240\n") - output_file.write(f"CONFIG_{idf_target.upper()}_DEFAULT_CPU_FREQ_240=y\n") - output_file.write(f"CONFIG_{idf_target.upper()}_DEFAULT_CPU_FREQ_MHZ=240\n") + output_file.write(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{cpu_frequency}=y\n") + output_file.write(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ={cpu_frequency}\n") if idf_target != "esp32": # Not available on original ESP32 output_file.write("# Enable usage of MALLOC_CAP_EXEC on IRAM:\n") output_file.write("CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n\n") @@ -177,7 +190,8 @@ def write_flash_variables(output_file, device_properties: dict): flash_size_number = flash_size[:-2] output_file.write(f"CONFIG_ESPTOOLPY_FLASHSIZE_{flash_size_number}MB=y\n") flash_mode = get_property_or_default(device_properties, "hardware.flashMode", 'QIO') - output_file.write(f"CONFIG_FLASHMODE_{flash_mode}=y\n") + output_file.write(f"CONFIG_ESPTOOLPY_FLASHMODE_{flash_mode}=y\n") + output_file.write(f"CONFIG_ESPTOOLPY_FLASHMODE=\"{flash_mode.lower()}\"\n") esptool_flash_freq = get_property_or_none(device_properties, "hardware.esptoolFlashFreq") if esptool_flash_freq is not None: output_file.write(f"CONFIG_ESPTOOLPY_FLASHFREQ_{esptool_flash_freq}=y\n")