Smart tab5keyboard (#533)

This commit is contained in:
Shadowtrance 2026-06-20 06:14:07 +10:00 committed by GitHub
parent e8b9a1f2a9
commit 594b8bd27e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 528 additions and 137 deletions

View File

@ -26,7 +26,6 @@ macro(tactility_project project_name)
set(COMPONENTS
TactilityFreeRtos
bm8563-module
bm8563-module
bmi270-module
mpu6886-module
pi4ioe5v6408-module

View File

@ -1,5 +1,4 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/Power.h"
#include "devices/Tab5Keyboard.h"
@ -13,11 +12,11 @@ using namespace tt::hal;
static constexpr auto* TAG = "Tab5";
static DeviceVector createDevices() {
::Device* i2c2 = device_find_by_name("i2c2");
auto* i2c2 = device_find_by_name("i2c2");
check(i2c2, "i2c2 not found");
return {
createPower(),
createDisplay(),
createSdCard(),
std::make_shared<Tab5Keyboard>(i2c2)
};
}

View File

@ -38,8 +38,10 @@ static std::shared_ptr<tt::hal::touch::TouchDevice> createGt911Touch() {
}
static std::shared_ptr<tt::hal::touch::TouchDevice> createSt7123Touch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c, "i2c0 not found");
auto configuration = std::make_unique<St7123Touch::Configuration>(
I2C_NUM_0,
i2c,
720,
1280,
false, // swapXY

View File

@ -102,7 +102,7 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.in_color_format = LCD_COLOR_FMT_RGB565,
.out_color_format = LCD_COLOR_FMT_RGB565,
.num_fbs = 1, // TODO: 2?
.num_fbs = 2,
.video_timing =
{
.h_size = 720,

View File

@ -1,26 +0,0 @@
#include "SdCard.h"
#include <tactility/device.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
constexpr auto SDCARD_PIN_CS = GPIO_NUM_42;
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
SDCARD_PIN_CS,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot
);
auto* spi_controller = device_find_by_name("spi0");
check(spi_controller, "spi0 not found");
return std::make_shared<SpiSdCardDevice>(
std::move(configuration),
spi_controller
);
}

View File

@ -1,8 +0,0 @@
#pragma once
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <memory>
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -99,7 +99,7 @@ bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 70,
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.num_fbs = 1,
.num_fbs = 2,
.video_timing = {
.h_size = 720,
.v_size = 1280,

View File

@ -1,6 +1,7 @@
#include "St7123Touch.h"
#include <Tactility/Logger.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <esp_lcd_touch_st7123.h>
#include <esp_err.h>
@ -8,11 +9,9 @@ static const auto LOGGER = tt::Logger("ST7123Touch");
bool St7123Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_ST7123_CONFIG();
return esp_lcd_new_panel_io_i2c(
static_cast<esp_lcd_i2c_bus_handle_t>(configuration->port),
&io_config,
&outHandle
) == ESP_OK;
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->controller);
i2c_master_bus_handle_t bus = esp32_i2c_master_get_bus_handle(configuration->controller);
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
}
bool St7123Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& config, esp_lcd_touch_handle_t& touchHandle) {

View File

@ -2,7 +2,7 @@
#include <EspLcdTouch.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <tactility/device.h>
class St7123Touch final : public EspLcdTouch {
@ -12,14 +12,14 @@ public:
public:
Configuration(
i2c_port_t port,
::Device* controller,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinInterrupt = GPIO_NUM_NC
) : port(port),
) : controller(controller),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
@ -28,7 +28,7 @@ public:
pinInterrupt(pinInterrupt)
{}
i2c_port_t port;
::Device* controller;
uint16_t xMax;
uint16_t yMax;
bool swapXy;

View File

@ -1,5 +1,7 @@
#include "Tab5Keyboard.h"
#include <Tactility/app/App.h>
#include <Tactility/lvgl/Keyboard.h>
#include <Tactility/lvgl/LvglSync.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <esp_timer.h>
@ -329,6 +331,113 @@ void Tab5Keyboard::processKeyboard() {
}
}
}
checkAttachState();
}
// ---------------------------------------------------------------------------
// applyAutoRotation - on attach, switches to landscape if not already (saving
// the prior rotation); on detach, restores the saved rotation if we were the
// ones who changed it. Only affects the live LVGL rotation, never persisted
// display settings.
// ---------------------------------------------------------------------------
bool Tab5Keyboard::applyAutoRotation(bool keyboardAttached) {
auto* display = lv_indev_get_display(kbHandle);
if (display == nullptr) {
return false;
}
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
return false; // retry next poll
}
if (keyboardAttached) {
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
savedRotation = lv_display_get_rotation(display);
rotationOverrideActive = true;
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
}
} else {
// Only restore if rotation is still what we set it to - if the user manually
// changed it since attaching, respect their choice instead.
if (rotationOverrideActive && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
lv_display_set_rotation(display, savedRotation);
}
rotationOverrideActive = false;
}
tt::lvgl::unlock();
return true;
}
// ---------------------------------------------------------------------------
// checkAttachState - throttled (~1s) hot-plug detection. Reapplies device
// register configuration and auto-rotation on detach/attach transitions.
// ---------------------------------------------------------------------------
void Tab5Keyboard::checkAttachState() {
static constexpr uint32_t ATTACH_CHECK_TICKS = 50; // ~1s at 20ms/tick
if (++attachCheckTickCounter < ATTACH_CHECK_TICKS) {
return;
}
attachCheckTickCounter = 0;
const bool attached = isAttached();
if (attached == wasAttached) {
pendingAttachConfirmCount = 0;
return;
}
// Require the new state to be confirmed on a second consecutive check before acting -
// a single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
if (attached != pendingAttachState || pendingAttachConfirmCount == 0) {
pendingAttachState = attached;
pendingAttachConfirmCount = 1;
return;
}
pendingAttachConfirmCount = 0;
if (attached) {
reinitDevice();
}
if (!applyAutoRotation(attached)) {
return; // keep prior state so transition is retried
}
wasAttached = attached;
}
// ---------------------------------------------------------------------------
// lateStart - see header comment. Brings up LVGL input handling for a keyboard
// that wasn't attached at boot (startLvgl() wasn't called from attachDevices()).
// ---------------------------------------------------------------------------
bool Tab5Keyboard::lateStart() {
if (kbHandle != nullptr) {
return true; // already started
}
auto* display = lv_display_get_default();
if (display == nullptr) {
return false; // LVGL not ready yet
}
if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
return false; // try again on the next attach-state check
}
bool started = startLvgl(display);
if (started) {
tt::lvgl::hardware_keyboard_set_indev(kbHandle);
// redraw() assigns every indev that exists at the time to the active screen's
// input group. This indev didn't exist yet at the last redraw(), so it has no
// group and won't deliver key events until the next app switch. Join the
// current default group now so input works immediately on the visible screen.
lv_indev_set_group(kbHandle, lv_group_get_default());
}
tt::lvgl::unlock();
return started;
}
// ---------------------------------------------------------------------------
@ -359,21 +468,31 @@ Tab5Keyboard::~Tab5Keyboard() {
}
}
// ---------------------------------------------------------------------------
// reinitDevice - (re)applies the device register configuration. Used at
// startLvgl() and again on hot-plug reattach, since the device's RGB mode and
// interrupt configuration are volatile and reset to power-on defaults when
// the keyboard is unplugged and reconnected.
// ---------------------------------------------------------------------------
void Tab5Keyboard::reinitDevice() {
writeReg(REG_KEYBOARD_MODE, 0x00); // Normal mode
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
writeReg(REG_INT_STAT, 0x00); // clear pending INT
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
updateLeds(); // restore current LED state
if (irqConfigured) {
writeReg(REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
}
}
bool Tab5Keyboard::startLvgl(lv_display_t* display) {
if (!queue) {
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
return false;
}
// Set Normal mode explicitly — device may power up in a different mode
if (!writeReg(REG_KEYBOARD_MODE, 0x00)) {
LOG_E("Tab5Keyboard", "Failed to set keyboard mode");
return false;
}
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
writeReg(REG_INT_STAT, 0x00); // clear pending INT
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
symActive = false;
aaSticky = false;
aaHeld = false;
@ -383,13 +502,14 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
repeatRow = 0xFF;
repeatCol = 0xFF;
repeatLastMs = 0;
updateLeds(); // both LEDs off initially
// Enable Normal-mode interrupt (bit 0)
if (!writeReg(REG_INT_CFG, 0x01)) {
LOG_E("Tab5Keyboard", "Failed to configure interrupt register");
return false;
}
configureIrqPin(); // best-effort; falls back to polling if it fails. Must run before
// reinitDevice() so REG_INT_CFG is written if IRQ setup succeeded.
// Best-effort: if the keyboard isn't attached yet (e.g. started speculatively at
// boot so it can be detected later via hot-plug), these I2C writes fail silently
// and reinitDevice() runs again once attach is detected.
reinitDevice();
kbHandle = lv_indev_create();
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
@ -397,7 +517,8 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
lv_indev_set_display(kbHandle, display);
lv_indev_set_user_data(kbHandle, this);
configureIrqPin(); // best-effort; falls back to polling if it fails
wasAttached = isAttached();
rotationOverrideActive = false;
assert(inputTimer == nullptr);
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
@ -405,6 +526,10 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
});
inputTimer->start();
if (wasAttached) {
applyAutoRotation(true);
}
return true;
}

