Compare commits

...

2 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
29e80cfd65
T-Deck Max & Pro support updates (#582)
- Created kernel driver for T-Deck Max & Pro display (needs work, doesn't refresh reliably on T-Deck Pro)
- Created T-Deck Pro device implementation (incubating)
2026-07-24 00:17:48 +02:00
Ken Van Hoeylandt
429125734a
Add drivers to module and make start/stop optional (#580) 2026-07-23 23:28:09 +02:00
157 changed files with 1692 additions and 3359 deletions

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module btt_panda_touch_module = { Module btt_panda_touch_module = {
.name = "btt-panda-touch", .name = "btt-panda-touch"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_2432s024c_module = { Module cyd_2432s024c_module = {
.name = "cyd-2432s024c", .name = "cyd-2432s024c",
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s024r_module = { Module cyd_2432s024r_module = {
.name = "cyd-2432s024r", .name = "cyd-2432s024r"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s028r_module = { Module cyd_2432s028r_module = {
.name = "cyd-2432s028r", .name = "cyd-2432s028r"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
struct Module cyd_2432s028rv3_module = { Module cyd_2432s028rv3_module = {
.name = "cyd-2432s028rv3", .name = "cyd-2432s028rv3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_2432s032c_module = { Module cyd_2432s032c_module = {
.name = "cyd-2432s032c", .name = "cyd-2432s032c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_3248s035c_module = { Module cyd_3248s035c_module = {
.name = "cyd-3248s035c", .name = "cyd-3248s035c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_4848s040c_module = { Module cyd_4848s040c_module = {
.name = "cyd-4848s040c", .name = "cyd-4848s040c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_8048s043c_module = { Module cyd_8048s043c_module = {
.name = "cyd-8048s043c", .name = "cyd-8048s043c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_e32r28t_module = { Module cyd_e32r28t_module = {
.name = "cyd-e32r28t", .name = "cyd-e32r28t"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module cyd_e32r32p_module = { Module cyd_e32r32p_module = {
.name = "cyd-e32r32p", .name = "cyd-e32r32p"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,13 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_28_module = { Module elecrow_crowpanel_advance_28_module = {
.name = "elecrow-crowpanel-advance-28", .name = "elecrow-crowpanel-advance-28"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_35_module = { Module elecrow_crowpanel_advance_35_module = {
.name = "elecrow-crowpanel-advance-35", .name = "elecrow-crowpanel-advance-35"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_advance_50_module = { Module elecrow_crowpanel_advance_50_module = {
.name = "elecrow-crowpanel-advance-50", .name = "elecrow-crowpanel-advance-50"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_28_module = { Module elecrow_crowpanel_basic_28_module = {
.name = "elecrow-crowpanel-basic-28", .name = "elecrow-crowpanel-basic-28"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_35_module = { Module elecrow_crowpanel_basic_35_module = {
.name = "elecrow-crowpanel-basic-35", .name = "elecrow-crowpanel-basic-35"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module elecrow_crowpanel_basic_50_module = { Module elecrow_crowpanel_basic_50_module = {
.name = "elecrow-crowpanel-basic-50", .name = "elecrow-crowpanel-basic-50"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module es3c28p_module = { Module es3c28p_module = {
.name = "es3c28p", .name = "es3c28p"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32_module = { Module generic_esp32_module = {
.name = "generic-esp32", .name = "generic-esp32"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32c6_module = { Module generic_esp32c6_module = {
.name = "generic-esp32c6", .name = "generic-esp32c6"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32p4_module = { Module generic_esp32p4_module = {
.name = "generic-esp32p4", .name = "generic-esp32p4"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module generic_esp32s3_module = { Module generic_esp32s3_module = {
.name = "generic-esp32s3", .name = "generic-esp32s3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc1060p470ciwy_module = { Module guition_jc1060p470ciwy_module = {
.name = "guition-jc1060p470ciwy", .name = "guition-jc1060p470ciwy"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc2432w328c_module = { Module guition_jc2432w328c_module = {
.name = "guition-jc2432w328c", .name = "guition-jc2432w328c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module guition_jc8048w550c_module = { Module guition_jc8048w550c_module = {
.name = "guition-jc8048w550c", .name = "guition-jc8048w550c"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module heltec_wifi_lora_32_v3_module = { Module heltec_wifi_lora_32_v3_module = {
.name = "heltec-wifi-lora-32-v3", .name = "heltec-wifi-lora-32-v3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source" INCLUDE_DIRS "source"
REQUIRES Tactility GDEQ031T10 driver REQUIRES TactilityKernel
) )

View File

@ -1,6 +1,6 @@
general.vendor=LilyGO general.vendor=LilyGO
general.name=T-Deck Max general.name=T-Deck Max
# SD card support is not yet working; remove once it is functional. # SD card support is not yet working, display driver is untested since refactoring; remove once it is functional.
general.incubating=true general.incubating=true
apps.launcherAppId=Launcher apps.launcherAppId=Launcher
@ -18,14 +18,17 @@ hardware.bluetooth=true
# Internal until the SD card is verified working on this board; switch to SD then. # Internal until the SD card is verified working on this board; switch to SD then.
storage.userDataLocation=Internal storage.userDataLocation=Internal
dependencies.useDeprecatedHal=false
display.size=3.1" display.size=3.1"
display.shape=rectangle display.shape=rectangle
display.dpi=128 display.dpi=128
# The GDEQ031T10 e-paper is monochrome, but its driver renders via lvgl.colorDepth=8
# LV_COLOR_FORMAT_I1 on its own display. The global LVGL color depth must stay
# at 16: esp_lvgl_port refuses to compile with LV_COLOR_DEPTH_1 set.
lvgl.colorDepth=16
# Monochrome theme: the default colour theme renders accent-coloured UI that # Monochrome theme: the default colour theme renders accent-coloured UI that
# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO). # thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO).
lvgl.theme=Mono lvgl.theme=Mono
cdn.warningMessage=Display not reliably working. Use at your own risk.
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0

View File

@ -2,6 +2,7 @@ dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/xl9555-module - Drivers/xl9555-module
- Drivers/cst66xx-module - Drivers/cst66xx-module
- Drivers/gdeq031t10-module
- Drivers/tca8418-module - Drivers/tca8418-module
- Drivers/sy6970-module - Drivers/sy6970-module
- Drivers/bq27220-module - Drivers/bq27220-module

View File

@ -11,6 +11,7 @@
#include <tactility/bindings/pwm_backlight.h> #include <tactility/bindings/pwm_backlight.h>
#include <bindings/xl9555.h> #include <bindings/xl9555.h>
#include <bindings/cst66xx.h> #include <bindings/cst66xx.h>
#include <bindings/gdeq031t10.h>
#include <bindings/tca8418.h> #include <bindings/tca8418.h>
#include <bindings/sy6970.h> #include <bindings/sy6970.h>
#include <bindings/bq27220.h> #include <bindings/bq27220.h>
@ -143,6 +144,17 @@
// exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it. // exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it.
max-transfer-size = <65536>; max-transfer-size = <65536>;
display@0 {
compatible = "gooddisplay,gdeq031t10";
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 9 GPIO_FLAG_NONE>;
pin-busy = <&gpio0 37 GPIO_FLAG_NONE>;
clock-speed-hz = <10000000>;
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
// full-screen flash they cause is as short as possible.
refresh-mode = <GDEQ031T10_REFRESH_FAST>;
};
sdcard@1 { sdcard@1 {
compatible = "espressif,esp32-sdspi"; compatible = "espressif,esp32-sdspi";
status = "disabled"; status = "disabled";

View File

@ -1,35 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/esp32_spi.h>
using namespace tt::hal;
static bool initBoot() {
// The LoRa and SD chip-selects share the EPD's SPI bus but aren't wired up
// yet. spi0's start() already drives every cs-gpio high during kernel init;
// re-assert deselection before the EPD driver first transacts, as a cheap
// guard against either chip latching stray EPD command bytes (same pattern
// as the esp32_sdspi mount path).
auto* spi0 = device_find_by_name("spi0");
check(spi0 != nullptr);
esp32_spi_deselect_all_cs(spi0);
return true;
}
static DeviceVector createDevices() {
// Touch, keyboard and battery/charging are kernel drivers (cst66xx, tca8418, sy6970,
// bq27220 - see the .dts), started independently of this HAL layer. Only the EPD
// display remains on the deprecated HAL.
return DeviceVector {
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,29 +0,0 @@
#include "Display.h"
#include <Gdeq031t10Display.h>
// Pins from Xinyuan-LilyGO/T-Deck-MAX's lib/TDeckMaxBoard/src/TDeckMaxBoard.h and docs/pinmap.md.
constexpr auto EPD_SPI_HOST = SPI2_HOST;
constexpr auto EPD_PIN_CS = GPIO_NUM_34;
constexpr auto EPD_PIN_DC = GPIO_NUM_35;
constexpr auto EPD_PIN_RST = GPIO_NUM_9;
constexpr auto EPD_PIN_BUSY = GPIO_NUM_37;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
// Touch is a kernel driver (hynitron,cst66xx - see the .dts), discovered and fed to LVGL
// independently of this display, so no touch device is passed into the configuration here.
auto configuration = std::make_unique<Gdeq031t10Display::Configuration>(
EPD_SPI_HOST,
EPD_PIN_CS,
EPD_PIN_DC,
EPD_PIN_RST,
EPD_PIN_BUSY,
nullptr,
10'000'000,
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
// full-screen flash they cause is as short as possible.
Gdeq031t10Display::RefreshMode::Fast
);
return std::make_shared<Gdeq031t10Display>(std::move(configuration));
}

View File

@ -1,5 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdeck_max_module = {
// Empty for now .name = "lilygo-tdeck-max"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_tdeck_max_module = {
.name = "lilygo-tdeck-max",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.8" display.size=2.8"

View File

@ -19,9 +19,6 @@
constexpr auto* TAG = "tdeck-plus"; constexpr auto* TAG = "tdeck-plus";
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
void subscribe_events() { void subscribe_events() {

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES TactilityKernel
)

View File

@ -0,0 +1,33 @@
general.vendor=LilyGO
general.name=T-Deck Pro
# The display driver is not reliable yet
general.incubating=true
apps.launcherAppId=Launcher
hardware.target=ESP32S3
hardware.flashSize=16MB
hardware.flashMode=DIO
hardware.spiRam=true
hardware.spiRamMode=AUTO
hardware.spiRamSpeed=80M
hardware.tinyUsb=true
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
storage.userDataLocation=SD
dependencies.useDeprecatedHal=false
display.size=3.1"
display.shape=rectangle
display.dpi=128
lvgl.colorDepth=8
# Monochrome theme: the default colour theme renders accent-coloured UI that
# thresholds to invisible on a 1bpp panel (enables CONFIG_LV_USE_THEME_MONO).
lvgl.theme=Mono
cdn.warningMessage=Only for hardware revision 1.0! Display not reliably working. Use at your own risk.
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0

View File

@ -0,0 +1,8 @@
dependencies:
- Platforms/platform-esp32
- Drivers/cst328-module
- Drivers/gdeq031t10-module
- Drivers/tca8418-module
- Drivers/sy6970-module
- Drivers/bq25896-module
dts: lilygo,tdeck-pro.dts

View File

@ -0,0 +1,138 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/cst328.h>
#include <bindings/gdeq031t10.h>
#include <bindings/tca8418.h>
#include <bindings/bq25896.h>
// Reference for V1.0 (not V1.1!): https://github.com/Xinyuan-LilyGO/T-Deck-Pro/tree/HD-V1-250326
// lib/TDeckMaxBoard/src/TDeckMaxBoard.h, docs/pinmap.md
/ {
compatible = "root";
model = "LilyGO T-Deck Pro";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
i2c0 {
compatible = "espressif,esp32-i2c-master";
port = <I2C_NUM_0>;
clock-frequency = <100000>;
pin-sda = <&gpio0 13 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 14 GPIO_FLAG_NONE>;
touch {
compatible = "hynitron,cst328";
reg = <0x1A>;
pin-reset = <&gpio0 45 GPIO_FLAG_NONE>;
};
// TCA8418 4x10 matrix scanner. Keymaps are written in the vendor's row/column
// orientation (Xinyuan-LilyGO/T-Deck-MAX examples/factory/peri_keypad.cpp); the
// TCA8418 columns are wired in reverse of that order, hence reverse-columns.
// ALT (shift/uppercase) is row 2 col 0; SYM (symbol layer) is row 3 col 8.
keyboard {
compatible = "ti,tca8418";
reg = <0x34>;
rows = <4>;
columns = <10>;
reverse-columns;
keymap-lc = [
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
97 115 100 102 103 104 106 107 108 8 // a s d f g h j k l BACKSPACE
0 122 120 99 118 98 110 109 36 10 // z x c v b n m $ ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
keymap-uc = [
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
65 83 68 70 71 72 74 75 76 8 // A S D F G H J K L BACKSPACE
0 90 88 67 86 66 78 77 36 10 // Z X C V B N M $ ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
keymap-sy = [
49 50 51 52 53 54 55 56 57 48 // 1 2 3 4 5 6 7 8 9 0
64 35 43 45 42 47 40 41 95 8 // @ # + - * / ( ) _ BACKSPACE
0 33 63 59 58 39 34 44 46 10 // ! ? ; : ' " , . ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
shift-row = <2>;
shift-col = <0>;
sym-row = <3>;
sym-col = <8>;
};
bq27220 {
compatible = "ti,bq25896";
reg = <0x6B>;
};
};
// Keyboard backlight (LED_PWM), GPIO42. The keypress-triggered flash/decay effect
// from the pre-migration driver isn't reproduced here - kernel drivers don't have a
// home for that kind of UI policy (same reasoning as the touch driver's dropped bezel
// keys). Off by default; app code can drive brightness via the generic pwm-backlight
// API if desired.
keyboard_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 42 GPIO_FLAG_NONE>;
period-ns = <33333>;
ledc-timer = <0>;
ledc-channel = <0>;
};
keyboard_backlight {
compatible = "pwm-backlight";
status = "disabled";
pwm = <&keyboard_backlight_pwm>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 34 GPIO_FLAG_NONE>, // 0: EPD display
<&gpio0 48 GPIO_FLAG_NONE>, // 1: SD card
<&gpio0 3 GPIO_FLAG_NONE>; // 2: LoRa radio (SX1262, not wired up yet)
pin-mosi = <&gpio0 33 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 47 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
// The EPD pushes its whole 9600-byte framebuffer in one SPI write, which
// exceeds the default ~4 KB transfer limit. Raise the bus limit to fit it.
max-transfer-size = <65536>;
display@0 {
compatible = "gooddisplay,gdeq031t10";
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
pin-busy = <&gpio0 37 GPIO_FLAG_NONE>;
clock-speed-hz = <10000000>;
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
// full-screen flash they cause is as short as possible.
refresh-mode = <GDEQ031T10_REFRESH_FAST>;
};
sdcard@1 {
compatible = "espressif,esp32-sdspi";
status = "disabled";
frequency-khz = <20000>;
};
};
};

View File

@ -0,0 +1,9 @@
#include <tactility/module.h>
extern "C" {
struct Module lilygo_tdeck_pro_module = {
.name = "lilygo-tdeck-pro"
};
}

View File

@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.8" display.size=2.8"

View File

@ -18,9 +18,6 @@
constexpr auto* TAG = "tdeck"; constexpr auto* TAG = "tdeck";
// Legacy placeholder (required until legacy HAL is cleaned up everywhere)
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
void subscribe_events() { void subscribe_events() {
@ -63,9 +60,7 @@ static error_t stop() {
Module lilygo_tdeck_module = { Module lilygo_tdeck_module = {
.name = "lilygo-tdeck", .name = "lilygo-tdeck",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -30,16 +30,17 @@ static error_t start() {
} }
static error_t stop() { static error_t stop() {
// Empty for now if (gpio_set_level(POWER_ON_PIN, 0) != ESP_OK) {
return ERROR_RESOURCE;
}
return ERROR_NONE; return ERROR_NONE;
} }
struct Module lilygo_tdisplay_s3_module = { Module lilygo_tdisplay_s3_module = {
.name = "lilygo-tdisplay-s3", .name = "lilygo-tdisplay-s3",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdisplay_module = {
// Empty for now .name = "lilygo-tdisplay"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_tdisplay_module = {
.name = "lilygo-tdisplay",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_tdongle_s3_module = {
// Empty for now .name = "lilygo-tdongle-s3"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_tdongle_s3_module = {
.name = "lilygo-tdongle-s3",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module lilygo_thmi_module = {
// Empty for now .name = "lilygo-thmi"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_thmi_module = {
.name = "lilygo-thmi",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -13,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=40M hardware.esptoolFlashFreq=40M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.33" display.size=2.33"

View File

@ -15,18 +15,15 @@
constexpr auto* TAG = "T-Lora Pager"; constexpr auto* TAG = "T-Lora Pager";
// Legacy placeholder (required until legacy HAL is cleaned up everywhere). All board
// peripherals are kernel drivers now (see the .dts): display (st7796), battery (bq27220),
// charger power-off (bq25896), haptics (drv2605), keyboard (tca8418) and the encoder wheel
// (lilygo,tpager-encoder, bound to LVGL below).
extern const tt::hal::Configuration hardwareConfiguration = {};
extern "C" { extern "C" {
static void subscribe_events() { tt::kernel::SystemEventSubscription event_subscription;
static error_t start() {
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) { event_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) {
// The kernel tpager_encoder device is already started by kernel_init(); this just // The kernel tpager_encoder device is already started by kernel_init(); this just
// registers it as an LVGL input device, which requires LVGL to be up first. // registers it as an LVGL input device, which requires LVGL to be up first.
lvgl_lock(); lvgl_lock();
@ -50,23 +47,18 @@ static void subscribe_events() {
} }
} }
}); });
}
static error_t start() {
subscribe_events();
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
tt::kernel::unsubscribeSystemEvent(event_subscription);
return ERROR_NONE; return ERROR_NONE;
} }
struct Module lilygo_tlora_pager_module = { Module lilygo_tlora_pager_module = {
.name = "lilygo-tlora-pager", .name = "lilygo-tlora-pager",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module m5stack_cardputer_adv_module = {
// Empty for now .name = "m5stack-cardputer-adv"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module m5stack_cardputer_adv_module = {
.name = "m5stack-cardputer-adv",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
Module m5stack_cardputer_module = { Module m5stack_cardputer_module = {
.name = "m5stack-cardputer", .name = "m5stack-cardputer"
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,4 +1,3 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
@ -7,11 +6,7 @@ extern "C" {
// and axp192-module's start()), replacing what used to be done by hand here via a // and axp192-module's start()), replacing what used to be done by hand here via a
// DEVICE_EVENT_STARTED device_listener. // DEVICE_EVENT_STARTED device_listener.
Module m5stack_core2_module = { Module m5stack_core2_module = {
.name = "m5stack-core2", .name = "m5stack-core2"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,16 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
#include <cstring>
extern "C" { extern "C" {
struct Module m5stack_cores3_module = { Module m5stack_cores3_module = {
.name = "m5stack-cores3", .name = "m5stack-cores3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,6 +1,4 @@
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
@ -9,26 +7,16 @@ extern Driver papers3_power_driver;
extern Driver papers3_power_supply_driver; extern Driver papers3_power_supply_driver;
extern Driver papers3_display_driver; extern Driver papers3_display_driver;
static error_t start() { static Driver* const papers3_drivers[] = {
check(driver_construct_add(&papers3_power_supply_driver) == ERROR_NONE); &papers3_display_driver,
check(driver_construct_add(&papers3_power_driver) == ERROR_NONE); &papers3_power_driver,
check(driver_construct_add(&papers3_display_driver) == ERROR_NONE); &papers3_power_supply_driver,
return ERROR_NONE; nullptr
} };
static error_t stop() {
check(driver_remove_destruct(&papers3_display_driver) == ERROR_NONE);
check(driver_remove_destruct(&papers3_power_driver) == ERROR_NONE);
check(driver_remove_destruct(&papers3_power_supply_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module m5stack_papers3_module = { Module m5stack_papers3_module = {
.name = "m5stack-papers3", .name = "m5stack-papers3",
.start = start, .drivers = papers3_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -56,9 +56,7 @@ static error_t stop() {
Module m5stack_stackchan_module = { Module m5stack_stackchan_module = {
.name = "m5stack-stackchan", .name = "m5stack-stackchan",
.start = start, .start = start,
.stop = stop, .stop = stop
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -7,7 +7,7 @@ constexpr auto* TAG = "StickCPlus2";
extern "C" { extern "C" {
static bool init_power() { static error_t init_power() {
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity. // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity.
// Setting output to high adds a bias of 3.3 V and suppresses over-voltage: // Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT);
@ -17,25 +17,12 @@ static bool init_power() {
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_4, 1); gpio_set_level(GPIO_NUM_4, 1);
return true;
}
static error_t start() {
init_power();
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
Module m5stack_stickc_plus2_module = { Module m5stack_stickc_plus2_module = {
.name = "m5stack-stickc-plus2", .name = "m5stack-stickc-plus2",
.start = start, .start = init_power
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module m5stack_sticks3_module = { Module m5stack_sticks3_module = {
.name = "m5stack-sticks3", .name = "m5stack-sticks3"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -16,7 +16,7 @@
#include "devices/tab5_power_control.h" #include "devices/tab5_power_control.h"
#include "devices/tab_5_camera.h" #include "devices/tab_5_camera.h"
#define TAG "Tab5" constexpr auto* TAG = "Tab5";
// PI4IOE5V6408-0 (0x43) bit 1 // PI4IOE5V6408-0 (0x43) bit 1
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1; constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
@ -65,44 +65,31 @@ static void on_io_expander0_started(Device* device, DeviceEvent event, void* con
extern "C" { extern "C" {
static error_t start() { static error_t start() {
/* Crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&tab5_keyboard_driver) == ERROR_NONE);
check(driver_construct_add(&tab5_power_control_driver) == ERROR_NONE);
tab5_detect_start(); tab5_detect_start();
tab5_camera_init(); tab5_camera_init();
device_listener_add(on_io_expander0_started, nullptr); device_listener_add(on_io_expander0_started, nullptr);
tab5_headphone_detect_start(); tab5_headphone_detect_start();
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
tab5_headphone_detect_stop(); tab5_headphone_detect_stop();
device_listener_remove(on_io_expander0_started); device_listener_remove(on_io_expander0_started);
tab5_detect_stop(); tab5_detect_stop();
/* Crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&tab5_keyboard_driver) == ERROR_NONE);
check(driver_remove_destruct(&tab5_power_control_driver) == ERROR_NONE);
return ERROR_NONE; return ERROR_NONE;
} }
static Driver* const tab5_drivers[] = {
&tab5_keyboard_driver,
&tab5_power_control_driver,
nullptr
};
Module m5stack_tab5_module = { Module m5stack_tab5_module = {
.name = "m5stack-tab5", .name = "m5stack-tab5",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = nullptr, .drivers = tab5_drivers
.internal = nullptr
}; };
} }

View File

@ -38,29 +38,26 @@ static void on_device_event(Device* device, DeviceEvent event, void* context) {
} }
static error_t start() { static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&unphone_power_switch_driver) == ERROR_NONE);
check(driver_construct_add(&unphone_nav_buttons_driver) == ERROR_NONE);
device_listener_add(on_device_event, nullptr); device_listener_add(on_device_event, nullptr);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
device_listener_remove(&on_device_event); device_listener_remove(&on_device_event);
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&unphone_nav_buttons_driver) == ERROR_NONE);
check(driver_remove_destruct(&unphone_power_switch_driver) == ERROR_NONE);
return ERROR_NONE; return ERROR_NONE;
} }
struct Module unphone_module = { static Driver* const unphone_drivers[] = {
&unphone_nav_buttons_driver,
&unphone_power_switch_driver,
nullptr
};
Module unphone_module = {
.name = "unphone", .name = "unphone",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = nullptr, .drivers = unphone_drivers
.internal = nullptr
}; };
} }

View File

@ -2,22 +2,8 @@
extern "C" { extern "C" {
static error_t start() { Module waveshare_esp32_s3_geek_module = {
// Empty for now .name = "waveshare-esp32-s3-geek"
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module waveshare_esp32_s3_geek_module = {
.name = "waveshare-esp32-s3-geek",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_lcd_13_module = { Module waveshare_s3_lcd_13_module = {
.name = "waveshare-s3-lcd-13", .name = "waveshare-s3-lcd-13"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_128_module = { Module waveshare_s3_touch_lcd_128_module = {
.name = "waveshare-s3-touch-lcd-128", .name = "waveshare-s3-touch-lcd-128"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_147_module = { Module waveshare_s3_touch_lcd_147_module = {
.name = "waveshare-s3-touch-lcd-147", .name = "waveshare-s3-touch-lcd-147"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module waveshare_s3_touch_lcd_43_module = { Module waveshare_s3_touch_lcd_43_module = {
.name = "waveshare-s3-touch-lcd-43", .name = "waveshare-s3-touch-lcd-43"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,14 +1,9 @@
#include <tactility/error.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
Module wireless_tag_wt32_sc01_plus_module = { Module wireless_tag_wt32_sc01_plus_module = {
.name = "wireless-tag-wt32-sc01-plus", .name = "wireless-tag-wt32-sc01-plus"
.start = [] -> error_t { return ERROR_NONE; },
.stop = [] -> error_t { return ERROR_NONE; },
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -13,6 +13,7 @@
## Higher Priority ## Higher Priority
- Drivers/audio-codec-module is not a module. Move it somewhere else. Or make it an actual module.
- LilyGO T-Dongle S3: 1 button control, stop auto-launching web server - LilyGO T-Dongle S3: 1 button control, stop auto-launching web server
- Core2: support power off via software - Core2: support power off via software
- Create `#define` for empty module (for modules that fully rely on device.properties and don't define drivers or have start/stop logic) - Create `#define` for empty module (for modules that fully rely on device.properties and don't define drivers or have start/stop logic)

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lcd esp_lcd_touch esp_lvgl_port
)

View File

@ -1,3 +0,0 @@
# EspLcdCompat
A set of helper classes to implement `esp_lcd` drivers in Tactility.

View File

@ -1,130 +0,0 @@
#include "EspLcdDisplay.h"
#include "EspLcdDisplayDriver.h"
#include <tactility/log.h>
#include <tactility/check.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <cassert>
#include <esp_lvgl_port_disp.h>
constexpr auto* TAG = "EspLcdDisplay";
EspLcdDisplay::~EspLcdDisplay() {
check(
displayDriver == nullptr || displayDriver.use_count() < 2, // 1 reference is held by this class
"DisplayDriver is still in use. This will cause memory access violations."
);
}
bool EspLcdDisplay::start() {
if (!createIoHandle(ioHandle)) {
LOG_E(TAG, "Failed to create IO handle");
return false;
}
if (!createPanelHandle(ioHandle, panelHandle)) {
LOG_E(TAG, "Failed to create panel handle");
esp_lcd_panel_io_del(ioHandle);
return false;
}
return true;
}
bool EspLcdDisplay::stop() {
if (lvglDisplay != nullptr) {
stopLvgl();
lvglDisplay = nullptr;
}
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
return false;
}
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
return false;
}
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
return true;
}
bool EspLcdDisplay::startLvgl() {
assert(lvglDisplay == nullptr);
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
auto lvgl_port_config = getLvglPortDisplayConfig(ioHandle, panelHandle);
if (isRgbPanel()) {
auto rgb_config = getLvglPortDisplayRgbConfig(ioHandle, panelHandle);
lvglDisplay = lvgl_port_add_disp_rgb(&lvgl_port_config , &rgb_config);
} else {
lvglDisplay = lvgl_port_add_disp(&lvgl_port_config );
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr && touch_device->supportsLvgl()) {
touch_device->startLvgl(lvglDisplay);
}
return lvglDisplay != nullptr;
}
bool EspLcdDisplay::stopLvgl() {
if (lvglDisplay == nullptr) {
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
lvgl_port_remove_disp(lvglDisplay);
lvglDisplay = nullptr;
return true;
}
std::shared_ptr<tt::hal::display::DisplayDriver> EspLcdDisplay::getDisplayDriver() {
assert(lvglDisplay == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
if (displayDriver == nullptr) {
auto lvgl_port_config = getLvglPortDisplayConfig(ioHandle, panelHandle);
tt::hal::display::ColorFormat color_format;
if (lvgl_port_config.color_format == LV_COLOR_FORMAT_I1) {
color_format = tt::hal::display::ColorFormat::Monochrome;
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB565) {
if (rgbElementOrder == LCD_RGB_ELEMENT_ORDER_RGB) {
if (lvgl_port_config.flags.swap_bytes) {
color_format = tt::hal::display::ColorFormat::RGB565Swapped;
} else {
color_format = tt::hal::display::ColorFormat::RGB565;
}
} else {
if (lvgl_port_config.flags.swap_bytes) {
color_format = tt::hal::display::ColorFormat::BGR565Swapped;
} else {
color_format = tt::hal::display::ColorFormat::BGR565;
}
}
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB888) {
color_format = tt::hal::display::ColorFormat::RGB888;
} else {
check(false, "unsupported driver");
}
displayDriver = std::make_shared<EspLcdDisplayDriver>(
panelHandle,
lvgl_port_config.hres,
lvgl_port_config.vres,
color_format
);
}
return displayDriver;
}

View File

@ -1,64 +0,0 @@
#pragma once
#include <tactility/check.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <esp_lcd_types.h>
#include <esp_lvgl_port_disp.h>
/** @deprecated use EspLcdDisplayV2 */
class EspLcdDisplay : public tt::hal::display::DisplayDevice {
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
lv_display_t* lvglDisplay = nullptr;
std::shared_ptr<tt::hal::display::DisplayDriver> displayDriver;
/** @warning This is never changed, so a driver that needs BGR is not supported */
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
protected:
// Used for sending commands such as setting curves
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) = 0;
virtual bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) = 0;
virtual lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) = 0;
virtual bool isRgbPanel() const { return false; }
virtual lvgl_port_display_rgb_cfg_t getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { check(false, "Not supported"); }
public:
EspLcdDisplay() = default;
~EspLcdDisplay() override;
bool start() final;
bool stop() final;
// region LVGL
bool supportsLvgl() const final { return true; }
bool startLvgl() final;
bool stopLvgl() final;
lv_display_t* getLvglDisplay() const final { return lvglDisplay; }
// endregion
// region DisplayDriver
bool supportsDisplayDriver() const override { return true; }
/** @return a NativeDisplay instance if this device supports it */
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() final;
// endregion
};

View File

@ -1,47 +0,0 @@
#pragma once
#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 {
esp_lcd_panel_handle_t panelHandle;
uint16_t hRes;
uint16_t vRes;
tt::hal::display::ColorFormat colorFormat;
public:
EspLcdDisplayDriver(
esp_lcd_panel_handle_t panelHandle,
uint16_t hRes,
uint16_t vRes,
tt::hal::display::ColorFormat colorFormat
) : panelHandle(panelHandle), hRes(hRes), vRes(vRes), colorFormat(colorFormat) {}
tt::hal::display::ColorFormat getColorFormat() const override {
return colorFormat;
}
bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) override {
bool result = esp_lcd_panel_draw_bitmap(panelHandle, xStart, yStart, xEnd, yEnd, pixelData) == ESP_OK;
return result;
}
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

@ -1,231 +0,0 @@
#include "EspLcdDisplayV2.h"
#include "EspLcdDisplayDriver.h"
#include <tactility/log.h>
#include <tactility/check.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <cassert>
#include <esp_lvgl_port_disp.h>
constexpr auto* TAG = "EspLcdDispV2";
inline unsigned int getBufferSize(const std::shared_ptr<EspLcdConfiguration>& configuration) {
if (configuration->bufferSize != DEFAULT_BUFFER_SIZE) {
return configuration->bufferSize;
} else {
return configuration->horizontalResolution * (configuration->verticalResolution / 10);
}
}
EspLcdDisplayV2::~EspLcdDisplayV2() {
check(
displayDriver == nullptr || displayDriver.use_count() < 2, // 1 reference is held by this class
"DisplayDriver is still in use. This will cause memory access violations."
);
}
bool EspLcdDisplayV2::applyConfiguration() const {
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
return false;
}
if (configuration->invertColor && esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to invert");
return false;
}
// Warning: it looks like LVGL rotation is broken when "gap" is set and the screen is moved to a non-default orientation
int gap_x = configuration->swapXY ? configuration->gapY : configuration->gapX;
int gap_y = configuration->swapXY ? configuration->gapX : configuration->gapY;
bool should_set_gap = gap_x != 0 || gap_y != 0;
if (should_set_gap && esp_lcd_panel_set_gap(panelHandle, gap_x, gap_y) != ESP_OK) {
LOG_E(TAG, "Failed to set panel gap");
return false;
}
if (configuration->swapXY && esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
LOG_E(TAG, "Failed to swap XY ");
return false;
}
bool should_set_mirror = configuration->mirrorX || configuration->mirrorY;
if (should_set_mirror && esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
if (configuration->invertColor && esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to set panel to invert");
return false;
}
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
LOG_E(TAG, "Failed to turn display on");
return false;
}
return true;
}
bool EspLcdDisplayV2::start() {
if (!createIoHandle(ioHandle)) {
LOG_E(TAG, "Failed to create IO handle");
return false;
}
esp_lcd_panel_dev_config_t panel_config = createPanelConfig(configuration, configuration->resetPin);
if (!createPanelHandle(ioHandle, panel_config, panelHandle)) {
LOG_E(TAG, "Failed to create panel handle");
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
return false;
}
if (!applyConfiguration()) {
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
return false;
}
return true;
}
bool EspLcdDisplayV2::stop() {
if (lvglDisplay != nullptr) {
stopLvgl();
lvglDisplay = nullptr;
}
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
return false;
}
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
return false;
}
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
return true;
}
bool EspLcdDisplayV2::startLvgl() {
assert(lvglDisplay == nullptr);
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
LOG_W(TAG, "DisplayDriver is still in use.");
}
auto lvgl_port_config = getLvglPortDisplayConfig(configuration, ioHandle, panelHandle);
if (useDsiPanel()) {
auto dsi_config = getLvglPortDisplayDsiConfig(ioHandle, panelHandle);
lvglDisplay = lvgl_port_add_disp_dsi(&lvgl_port_config, &dsi_config);
} else if (isRgbPanel()) {
auto rgb_config = getLvglPortDisplayRgbConfig(ioHandle, panelHandle);
lvglDisplay = lvgl_port_add_disp_rgb(&lvgl_port_config, &rgb_config);
} else {
lvglDisplay = lvgl_port_add_disp(&lvgl_port_config);
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr && touch_device->supportsLvgl()) {
touch_device->startLvgl(lvglDisplay);
}
return lvglDisplay != nullptr;
}
bool EspLcdDisplayV2::stopLvgl() {
if (lvglDisplay == nullptr) {
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
lvgl_port_remove_disp(lvglDisplay);
lvglDisplay = nullptr;
return true;
}
lvgl_port_display_cfg_t EspLcdDisplayV2::getLvglPortDisplayConfig(std::shared_ptr<EspLcdConfiguration> configuration, esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
return lvgl_port_display_cfg_t {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = getBufferSize(configuration),
.double_buffer = false,
.trans_size = 0,
.hres = configuration->horizontalResolution,
.vres = configuration->verticalResolution,
.monochrome = configuration->monochrome,
.rotation = {
.swap_xy = configuration->swapXY,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.color_format = configuration->lvglColorFormat,
.flags = {
.buff_dma = configuration->buffSpiram ? 0u : 1u,
.buff_spiram = configuration->buffSpiram ? 1u : 0u,
.sw_rotate = configuration->swRotate ? 1u : 0u,
.swap_bytes = configuration->lvglSwapBytes,
.full_refresh = 0,
.direct_mode = 0
}
};
}
std::shared_ptr<tt::hal::display::DisplayDriver> EspLcdDisplayV2::getDisplayDriver() {
assert(lvglDisplay == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
if (displayDriver == nullptr) {
auto lvgl_port_config = getLvglPortDisplayConfig(configuration, ioHandle, panelHandle);
auto panel_config = createPanelConfig(configuration, GPIO_NUM_NC);
tt::hal::display::ColorFormat color_format;
if (lvgl_port_config.color_format == LV_COLOR_FORMAT_I1) {
color_format = tt::hal::display::ColorFormat::Monochrome;
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB565) {
if (panel_config.rgb_ele_order == LCD_RGB_ELEMENT_ORDER_RGB) {
if (lvgl_port_config.flags.swap_bytes) {
color_format = tt::hal::display::ColorFormat::RGB565Swapped;
} else {
color_format = tt::hal::display::ColorFormat::RGB565;
}
} else {
if (lvgl_port_config.flags.swap_bytes) {
color_format = tt::hal::display::ColorFormat::BGR565Swapped;
} else {
color_format = tt::hal::display::ColorFormat::BGR565;
}
}
} else if (lvgl_port_config.color_format == LV_COLOR_FORMAT_RGB888) {
color_format = tt::hal::display::ColorFormat::RGB888;
} else {
check(false, "unsupported driver");
}
displayDriver = std::make_shared<EspLcdDisplayDriver>(
panelHandle,
lvgl_port_config.hres,
lvgl_port_config.vres,
color_format
);
}
return displayDriver;
}

View File

@ -1,114 +0,0 @@
#pragma once
#include <tactility/check.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <esp_lcd_panel_dev.h>
#include <esp_lcd_types.h>
#include <esp_lvgl_port_disp.h>
constexpr auto DEFAULT_BUFFER_SIZE = 0;
struct EspLcdConfiguration {
unsigned int horizontalResolution;
unsigned int verticalResolution;
int gapX;
int gapY;
bool monochrome;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size
bool swRotate = false; // Use LVGL software rotation instead of hardware swap_xy (required for MIPI-DSI panels that don't support swap_xy)
bool buffSpiram = false; // Allocate LVGL draw buffers from PSRAM instead of DMA-capable internal SRAM (required when sw_rotate needs a 3rd buffer that won't fit in internal SRAM)
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
gpio_num_t resetPin;
lv_color_format_t lvglColorFormat;
bool lvglSwapBytes;
lcd_rgb_element_order_t rgbElementOrder;
uint32_t bitsPerPixel;
};
class EspLcdDisplayV2 : public tt::hal::display::DisplayDevice {
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
esp_lcd_panel_handle_t _Nullable panelHandle = nullptr;
lv_display_t* _Nullable lvglDisplay = nullptr;
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable displayDriver;
std::shared_ptr<EspLcdConfiguration> configuration;
bool applyConfiguration() const;
lvgl_port_display_cfg_t getLvglPortDisplayConfig(std::shared_ptr<EspLcdConfiguration> configuration, esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle);
protected:
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) = 0;
virtual esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) = 0;
virtual bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) = 0;
virtual bool isRgbPanel() const { return false; }
virtual lvgl_port_display_rgb_cfg_t getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { check(false, "Not supported"); }
// Hook for MIPI-DSI DPI panels to let LVGL port use DSI-specific path
virtual bool useDsiPanel() const { return false; }
virtual lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) {
return lvgl_port_display_dsi_cfg_t{ .flags = { .avoid_tearing = 0 } };
}
// Used for sending commands such as setting curves
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
public:
explicit EspLcdDisplayV2(const std::shared_ptr<EspLcdConfiguration>& configuration) :
configuration(configuration)
{}
~EspLcdDisplayV2() override;
bool start() final;
bool stop() final;
// region LVGL
bool supportsLvgl() const final { return true; }
bool startLvgl() final;
bool stopLvgl() final;
lv_display_t* _Nullable getLvglDisplay() const final { return lvglDisplay; }
// endregion
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
// region Backlight
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration->backlightDutyFunction != nullptr) {
configuration->backlightDutyFunction(backlightDuty);
}
}
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
// endregion
// region DisplayDriver
bool supportsDisplayDriver() const override { return true; }
/** @return a NativeDisplay instance if this device supports it */
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() final;
// endregion
};

View File

@ -1,70 +0,0 @@
#include "EspLcdSpiDisplay.h"
#include <esp_lcd_panel_commands.h>
#include <tactility/log.h>
constexpr auto* TAG = "EspLcdSpiDisplay";
bool EspLcdSpiDisplay::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
LOG_I(TAG, "createIoHandle");
const esp_lcd_panel_io_spi_config_t panel_io_config = {
.cs_gpio_num = spiConfiguration->csPin,
.dc_gpio_num = spiConfiguration->dcPin,
.spi_mode = 0,
.pclk_hz = spiConfiguration->pixelClockFrequency,
.trans_queue_depth = spiConfiguration->transactionQueueDepth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = {
.dc_high_on_cmd = 0,
.dc_low_on_data = 0,
.dc_low_on_param = 0,
.octal_mode = 0,
.quad_mode = 0,
.sio_mode = 1,
.lsb_first = 0,
.cs_high_active = 0
}
};
if (esp_lcd_new_panel_io_spi(spiConfiguration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
return true;
}
void EspLcdSpiDisplay::setGammaCurve(uint8_t index) {
uint8_t gamma_curve;
switch (index) {
case 0:
gamma_curve = 0x01;
break;
case 1:
gamma_curve = 0x04;
break;
case 2:
gamma_curve = 0x02;
break;
case 3:
gamma_curve = 0x08;
break;
default:
return;
}
const uint8_t param[] = {
gamma_curve
};
auto io_handle = getIoHandle();
assert(io_handle != nullptr);
if (esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_GAMSET, param, 1) != ESP_OK) {
LOG_E(TAG, "Failed to set gamma");
}
}

View File

@ -1,49 +0,0 @@
#pragma once
#include "EspLcdDisplayV2.h"
#include <driver/spi_common.h>
#include <esp_lcd_io_spi.h>
#include <esp_lcd_types.h>
/**
* Adds IO implementations on top of EspLcdDisplayV2
* @warning This is an abstract class. You need to extend it to use it.
*/
class EspLcdSpiDisplay : public EspLcdDisplayV2 {
public:
struct SpiConfiguration {
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t dcPin;
unsigned int pixelClockFrequency = 80'000'000; // Hertz
size_t transactionQueueDepth = 10;
};
explicit EspLcdSpiDisplay(const std::shared_ptr<EspLcdConfiguration>& configuration, const std::shared_ptr<SpiConfiguration> spiConfiguration, int gammaCurveCount) :
EspLcdDisplayV2(configuration),
spiConfiguration(spiConfiguration),
gammaCurveCount(gammaCurveCount)
{}
private:
std::shared_ptr<SpiConfiguration> spiConfiguration;
int gammaCurveCount;
protected:
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
// region Gamma
void setGammaCurve(uint8_t index) override;
uint8_t getGammaCurveCount() const override { return gammaCurveCount; }
// endregion
};

View File

@ -1,93 +0,0 @@
#include "EspLcdTouch.h"
#include <EspLcdTouchDriver.h>
#include <tactility/log.h>
#include <esp_lvgl_port_touch.h>
constexpr auto* TAG = "EspLcdTouch";
bool EspLcdTouch::start() {
if (!createIoHandle(ioHandle) != ESP_OK) {
LOG_E(TAG, "Touch IO failed");
return false;
}
config = createEspLcdTouchConfig();
if (!createTouchHandle(ioHandle, config, touchHandle)) {
LOG_E(TAG, "Driver init failed");
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
return false;
}
return true;
}
bool EspLcdTouch::stop() {
if (lvglDevice != nullptr) {
stopLvgl();
}
if (ioHandle != nullptr) {
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
}
if (touchHandle != nullptr) {
esp_lcd_touch_del(touchHandle);
touchHandle = nullptr;
}
return true;
}
bool EspLcdTouch::startLvgl(lv_disp_t* display) {
if (lvglDevice != nullptr) {
return false;
}
if (touchDriver != nullptr && touchDriver.use_count() > 1) {
LOG_W(TAG, "TouchDriver is still in use.");
}
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = display,
.handle = touchHandle,
};
LOG_I(TAG, "Adding touch to LVGL");
lvglDevice = lvgl_port_add_touch(&touch_cfg);
if (lvglDevice == nullptr) {
LOG_E(TAG, "Adding touch failed");
return false;
}
return true;
}
bool EspLcdTouch::stopLvgl() {
if (lvglDevice == nullptr) {
return false;
}
lvgl_port_remove_touch(lvglDevice);
lvglDevice = nullptr;
return true;
}
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable EspLcdTouch::getTouchDriver() {
assert(lvglDevice == nullptr); // Still attached to LVGL context. Call stopLvgl() first.
if (touchHandle == nullptr) {
return nullptr;
}
if (touchDriver == nullptr) {
touchDriver = std::make_shared<EspLcdTouchDriver>(touchHandle);
}
return touchDriver;
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <esp_lcd_touch.h>
#include <esp_lcd_types.h>
#include <lvgl.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/hal/touch/TouchDriver.h>
class EspLcdTouch : public tt::hal::touch::TouchDevice {
esp_lcd_touch_config_t config;
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
esp_lcd_touch_handle_t _Nullable touchHandle = nullptr;
lv_indev_t* lvglDevice = nullptr;
std::shared_ptr<tt::hal::touch::TouchDriver> touchDriver;
protected:
esp_lcd_touch_handle_t _Nullable getTouchHandle() const { return touchHandle; }
virtual bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) = 0;
virtual bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& touchHandle) = 0;
virtual esp_lcd_touch_config_t createEspLcdTouchConfig() = 0;
public:
bool start() final;
bool stop() final;
bool supportsLvgl() const final { return true; }
bool startLvgl(lv_display_t* display) final;
bool stopLvgl() final;
lv_indev_t* getLvglIndev() final { return lvglDevice; }
bool supportsTouchDriver() override { return true; }
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable getTouchDriver() final;
};

View File

@ -1,13 +0,0 @@
#include "EspLcdTouchDriver.h"
#include <tactility/log.h>
constexpr auto* TAG = "EspLcdTouchDriver";
bool EspLcdTouchDriver::getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* _Nullable strength, uint8_t* pointCount, uint8_t maxPointCount) {
if (esp_lcd_touch_read_data(handle) != ESP_OK) {
LOG_E(TAG, "Read data failed");
return false;
}
return esp_lcd_touch_get_coordinates(handle, x, y, strength, pointCount, maxPointCount);
}

View File

@ -1,15 +0,0 @@
#pragma once
#include <esp_lcd_touch.h>
#include <Tactility/hal/touch/TouchDriver.h>
class EspLcdTouchDriver final : public tt::hal::touch::TouchDriver {
esp_lcd_touch_handle_t handle;
public:
EspLcdTouchDriver(esp_lcd_touch_handle_t handle) : handle(handle) {}
bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) override;
};

View File

@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver
)

View File

@ -1,35 +0,0 @@
# GDEQ031T10
Display driver for the GoodDisplay GDEQ031T10, a 3.1" 320x240 SPI e-paper panel
(UC8253-family controller) used by the LilyGO T-Deck Pro/Max.
The command sequence (panel setting, power on/off, fast/partial LUT timings, deep
sleep) is ported from the vendor reference driver in
[Xinyuan-LilyGO/T-Deck-MAX](https://github.com/Xinyuan-LilyGO/T-Deck-MAX)
(`examples/Elink_paper/GDEQ031T10_Arduino/Display_EPD_W21.cpp`).
Unlike `EPDiyDisplay` (which targets parallel-bus "EPD47"-style panels via the
`epdiy` library), this panel uses a simple 4-wire SPI interface (CS/DC/RST/BUSY +
SCK/MOSI), so it's driven directly with ESP-IDF's `spi_master` and `gpio` APIs
rather than `epdiy` or `esp_lcd_panel`.
Each LVGL flush is staged and handed off to a dedicated refresh task (the physical
refresh takes hundreds of ms to over a second, too long to run in LVGL's flush
callback without stalling input). The task diffs the new frame against a shadow of
what the panel currently holds and, where possible, performs a windowed partial
refresh of just the changed region instead of redrawing the whole panel. A full
refresh is still done on the first draw, on an explicit `requestFullRefresh()`,
when a change covers most of the screen, or periodically to clear the ghosting
that partial updates accumulate (gated by `MAX_PARTIAL_REFRESHES`, with a
localized ghost-clear preferred over a full flash when the accumulated changes are
confined to a small region). The panel's charge pump is powered off once the
refresh task's queue goes idle, not after every single refresh. Four refresh
modes are available, trading speed for quality:
- `Full` (~3s) - best quality, used by default
- `Fast` (~1.0s)
- `Slow` (~1.5s)
- `Partial` (~0.5s) - most ghosting
`requestFullRefresh()` forces the next flush to use `Full` mode, useful for
clearing up ghosting accumulated from a run of fast/partial refreshes.

View File

@ -1,638 +0,0 @@
#include "Gdeq031t10Display.h"
#include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <themes/mono/lv_theme_mono.h>
// Full lv_theme_t definition (opaque in public lvgl.h) — needed to extend the
// mono theme with a custom apply callback, per LVGL's theme-extension pattern.
#include <themes/lv_theme_private.h>
constexpr auto* TAG = "GDEQ031T10";
// UC8253-family commands, ported from Xinyuan-LilyGO/T-Deck-MAX's
// Display_EPD_W21.cpp reference driver.
namespace {
constexpr uint8_t CMD_PANEL_SETTING = 0x00;
constexpr uint8_t CMD_POWER_ON_OFF = 0x02; // shared opcode: power on when followed by 0x04, power off as standalone 0x02
constexpr uint8_t CMD_POWER_ON = 0x04;
constexpr uint8_t CMD_DEEP_SLEEP = 0x07;
constexpr uint8_t CMD_DATA_START_OLD = 0x10;
constexpr uint8_t CMD_DISPLAY_REFRESH = 0x12;
constexpr uint8_t CMD_DATA_START_NEW = 0x13;
constexpr uint8_t CMD_VCOM_DATA_INTERVAL = 0x50;
constexpr uint8_t CMD_PARTIAL_WINDOW = 0x90;
constexpr uint8_t CMD_PARTIAL_IN = 0x91;
constexpr uint8_t CMD_PARTIAL_OUT = 0x92;
constexpr uint8_t CMD_FAST_MODE_ENABLE = 0xE0;
constexpr uint8_t CMD_FAST_MODE_TIMING = 0xE5;
constexpr uint8_t DEEP_SLEEP_CHECK_CODE = 0xA5;
}
bool Gdeq031t10Display::writeCommand(uint8_t command) {
gpio_set_level(configuration->pinDc, 0);
spi_transaction_t transaction = {};
transaction.length = 8;
transaction.tx_buffer = &command;
if (spi_device_polling_transmit(spiDevice, &transaction) != ESP_OK) {
LOG_E(TAG, "SPI command transfer failed");
return false;
}
return true;
}
bool Gdeq031t10Display::writeData(const uint8_t* data, size_t length) {
gpio_set_level(configuration->pinDc, 1);
spi_transaction_t transaction = {};
transaction.length = length * 8;
transaction.tx_buffer = data;
if (spi_device_polling_transmit(spiDevice, &transaction) != ESP_OK) {
LOG_E(TAG, "SPI data transfer failed");
return false;
}
return true;
}
bool Gdeq031t10Display::waitWhileBusy() const {
// BUSY pin reads high when the controller is idle/ready.
constexpr TickType_t timeout = pdMS_TO_TICKS(5000);
const TickType_t start = xTaskGetTickCount();
while (gpio_get_level(configuration->pinBusy) != 1) {
if (xTaskGetTickCount() - start > timeout) {
LOG_E(TAG, "Timed out waiting for panel BUSY");
return false;
}
vTaskDelay(pdMS_TO_TICKS(2));
}
return true;
}
void Gdeq031t10Display::reset() const {
gpio_set_level(configuration->pinReset, 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(configuration->pinReset, 1);
vTaskDelay(pdMS_TO_TICKS(10));
}
bool Gdeq031t10Display::initFull() {
reset();
bool ok = writeCommand(CMD_PANEL_SETTING);
ok = ok && writeData(configuration->mirror180 ? 0x13 : 0x1F);
ok = ok && writeCommand(CMD_POWER_ON);
ok = ok && waitWhileBusy();
if (!ok) {
LOG_E(TAG, "Full init failed");
return false;
}
panelMode = RefreshMode::Full;
panelPowerOn = true;
return true;
}
bool Gdeq031t10Display::initFast() {
if (!initFull()) {
return false;
}
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
ok = ok && writeData(0x02);
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
ok = ok && writeData(0x5A); // ~1.0s
if (!ok) {
LOG_E(TAG, "Fast mode init failed");
return false;
}
panelMode = RefreshMode::Fast;
return true;
}
bool Gdeq031t10Display::initSlow() {
if (!initFull()) {
return false;
}
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
ok = ok && writeData(0x02);
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
ok = ok && writeData(0x6E); // ~1.5s
if (!ok) {
LOG_E(TAG, "Slow mode init failed");
return false;
}
panelMode = RefreshMode::Slow;
return true;
}
bool Gdeq031t10Display::initPartial() {
if (!initFull()) {
return false;
}
bool ok = writeCommand(CMD_FAST_MODE_ENABLE);
ok = ok && writeData(0x02);
ok = ok && writeCommand(CMD_FAST_MODE_TIMING);
ok = ok && writeData(0x79);
ok = ok && writeCommand(CMD_VCOM_DATA_INTERVAL);
ok = ok && writeData(0xD7);
if (!ok) {
LOG_E(TAG, "Partial mode init failed");
return false;
}
panelMode = RefreshMode::Partial;
return true;
}
bool Gdeq031t10Display::ensurePanelReady(RefreshMode mode) {
if (panelMode != mode) {
// A mode change needs the full init path: it starts with reset(), which
// restores register defaults (required when leaving partial mode, whose
// VCOM/data-interval setting would otherwise linger).
switch (mode) {
case RefreshMode::Full: return initFull();
case RefreshMode::Fast: return initFast();
case RefreshMode::Slow: return initSlow();
case RefreshMode::Partial: return initPartial();
}
return false;
} else if (!panelPowerOn) {
// Registers still hold the mode; only the charge pump was idled.
if (!writeCommand(CMD_POWER_ON) || !waitWhileBusy()) {
LOG_E(TAG, "Panel did not become ready after power-on");
return false;
}
panelPowerOn = true;
}
return true;
}
bool Gdeq031t10Display::powerOff() {
// Command the panel off regardless of whether BUSY confirms it: retrying
// forever here would just as likely hang, and a stuck-BUSY panel is
// already unusable either way.
bool ok = writeCommand(CMD_POWER_ON_OFF); // 0x02 standalone = power off
if (!ok || !waitWhileBusy()) {
LOG_E(TAG, "Panel did not confirm power-off");
ok = false;
}
panelPowerOn = false;
return ok;
}
void Gdeq031t10Display::queueRefresh() {
xSemaphoreTake(bufferMutex, portMAX_DELAY);
// renderFramebuffer always holds a complete frame (single buffer, full
// render mode), so the staged copy is self-contained.
std::memcpy(pendingFramebuffer.get(), renderFramebuffer.get() + LVGL_I1_PALETTE_SIZE, FRAMEBUFFER_SIZE);
framePending = true;
xSemaphoreGive(bufferMutex);
xTaskNotifyGive(refreshTask);
}
void Gdeq031t10Display::refreshTaskMain(void* parameter) {
auto* self = static_cast<Gdeq031t10Display*>(parameter);
self->runRefreshTask();
xSemaphoreGive(self->refreshTaskExited);
vTaskDelete(nullptr);
}
void Gdeq031t10Display::runRefreshTask() {
while (true) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (refreshTaskShouldExit) {
return;
}
// Drain staged frames latest-wins: everything LVGL flushed while the
// previous refresh was in progress collapses into one panel update.
// A frame staged while the panel is off stays staged; the power-on
// path kicks this task to draw it.
while (!refreshTaskShouldExit && powered) {
xSemaphoreTake(bufferMutex, portMAX_DELAY);
const bool havePending = framePending;
if (havePending) {
std::swap(pendingFramebuffer, taskFramebuffer);
framePending = false;
}
xSemaphoreGive(bufferMutex);
if (!havePending) {
break;
}
refresh();
}
// The pipeline went idle: drop the charge pump now rather than on a
// timer. The mode registers survive a power-off, so the next refresh
// only pays a short power-on wait (on this task, invisible to the UI).
xSemaphoreTake(panelMutex, portMAX_DELAY);
if (initialized && powered && panelPowerOn) {
powerOff();
}
xSemaphoreGive(panelMutex);
}
}
void Gdeq031t10Display::refresh() {
const uint8_t* renderBitmap = taskFramebuffer.get();
constexpr int BYTES_PER_ROW = WIDTH / 8;
// Find the bounding box of bytes that differ from what the panel holds. The
// panel stores the inverted (shadow) polarity, so compare against ~render.
int firstCol = BYTES_PER_ROW, lastCol = -1, firstRow = HEIGHT, lastRow = -1;
for (int row = 0; row < HEIGHT; row++) {
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW;
for (int col = 0; col < BYTES_PER_ROW; col++) {
if (static_cast<uint8_t>(~renderBitmap[base + col]) != shadowFramebuffer[base + col]) {
if (col < firstCol) firstCol = col;
if (col > lastCol) lastCol = col;
if (row < firstRow) firstRow = row;
if (row > lastRow) lastRow = row;
}
}
}
const bool nothingChanged = (lastCol < 0);
if (nothingChanged && !forceFullRefresh) {
return; // panel already shows this frame; don't refresh needlessly
}
// A change covering most of the screen or an explicit request warrants a
// full refresh; otherwise refresh just the box.
const int changedRows = nothingChanged ? 0 : (lastRow - firstRow + 1);
const bool largeChange = changedRows > (HEIGHT * 3 / 4);
xSemaphoreTake(panelMutex, portMAX_DELAY);
if (!powered || !initialized) {
// The display was turned off/stopped while this frame was staged; the
// shadow mismatch makes the frame redraw after the next power-on.
xSemaphoreGive(panelMutex);
return;
}
if (forceFullRefresh || largeChange || nothingChanged) {
// Explicit requests get the best-quality LUT; automatic ghost-clears use
// the configured (typically faster) mode.
refreshFull(forceFullRefresh ? RefreshMode::Full : currentRefreshMode.load());
forceFullRefresh = false;
partialRefreshCount = 0;
ghostAreaValid = false;
xSemaphoreGive(panelMutex);
return;
}
// Grow the ghost union with this change so the gate sees where partial
// updates have accumulated since the last clear.
if (!ghostAreaValid) {
ghostFirstCol = firstCol;
ghostLastCol = lastCol;
ghostFirstRow = firstRow;
ghostLastRow = lastRow;
ghostAreaValid = true;
} else {
ghostFirstCol = std::min(ghostFirstCol, firstCol);
ghostLastCol = std::max(ghostLastCol, lastCol);
ghostFirstRow = std::min(ghostFirstRow, firstRow);
ghostLastRow = std::max(ghostLastRow, lastRow);
}
if (partialRefreshCount >= MAX_PARTIAL_REFRESHES) {
// Ghost-clear gate. If the accumulated churn is confined to a small
// region (a blinking cursor, a line being typed into), scrub just that
// window — a full-screen flash there is needless and distracting. Only
// widespread churn earns a whole-panel refresh.
const int unionWidth = (ghostLastCol - ghostFirstCol + 1) * 8;
const int unionHeight = ghostLastRow - ghostFirstRow + 1;
const bool unionIsLarge = unionWidth * unionHeight * 4 >= WIDTH * HEIGHT; // >= 25% of the panel
if (unionIsLarge) {
refreshFull(currentRefreshMode);
} else {
refreshWindow(ghostFirstCol, ghostLastCol, ghostFirstRow, ghostLastRow, true);
}
partialRefreshCount = 0;
ghostAreaValid = false;
} else {
refreshWindow(firstCol, lastCol, firstRow, lastRow, false);
partialRefreshCount++;
}
xSemaphoreGive(panelMutex);
}
void Gdeq031t10Display::refreshFull(RefreshMode mode) {
if (!ensurePanelReady(mode)) {
LOG_E(TAG, "Skipping full refresh: panel not ready");
return;
}
const uint8_t* renderBitmap = taskFramebuffer.get();
// shadowFramebuffer keeps the panel-polarity copy of the last frame for the
// controller's old/new differential refresh; LVGL's I1 polarity is inverted.
if (!writeCommand(CMD_DATA_START_OLD) || !writeData(shadowFramebuffer.get(), FRAMEBUFFER_SIZE)) {
LOG_E(TAG, "Failed to send old frame data");
return;
}
for (size_t i = 0; i < FRAMEBUFFER_SIZE; i++) {
shadowFramebuffer[i] = static_cast<uint8_t>(~renderBitmap[i]);
}
if (!writeCommand(CMD_DATA_START_NEW) || !writeData(shadowFramebuffer.get(), FRAMEBUFFER_SIZE)) {
LOG_E(TAG, "Failed to send new frame data");
return;
}
if (!writeCommand(CMD_DISPLAY_REFRESH)) {
LOG_E(TAG, "Failed to trigger display refresh");
return;
}
vTaskDelay(pdMS_TO_TICKS(1)); // datasheet requires >=200us settle before polling BUSY
if (!waitWhileBusy()) {
LOG_E(TAG, "Full refresh did not complete");
}
}
void Gdeq031t10Display::refreshWindow(int firstByteCol, int lastByteCol, int firstRow, int lastRow, bool scrubGhosts) {
// Partial LUT (fast waveform via temperature force) on a sub-region only. The
// RAM window must be byte-aligned in X, which it already is (byte columns).
if (!ensurePanelReady(RefreshMode::Partial)) {
LOG_E(TAG, "Skipping window refresh: panel not ready");
return;
}
const uint8_t* renderBitmap = taskFramebuffer.get();
constexpr int BYTES_PER_ROW = WIDTH / 8;
const int widthBytes = lastByteCol - firstByteCol + 1;
const uint16_t x = static_cast<uint16_t>(firstByteCol * 8);
const uint16_t xe = static_cast<uint16_t>(lastByteCol * 8 + 7);
const uint16_t y = static_cast<uint16_t>(firstRow);
const uint16_t ye = static_cast<uint16_t>(lastRow);
// Set the partial RAM window (GxEPD2 GDEQ031T10 sequence).
bool ok = writeCommand(CMD_PARTIAL_IN);
ok = ok && writeCommand(CMD_PARTIAL_WINDOW);
ok = ok && writeData(static_cast<uint8_t>(x));
ok = ok && writeData(static_cast<uint8_t>(xe));
ok = ok && writeData(static_cast<uint8_t>(y >> 8));
ok = ok && writeData(static_cast<uint8_t>(y & 0xFF));
ok = ok && writeData(static_cast<uint8_t>(ye >> 8));
ok = ok && writeData(static_cast<uint8_t>(ye & 0xFF));
ok = ok && writeData(0x01);
if (!ok) {
LOG_E(TAG, "Failed to set partial refresh window");
writeCommand(CMD_PARTIAL_OUT); // best-effort: leave partial-window mode
return;
}
// Old region: normally the region's current panel contents (shadow),
// gathered contiguously. For a ghost scrub, feed the complement of the new
// data instead: the controller then sees every pixel as changed and drives
// each one through a transition, clearing accumulated partial-refresh
// ghosting in this window only.
size_t n = 0;
for (int row = firstRow; row <= lastRow; row++) {
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW + firstByteCol;
if (scrubGhosts) {
// New panel data is ~renderBitmap, so its complement is renderBitmap.
std::memcpy(&regionBuffer[n], &renderBitmap[base], widthBytes);
} else {
std::memcpy(&regionBuffer[n], &shadowFramebuffer[base], widthBytes);
}
n += widthBytes;
}
if (!writeCommand(CMD_DATA_START_OLD) || !writeData(regionBuffer.get(), n)) {
LOG_E(TAG, "Failed to send old window data");
writeCommand(CMD_PARTIAL_OUT);
return;
}
// New region: inverted render, and update the shadow for this region as we go.
n = 0;
for (int row = firstRow; row <= lastRow; row++) {
const size_t base = static_cast<size_t>(row) * BYTES_PER_ROW + firstByteCol;
for (int c = 0; c < widthBytes; c++) {
const uint8_t value = static_cast<uint8_t>(~renderBitmap[base + c]);
regionBuffer[n++] = value;
shadowFramebuffer[base + c] = value;
}
}
if (!writeCommand(CMD_DATA_START_NEW) || !writeData(regionBuffer.get(), n)) {
LOG_E(TAG, "Failed to send new window data");
writeCommand(CMD_PARTIAL_OUT);
return;
}
if (writeCommand(CMD_DISPLAY_REFRESH)) {
vTaskDelay(pdMS_TO_TICKS(1));
if (!waitWhileBusy()) {
LOG_E(TAG, "Window refresh did not complete");
}
} else {
LOG_E(TAG, "Failed to trigger window refresh");
}
writeCommand(CMD_PARTIAL_OUT);
}
void Gdeq031t10Display::flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap) {
// pixelMap points into renderFramebuffer (it was passed directly to
// lv_display_set_buffers). Only stage the frame here: the physical refresh
// takes up to ~1s, so it runs on the driver's refresh task instead of
// blocking the LVGL task (which would stall all input handling).
auto* self = static_cast<Gdeq031t10Display*>(lv_display_get_user_data(display));
if (lv_display_flush_is_last(display)) {
self->queueRefresh();
}
lv_display_flush_ready(display);
}
void Gdeq031t10Display::themeApplyCallback(lv_theme_t* /*theme*/, lv_obj_t* obj) {
// Keep textarea cursors solid (no blink): on e-paper each blink toggle is a
// panel refresh. anim_duration 0 makes lv_textarea skip the blink animation.
if (lv_obj_check_type(obj, &lv_textarea_class)) {
lv_obj_set_style_anim_duration(obj, 0, LV_PART_CURSOR);
}
}
bool Gdeq031t10Display::start() {
if (initialized) {
return true;
}
gpio_config_t outputConfig = {
.pin_bit_mask = (1ULL << configuration->pinDc) | (1ULL << configuration->pinReset),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&outputConfig);
gpio_config_t busyConfig = {
.pin_bit_mask = 1ULL << configuration->pinBusy,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&busyConfig);
spi_device_interface_config_t deviceConfig = {
.mode = 0,
.clock_speed_hz = configuration->clockSpeedHz,
.spics_io_num = configuration->pinCs,
.queue_size = 1,
};
if (spi_bus_add_device(configuration->spiHost, &deviceConfig, &spiDevice) != ESP_OK) {
LOG_E(TAG, "Failed to add SPI device");
return false;
}
if (panelMutex == nullptr) {
panelMutex = xSemaphoreCreateMutex();
}
if (bufferMutex == nullptr) {
bufferMutex = xSemaphoreCreateMutex();
}
if (refreshTaskExited == nullptr) {
refreshTaskExited = xSemaphoreCreateBinary();
}
shadowFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
// LVGL needs room for the 8-byte I1 palette ahead of the 1bpp bitmap.
renderFramebuffer = std::make_unique<uint8_t[]>(LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE);
// Scratch for gathering a windowed region (worst case = the whole frame).
regionBuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
pendingFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
taskFramebuffer = std::make_unique<uint8_t[]>(FRAMEBUFFER_SIZE);
std::memset(shadowFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
std::memset(renderFramebuffer.get(), 0xFF, LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE);
std::memset(pendingFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
std::memset(taskFramebuffer.get(), 0xFF, FRAMEBUFFER_SIZE);
currentRefreshMode = configuration->defaultRefreshMode;
initFull();
refreshTaskShouldExit = false;
if (xTaskCreate(&refreshTaskMain, "epd_refresh", REFRESH_TASK_STACK_SIZE, this, REFRESH_TASK_PRIORITY, &refreshTask) != pdPASS) {
LOG_E(TAG, "Failed to create refresh task");
spi_bus_remove_device(spiDevice);
spiDevice = nullptr;
shadowFramebuffer.reset();
renderFramebuffer.reset();
regionBuffer.reset();
pendingFramebuffer.reset();
taskFramebuffer.reset();
return false;
}
powered = true;
initialized = true;
return true;
}
bool Gdeq031t10Display::stop() {
if (!initialized) {
return true;
}
stopLvgl();
// Join the refresh task before touching the panel: it may be mid-refresh.
refreshTaskShouldExit = true;
xTaskNotifyGive(refreshTask);
xSemaphoreTake(refreshTaskExited, portMAX_DELAY);
refreshTask = nullptr;
setPowerOn(false);
xSemaphoreTake(panelMutex, portMAX_DELAY);
initialized = false;
spi_bus_remove_device(spiDevice);
spiDevice = nullptr;
shadowFramebuffer.reset();
renderFramebuffer.reset();
regionBuffer.reset();
pendingFramebuffer.reset();
taskFramebuffer.reset();
framePending = false;
xSemaphoreGive(panelMutex);
return true;
}
void Gdeq031t10Display::setPowerOn(bool turnOn) {
if (turnOn == powered) {
return;
}
xSemaphoreTake(panelMutex, portMAX_DELAY);
if (turnOn) {
initFull(); // toggling RST also wakes the panel from deep sleep
} else {
if (panelPowerOn) {
powerOff();
}
vTaskDelay(pdMS_TO_TICKS(100));
writeCommand(CMD_DEEP_SLEEP);
writeData(DEEP_SLEEP_CHECK_CODE);
// Deep sleep needs a reset to wake, which restores register defaults.
panelMode.reset();
}
powered = turnOn;
xSemaphoreGive(panelMutex);
if (turnOn && refreshTask != nullptr) {
// initFull left the charge pump on. Kick the refresh task: it redraws
// any frame staged while the panel was off, then idles the pump down.
xTaskNotifyGive(refreshTask);
}
}
void Gdeq031t10Display::requestFullRefresh() {
forceFullRefresh = true;
}
bool Gdeq031t10Display::startLvgl() {
if (lvglDisplay != nullptr) {
return true;
}
lvglDisplay = lv_display_create(Gdeq031t10Display::WIDTH, Gdeq031t10Display::HEIGHT);
if (lvglDisplay == nullptr) {
return false;
}
lv_display_set_user_data(lvglDisplay, this);
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_I1);
// The default colour theme renders accent-coloured text/icons that threshold
// to near-invisible on a 1bpp panel. Apply LVGL's monochrome theme (light
// background, dark foreground) so UI content shows as solid black on white.
lv_theme_t* baseTheme = lv_theme_mono_init(lvglDisplay, false, LV_FONT_DEFAULT);
// Chain a theme on top of the mono theme that disables the textarea cursor
// blink. A blinking cursor invalidates its region ~twice a second, and on
// e-paper every invalidation is a panel refresh, so text-entry screens (e.g.
// the Wi-Fi password field) flash continuously. The mono theme still applies
// first (it's the parent); themeApplyCallback only pins the cursor solid.
static lv_theme_t epaperTheme;
epaperTheme = *baseTheme;
lv_theme_set_parent(&epaperTheme, baseTheme);
lv_theme_set_apply_cb(&epaperTheme, &Gdeq031t10Display::themeApplyCallback);
lv_display_set_theme(lvglDisplay, &epaperTheme);
lv_display_set_render_mode(lvglDisplay, LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_buffers(lvglDisplay, renderFramebuffer.get(), nullptr, LVGL_I1_PALETTE_SIZE + FRAMEBUFFER_SIZE, LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(lvglDisplay, flushCallback);
return true;
}
bool Gdeq031t10Display::stopLvgl() {
if (lvglDisplay == nullptr) {
return true;
}
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return true;
}

View File

@ -1,225 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <atomic>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <lvgl.h>
#include <memory>
#include <optional>
/**
* Driver for the GoodDisplay GDEQ031T10 (UC8253-family controller) 3.1" 320x240
* SPI e-paper panel, as used on the LilyGO T-Deck Pro/Max.
*
* Command sequence and timings are ported from the vendor reference driver in
* Xinyuan-LilyGO/T-Deck-MAX (examples/Elink_paper/GDEQ031T10_Arduino).
*/
class Gdeq031t10Display final : public tt::hal::display::DisplayDevice {
public:
enum class RefreshMode {
Full, // ~3s, best quality
Fast, // ~1.0s
Slow, // ~1.5s, fast LUT with extra settling
Partial // ~0.5s, full-frame partial-mode refresh (more ghosting)
};
class Configuration {
public:
Configuration(
spi_host_device_t spiHost,
gpio_num_t pinCs,
gpio_num_t pinDc,
gpio_num_t pinReset,
gpio_num_t pinBusy,
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
int clockSpeedHz = 4'000'000,
RefreshMode defaultRefreshMode = RefreshMode::Full,
bool mirror180 = false
) : spiHost(spiHost),
pinCs(pinCs),
pinDc(pinDc),
pinReset(pinReset),
pinBusy(pinBusy),
touch(std::move(touch)),
clockSpeedHz(clockSpeedHz),
defaultRefreshMode(defaultRefreshMode),
mirror180(mirror180)
{}
spi_host_device_t spiHost;
gpio_num_t pinCs;
gpio_num_t pinDc;
gpio_num_t pinReset;
gpio_num_t pinBusy;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
int clockSpeedHz;
RefreshMode defaultRefreshMode;
/** Panel is mounted upside down relative to the reference orientation */
bool mirror180;
};
static constexpr uint16_t WIDTH = 240;
static constexpr uint16_t HEIGHT = 320;
static constexpr size_t FRAMEBUFFER_SIZE = (WIDTH * HEIGHT) / 8; // 1 bpp packed
// LVGL 9 stores a 2-colour palette (2 x lv_color32_t = 8 bytes) at the start
// of an LV_COLOR_FORMAT_I1 buffer, before the packed 1bpp bitmap.
static constexpr size_t LVGL_I1_PALETTE_SIZE = 8;
private:
std::unique_ptr<Configuration> configuration;
spi_device_handle_t spiDevice = nullptr;
lv_display_t* _Nullable lvglDisplay = nullptr;
/** Mirrors what the panel currently holds, required by the controller's
* "old data" + "new data" double-buffered refresh protocol. */
std::unique_ptr<uint8_t[]> shadowFramebuffer;
/** Render target for LVGL; copied to the panel on flush */
std::unique_ptr<uint8_t[]> renderFramebuffer;
bool initialized = false;
std::atomic<bool> powered = false;
std::atomic<RefreshMode> currentRefreshMode = RefreshMode::Full;
/** Number of windowed partial refreshes since the last full refresh. A full
* refresh is forced once this reaches MAX_PARTIAL_REFRESHES to clear the
* ghosting that partial updates accumulate. */
uint8_t partialRefreshCount = 0;
/** Forces the next refresh to be a full-screen refresh (set at boot and by
* requestFullRefresh, read on the refresh task). */
std::atomic<bool> forceFullRefresh = true;
/** Scratch buffer used to gather a windowed region's bytes for one SPI write. */
std::unique_ptr<uint8_t[]> regionBuffer;
static constexpr uint8_t MAX_PARTIAL_REFRESHES = 8;
/** Union bounding box (byte-column/row space) of all windowed partial
* refreshes since the last ghost-clear. When the partial-refresh gate
* expires, this decides between a localized scrub and a full-screen
* refresh: small repeated updates (a text cursor, a typing line) shouldn't
* flash the whole panel. */
bool ghostAreaValid = false;
int ghostFirstCol = 0;
int ghostLastCol = 0;
int ghostFirstRow = 0;
int ghostLastRow = 0;
/** Waveform mode the panel registers currently hold. Empty when the
* registers are in an unknown state (before first init, or after deep sleep,
* which requires a reset that restores defaults). */
std::optional<RefreshMode> panelMode;
/** True while the panel's charge pump is on (CMD_POWER_ON issued, no
* power-off since). */
bool panelPowerOn = false;
/** Serializes panel/SPI access between the refresh task and external
* callers (setPowerOn, stop). */
SemaphoreHandle_t panelMutex = nullptr;
// The physical refresh takes hundreds of ms to over a second of ink
// movement. Running it inside LVGL's flush callback would block the LVGL
// task (and with it all input handling) for that long, so flushes only
// stage the frame and a dedicated task drives the panel. Frames coalesce
// latest-wins: however many flushes arrive during a refresh, only the
// newest staged frame is drawn next.
static constexpr uint32_t REFRESH_TASK_PRIORITY = 3; // below LVGL (6), above idle
static constexpr uint32_t REFRESH_TASK_STACK_SIZE = 4096;
TaskHandle_t refreshTask = nullptr;
/** Signalled by the refresh task right before it exits; stop() joins on it. */
SemaphoreHandle_t refreshTaskExited = nullptr;
/** Guards pendingFramebuffer and framePending (LVGL flush vs refresh task). */
SemaphoreHandle_t bufferMutex = nullptr;
/** Latest complete frame staged by the LVGL flush callback. */
std::unique_ptr<uint8_t[]> pendingFramebuffer;
/** The refresh task's working copy of the frame it is drawing (swapped with
* pendingFramebuffer under bufferMutex; no copy on the task side). */
std::unique_ptr<uint8_t[]> taskFramebuffer;
bool framePending = false;
std::atomic<bool> refreshTaskShouldExit = false;
/** Returns false if the SPI transfer failed; the panel/shadow state should
* not be advanced as if it succeeded. */
bool writeCommand(uint8_t command);
bool writeData(const uint8_t* data, size_t length);
bool writeData(uint8_t data) { return writeData(&data, 1); }
/** Waits for the BUSY pin to report ready, up to a fixed timeout. Returns
* false (and logs) if the panel never became ready, e.g. bad wiring or no
* panel attached callers must not assume the operation completed. */
bool waitWhileBusy() const;
void reset() const;
bool initFull();
bool initFast();
bool initSlow();
bool initPartial();
/** Puts the panel in the given waveform mode with the charge pump on,
* skipping the (expensive) init sequence when it already is. */
bool ensurePanelReady(RefreshMode mode);
bool powerOff();
/** Stages the LVGL-rendered frame for the refresh task (called on flush). */
void queueRefresh();
static void refreshTaskMain(void* parameter);
void runRefreshTask();
void refresh();
void refreshFull(RefreshMode mode);
/** Windowed partial refresh. With scrubGhosts the controller is fed "old"
* data that is the complement of the new frame, so it drives every pixel in
* the window through a transition a localized ghost-clear that only
* flashes the window itself. */
void refreshWindow(int firstByteCol, int lastByteCol, int firstRow, int lastRow, bool scrubGhosts);
static void flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap);
/** Theme hook: disables the textarea cursor blink (see startLvgl). */
static void themeApplyCallback(lv_theme_t* theme, lv_obj_t* obj);
public:
explicit Gdeq031t10Display(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
~Gdeq031t10Display() override {
if (panelMutex != nullptr) {
vSemaphoreDelete(panelMutex);
}
if (bufferMutex != nullptr) {
vSemaphoreDelete(bufferMutex);
}
if (refreshTaskExited != nullptr) {
vSemaphoreDelete(refreshTaskExited);
}
}
std::string getName() const override { return "GDEQ031T10"; }
std::string getDescription() const override { return "GoodDisplay GDEQ031T10 e-paper display"; }
bool start() override;
bool stop() override;
void setPowerOn(bool turnOn) override;
bool isPoweredOn() const override { return powered; }
bool supportsPowerControl() const override { return true; }
void requestFullRefresh() override;
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override {
return configuration->touch;
}
bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override;
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
bool supportsDisplayDriver() const override { return false; }
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override { return nullptr; }
/** Sets the refresh mode used for automatic (non-full) refreshes from now on,
* until changed again by a subsequent call. */
void setRefreshMode(RefreshMode mode) { currentRefreshMode = mode; }
};

View File

@ -1,19 +1,14 @@
# Audio codec module # Audio codec module
Defines `AUDIO_CODEC_TYPE` and the codec-agnostic `AudioCodecApi` (open/close/ Defines codec-agnostic `AudioCodecApi` (open/close/read/write/volume/mute/native-rate/native-channels/capabilities)
read/write/volume/mute/native-rate/native-channels/capabilities) that every that every audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298, the dummy I2S amps, the PDM mic).
audio codec driver in this repo implements (ES8311, ES8388, ES7210, AW88298, This is intentionally low-level: no resampling or mixing, one codec device per chip. Most app code should go through
the dummy I2S amps, the PDM mic). This is intentionally low-level: no `audio_stream` (see `Drivers/audio-stream-module`) instead of talking to a codec device directly.
resampling or mixing, one codec device per chip. Most app code should go
through `audio_stream` (see `Drivers/audio-stream-module`) instead of talking
to a codec device directly.
Also provides `audio_codec_adapters.h`: glue that lets a codec driver build an Also provides `audio_codec_adapters.h`: glue that lets a codec driver build an `esp_codec_dev` control/data/GPIO
`esp_codec_dev` control/data/GPIO interface (`audio_codec_ctrl_if_t` / interface (`audio_codec_ctrl_if_t` / `audio_codec_data_if_t` / `audio_codec_gpio_if_t`) directly from Tactility's
`audio_codec_data_if_t` / `audio_codec_gpio_if_t`) directly from Tactility's own I2C controller, I2S controller, and GPIO pin devices, so a codec driver only ever needs
own I2C controller, I2S controller, and GPIO pin devices, so a codec driver `device_get_parent()`/`device_find_by_name()` plus a handful of devicetree properties -- it never touches the platform
only ever needs `device_get_parent()`/`device_find_by_name()` plus a handful I2C/I2S/GPIO APIs directly.
of devicetree properties -- it never touches the platform I2C/I2S/GPIO APIs
directly.
License: [Apache v2.0](LICENSE-Apache-2.0.md) License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -44,8 +44,7 @@ Module audio_stream_module = {
.name = "audio-stream", .name = "audio-stream",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = audio_stream_module_symbols, .symbols = audio_stream_module_symbols
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,24 +6,17 @@ extern "C" {
extern Driver aw88298_driver; extern Driver aw88298_driver;
static error_t start() {
check(driver_construct_add(&aw88298_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
check(driver_remove_destruct(&aw88298_driver) == ERROR_NONE);
return ERROR_NONE;
}
extern const ModuleSymbol aw88298_module_symbols[]; extern const ModuleSymbol aw88298_module_symbols[];
static Driver* const aw88298_drivers[] = {
&aw88298_driver,
nullptr
};
Module aw88298_module = { Module aw88298_module = {
.name = "aw88298", .name = "aw88298",
.start = start, .drivers = aw88298_drivers,
.stop = stop, .symbols = aw88298_module_symbols
.symbols = aw88298_module_symbols,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,22 +6,14 @@ extern "C" {
extern Driver aw9523b_driver; extern Driver aw9523b_driver;
static error_t start() { static Driver* const aw9523b_drivers[] = {
check(driver_construct_add(&aw9523b_driver) == ERROR_NONE); &aw9523b_driver,
return ERROR_NONE; nullptr
} };
static error_t stop() {
check(driver_remove_destruct(&aw9523b_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module aw9523b_module = { Module aw9523b_module = {
.name = "aw9523b", .name = "aw9523b",
.start = start, .drivers = aw9523b_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,6 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <drivers/axp192.h> #include <drivers/axp192.h>
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -10,6 +9,13 @@ extern Driver axp192_driver;
extern Driver axp192_power_supply_driver; extern Driver axp192_power_supply_driver;
extern Driver axp192_backlight_driver; extern Driver axp192_backlight_driver;
static Driver* const axp192_drivers[] = {
&axp192_driver,
&axp192_power_supply_driver,
&axp192_backlight_driver,
nullptr
};
const struct ModuleSymbol axp192_module_symbols[] = { const struct ModuleSymbol axp192_module_symbols[] = {
DEFINE_MODULE_SYMBOL(axp192_is_rail_enabled), DEFINE_MODULE_SYMBOL(axp192_is_rail_enabled),
DEFINE_MODULE_SYMBOL(axp192_set_rail_enabled), DEFINE_MODULE_SYMBOL(axp192_set_rail_enabled),
@ -27,28 +33,9 @@ const struct ModuleSymbol axp192_module_symbols[] = {
MODULE_SYMBOL_TERMINATOR MODULE_SYMBOL_TERMINATOR
}; };
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&axp192_driver) == ERROR_NONE);
check(driver_construct_add(&axp192_power_supply_driver) == ERROR_NONE);
check(driver_construct_add(&axp192_backlight_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&axp192_backlight_driver) == ERROR_NONE);
check(driver_remove_destruct(&axp192_power_supply_driver) == ERROR_NONE);
check(driver_remove_destruct(&axp192_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module axp192_module = { Module axp192_module = {
.name = "axp192", .name = "axp192",
.start = start, .drivers = axp192_drivers,
.stop = stop,
.symbols = axp192_module_symbols, .symbols = axp192_module_symbols,
.internal = nullptr .internal = nullptr
}; };

View File

@ -1,6 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <drivers/axp2101.h> #include <drivers/axp2101.h>
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -10,6 +9,13 @@ extern Driver axp2101_driver;
extern Driver axp2101_power_supply_driver; extern Driver axp2101_power_supply_driver;
extern Driver axp2101_backlight_driver; extern Driver axp2101_backlight_driver;
static Driver* const axp2101_drivers[] = {
&axp2101_driver,
&axp2101_power_supply_driver,
&axp2101_backlight_driver,
nullptr
};
const struct ModuleSymbol axp2101_module_symbols[] = { const struct ModuleSymbol axp2101_module_symbols[] = {
DEFINE_MODULE_SYMBOL(axp2101_is_dcdc_enabled), DEFINE_MODULE_SYMBOL(axp2101_is_dcdc_enabled),
DEFINE_MODULE_SYMBOL(axp2101_set_dcdc_enabled), DEFINE_MODULE_SYMBOL(axp2101_set_dcdc_enabled),
@ -28,30 +34,10 @@ const struct ModuleSymbol axp2101_module_symbols[] = {
MODULE_SYMBOL_TERMINATOR MODULE_SYMBOL_TERMINATOR
}; };
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&axp2101_driver) == ERROR_NONE);
check(driver_construct_add(&axp2101_power_supply_driver) == ERROR_NONE);
check(driver_construct_add(&axp2101_backlight_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&axp2101_backlight_driver) == ERROR_NONE);
check(driver_remove_destruct(&axp2101_power_supply_driver) == ERROR_NONE);
check(driver_remove_destruct(&axp2101_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module axp2101_module = { Module axp2101_module = {
.name = "axp2101", .name = "axp2101",
.start = start, .drivers = axp2101_drivers,
.stop = stop, .symbols = axp2101_module_symbols
.symbols = axp2101_module_symbols,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver axs5106_driver; extern Driver axs5106_driver;
static error_t start() { static Driver* const axs5106_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &axs5106_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&axs5106_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&axs5106_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module axs5106_module = { Module axs5106_module = {
.name = "axs5106", .name = "axs5106",
.start = start, .drivers = axs5106_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver bm8563_driver; extern Driver bm8563_driver;
static error_t start() { static Driver* const bm8563_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &bm8563_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&bm8563_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bm8563_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module bm8563_module = { Module bm8563_module = {
.name = "bm8563", .name = "bm8563",
.start = start, .drivers = bm8563_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,28 +6,17 @@ extern "C" {
extern Driver bmi270_driver; extern Driver bmi270_driver;
static error_t start() { static Driver* const bmi270_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &bmi270_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&bmi270_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bmi270_driver) == ERROR_NONE);
return ERROR_NONE;
}
extern const ModuleSymbol bmi270_module_symbols[]; extern const ModuleSymbol bmi270_module_symbols[];
Module bmi270_module = { Module bmi270_module = {
.name = "bmi270", .name = "bmi270",
.start = start, .drivers = bmi270_drivers,
.stop = stop, .symbols = bmi270_module_symbols
.symbols = bmi270_module_symbols,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -8,28 +7,15 @@ extern "C" {
extern Driver bq24295_driver; extern Driver bq24295_driver;
extern Driver bq24295_power_supply_driver; extern Driver bq24295_power_supply_driver;
static error_t start() { static Driver* const bq24295_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &bq24295_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ &bq24295_power_supply_driver,
check(driver_construct_add(&bq24295_driver) == ERROR_NONE); nullptr
check(driver_construct_add(&bq24295_power_supply_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bq24295_power_supply_driver) == ERROR_NONE);
check(driver_remove_destruct(&bq24295_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module bq24295_module = { Module bq24295_module = {
.name = "bq24295", .name = "bq24295",
.start = start, .drivers = bq24295_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -8,28 +7,15 @@ extern "C" {
extern Driver bq25896_driver; extern Driver bq25896_driver;
extern Driver bq25896_power_supply_driver; extern Driver bq25896_power_supply_driver;
static error_t start() { static Driver* const bq25896_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &bq25896_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ &bq25896_power_supply_driver,
check(driver_construct_add(&bq25896_power_supply_driver) == ERROR_NONE); nullptr
check(driver_construct_add(&bq25896_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bq25896_driver) == ERROR_NONE);
check(driver_remove_destruct(&bq25896_power_supply_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module bq25896_module = { Module bq25896_module = {
.name = "bq25896", .name = "bq25896",
.start = start, .drivers = bq25896_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -8,28 +7,15 @@ extern "C" {
extern Driver bq27220_driver; extern Driver bq27220_driver;
extern Driver bq27220_power_supply_driver; extern Driver bq27220_power_supply_driver;
static error_t start() { static Driver* const bq27220_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &bq27220_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ &bq27220_power_supply_driver,
check(driver_construct_add(&bq27220_power_supply_driver) == ERROR_NONE); nullptr
check(driver_construct_add(&bq27220_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bq27220_driver) == ERROR_NONE);
check(driver_remove_destruct(&bq27220_power_supply_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module bq27220_module = { Module bq27220_module = {
.name = "bq27220", .name = "bq27220",
.start = start, .drivers = bq27220_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver button_control_driver; extern Driver button_control_driver;
static error_t start() { static Driver* const button_control_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &button_control_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&button_control_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&button_control_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module button_control_module = { Module button_control_module = {
.name = "button-control", .name = "button-control",
.start = start, .drivers = button_control_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver ch422g_driver; extern Driver ch422g_driver;
static error_t start() { static Driver* const ch422g_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &ch422g_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&ch422g_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&ch422g_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module ch422g_module = { Module ch422g_module = {
.name = "ch422g", .name = "ch422g",
.start = start, .drivers = ch422g_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} }

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver cst328_driver; extern Driver cst328_driver;
static error_t start() { static Driver* const cst328_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &cst328_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&cst328_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&cst328_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module cst328_module = { Module cst328_module = {
.name = "cst328", .name = "cst328",
.start = start, .drivers = cst328_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver cst66xx_driver; extern Driver cst66xx_driver;
static error_t start() { static Driver* const cst66xx_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &cst66xx_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&cst66xx_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&cst66xx_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module cst66xx_module = { Module cst66xx_module = {
.name = "cst66xx", .name = "cst66xx",
.start = start, .drivers = cst66xx_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
@ -7,26 +6,14 @@ extern "C" {
extern Driver cst816s_driver; extern Driver cst816s_driver;
static error_t start() { static Driver* const cst816s_drivers[] = {
/* We crash when construct fails, because if a single driver fails to construct, &cst816s_driver,
* there is no guarantee that the previously constructed drivers can be destroyed */ nullptr
check(driver_construct_add(&cst816s_driver) == ERROR_NONE); };
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&cst816s_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module cst816s_module = { Module cst816s_module = {
.name = "cst816s", .name = "cst816s",
.start = start, .drivers = cst816s_drivers
.stop = stop,
.symbols = nullptr,
.internal = nullptr
}; };
} // extern "C" } // extern "C"

Some files were not shown because too many files have changed in this diff Show More