View File

@ -29,6 +29,16 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
volatile bool irqPending = false;
bool irqConfigured = false;
// Hot-plug attach-state polling (piggybacks on the 20ms inputTimer)
bool wasAttached = false;
uint32_t attachCheckTickCounter = 0;
// I2C probes can false-positive on a floating/half-connected bus (e.g. mid-unplug), so a
// state change is only acted on once it's seen on two consecutive ~1s checks in a row.
bool pendingAttachState = false;
uint8_t pendingAttachConfirmCount = 0;
lv_display_rotation_t savedRotation = LV_DISPLAY_ROTATION_0;
bool rotationOverrideActive = false;
// Software key-repeat state (tracked by position to survive modifier changes)
uint32_t repeatKey = 0;
uint8_t repeatRow = 0xFF;
@ -44,6 +54,10 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
void removeIrqPin();
static void IRAM_ATTR irqHandler(void* arg);
void reinitDevice();
bool applyAutoRotation(bool keyboardAttached);
void checkAttachState();
void drainEvents();
void processKeyboard();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
@ -62,4 +76,10 @@ public:
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return kbHandle; }
// Starts LVGL input handling and registers the hardware keyboard indev for a device
// that wasn't attached at boot (so startLvgl() was never called from Lvgl.cpp's
// attachDevices()). Called from the device module's attach-detection timer once the
// keyboard is first detected post-boot. No-op if LVGL input is already started.
bool lateStart();
};

View File

@ -3,6 +3,10 @@
#include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include "devices/Tab5Keyboard.h"
#include <freertos/FreeRTOS.h>
#include <freertos/timers.h>
@ -13,14 +17,17 @@
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
constexpr auto HP_DETECT_POLL_MS = 1000;
constexpr auto KB_DETECT_POLL_MS = 1000;
// hp_detect_timer is only touched from start()/stop(), which are called serially
// by the module manager — no atomic needed for the handle itself.
// hp_detect_timer and kb_detect_timer are only touched from start()/stop(), which are called
// serially by the module manager — no atomic needed for the handles themselves.
static TimerHandle_t hp_detect_timer = nullptr;
static TimerHandle_t kb_detect_timer = nullptr;
static std::atomic<Device*> io_expander0_cached { nullptr };
// Flags are written by the timer daemon task and read by start()/stop() — use atomics.
static std::atomic<bool> hp_detect_last { false };
static std::atomic<bool> hp_detect_initialized { false };
static std::atomic<bool> kb_late_started { false };
static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
Device* cached = io_expander0_cached.load(std::memory_order_acquire);
@ -68,6 +75,40 @@ static void headphoneDetectCallback(TimerHandle_t /*timer*/) {
}
}
// Detects a Tab5 Keyboard add-on that was plugged in after boot (so it wasn't started by
// Lvgl.cpp's attachDevices()). Once lateStart() succeeds, this stops polling for good — there's
// no support for re-detecting after the indev is torn down again.
static void keyboardDetectCallback(TimerHandle_t timer) {
if (kb_late_started.load(std::memory_order_acquire)) {
xTimerStop(timer, 0);
return;
}
using namespace tt::hal;
auto keyboard = findFirstDevice<keyboard::KeyboardDevice>(tt::hal::Device::Type::Keyboard);
if (!keyboard) {
return; // Not registered yet, will retry on next tick
}
if (keyboard->getLvglIndev() != nullptr) {
// Already started (boot-time attach) — nothing left to do.
kb_late_started.store(true, std::memory_order_release);
xTimerStop(timer, 0);
return;
}
if (!keyboard->isAttached()) {
return; // Not plugged in yet, will retry on next tick
}
auto tab5_keyboard = std::static_pointer_cast<Tab5Keyboard>(keyboard);
if (tab5_keyboard->lateStart()) {
LOG_I(TAG, "kb_detect: keyboard attached post-boot, LVGL input started");
kb_late_started.store(true, std::memory_order_release);
xTimerStop(timer, 0);
}
}
extern "C" {
static error_t start() {
@ -91,23 +132,48 @@ static error_t start() {
hp_detect_timer = nullptr;
return ERROR_RESOURCE;
}
kb_late_started = false;
kb_detect_timer = xTimerCreate("kb_detect", pdMS_TO_TICKS(KB_DETECT_POLL_MS), pdTRUE, nullptr, keyboardDetectCallback);
if (!kb_detect_timer) {
LOG_E(TAG, "Failed to create kb_detect timer");
return ERROR_RESOURCE;
}
if (xTimerStart(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_E(TAG, "Failed to start kb_detect timer");
xTimerDelete(kb_detect_timer, pdMS_TO_TICKS(100));
kb_detect_timer = nullptr;
return ERROR_RESOURCE;
}
return ERROR_NONE;
}
static error_t stop() {
if (hp_detect_timer == nullptr) {
return ERROR_NONE;
if (hp_detect_timer != nullptr) {
if (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_W(TAG, "Failed to stop hp_detect timer");
}
if (xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_E(TAG, "Failed to delete hp_detect timer");
}
// Always clear the handle — stale non-null handle is worse than a resource leak,
// as it would cause start() to silently skip re-creating the timer.
hp_detect_timer = nullptr;
io_expander0_cached.store(nullptr, std::memory_order_release);
}
if (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_W(TAG, "Failed to stop hp_detect timer");
if (kb_detect_timer != nullptr) {
if (xTimerStop(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_W(TAG, "Failed to stop kb_detect timer");
}
if (xTimerDelete(kb_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_E(TAG, "Failed to delete kb_detect timer");
}
kb_detect_timer = nullptr;
}
if (xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
LOG_E(TAG, "Failed to delete hp_detect timer");
}
// Always clear the handle — stale non-null handle is worse than a resource leak,
// as it would cause start() to silently skip re-creating the timer.
hp_detect_timer = nullptr;
io_expander0_cached.store(nullptr, std::memory_order_release);
return ERROR_NONE;
}

View File

@ -3,10 +3,10 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_usbhost.h>
#include <bindings/bmi270.h>
@ -28,11 +28,11 @@
};
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
compatible = "espressif,esp32-i2c-master";
port = <I2C_NUM_0>;
clock-frequency = <100000>;
pin-sda = <&gpio0 31 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
io_expander0 {
compatible = "diodes,pi4ioe5v6408";
@ -61,12 +61,14 @@
};
};
i2c_port_a: i2c1 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <100000>;
pin-sda = <&gpio0 53 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 54 GPIO_FLAG_NONE>;
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
pinSdaTx = <&gpio0 53 GPIO_FLAG_NONE>;
pinSclRx = <&gpio0 54 GPIO_FLAG_NONE>;
uartPort = <UART_NUM_1>;
i2cPort = <I2C_NUM_1>;
i2cClockFrequency = <100000>;
};
i2c_keyboard: i2c2 {
@ -78,12 +80,18 @@
pin-scl = <&gpio0 1 GPIO_FLAG_PULL_UP>;
};
sdcard_spi: spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
pin-mosi = <&gpio0 44 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 43 GPIO_FLAG_NONE>;
sdmmc0 {
compatible = "espressif,esp32-sdmmc";
pin-clk = <&gpio0 43 GPIO_FLAG_NONE>;
pin-cmd = <&gpio0 44 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
pin-d1 = <&gpio0 40 GPIO_FLAG_NONE>;
pin-d2 = <&gpio0 41 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 42 GPIO_FLAG_NONE>;
bus-width = <4>;
slot = <SDMMC_HOST_SLOT_0>;
max-freq-khz = <SDMMC_FREQ_HIGHSPEED>;
on-chip-ldo-chan = <4>;
};
// ES8388 and ES7210
@ -97,14 +105,6 @@
pin-mclk = <&gpio0 30 GPIO_FLAG_NONE>;
};
uart_port_a: uart1 {
compatible = "espressif,esp32-uart";
status = "disabled";
port = <UART_NUM_1>;
pin-tx = <&gpio0 53 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 54 GPIO_FLAG_NONE>;
};
usbhost0 {
compatible = "espressif,esp32-usbhost";
peripheral-map = <0>;

View File

@ -3,6 +3,9 @@
#include <Tactility/hal/display/DisplayDriver.h>
#include <esp_lcd_panel_ops.h>
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
#include <esp_lcd_mipi_dsi.h>
#endif
class EspLcdDisplayDriver : public tt::hal::display::DisplayDriver {
@ -32,4 +35,13 @@ public:
uint16_t getPixelWidth() const override { return hRes; }
uint16_t getPixelHeight() const override { return vRes; }
#if CONFIG_SOC_MIPI_DSI_SUPPORTED
uint8_t getFrameBuffers(void* outBuffers[2]) const override {
if (outBuffers == nullptr) {
return 0;
}
return (esp_lcd_dpi_panel_get_frame_buffer(panelHandle, 2, &outBuffers[0], &outBuffers[1]) == ESP_OK) ? 2 : 0;
}
#endif
};

View File

@ -43,6 +43,16 @@ properties:
type: int
required: true
description: Bus width in bits
slot:
type: int
default: 1
enum: [0, 1]
description: SDMMC host slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1). On ESP32-P4, slot 0 uses the dedicated (non-GPIO-matrix) pins.
max-freq-khz:
type: int
default: 20000
minimum: 1
description: Maximum SDMMC clock frequency in kHz (e.g. 40000 for SDMMC_FREQ_HIGHSPEED)
wp-active-high:
type: boolean
default: false

View File

@ -2,6 +2,8 @@
#pragma once
#include <tactility/drivers/gpio.h>
#include <tactility/device.h>
#include <driver/i2c_types.h>
#include <driver/i2c_master.h>
#ifdef __cplusplus
@ -18,8 +20,18 @@ struct Esp32I2cMasterConfig {
struct GpioPinSpec pinScl;
};
/**
* Returns the i2c_master_bus_handle_t for an esp32_i2c_master Device.
* The device must be started. Returns NULL if the device has no driver data.
*/
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device);
/**
* Returns the SCL clock frequency (Hz) configured for an esp32_i2c_master Device.
* Reads directly from the device tree config, so the device does not need to be started.
*/
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device);
#ifdef __cplusplus
}
#endif

View File

@ -3,6 +3,7 @@
#include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED
#include <driver/sdmmc_default_configs.h>
#include <sd_protocol_types.h>
#include <stdbool.h>
#include <stdint.h>
@ -26,6 +27,8 @@ struct Esp32SdmmcConfig {
struct GpioPinSpec pin_cd;
struct GpioPinSpec pin_wp;
uint8_t bus_width;
int32_t slot;
int32_t max_freq_khz;
bool wp_active_high;
bool enable_uhs;
bool pullups;

View File

@ -286,6 +286,14 @@ static error_t stop(Device* device) {
return ERROR_NONE;
}
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(struct Device* device) {
return GET_DATA(device)->bus_handle;
}
uint32_t esp32_i2c_master_get_clock_frequency(struct Device* device) {
return GET_CONFIG(device)->clockFrequency;
}
static constexpr I2cControllerApi ESP32_I2C_MASTER_API = {
.read = read,
.write = write,
@ -308,8 +316,4 @@ Driver esp32_i2c_master_driver = {
.internal = nullptr
};
i2c_master_bus_handle_t esp32_i2c_master_get_bus_handle(Device* device) {
return GET_DATA(device)->bus_handle;
}
} // extern "C"

View File

@ -10,6 +10,8 @@
#include <driver/sdmmc_host.h>
#include <esp_vfs_fat.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <sdmmc_cmd.h>
#include <string>
@ -76,6 +78,8 @@ static error_t mount(void* data) {
};
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.slot = config->slot;
host.max_freq_khz = config->max_freq_khz;
#if SOC_SD_PWR_CTRL_SUPPORTED
// Treat non-positive values as disabled to remain safe with zero-initialized configs.
@ -89,6 +93,10 @@ static error_t mount(void* data) {
return ERROR_NOT_SUPPORTED;
}
host.pwr_ctrl_handle = fs_data->pwr_ctrl_handle;
// On cold boot the SD card needs time for its supply rail to ramp up after the
// on-chip LDO is enabled, otherwise the initial ACMD41 (send_op_cond) times out.
vTaskDelay(pdMS_TO_TICKS(10));
}
#endif

View File

@ -24,6 +24,14 @@ public:
virtual uint16_t getPixelWidth() const = 0;
virtual uint16_t getPixelHeight() const = 0;
virtual bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) = 0;
/**
* Returns direct pointers to the panel's hardware frame buffer(s), if the
* underlying driver supports it (DPI/MIPI-DSI panels only).
* @param[out] outBuffers receives up to 2 frame buffer pointers
* @return number of buffers written to outBuffers (0 if unsupported)
*/
virtual uint8_t getFrameBuffers(void* outBuffers[2]) const { return 0; }
};
}

View File

@ -15,7 +15,7 @@ enum class BootMode {
Flash
};
bool startMassStorageWithSdmmc();
bool startMassStorageWithSdmmc(bool fromBootMode = false);
void stop();
Mode getMode();
bool isSupported();
@ -28,7 +28,7 @@ void resetUsbBootMode();
BootMode getUsbBootMode();
// Flash-based mass storage
bool startMassStorageWithFlash();
bool startMassStorageWithFlash(bool fromBootMode = false);
bool canRebootIntoMassStorageFlash();
void rebootIntoMassStorageFlash();

View File

@ -1,7 +1,7 @@
#pragma once
bool tusbIsSupported();
bool tusbStartMassStorageWithSdmmc();
bool tusbStartMassStorageWithFlash();
bool tusbStartMassStorageWithSdmmc(bool fromBootMode = false);
bool tusbStartMassStorageWithFlash(bool fromBootMode = false);
void tusbStop();
bool tusbCanStartMassStorageWithFlash();

View File

@ -16,9 +16,12 @@
#include <lvgl.h>
#include <atomic>
#ifdef ESP_PLATFORM
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h"
#include <Tactility/kernel/PanicHandler.h>
#include <esp_system.h>
#include <sdkconfig.h>
#else
#define CONFIG_TT_SPLASH_DURATION 0
@ -36,6 +39,11 @@ static std::shared_ptr<hal::display::DisplayDevice> getHalDisplay() {
class BootApp : public App {
// Snapshot of hal::usb::isUsbBootMode(), taken before the boot thread starts and
// potentially clears the underlying flag via setupUsbBootMode()/resetUsbBootMode().
// onShow() reads this instead of the live flag to avoid a race between the two.
static std::atomic<bool> isUsbBootSplash;
Thread thread = Thread(
"boot",
5120,
@ -76,12 +84,12 @@ class BootApp : public App {
auto mode = hal::usb::getUsbBootMode(); // Get mode before reset
hal::usb::resetUsbBootMode();
if (mode == hal::usb::BootMode::Flash) {
if (!hal::usb::startMassStorageWithFlash()) {
if (!hal::usb::startMassStorageWithFlash(true)) {
LOGGER.error("Unable to start flash mass storage");
return false;
}
} else if (mode == hal::usb::BootMode::Sdmmc) {
if (!hal::usb::startMassStorageWithSdmmc()) {
if (!hal::usb::startMassStorageWithSdmmc(true)) {
LOGGER.error("Unable to start SD mass storage");
return false;
}
@ -174,6 +182,9 @@ class BootApp : public App {
public:
void onCreate(AppContext& app) override {
// Snapshot before the boot thread potentially clears the flag via setupUsbBootMode()
isUsbBootSplash = hal::usb::isUsbBootMode();
// Just in case this app is somehow resumed
if (thread.getState() == Thread::State::Stopped) {
thread.start();
@ -197,16 +208,31 @@ public:
const char* logo;
// TODO: Replace with automatic asset buckets like on Android
if (getSmallestDimension() < 150) { // e.g. Cardputer
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo_small.png";
logo = isUsbBootSplash ? "logo_usb.png" : "logo_small.png";
} else {
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo.png";
logo = isUsbBootSplash ? "logo_usb.png" : "logo.png";
}
const auto logo_path = lvgl::PATH_PREFIX + paths->getAssetsPath(logo);
LOGGER.info("{}", logo_path);
lv_image_set_src(image, logo_path.c_str());
#ifdef ESP_PLATFORM
if (isUsbBootSplash) {
auto* button = lv_button_create(parent);
lv_obj_align(button, LV_ALIGN_BOTTOM_MID, 0, -16);
auto* label = lv_label_create(button);
lv_label_set_text(label, "Return to OS");
lv_obj_add_event_cb(button, [](lv_event_t*) {
hal::usb::stop();
esp_restart();
}, LV_EVENT_SHORT_CLICKED, nullptr);
}
#endif
}
};
std::atomic<bool> BootApp::isUsbBootSplash = false;
extern const AppManifest manifest = {
.appId = "Boot",
.appName = "Boot",

View File

@ -232,7 +232,7 @@ void I2cScannerApp::onScanTimer() {
return;
}
for (uint8_t address = 0; address < 128; ++address) {
for (uint8_t address = 1; address < 128; ++address) {
if (i2c_controller_has_device_at_address(safe_port, address, 10 / portTICK_PERIOD_MS) == ERROR_NONE) {
logger.info("Found device at address 0x{:02X}", address);
if (!shouldStopScanTimer()) {

View File

@ -26,6 +26,11 @@ static uint32_t getButtonPadding(UiDensity density, uint32_t buttonSize) {
}
}
static int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) {
const int32_t usable = std::max<int32_t>(0, available_span - (3 * total_button_size));
return std::min<int32_t>(usable / 16, total_button_size / 2);
}
class LauncherApp final : public App {
static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
@ -84,6 +89,50 @@ class LauncherApp final : public App {
}
}
// The screen object outlives the launcher's views (it's recreated by GuiService::redraw()
// via lv_obj_clean() on every app switch), so the LV_EVENT_SIZE_CHANGED callback registered
// on it must be removed once buttons_wrapper is destroyed, to avoid a dangling user-data
// pointer on the next rotation while a different app is visible.
static void onButtonsWrapperDeleted(lv_event_t* e) {
auto* buttons_wrapper = lv_event_get_target_obj(e);
auto* screen = lv_obj_get_screen(buttons_wrapper);
lv_obj_remove_event_cb_with_user_data(screen, onButtonsWrapperResized, buttons_wrapper);
}
// Re-applies the flex direction and per-button margins when the display orientation
// changes while the launcher is the visible app (these are decided once at onShow()
// based on the resolution at that time, so a later rotation needs this to catch up).
static void onButtonsWrapperResized(lv_event_t* e) {
auto* buttons_wrapper = static_cast<lv_obj_t*>(lv_event_get_user_data(e));
const auto* display = lv_obj_get_display(buttons_wrapper);
const auto button_size = lvgl_get_launcher_icon_font_height();
const auto button_padding = getButtonPadding(lvgl_get_ui_density(), button_size);
const auto total_button_size = button_size + (button_padding * 2);
const auto horizontal_px = lv_display_get_horizontal_resolution(display);
const auto vertical_px = lv_display_get_vertical_resolution(display);
const bool is_landscape_display = horizontal_px >= vertical_px;
const auto current_flow = lv_obj_get_style_flex_flow(buttons_wrapper, LV_PART_MAIN);
const bool was_landscape = current_flow == LV_FLEX_FLOW_ROW;
if (is_landscape_display == was_landscape) {
return;
}
lv_obj_set_flex_flow(buttons_wrapper, is_landscape_display ? LV_FLEX_FLOW_ROW : LV_FLEX_FLOW_COLUMN);
const int32_t margin = is_landscape_display
? computeButtonMargin(horizontal_px, total_button_size)
: computeButtonMargin(vertical_px, total_button_size);
const uint32_t child_count = lv_obj_get_child_count(buttons_wrapper);
for (uint32_t i = 0; i < child_count; i++) {
auto* button = lv_obj_get_child(buttons_wrapper, i);
lv_obj_set_style_margin_hor(button, is_landscape_display ? margin : 0, LV_STATE_DEFAULT);
lv_obj_set_style_margin_ver(button, is_landscape_display ? 0 : margin, LV_STATE_DEFAULT);
}
}
public:
void onCreate(AppContext& app) override {
@ -133,19 +182,20 @@ public:
lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN);
}
int32_t margin;
if (is_landscape_display) {
const int32_t available_width = std::max<int32_t>(0, lv_display_get_horizontal_resolution(display) - (3 * total_button_size));
margin = std::min<int32_t>(available_width / 16, total_button_size / 2);
} else {
const int32_t available_height = std::max<int32_t>(0, lv_display_get_vertical_resolution(display) - (3 * total_button_size));
margin = std::min<int32_t>(available_height / 16, total_button_size / 2);
}
const int32_t margin = is_landscape_display
? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size)
: computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size);
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "AppList", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "Files", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
// The launcher's container is several levels below the screen, and LVGL only sends
// LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the
// handler is attached there, with buttons_wrapper passed through as user data.
lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper);
lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr);
if (shouldShowPowerButton()) {
auto* power_button = lv_button_create(parent);
lv_obj_set_style_pad_all(power_button, 8, 0);

View File

@ -69,13 +69,13 @@ bool isSupported() {
return tusbIsSupported();
}
bool startMassStorageWithSdmmc() {
bool startMassStorageWithSdmmc(bool fromBootMode) {
if (!canStartNewMode()) {
LOGGER.error("Can't start");
return false;
}
if (tusbStartMassStorageWithSdmmc()) {
if (tusbStartMassStorageWithSdmmc(fromBootMode)) {
currentMode = Mode::MassStorageSdmmc;
return true;
} else {
@ -110,13 +110,13 @@ void rebootIntoMassStorageSdmmc() {
}
// NEW: Flash mass storage functions
bool startMassStorageWithFlash() {
bool startMassStorageWithFlash(bool fromBootMode) {
if (!canStartNewMode()) {
LOGGER.error("Can't start flash mass storage");
return false;
}
if (tusbStartMassStorageWithFlash()) {
if (tusbStartMassStorageWithFlash(fromBootMode)) {
currentMode = Mode::MassStorageFlash;
return true;
} else {

View File

@ -4,7 +4,7 @@
namespace tt::hal::usb {
bool startMassStorageWithSdmmc() { return false; }
bool startMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
void stop() {}
Mode getMode() { return Mode::Default; }
BootMode getUsbBootMode() { return BootMode::None; }
@ -12,7 +12,7 @@ bool isSupported() { return false; }
bool canRebootIntoMassStorageSdmmc() { return false; }
void rebootIntoMassStorageSdmmc() {}
bool startMassStorageWithFlash() { return false; }
bool startMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
bool canRebootIntoMassStorageFlash() { return false; }
void rebootIntoMassStorageFlash() {}
bool isUsbBootMode() { return false; }

View File

@ -8,6 +8,9 @@
#if CONFIG_TINYUSB_MSC_ENABLED == 1
#include <Tactility/Logger.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <tinyusb.h>
#include <tusb_msc_storage.h>
#include <wear_levelling.h>
@ -26,6 +29,10 @@ namespace tt::hal::usb {
extern sdmmc_card_t* getCard();
}
// Set when mass storage was started as part of the dedicated reboot-into-MSC boot flow.
// Used to decide whether ejecting the volume should automatically reboot back to normal OS.
static bool startedFromBootMode = false;
enum {
ITF_NUM_MSC = 0,
ITF_NUM_TOTAL
@ -99,6 +106,15 @@ static uint8_t const msc_hs_configuration_desc[] = {
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
if (event->mount_changed_data.is_mounted) {
LOGGER.info("MSC Mounted");
// Storage is only (re)mounted into our own filesystem after the host sends a SCSI
// START STOP UNIT eject (see tud_msc_start_stop_cb() in tusb_msc_storage.c). Windows
// is known not to send this reliably, so this is a best-effort path for hosts that do
// (e.g. Linux/macOS) - the "Return to OS" button on the boot screen is the primary one.
// If we got here while booted into MSC mode, it's safe to reboot back into normal OS now.
if (startedFromBootMode) {
LOGGER.info("MSC ejected by host, rebooting into normal OS");
esp_restart();
}
} else {
LOGGER.info("MSC Unmounted");
}
@ -147,8 +163,11 @@ static bool ensureDriverInstalled() {
bool tusbIsSupported() { return true; }
bool tusbStartMassStorageWithSdmmc() {
ensureDriverInstalled();
bool tusbStartMassStorageWithSdmmc(bool fromBootMode) {
if (!ensureDriverInstalled()) {
return false;
}
startedFromBootMode = fromBootMode;
auto* card = tt::hal::usb::getCard();
if (card == nullptr) {
@ -179,9 +198,12 @@ bool tusbStartMassStorageWithSdmmc() {
return result == ESP_OK;
}
bool tusbStartMassStorageWithFlash() {
bool tusbStartMassStorageWithFlash(bool fromBootMode) {
LOGGER.info("Starting flash MSC");
ensureDriverInstalled();
if (!ensureDriverInstalled()) {
return false;
}
startedFromBootMode = fromBootMode;
wl_handle_t handle = tt::getDataPartitionWlHandle();
if (handle == WL_INVALID_HANDLE) {
@ -212,6 +234,12 @@ bool tusbStartMassStorageWithFlash() {
}
void tusbStop() {
// Actively signal a disconnect to the host before tearing down the peripheral, otherwise
// a subsequent esp_restart() resets the chip too fast for the host to notice the device
// went away, leaving it stuck showing the old MSC device until the cable is replugged.
tud_disconnect();
vTaskDelay(pdMS_TO_TICKS(250));
tinyusb_msc_storage_deinit();
#if CONFIG_IDF_TARGET_ESP32P4
usb_wrap_ll_phy_select(&USB_WRAP, 1);
@ -225,8 +253,8 @@ bool tusbCanStartMassStorageWithFlash() {
#else
bool tusbIsSupported() { return false; }
bool tusbStartMassStorageWithSdmmc() { return false; }
bool tusbStartMassStorageWithFlash() { return false; }
bool tusbStartMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
bool tusbStartMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
void tusbStop() {}
bool tusbCanStartMassStorageWithFlash() { return false; }

View File

@ -17,6 +17,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
list(APPEND PRIV_REQUIRES_LIST elf_loader)
endif ()
if (IDF_TARGET STREQUAL "esp32p4")
list(APPEND PRIV_REQUIRES_LIST esp_driver_ppa esp_mm)
endif ()
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
tactility_add_module(TactilityC

View File

@ -86,6 +86,15 @@ uint16_t tt_hal_display_driver_get_pixel_height(DisplayDriverHandle handle);
*/
void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, int yStart, int xEnd, int yEnd, const void* pixelData);
/**
* Get direct pointers to the display's hardware frame buffer(s), if supported.
* Only available for panels with direct CPU-addressable frame buffers (e.g. MIPI-DSI/DPI).
* @param[in] handle the display driver handle
* @param[out] outBuffers receives up to 2 frame buffer pointers
* @return number of buffers written to outBuffers (0 if unsupported)
*/
uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]);
#ifdef __cplusplus
}
#endif

View File

@ -135,6 +135,10 @@ int __gtdf2(double a, double b);
// GCC integer/bitwise helpers (compiler-rt)
int __clzsi2(unsigned int x);
// GCC 64-bit integer arithmetic helpers (needed for 64-bit div on 32-bit RISC-V)
long long __divdi3(long long a, long long b);
unsigned long long __udivdi3(unsigned long long a, unsigned long long b);
} // extern "C"
const esp_elfsym gcc_soft_float_symbols[] = {
@ -261,6 +265,10 @@ const esp_elfsym gcc_soft_float_symbols[] = {
// GCC integer/bitwise helpers
ESP_ELFSYM_EXPORT(__clzsi2),
// GCC 64-bit integer arithmetic helpers
ESP_ELFSYM_EXPORT(__divdi3),
ESP_ELFSYM_EXPORT(__udivdi3),
ESP_ELFSYM_END
};

View File

@ -85,4 +85,9 @@ void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, i
wrapper->driver->drawBitmap(xStart, yStart, xEnd, yEnd, pixelData);
}
uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]) {
auto wrapper = static_cast<DriverWrapper*>(handle);
return wrapper->driver->getFrameBuffers(outBuffers);
}
}

View File

@ -62,9 +62,15 @@
#include <driver/i2s_std.h>
#include <driver/gpio.h>
#ifdef CONFIG_IDF_TARGET_ESP32P4
#include <driver/ppa.h>
#include <esp_cache.h>
#endif
extern "C" {
extern double __floatsidf(int x);
extern void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression);
const esp_elfsym main_symbols[] {
// stdlib.h
@ -77,11 +83,13 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(rand_r),
ESP_ELFSYM_EXPORT(atoi),
ESP_ELFSYM_EXPORT(atol),
ESP_ELFSYM_EXPORT(system),
// esp random
ESP_ELFSYM_EXPORT(esp_random),
ESP_ELFSYM_EXPORT(esp_fill_random),
// esp other
ESP_ELFSYM_EXPORT(__floatsidf),
ESP_ELFSYM_EXPORT(_esp_error_check_failed),
// unistd.h
ESP_ELFSYM_EXPORT(usleep),
ESP_ELFSYM_EXPORT(sleep),
@ -205,6 +213,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(fwrite),
ESP_ELFSYM_EXPORT(getc),
ESP_ELFSYM_EXPORT(putc),
ESP_ELFSYM_EXPORT(putchar),
ESP_ELFSYM_EXPORT(puts),
ESP_ELFSYM_EXPORT(printf),
ESP_ELFSYM_EXPORT(sscanf),
@ -212,6 +221,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(sprintf),
ESP_ELFSYM_EXPORT(vsprintf),
ESP_ELFSYM_EXPORT(vsnprintf),
ESP_ELFSYM_EXPORT(vfprintf),
// cstring
ESP_ELFSYM_EXPORT(strlen),
ESP_ELFSYM_EXPORT(strcmp),
@ -236,6 +246,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(memcmp),
ESP_ELFSYM_EXPORT(memchr),
ESP_ELFSYM_EXPORT(memmove),
ESP_ELFSYM_EXPORT(strdup),
// ctype
ESP_ELFSYM_EXPORT(isalnum),
@ -296,6 +307,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(tt_hal_display_driver_lock),
ESP_ELFSYM_EXPORT(tt_hal_display_driver_unlock),
ESP_ELFSYM_EXPORT(tt_hal_display_driver_supported),
ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_frame_buffers),
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_supported),
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_alloc),
ESP_ELFSYM_EXPORT(tt_hal_touch_driver_free),
@ -363,6 +375,8 @@ const esp_elfsym main_symbols[] {
// stdio.h
ESP_ELFSYM_EXPORT(rename),
ESP_ELFSYM_EXPORT(rewind),
ESP_ELFSYM_EXPORT(remove),
// dirent.h
ESP_ELFSYM_EXPORT(opendir),
ESP_ELFSYM_EXPORT(closedir),
@ -439,6 +453,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size),
ESP_ELFSYM_EXPORT(heap_caps_get_free_size),
ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block),
ESP_ELFSYM_EXPORT(heap_caps_aligned_alloc),
ESP_ELFSYM_EXPORT(heap_caps_malloc),
ESP_ELFSYM_EXPORT(heap_caps_calloc),
ESP_ELFSYM_EXPORT(heap_caps_free),
@ -449,6 +464,14 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(esp_timer_start_periodic),
ESP_ELFSYM_EXPORT(esp_timer_start_once),
ESP_ELFSYM_EXPORT(esp_timer_get_time),
#ifdef CONFIG_IDF_TARGET_ESP32P4
// driver/ppa.h
ESP_ELFSYM_EXPORT(ppa_register_client),
ESP_ELFSYM_EXPORT(ppa_unregister_client),
ESP_ELFSYM_EXPORT(ppa_do_scale_rotate_mirror),
// esp_cache.h
ESP_ELFSYM_EXPORT(esp_cache_msync),
#endif
// delimiter
ESP_ELFSYM_END
};

View File

@ -12,6 +12,7 @@
#include <tactility/drivers/usb_host_midi.h>
#include <tactility/drivers/usb_host_msc.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/grove.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/drivers/i2s_controller.h>
#include <tactility/drivers/root.h>
@ -85,6 +86,10 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(gpio_controller_init_descriptors),
DEFINE_MODULE_SYMBOL(gpio_controller_deinit_descriptors),
DEFINE_MODULE_SYMBOL(GPIO_CONTROLLER_TYPE),
// drivers/grove
DEFINE_MODULE_SYMBOL(grove_set_mode),
DEFINE_MODULE_SYMBOL(grove_get_mode),
DEFINE_MODULE_SYMBOL(GROVE_TYPE),
// drivers/i2c_controller
DEFINE_MODULE_SYMBOL(i2c_controller_read),
DEFINE_MODULE_SYMBOL(i2c_controller_write),