Compare commits

..

14 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
139d35cb69 Merge branch 'main' into develop 2026-07-14 08:35:47 +02:00
Ken Van Hoeylandt
9f92c6daaf Add support for min/max property range in dts 2026-07-14 01:30:44 +02:00
Ken Van Hoeylandt
7fef42426f Fix for display orientation 2026-07-14 01:16:55 +02:00
Ken Van Hoeylandt
4d41d8497a unphone display and touch migration 2026-07-14 01:07:08 +02:00
Ken Van Hoeylandt
44f3eec365 Fix for ADC channel 2026-07-14 00:33:54 +02:00
Ken Van Hoeylandt
ceed6f1d27 Migrate crowpanel-basic-28 2026-07-14 00:27:20 +02:00
Ken Van Hoeylandt
daded517aa Migrate cyd-e32r32p to kernel drivers 2026-07-14 00:09:22 +02:00
Ken Van Hoeylandt
f1e96d2ef5 Fix for build 2026-07-14 00:07:06 +02:00
Ken Van Hoeylandt
b02663b4bf Remove unused driver 2026-07-14 00:03:50 +02:00
Ken Van Hoeylandt
1069032148 Improve gpio-hog driver 2026-07-14 00:02:40 +02:00
Ken Van Hoeylandt
49af1d42f8 Fixes and improvements 2026-07-13 23:58:14 +02:00
Ken Van Hoeylandt
d2889b2d3d Support for gamma curves 2026-07-13 23:42:37 +02:00
Ken Van Hoeylandt
f31103882d Device and driver migrations 2026-07-13 23:18:37 +02:00
Ken Van Hoeylandt
8f5e92d6e4 Device implementations and touch calibration updates 2026-07-13 21:38:28 +02:00
109 changed files with 1295 additions and 3867 deletions

View File

@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES Tactility
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd RgbDisplay GT911 PwmBacklight driver vfs fatfs
)

View File

@ -0,0 +1,31 @@
#include <PwmBacklight.h>
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
using namespace tt::hal;
static bool initBoot() {
//Display Reset
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_46, GPIO_MODE_OUTPUT));
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_46, 0));
vTaskDelay(pdMS_TO_TICKS(100));
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_46, 1));
vTaskDelay(pdMS_TO_TICKS(10));
return driver::pwmbacklight::init(GPIO_NUM_21);
}
static DeviceVector createDevices() {
return {
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -0,0 +1,107 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 41 and interrupt pin is 40
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
800,
480
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
constexpr uint32_t bufferPixels = 800 * 10;
esp_lcd_rgb_panel_config_t rgb_panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = 14000000,
.h_res = 800,
.v_res = 480,
.hsync_pulse_width = 4,
.hsync_back_porch = 8,
.hsync_front_porch = 8,
.vsync_pulse_width = 4,
.vsync_back_porch = 16,
.vsync_front_porch = 16,
.flags = {
.hsync_idle_low = false,
.vsync_idle_low = false,
.de_idle_high = false,
.pclk_active_neg = true,
.pclk_idle_high = true
}
},
.data_width = 16,
.bits_per_pixel = 0,
.num_fbs = 2,
.bounce_buffer_size_px = bufferPixels,
.sram_trans_align = 8,
.psram_trans_align = 64,
.hsync_gpio_num = GPIO_NUM_NC,
.vsync_gpio_num = GPIO_NUM_NC,
.de_gpio_num = GPIO_NUM_38,
.pclk_gpio_num = GPIO_NUM_5,
.disp_gpio_num = GPIO_NUM_NC,
.data_gpio_nums = {
GPIO_NUM_17, // B
GPIO_NUM_18, // B
GPIO_NUM_48, // B
GPIO_NUM_47, // B
GPIO_NUM_39, // B
GPIO_NUM_11, // G
GPIO_NUM_12, // G
GPIO_NUM_13, // G
GPIO_NUM_14, // G
GPIO_NUM_15, // G
GPIO_NUM_16, // G
GPIO_NUM_6, // R
GPIO_NUM_7, // R
GPIO_NUM_8, // R
GPIO_NUM_9, // R
GPIO_NUM_10, // R
},
.flags = {
.disp_active_low = false,
.refresh_on_demand = false,
.fb_in_psram = true,
.double_fb = true,
.no_fb = false,
.bb_invalidate_cache = false
}
};
RgbDisplay::BufferConfiguration buffer_config = {
.size = (800 * 480),
.useSpi = true,
.doubleBuffer = true,
.bounceBufferMode = true,
.avoidTearing = false
};
auto configuration = std::make_unique<RgbDisplay::Configuration>(
rgb_panel_config,
buffer_config,
touch,
LV_COLOR_FORMAT_RGB565,
false,
false,
false,
false,
driver::pwmbacklight::setBacklightDuty
);
return std::make_shared<RgbDisplay>(std::move(configuration));
}

View File

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

View File

@ -3,10 +3,12 @@
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}

View File

@ -6,9 +6,6 @@
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_usbhost.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/rgb_display.h>
#include <bindings/gt911.h>
// Reference: https://github.com/bigtreetech/PandaTouch_PlatformIO/blob/master/docs/PINOUT.md
/ {
@ -36,15 +33,6 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 2 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 1 GPIO_FLAG_NONE>;
touch0 {
// Reset pin 41 and interrupt pin 40 exist on the board but are not wired up here
// (unverified - left disconnected like the original deprecated-HAL config).
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <800>;
y-max = <480>;
};
};
i2c_external: i2c1 {
@ -55,49 +43,6 @@
pin-scl = <&gpio0 3 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
pin-backlight = <&gpio0 21 GPIO_FLAG_NONE>;
};
display0 {
compatible = "espressif,esp32-rgb-display";
horizontal-resolution = <800>;
vertical-resolution = <480>;
pixel-clock-hz = <14000000>;
hsync-pulse-width = <4>;
hsync-back-porch = <8>;
hsync-front-porch = <8>;
vsync-pulse-width = <4>;
vsync-back-porch = <16>;
vsync-front-porch = <16>;
pclk-active-neg;
pclk-idle-high;
num-fbs = <2>;
double-fb;
bounce-buffer-size-px = <8000>;
pin-de = <&gpio0 38 GPIO_FLAG_NONE>;
pin-pclk = <&gpio0 5 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 46 GPIO_FLAG_NONE>;
pin-data0 = <&gpio0 17 GPIO_FLAG_NONE>; // B
pin-data1 = <&gpio0 18 GPIO_FLAG_NONE>; // B
pin-data2 = <&gpio0 48 GPIO_FLAG_NONE>; // B
pin-data3 = <&gpio0 47 GPIO_FLAG_NONE>; // B
pin-data4 = <&gpio0 39 GPIO_FLAG_NONE>; // B
pin-data5 = <&gpio0 11 GPIO_FLAG_NONE>; // G
pin-data6 = <&gpio0 12 GPIO_FLAG_NONE>; // G
pin-data7 = <&gpio0 13 GPIO_FLAG_NONE>; // G
pin-data8 = <&gpio0 14 GPIO_FLAG_NONE>; // G
pin-data9 = <&gpio0 15 GPIO_FLAG_NONE>; // G
pin-data10 = <&gpio0 16 GPIO_FLAG_NONE>; // G
pin-data11 = <&gpio0 6 GPIO_FLAG_NONE>; // R
pin-data12 = <&gpio0 7 GPIO_FLAG_NONE>; // R
pin-data13 = <&gpio0 8 GPIO_FLAG_NONE>; // R
pin-data14 = <&gpio0 9 GPIO_FLAG_NONE>; // R
pin-data15 = <&gpio0 10 GPIO_FLAG_NONE>; // R
backlight = <&display_backlight>;
};
usbhost0 {
compatible = "espressif,esp32-usbhost";

View File

@ -12,8 +12,6 @@ hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
hardware.usbHostEnabled=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=Internal
display.size=5"

View File

@ -1,5 +1,3 @@
dependencies:
- Platforms/platform-esp32
- Drivers/rgb-display-module
- Drivers/gt911-module
dts: bigtreetech,panda-touch.dts

View File

@ -74,8 +74,7 @@
pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 19 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
miso-pull-up;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -7,7 +7,7 @@
#include <PwmBacklight.h>
static bool init_boot() {
static bool initBoot() {
if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT)) {
return false;
}
@ -42,6 +42,6 @@ static tt::hal::DeviceVector createDevices() {
}
extern const tt::hal::Configuration hardwareConfiguration = {
.initBoot = init_boot,
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -81,8 +81,7 @@
pin-mosi = <&gpio0 6 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>;
miso-pull-up;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -77,8 +77,7 @@
pin-mosi = <&gpio0 23 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 19 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
miso-pull-up;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES Tactility
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port RgbDisplay GT911 PwmBacklight driver
)

View File

@ -0,0 +1,22 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static bool initBoot() {
// Note: I tried 100 Hz to 100 kHz and couldn't get the flickering to stop
return driver::pwmbacklight::init(GPIO_NUM_2);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -0,0 +1,109 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
800,
480
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
constexpr uint32_t bufferPixels = 800 * 10;
esp_lcd_rgb_panel_config_t rgb_panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = 15000000,
.h_res = 800,
.v_res = 480,
.hsync_pulse_width = 4,
.hsync_back_porch = 43,
.hsync_front_porch = 8,
.vsync_pulse_width = 4,
.vsync_back_porch = 12,
.vsync_front_porch = 8,
.flags = {
.hsync_idle_low = false,
.vsync_idle_low = false,
.de_idle_high = false,
.pclk_active_neg = true,
.pclk_idle_high = false
}
},
.data_width = 16,
.bits_per_pixel = 0,
.num_fbs = 2,
.bounce_buffer_size_px = bufferPixels,
.sram_trans_align = 8,
.psram_trans_align = 64,
.hsync_gpio_num = GPIO_NUM_39,
.vsync_gpio_num = GPIO_NUM_41,
.de_gpio_num = GPIO_NUM_40 ,
.pclk_gpio_num = GPIO_NUM_0,
.disp_gpio_num = GPIO_NUM_NC,
.data_gpio_nums = {
GPIO_NUM_8, // B0
GPIO_NUM_3, // B1
GPIO_NUM_46, // B2
GPIO_NUM_9, // B3
GPIO_NUM_1, // B4
GPIO_NUM_5, // G0
GPIO_NUM_6, // G1
GPIO_NUM_7, // G2
GPIO_NUM_15, // G3
GPIO_NUM_16, // G4
GPIO_NUM_4, // G5
GPIO_NUM_45, // R0
GPIO_NUM_48, // R1
GPIO_NUM_47, // R2
GPIO_NUM_21, // R3
GPIO_NUM_14, // R4
},
.flags = {
.disp_active_low = false,
.refresh_on_demand = false,
.fb_in_psram = true,
.double_fb = true,
.no_fb = false,
.bb_invalidate_cache = false
}
};
RgbDisplay::BufferConfiguration buffer_config = {
.size = (800 * 480),
.useSpi = true,
.doubleBuffer = true,
.bounceBufferMode = true,
.avoidTearing = false
};
auto configuration = std::make_unique<RgbDisplay::Configuration>(
rgb_panel_config,
buffer_config,
touch,
LV_COLOR_FORMAT_RGB565,
false,
false,
false,
false,
driver::pwmbacklight::setBacklightDuty
);
auto display = std::make_shared<RgbDisplay>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}

View File

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

View File

@ -3,10 +3,12 @@
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}

View File

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

View File

@ -1,5 +1,3 @@
dependencies:
- Platforms/platform-esp32
- Drivers/rgb-display-module
- Drivers/gt911-module
dts: elecrow,crowpanel-basic-50.dts

View File

@ -8,9 +8,6 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/rgb_display.h>
#include <bindings/gt911.h>
/ {
compatible = "root";
@ -37,16 +34,6 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 19 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 20 GPIO_FLAG_NONE>;
touch0 {
// Reset pin 38 and interrupt pin 18 (or INT = NC) exist on the board but are not
// wired up here - conflicting schematic info, unverified (matches the original
// deprecated-HAL config).
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <800>;
y-max = <480>;
};
};
spi0 {
@ -63,49 +50,6 @@
};
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
pin-backlight = <&gpio0 2 GPIO_FLAG_NONE>;
};
display0 {
compatible = "espressif,esp32-rgb-display";
horizontal-resolution = <800>;
vertical-resolution = <480>;
pixel-clock-hz = <15000000>;
hsync-pulse-width = <4>;
hsync-back-porch = <43>;
hsync-front-porch = <8>;
vsync-pulse-width = <4>;
vsync-back-porch = <12>;
vsync-front-porch = <8>;
pclk-active-neg;
num-fbs = <2>;
double-fb;
bounce-buffer-size-px = <8000>;
pin-hsync = <&gpio0 39 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>;
pin-de = <&gpio0 40 GPIO_FLAG_NONE>;
pin-pclk = <&gpio0 0 GPIO_FLAG_NONE>;
pin-data0 = <&gpio0 8 GPIO_FLAG_NONE>; // B0
pin-data1 = <&gpio0 3 GPIO_FLAG_NONE>; // B1
pin-data2 = <&gpio0 46 GPIO_FLAG_NONE>; // B2
pin-data3 = <&gpio0 9 GPIO_FLAG_NONE>; // B3
pin-data4 = <&gpio0 1 GPIO_FLAG_NONE>; // B4
pin-data5 = <&gpio0 5 GPIO_FLAG_NONE>; // G0
pin-data6 = <&gpio0 6 GPIO_FLAG_NONE>; // G1
pin-data7 = <&gpio0 7 GPIO_FLAG_NONE>; // G2
pin-data8 = <&gpio0 15 GPIO_FLAG_NONE>; // G3
pin-data9 = <&gpio0 16 GPIO_FLAG_NONE>; // G4
pin-data10 = <&gpio0 4 GPIO_FLAG_NONE>; // G5
pin-data11 = <&gpio0 45 GPIO_FLAG_NONE>; // R0
pin-data12 = <&gpio0 48 GPIO_FLAG_NONE>; // R1
pin-data13 = <&gpio0 47 GPIO_FLAG_NONE>; // R2
pin-data14 = <&gpio0 21 GPIO_FLAG_NONE>; // R3
pin-data15 = <&gpio0 14 GPIO_FLAG_NONE>; // R4
backlight = <&display_backlight>;
};
uart0 {
compatible = "espressif,esp32-uart";
port = <UART_NUM_0>;

View File

@ -21,5 +21,3 @@ display.dpi=143
cdn.infoMessage=To put the device into bootloader mode: <br/>1. Press the trackball and then the reset button at the same time,<br/>2. Let go of the reset button, then the trackball.<br/><br/>When this website reports that flashing is finished, you likely have to press the reset button.
lvgl.colorDepth=16
sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y

View File

@ -3,7 +3,4 @@ dependencies:
- Drivers/st7789-module
- Drivers/gt911-module
- Drivers/lilygo-module
- Drivers/es7210-module
- Drivers/dummy-i2s-amp-module
- Drivers/audio-stream-module
dts: lilygo,tdeck.dts

View File

@ -16,8 +16,6 @@
#include <bindings/gt911.h>
#include <bindings/st7789.h>
#include <bindings/es7210.h>
#include <bindings/dummy_i2s_amp.h>
#include <lilygo/bindings/tdeck_keyboard.h>
#include <lilygo/bindings/tdeck_keyboard_backlight.h>
@ -66,34 +64,6 @@
pin-click = <&gpio0 0 GPIO_FLAG_NONE>;
};
// i2s0 and i2s1 are declared before i2c0 so i2s1 has started before the es7210 codec (below) references it.
// Speaker I2S (MAX98357A amplifier)
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 7 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 5 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>;
};
// Microphone I2S (ES7210), separate port from the speaker
i2s1 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_1>;
pin-bclk = <&gpio0 47 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 21 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 48 GPIO_FLAG_NONE>;
};
// MAX98357A class-D speaker amplifier. No I2C. Per schematic, SD_MODE/GAIN_SLOT is
// tied via fixed resistors (R21/R22), not driven by an MCU GPIO -- the amp is always
// enabled when powered, so no enable-gpio is wired here.
speaker0 {
compatible = "maxim,max98357a";
i2s = <&i2s0>;
};
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
@ -120,18 +90,14 @@
compatible = "lilygo,tdeck-keyboard-backlight";
reg = <0x55>;
};
};
// ES7210 microphone ADC. Per schematic all 4 MSM381A3729H9CP mic capsules are
// populated (MIC1-4), and AD0/AD1 are unconnected (default low -> address 0x40).
// Stock LilyGO firmware drives MIC1/MIC2 at 0dB and MIC3/MIC4 at 37.5dB gain --
// the es7210 driver here applies a single gain to all active mics, so per-pair
// gain differentiation is not yet replicated.
es7210 {
compatible = "everest,es7210";
reg = <0x40>;
i2s = <&i2s1>;
mic-mask = <15>;
};
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 7 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 5 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>;
};
display_backlight {

View File

@ -99,7 +99,6 @@
vertical-resolution = <320>;
pixel-clock-hz = <16000000>;
backlight = <&display_backlight>;
gamma-curve = <2>;
};
};
@ -113,6 +112,13 @@
y-max = <320>;
};
// One-button mode: short press = select next, long press = press/enter (matches the
// deprecated HAL's old ButtonControl::createOneButtonControl(0)).
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
};
sdmmc0 {
compatible = "espressif,esp32-sdmmc";
status = "disabled"; // Late-init is required, otherwise mounting fails

View File

@ -122,8 +122,7 @@
pin-mosi = <&gpio0 14 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
miso-pull-up;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
status = "disabled";

View File

@ -104,8 +104,7 @@
pin-mosi = <&gpio0 14 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
miso-pull-up;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;

View File

@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES Tactility bq24295-module
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_io_expander esp_io_expander_tca95xx_16bit BQ24295
)

View File

@ -0,0 +1,7 @@
#include <Tactility/hal/Configuration.h>
bool initBoot();
extern const tt::hal::Configuration hardwareConfiguration = {
.initBoot = initBoot
};

View File

@ -0,0 +1,201 @@
#include "UnPhoneFeatures.h"
#include <tactility/device.h>
#include <Tactility/LogMessages.h>
#include <Tactility/Preferences.h>
#include <esp_sleep.h>
#include <tactility/log.h>
constexpr auto* TAG = "unPhone";
std::shared_ptr<UnPhoneFeatures> unPhoneFeatures;
static std::unique_ptr<tt::Thread> powerThread;
static const char* bootCountKey = "boot_count";
static const char* powerOffCountKey = "power_off_count";
static const char* powerSleepKey = "power_sleep_key";
class DeviceStats {
tt::Preferences preferences = tt::Preferences("unphone");
int32_t getValue(const char* key) {
int32_t value = 0;
preferences.optInt32(key, value);
return value;
}
void setValue(const char* key, int32_t value) {
preferences.putInt32(key, value);
}
void increaseValue(const char* key) {
int32_t new_value = getValue(key) + 1;
setValue(key, new_value);
}
public:
void notifyBootStart() {
increaseValue(bootCountKey);
}
void notifyPowerOff() {
increaseValue(powerOffCountKey);
}
void notifyPowerSleep() {
increaseValue(powerSleepKey);
}
void printInfo() {
LOG_I(TAG, "Device stats:");
LOG_I(TAG, " boot: %d", (int)getValue(bootCountKey));
LOG_I(TAG, " power off: %d", (int)getValue(powerOffCountKey));
LOG_I(TAG, " power sleep: %d", (int)getValue(powerSleepKey));
}
};
DeviceStats bootStats;
enum class PowerState {
Initial,
On,
Off
};
#define DEBUG_POWER_STATES false
#if DEBUG_POWER_STATES
/** Helper method to use the buzzer to signal the different power stages */
static void powerInfoBuzz(uint8_t count) {
if (DEBUG_POWER_STATES) {
uint8_t index = 0;
while (index < count) {
unPhoneFeatures.setVibePower(true);
tt::kernel::delayMillis(50);
unPhoneFeatures.setVibePower(false);
index++;
if (index < count) {
tt::kernel::delayMillis(100);
}
}
}
}
#endif
static void updatePowerSwitch() {
static PowerState last_state = PowerState::Initial;
if (!unPhoneFeatures->isPowerSwitchOn()) {
if (last_state != PowerState::Off) {
last_state = PowerState::Off;
LOG_W(TAG, "Power off");
}
if (!unPhoneFeatures->isUsbPowerConnected()) { // and usb unplugged we go into shipping mode
LOG_W(TAG, "Shipping mode until USB connects");
#if DEBUG_POWER_STATES
unPhoneFeatures.setExpanderPower(true);
powerInfoBuzz(3);
unPhoneFeatures.setExpanderPower(false);
#endif
unPhoneFeatures->turnPeripheralsOff();
bootStats.notifyPowerOff();
unPhoneFeatures->setShipping(true); // tell BM to stop supplying power until USB connects
} else { // When power switch is off, but USB is plugged in, we wait (deep sleep) until USB is unplugged.
LOG_W(TAG, "Waiting for USB disconnect to power off");
#if DEBUG_POWER_STATES
powerInfoBuzz(2);
#endif
unPhoneFeatures->turnPeripheralsOff();
bootStats.notifyPowerSleep();
// Deep sleep for 1 minute, then awaken to check power state again
// GPIO trigger from power switch also awakens the device
unPhoneFeatures->wakeOnPowerSwitch();
esp_sleep_enable_timer_wakeup(60000000);
esp_deep_sleep_start();
}
} else {
if (last_state != PowerState::On) {
last_state = PowerState::On;
LOG_W(TAG, "Power on");
#if DEBUG_POWER_STATES
powerInfoBuzz(1);
#endif
}
}
}
static int32_t powerSwitchMain() { // check power switch every 10th of sec
while (true) {
updatePowerSwitch();
tt::kernel::delayMillis(200);
}
}
static void startPowerSwitchThread() {
powerThread = std::make_unique<tt::Thread>(
"unphone_power_switch",
4096,
[]() { return powerSwitchMain(); }
);
powerThread->start();
}
std::shared_ptr<Bq24295> bq24295;
static bool unPhonePowerOn() {
// Print early, in case of early crash (info will be from previous boot)
bootStats.printInfo();
bootStats.notifyBootStart();
::Device* i2c_internal = nullptr;
check(device_get_by_name("i2c_internal", &i2c_internal) == ERROR_NONE);
bq24295 = std::make_shared<Bq24295>(i2c_internal);
device_put(i2c_internal);
unPhoneFeatures = std::make_shared<UnPhoneFeatures>(bq24295);
if (!unPhoneFeatures->init()) {
LOG_E(TAG, "UnPhoneFeatures init failed");
return false;
}
unPhoneFeatures->printInfo();
// Kernel devicetree devices (incl. the hx8357 display) already started by kernel_init()
// before initBoot() runs, so it's safe to turn the backlight on here now.
unPhoneFeatures->setBacklightPower(true);
unPhoneFeatures->setVibePower(false);
unPhoneFeatures->setIrPower(false);
unPhoneFeatures->setExpanderPower(false);
// Turn off the device if power switch is on off state,
// instead of waiting for the Thread to start and continue booting
updatePowerSwitch();
startPowerSwitchThread();
return true;
}
bool initBoot() {
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
if (!unPhonePowerOn()) {
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
return false;
}
return true;
}

View File

@ -0,0 +1,306 @@
#include "UnPhoneFeatures.h"
#include <Tactility/app/App.h>
#include <Tactility/kernel/Kernel.h>
#include <driver/gpio.h>
#include <driver/rtc_io.h>
#include <esp_io_expander.h>
#include <esp_sleep.h>
#include <tactility/log.h>
constexpr auto* TAG = "unPhoneFeatures";
namespace pin {
static const gpio_num_t BUTTON1 = GPIO_NUM_45; // left button
static const gpio_num_t BUTTON2 = GPIO_NUM_0; // middle button
static const gpio_num_t BUTTON3 = GPIO_NUM_21; // right button
static const gpio_num_t IR_LEDS = GPIO_NUM_12;
static const gpio_num_t LED_RED = GPIO_NUM_13;
static const gpio_num_t POWER_SWITCH = GPIO_NUM_18;
} // namespace pin
namespace expanderpin {
static const esp_io_expander_pin_num_t BACKLIGHT = IO_EXPANDER_PIN_NUM_2;
static const esp_io_expander_pin_num_t EXPANDER_POWER = IO_EXPANDER_PIN_NUM_0; // enable exp brd if high
static const esp_io_expander_pin_num_t LED_GREEN = IO_EXPANDER_PIN_NUM_9;
static const esp_io_expander_pin_num_t LED_BLUE = IO_EXPANDER_PIN_NUM_13;
static const esp_io_expander_pin_num_t USB_VSENSE = IO_EXPANDER_PIN_NUM_14;
static const esp_io_expander_pin_num_t VIBE = IO_EXPANDER_PIN_NUM_7;
} // namespace expanderpin
// TODO: Make part of a new type of UnPhoneFeatures data struct that holds all the thread-related data
QueueHandle_t interruptQueue;
static void IRAM_ATTR navButtonInterruptHandler(void* args) {
int pinNumber = (int)args;
xQueueSendFromISR(interruptQueue, &pinNumber, NULL);
}
static int32_t buttonHandlingThreadMain(const bool* interrupted) {
int pinNumber;
while (!*interrupted) {
if (xQueueReceive(interruptQueue, &pinNumber, portMAX_DELAY)) {
// The buttons might generate more than 1 click because of how they are built
LOG_I(TAG, "Pressed button %d", pinNumber);
if (pinNumber == pin::BUTTON1) {
tt::app::stop();
}
// Debounce all events for a short period of time
// This is easier than keeping track when each button was last pressed
tt::kernel::delayMillis(50);
xQueueReset(interruptQueue);
tt::kernel::delayMillis(50);
xQueueReset(interruptQueue);
}
}
return 0;
}
UnPhoneFeatures::~UnPhoneFeatures() {
if (buttonHandlingThread.getState() != tt::Thread::State::Stopped) {
buttonHandlingThreadInterruptRequest = true;
buttonHandlingThread.join();
}
}
bool UnPhoneFeatures::initPowerSwitch() {
gpio_config_t config = {
.pin_bit_mask = BIT64(pin::POWER_SWITCH),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_POSEDGE,
};
if (gpio_config(&config) != ESP_OK) {
LOG_E(TAG, "Power pin init failed");
return false;
}
if (rtc_gpio_pullup_en(pin::POWER_SWITCH) == ESP_OK &&
rtc_gpio_pulldown_en(pin::POWER_SWITCH) == ESP_OK) {
return true;
} else {
LOG_E(TAG, "Failed to set RTC for power switch");
return false;
}
}
bool UnPhoneFeatures::initNavButtons() {
if (!initGpioExpander()) {
LOG_E(TAG, "GPIO expander init failed");
return false;
}
interruptQueue = xQueueCreate(4, sizeof(int));
buttonHandlingThread.setName("unphone_buttons");
buttonHandlingThread.setPriority(tt::Thread::Priority::High);
buttonHandlingThread.setStackSize(3072);
buttonHandlingThread.setMainFunction(
[this] {
return buttonHandlingThreadMain(&this->buttonHandlingThreadInterruptRequest);
}
);
buttonHandlingThread.start();
uint64_t pin_mask =
BIT64(pin::BUTTON1) |
BIT64(pin::BUTTON2) |
BIT64(pin::BUTTON3);
gpio_config_t config = {
.pin_bit_mask = pin_mask,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
/**
* We have to listen to the button release (= positive signal).
* If we listen to button press, the buttons might create more than 1 signal
* when they are continuously pressed.
*/
.intr_type = GPIO_INTR_POSEDGE,
};
if (gpio_config(&config) != ESP_OK) {
LOG_E(TAG, "Nav button pin init failed");
return false;
}
if (
gpio_install_isr_service(0) != ESP_OK ||
gpio_isr_handler_add(pin::BUTTON1, navButtonInterruptHandler, reinterpret_cast<void*>(pin::BUTTON1)) != ESP_OK ||
gpio_isr_handler_add(pin::BUTTON2, navButtonInterruptHandler, reinterpret_cast<void*>(pin::BUTTON2)) != ESP_OK ||
gpio_isr_handler_add(pin::BUTTON3, navButtonInterruptHandler, reinterpret_cast<void*>(pin::BUTTON3)) != ESP_OK
) {
LOG_E(TAG, "Nav buttons ISR init failed");
return false;
}
return true;
}
bool UnPhoneFeatures::initOutputPins() {
uint64_t output_pin_mask =
BIT64(pin::IR_LEDS) |
BIT64(pin::LED_RED);
gpio_config_t config = {
.pin_bit_mask = output_pin_mask,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&config) != ESP_OK) {
LOG_E(TAG, "Output pin init failed");
return false;
}
return true;
}
bool UnPhoneFeatures::initGpioExpander() {
// ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110 corresponds with 0x26 from the docs at
// https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads#L206
if (esp_io_expander_new_i2c_tca95xx_16bit(I2C_NUM_0, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110, &ioExpander) != ESP_OK) {
LOG_E(TAG, "IO expander init failed");
return false;
}
assert(ioExpander != nullptr);
// Output pins
/**
* Important:
* If you clear the pins too late, the display or vibration motor might briefly turn on.
*/
esp_io_expander_set_dir(ioExpander, expanderpin::BACKLIGHT, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, 0);
esp_io_expander_set_dir(ioExpander, expanderpin::EXPANDER_POWER, IO_EXPANDER_OUTPUT);
esp_io_expander_set_dir(ioExpander, expanderpin::LED_GREEN, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::LED_GREEN, 0);
esp_io_expander_set_dir(ioExpander, expanderpin::LED_BLUE, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, 0);
esp_io_expander_set_dir(ioExpander, expanderpin::VIBE, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(ioExpander, expanderpin::VIBE, 0);
// Input pins
esp_io_expander_set_dir(ioExpander, expanderpin::USB_VSENSE, IO_EXPANDER_INPUT);
return true;
}
bool UnPhoneFeatures::init() {
LOG_I(TAG, "init");
if (!initGpioExpander()) {
LOG_E(TAG, "GPIO expander init failed");
return false;
}
if (!initNavButtons()) {
LOG_E(TAG, "Input pin init failed");
return false;
}
if (!initOutputPins()) {
LOG_E(TAG, "Output pin init failed");
return false;
}
if (!initPowerSwitch()) {
LOG_E(TAG, "Power button init failed");
return false;
}
return true;
}
void UnPhoneFeatures::printInfo() const {
esp_io_expander_print_state(ioExpander);
batteryManagement->printInfo();
bool backlight_power;
const char* backlight_power_state = getBacklightPower(backlight_power) && backlight_power ? "on" : "off";
LOG_I(TAG, "Backlight: %s", backlight_power_state);
}
bool UnPhoneFeatures::setRgbLed(bool red, bool green, bool blue) const {
assert(ioExpander != nullptr);
return gpio_set_level(pin::LED_RED, red ? 1U : 0U) == ESP_OK &&
esp_io_expander_set_level(ioExpander, expanderpin::LED_GREEN, green ? 1U : 0U) == ESP_OK &&
esp_io_expander_set_level(ioExpander, expanderpin::LED_BLUE, blue ? 1U : 0U) == ESP_OK;
}
bool UnPhoneFeatures::setBacklightPower(bool on) const {
assert(ioExpander != nullptr);
return esp_io_expander_set_level(ioExpander, expanderpin::BACKLIGHT, on ? 1U : 0U) == ESP_OK;
}
bool UnPhoneFeatures::getBacklightPower(bool& on) const {
assert(ioExpander != nullptr);
uint32_t level_mask;
if (esp_io_expander_get_level(ioExpander, expanderpin::BACKLIGHT, &level_mask) == ESP_OK) {
on = level_mask != 0U;
return true;
} else {
return false;
}
}
bool UnPhoneFeatures::setIrPower(bool on) const {
assert(ioExpander != nullptr);
return gpio_set_level(pin::IR_LEDS, on ? 1U : 0U) == ESP_OK;
}
bool UnPhoneFeatures::setVibePower(bool on) const {
assert(ioExpander != nullptr);
return esp_io_expander_set_level(ioExpander, expanderpin::VIBE, on ? 1U : 0U) == ESP_OK;
}
bool UnPhoneFeatures::setExpanderPower(bool on) const {
assert(ioExpander != nullptr);
return esp_io_expander_set_level(ioExpander, expanderpin::EXPANDER_POWER, on ? 1U : 0U) == ESP_OK;
}
bool UnPhoneFeatures::isPowerSwitchOn() const {
return gpio_get_level(pin::POWER_SWITCH) > 0;
}
void UnPhoneFeatures::turnPeripheralsOff() const {
setExpanderPower(false);
setBacklightPower(false);
setIrPower(false);
setRgbLed(false, false, false);
setVibePower(false);
}
bool UnPhoneFeatures::setShipping(bool on) const {
if (on) {
LOG_W(TAG, "setShipping: on");
batteryManagement->setWatchDogTimer(Bq24295::WatchDogTimer::Disabled);
batteryManagement->setBatFetOn(false);
} else {
LOG_W(TAG, "setShipping: off");
batteryManagement->setWatchDogTimer(Bq24295::WatchDogTimer::Enabled40s);
batteryManagement->setBatFetOn(true);
}
return true;
}
void UnPhoneFeatures::wakeOnPowerSwitch() const {
esp_sleep_enable_ext0_wakeup(pin::POWER_SWITCH, 1);
}
bool UnPhoneFeatures::isUsbPowerConnected() const {
return batteryManagement->isUsbPowerConnected();
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <Bq24295.h>
#include <Tactility/Thread.h>
#include <esp_io_expander_tca95xx_16bit.h>
/**
* Easy access to GPIO pins
*/
class UnPhoneFeatures final {
private:
esp_io_expander_handle_t ioExpander = nullptr;
tt::Thread buttonHandlingThread;
bool buttonHandlingThreadInterruptRequest = false;
bool initNavButtons();
static bool initOutputPins();
static bool initPowerSwitch();
bool initGpioExpander();
std::shared_ptr<Bq24295> batteryManagement;
public:
explicit UnPhoneFeatures(std::shared_ptr<Bq24295> bq24295) : batteryManagement(std::move(bq24295)) {
assert(batteryManagement != nullptr);
}
~UnPhoneFeatures();
bool init();
bool setBacklightPower(bool on) const;
bool getBacklightPower(bool& on) const;
bool setIrPower(bool on) const;
bool setVibePower(bool on) const;
bool setExpanderPower(bool on) const;
bool isPowerSwitchOn() const;
void turnPeripheralsOff() const;
/** Battery management (BQ24295) will stop supplying power until USB connects */
bool setShipping(bool on) const;
void wakeOnPowerSwitch() const;
bool isUsbPowerConnected() const;
bool setRgbLed(bool red, bool green, bool blue) const;
void printInfo() const;
};

View File

@ -0,0 +1,23 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module unphone_module = {
.name = "unphone",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -1,20 +0,0 @@
description: >
unPhone physical navigation buttons: 3 buttons wired directly to native ESP32 GPIO
pins. Button 1 (left) stops the currently running app on release; buttons 2 and 3
are read but currently have no assigned action.
compatible: "unphone,nav-buttons"
properties:
pin-button1:
type: phandles
required: true
description: GPIO pin connected to the left (button 1) nav button
pin-button2:
type: phandles
required: true
description: GPIO pin connected to the middle (button 2) nav button
pin-button3:
type: phandles
required: true
description: GPIO pin connected to the right (button 3) nav button

View File

@ -1,11 +0,0 @@
description: >
unPhone physical power slide switch. Reflects whether the switch is in the "on"
position and can arm deep-sleep wakeup for when the switch is moved to "on".
compatible: "unphone,power-switch"
properties:
pin:
type: phandles
required: true
description: GPIO pin connected to the power switch

View File

@ -10,8 +10,6 @@ hardware.spiRamMode=OCT
hardware.spiRamSpeed=80M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=3.5"

View File

@ -2,7 +2,4 @@ dependencies:
- Platforms/platform-esp32
- Drivers/hx8357-module
- Drivers/xpt2046-module
- Drivers/tca95xx-16bit-module
- Drivers/bq24295-module
bindings: bindings
dts: unphone.dts

View File

@ -1,72 +0,0 @@
#include "BatteryManager.h"
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h>
#include <drivers/bq24295.h>
constexpr auto* TAG = "unPhoneFeatures";
namespace expanderpin {
static constexpr gpio_pin_t EXPANDER_POWER = 0; // enable exp brd if high
}
bool BatteryManager::initGpioExpander() {
if (device_get_by_name("tca9535", &expander) != ERROR_NONE) {
LOG_E(TAG, "IO expander device not found");
return false;
}
expanderPowerPin = gpio_descriptor_acquire(expander, expanderpin::EXPANDER_POWER, GPIO_OWNER_GPIO);
check(expanderPowerPin != nullptr);
gpio_descriptor_set_flags(expanderPowerPin, GPIO_FLAG_DIRECTION_OUTPUT);
device_put(expander);
return true;
}
bool BatteryManager::init() {
LOG_I(TAG, "init");
if (!initGpioExpander()) {
LOG_E(TAG, "GPIO expander init failed");
return false;
}
return true;
}
bool BatteryManager::setExpanderPower(bool on) const {
return gpio_descriptor_set_level(expanderPowerPin, on) == ERROR_NONE;
}
void BatteryManager::turnPeripheralsOff() const {
setExpanderPower(false);
Device* backlight = nullptr;
check(device_get_by_name("display_backlight", &backlight) == ERROR_NONE);
backlight_set_brightness(backlight, 0); // Allowed to fail, we don't care about the result
device_put(backlight);
}
void BatteryManager::setShipping(bool on) const {
if (on) {
LOG_W(TAG, "setShipping: on");
bq24295_set_watchdog_timer(batteryManagement, BQ24295_WATCHDOG_DISABLED);
bq24295_set_bat_fet_on(batteryManagement, false);
} else {
LOG_W(TAG, "setShipping: off");
bq24295_set_watchdog_timer(batteryManagement, BQ24295_WATCHDOG_ENABLED_40S);
bq24295_set_bat_fet_on(batteryManagement, true);
}
}
bool BatteryManager::isUsbPowerConnected() const {
bool connected = false;
return bq24295_is_usb_power_connected(batteryManagement, &connected) == ERROR_NONE && connected;
}

View File

@ -1,41 +0,0 @@
#pragma once
#include <tactility/check.h>
#include <tactility/device.h>
struct Device;
struct GpioDescriptor;
/**
* Easy access to GPIO pins
*/
class BatteryManager final {
::Device* expander = nullptr;
::Device* batteryManagement = nullptr;
::GpioDescriptor* expanderPowerPin = nullptr;
bool initGpioExpander();
public:
explicit BatteryManager() {
check(device_get_by_name("bq24295", &batteryManagement) == ERROR_NONE);
}
~BatteryManager() {
device_put(batteryManagement);
}
bool init();
bool setExpanderPower(bool on) const;
void turnPeripheralsOff() const;
/** Battery management (BQ24295) will stop supplying power until USB connects */
void setShipping(bool on) const;
bool isUsbPowerConnected() const;
};

View File

@ -1,18 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/bindings/bindings.h>
#include <drivers/unphone_nav_buttons.h>
// The devicetree compiler derives the expected config typedef name from the compatible
// string's suffix (e.g. "unphone,nav-buttons" -> nav_buttons_config_dt), not from the
// node name or driver name, so the tag here must match that exactly.
DEFINE_DEVICETREE(nav_buttons, struct UnphoneNavButtonsConfig)
#ifdef __cplusplus
}
#endif

View File

@ -1,17 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/bindings/bindings.h>
#include <drivers/unphone_power_switch.h>
// The devicetree compiler derives the expected config typedef name from the compatible
// string's suffix (e.g. "unphone,power-switch" -> power_switch_config_dt), not from the
// node name or driver name, so the tag here must match that exactly.
DEFINE_DEVICETREE(power_switch, struct UnphonePowerSwitchConfig)
#ifdef __cplusplus
}
#endif

View File

@ -1,209 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include "unphone_nav_buttons.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <Tactility/Thread.h>
#include <Tactility/app/App.h>
#include <Tactility/kernel/Kernel.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <new>
#define TAG "UnphoneNavButtons"
#define GET_CONFIG(device) (static_cast<const UnphoneNavButtonsConfig*>((device)->config))
#define GET_INTERNAL(device) (static_cast<UnphoneNavButtonsInternal*>(device_get_driver_data(device)))
namespace {
// Only button 1 currently drives any behavior (stopping the running app); 2 and 3 are
// read and queued like button 1 but nothing consumes them yet (mirrors the original
// unPhone nav button behavior).
constexpr int BUTTON1_INDEX = 1;
constexpr int BUTTON2_INDEX = 2;
constexpr int BUTTON3_INDEX = 3;
}
struct UnphoneNavButtonsInternal {
GpioDescriptor* pin_button1 = nullptr;
GpioDescriptor* pin_button2 = nullptr;
GpioDescriptor* pin_button3 = nullptr;
QueueHandle_t event_queue = nullptr;
tt::Thread thread;
bool thread_interrupt_requested = false;
};
// region ISR callbacks
static void IRAM_ATTR on_button1(void* arg) {
auto* internal = static_cast<UnphoneNavButtonsInternal*>(arg);
int index = BUTTON1_INDEX;
xQueueSendFromISR(internal->event_queue, &index, nullptr);
}
static void IRAM_ATTR on_button2(void* arg) {
auto* internal = static_cast<UnphoneNavButtonsInternal*>(arg);
int index = BUTTON2_INDEX;
xQueueSendFromISR(internal->event_queue, &index, nullptr);
}
static void IRAM_ATTR on_button3(void* arg) {
auto* internal = static_cast<UnphoneNavButtonsInternal*>(arg);
int index = BUTTON3_INDEX;
xQueueSendFromISR(internal->event_queue, &index, nullptr);
}
// endregion
// region Consumer thread
static int32_t nav_buttons_thread_main(UnphoneNavButtonsInternal* internal) {
int button_index;
while (!internal->thread_interrupt_requested) {
if (xQueueReceive(internal->event_queue, &button_index, portMAX_DELAY)) {
// The buttons might generate more than 1 click because of how they are built
LOG_I(TAG, "Pressed button %d", button_index);
if (button_index == BUTTON1_INDEX) {
tt::app::stop();
}
// Debounce all events for a short period of time
// This is easier than keeping track when each button was last pressed
tt::kernel::delayMillis(50);
xQueueReset(internal->event_queue);
tt::kernel::delayMillis(50);
xQueueReset(internal->event_queue);
}
}
return 0;
}
// endregion
// region Pin acquisition
static error_t acquire_button(const GpioPinSpec& pin, void (*callback)(void*), void* arg, GpioDescriptor** out_descriptor) {
auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
return ERROR_RESOURCE;
}
// Digital pull-up; the buttons pull the pin low while pressed. Listen for the release
// (positive edge) rather than the press - if we listen to the press, these buttons can
// generate more than one signal when held down (mirrors the original unPhone init).
gpio_flags_t flags = GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP;
flags = GPIO_FLAG_INTERRUPT_TO_OPTIONS(flags, GPIO_INTERRUPT_POS_EDGE);
error_t error = gpio_descriptor_set_flags(descriptor, flags);
if (error == ERROR_NONE) {
error = gpio_descriptor_add_callback(descriptor, callback, arg);
}
if (error == ERROR_NONE) {
error = gpio_descriptor_enable_interrupt(descriptor);
}
if (error != ERROR_NONE) {
gpio_descriptor_remove_callback(descriptor);
gpio_descriptor_release(descriptor);
return error;
}
*out_descriptor = descriptor;
return ERROR_NONE;
}
static void release_button(GpioDescriptor*& descriptor) {
if (descriptor == nullptr) {
return;
}
gpio_descriptor_disable_interrupt(descriptor);
gpio_descriptor_remove_callback(descriptor);
gpio_descriptor_release(descriptor);
descriptor = nullptr;
}
// endregion
extern "C" {
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = new(std::nothrow) UnphoneNavButtonsInternal();
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->event_queue = xQueueCreate(4, sizeof(int));
if (internal->event_queue == nullptr) {
delete internal;
return ERROR_OUT_OF_MEMORY;
}
error_t error = acquire_button(config->pin_button1, on_button1, internal, &internal->pin_button1);
if (error == ERROR_NONE) {
error = acquire_button(config->pin_button2, on_button2, internal, &internal->pin_button2);
}
if (error == ERROR_NONE) {
error = acquire_button(config->pin_button3, on_button3, internal, &internal->pin_button3);
}
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to acquire nav button pins");
release_button(internal->pin_button1);
release_button(internal->pin_button2);
release_button(internal->pin_button3);
vQueueDelete(internal->event_queue);
delete internal;
return error;
}
internal->thread.setName("unphone_nav_buttons");
internal->thread.setPriority(tt::Thread::Priority::High);
internal->thread.setStackSize(3072);
internal->thread.setMainFunction([internal] { return nav_buttons_thread_main(internal); });
internal->thread.start();
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = GET_INTERNAL(device);
if (internal->thread.getState() != tt::Thread::State::Stopped) {
internal->thread_interrupt_requested = true;
internal->thread.join();
}
release_button(internal->pin_button1);
release_button(internal->pin_button2);
release_button(internal->pin_button3);
vQueueDelete(internal->event_queue);
device_set_driver_data(device, nullptr);
delete internal;
return ERROR_NONE;
}
extern Module unphone_module;
Driver unphone_nav_buttons_driver = {
.name = "unphone_nav_buttons",
.compatible = (const char*[]) { "unphone,nav-buttons", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &unphone_module,
.internal = nullptr
};
}

View File

@ -1,18 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/drivers/gpio.h>
struct UnphoneNavButtonsConfig {
struct GpioPinSpec pin_button1;
struct GpioPinSpec pin_button2;
struct GpioPinSpec pin_button3;
};
#ifdef __cplusplus
}
#endif

View File

@ -1,90 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include "unphone_power_switch.h"
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/error_esp32.h>
#include <tactility/log.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#define TAG "UnphonePowerSwitch"
#define GET_CONFIG(device) (static_cast<const UnphonePowerSwitchConfig*>((device)->config))
struct UnphonePowerSwitchInternal {
GpioDescriptor* descriptor;
gpio_num_t native_pin;
};
extern "C" {
extern Module unphone_module;
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* descriptor = gpio_descriptor_acquire(config->pin.gpio_controller, config->pin.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptor");
return ERROR_RESOURCE;
}
if (gpio_descriptor_set_flags(descriptor, config->pin.flags | GPIO_FLAG_DIRECTION_INPUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure power switch pin as input");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
gpio_num_t native_pin;
if (gpio_descriptor_get_native_pin_number(descriptor, &native_pin) != ERROR_NONE) {
LOG_E(TAG, "Power switch pin has no native pin number");
gpio_descriptor_release(descriptor);
return ERROR_NOT_SUPPORTED;
}
// Digital pull resistors are powered down during deep sleep; the RTC domain's own pull
// resistors are what actually hold the pin state while asleep, so both must be armed
// here (mirrors the original unPhone power switch init).
if (rtc_gpio_pullup_en(native_pin) != ESP_OK || rtc_gpio_pulldown_en(native_pin) != ESP_OK) {
LOG_E(TAG, "Failed to configure RTC pull resistors for power switch");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
auto* internal = new UnphonePowerSwitchInternal { .descriptor = descriptor, .native_pin = native_pin };
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<UnphonePowerSwitchInternal*>(device_get_driver_data(device));
gpio_descriptor_release(internal->descriptor);
delete internal;
return ERROR_NONE;
}
error_t unphone_power_switch_is_on(Device* device, bool* on) {
auto* internal = static_cast<UnphonePowerSwitchInternal*>(device_get_driver_data(device));
return gpio_descriptor_get_level(internal->descriptor, on);
}
error_t unphone_power_switch_enable_wake(Device* device) {
auto* internal = static_cast<UnphonePowerSwitchInternal*>(device_get_driver_data(device));
auto esp_error = esp_sleep_enable_ext0_wakeup(internal->native_pin, 1);
return esp_err_to_error(esp_error);
}
Driver unphone_power_switch_driver = {
.name = "unphone_power_switch",
.compatible = (const char*[]) { "unphone,power-switch", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &unphone_module,
.internal = nullptr
};
}

View File

@ -1,28 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <tactility/device.h>
#include <tactility/drivers/gpio.h>
#include <tactility/error.h>
struct UnphonePowerSwitchConfig {
struct GpioPinSpec pin;
};
/** @brief Reads whether the physical power switch is currently in the "on" position. */
error_t unphone_power_switch_is_on(struct Device* device, bool* on);
/**
* @brief Arms deep-sleep wakeup so the device wakes when the switch moves to "on".
* Only applies to the deep sleep entered right after this call.
*/
error_t unphone_power_switch_enable_wake(struct Device* device);
#ifdef __cplusplus
}
#endif

View File

@ -1,108 +0,0 @@
#include "BatteryManager.h"
#include "drivers/unphone_power_switch.h"
#include <tactility/device.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_sleep.h>
#include <memory>
#include <Tactility/LogMessages.h>
#include <Tactility/Thread.h>
constexpr auto* TAG = "unPhone";
std::shared_ptr<BatteryManager> batteryManagement;
static std::unique_ptr<tt::Thread> powerThread;
enum class PowerState {
Initial,
On,
Off
};
static void update_power_switch(Device* power_switch) {
static PowerState last_state = PowerState::Initial;
bool power_switch_on = false;
check(unphone_power_switch_is_on(power_switch, &power_switch_on) == ERROR_NONE);
if (!power_switch_on) {
if (last_state != PowerState::Off) {
last_state = PowerState::Off;
LOG_W(TAG, "Power off");
}
if (!batteryManagement->isUsbPowerConnected()) { // and usb unplugged we go into shipping mode
LOG_W(TAG, "Shipping mode until USB connects");
batteryManagement->turnPeripheralsOff();
batteryManagement->setShipping(true); // tell BM to stop supplying power until USB connects
} else { // When power switch is off, but USB is plugged in, we wait (deep sleep) until USB is unplugged.
LOG_W(TAG, "Waiting for USB disconnect to power off");
batteryManagement->turnPeripheralsOff();
// Deep sleep for 1 minute, then awaken to check power state again
// GPIO trigger from power switch also awakens the device
unphone_power_switch_enable_wake(power_switch);
esp_sleep_enable_timer_wakeup(60000000);
esp_deep_sleep_start();
}
} else {
if (last_state != PowerState::On) {
last_state = PowerState::On;
LOG_I(TAG, "Power on");
}
}
}
static int32_t power_switch_thread_main() { // check power switch every 10th of sec
Device* power_switch = nullptr;
check(device_get_by_name("power_switch", &power_switch) == ERROR_NONE);
while (true) {
update_power_switch(power_switch);
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
static void power_switch_thread_start() {
powerThread = std::make_unique<tt::Thread>(
"unphone_power_switch",
4096,
[]() -> int32_t { return power_switch_thread_main(); }
);
powerThread->start();
}
static bool power_on() {
batteryManagement = std::make_shared<BatteryManager>();
if (!batteryManagement->init()) {
LOG_E(TAG, "UnPhoneFeatures init failed");
return false;
}
batteryManagement->setExpanderPower(false);
// Turn off the device if power switch is on off state,
// instead of waiting for the Thread to start and continue booting
Device* power_switch = nullptr;
check(device_get_by_name("power_switch", &power_switch) == ERROR_NONE);
update_power_switch(power_switch);
device_put(power_switch);
power_switch_thread_start();
return true;
}
bool init_boot() {
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
if (!power_on()) {
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
return false;
}
return true;
}

View File

@ -1,66 +0,0 @@
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/device_listener.h>
#include <tactility/driver.h>
#include <tactility/error.h>
#include <tactility/module.h>
#include <cstring>
bool init_boot();
extern "C" {
extern Driver unphone_power_switch_driver;
extern Driver unphone_nav_buttons_driver;
// The root device (the only device with no parent) reaching DEVICE_EVENT_STARTED means the
// whole devicetree has finished constructing/starting, so it's now safe to power on the
// unPhone-specific peripherals that initBoot() depends on (e.g. the bq24295 fuel gauge).
static void on_device_event(Device* device, DeviceEvent event, void* context) {
(void)context;
static bool has_power_switch = false;
static bool has_bq24295 = false;
static bool has_backlight = false;
static bool did_init = false;
if (event == DEVICE_EVENT_STARTED) {
if (strcmp(device->name, "power_switch") == 0) { has_power_switch = true; }
if (strcmp(device->name, "bq24295") == 0) { has_bq24295 = true; }
if (strcmp(device->name, "display_backlight") == 0) { has_backlight = true; }
}
if (!did_init && has_power_switch && has_bq24295 && has_backlight) {
check(init_boot(), "unPhone initBoot failed");
did_init = true;
}
}
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);
return ERROR_NONE;
}
static error_t stop() {
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;
}
struct Module unphone_module = {
.name = "unphone",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -9,12 +9,6 @@
#include <tactility/bindings/esp32_sdspi.h>
#include <bindings/hx8357.h>
#include <bindings/xpt2046.h>
#include <bindings/tca95xx.h>
#include <bindings/bq24295.h>
#include <bindings/unphone_power_switch.h>
#include <bindings/unphone_nav_buttons.h>
#include <tactility/bindings/esp32_gpio_backlight.h>
#include <tactility/bindings/gpio_hog.h>
/ {
compatible = "root";
@ -41,69 +35,6 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 3 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 4 GPIO_FLAG_NONE>;
tca9535 {
compatible = "ti,tca9535";
reg = <0x26>;
};
bq24295 {
compatible = "ti,bq24295";
reg = <0x6B>;
};
};
power_switch {
compatible = "unphone,power-switch";
pin = <&gpio0 18 GPIO_FLAG_NONE>;
};
nav_buttons {
compatible = "unphone,nav-buttons";
pin-button1 = <&gpio0 45 GPIO_FLAG_NONE>; // left button
pin-button2 = <&gpio0 0 GPIO_FLAG_NONE>; // middle button
pin-button3 = <&gpio0 21 GPIO_FLAG_NONE>; // right button
};
display_backlight {
compatible = "espressif,esp32-gpio-backlight";
pin-backlight = <&tca9535 2 GPIO_FLAG_NONE>;
};
usb_vsense {
compatible = "tactility,gpio-hog";
pin = <&tca9535 14 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_INPUT>;
};
vibration_motor {
compatible = "tactility,gpio-hog";
pin = <&tca9535 7 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
ir_leds {
compatible = "tactility,gpio-hog";
pin = <&gpio0 12 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
red_led {
compatible = "tactility,gpio-hog";
pin = <&gpio0 13 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
green_led {
compatible = "tactility,gpio-hog";
pin = <&tca9535 9 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
blue_led {
compatible = "tactility,gpio-hog";
pin = <&tca9535 13 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
spi0 {
@ -125,14 +56,12 @@
pixel-clock-hz = <26000000>;
pin-dc = <&gpio0 47 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 46 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
touch@1 {
compatible = "xptek,xpt2046";
x-max = <320>;
y-max = <480>;
power-supply;
};
sdcard@2 {

View File

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

View File

@ -0,0 +1,5 @@
# BQ24295
Power management: I2C-controlled 3A single cell USB charger with narrow VDC 4.5-5.5V adjustable voltage at 1.5A synchronous boost operation.
[Datasheet](https://www.ti.com/lit/ds/symlink/bq24295.pdf)

View File

@ -0,0 +1,105 @@
#include "Bq24295.h"
#include <tactility/log.h>
constexpr auto* TAG = "BQ24295";
/** Reference:
* https://www.ti.com/lit/ds/symlink/bq24295.pdf
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
*/
namespace registers {
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
} // namespace registers
bool Bq24295::readChargeTermination(uint8_t& out) const {
return readRegister8(registers::CHARGE_TERMINATION, out);
}
// region Watchdog
bool Bq24295::getWatchDogTimer(WatchDogTimer& out) const {
uint8_t value;
if (readChargeTermination(value)) {
uint8_t relevant_bits = value & (BIT(4) | BIT(5));
switch (relevant_bits) {
case 0b000000:
out = WatchDogTimer::Disabled;
return true;
case 0b010000:
out = WatchDogTimer::Enabled40s;
return true;
case 0b100000:
out = WatchDogTimer::Enabled80s;
return true;
case 0b110000:
out = WatchDogTimer::Enabled160s;
return true;
default:
return false;
}
}
return false;
}
bool Bq24295::setWatchDogTimer(WatchDogTimer in) const {
uint8_t value;
if (readChargeTermination(value)) {
uint8_t bits_to_set = 0b00110000 & static_cast<uint8_t>(in);
uint8_t value_cleared = value & 0b11001111;
uint8_t to_set = bits_to_set | value_cleared;
LOG_I(TAG, "WatchDogTimer: %02X -> %02X", value, to_set);
return writeRegister8(registers::CHARGE_TERMINATION, to_set);
}
return false;
}
// endregoin
// region Operation Control (REG07)
bool Bq24295::setBatFetOn(bool on) const {
if (on) {
// bit 5 low means bat fet is on
return bitOff(registers::OPERATION_CONTROL, BIT(5));
} else {
// bit 5 high means bat fet is off
return bitOn(registers::OPERATION_CONTROL, BIT(5));
}
}
// endregion
// region Other
bool Bq24295::getStatus(uint8_t& value) const {
return readRegister8(registers::STATUS, value);
}
bool Bq24295::isUsbPowerConnected() const {
uint8_t status;
if (getStatus(status)) {
return (status & BIT(2)) != 0U;
} else {
return false;
}
}
bool Bq24295::getVersion(uint8_t& value) const {
return readRegister8(registers::VERSION, value);
}
void Bq24295::printInfo() const {
uint8_t version, status, charge_termination;
if (getStatus(status) && getVersion(version) && readChargeTermination(charge_termination)) {
LOG_I(TAG, "Version %d, status %02X, charge termination %02X", version, status, charge_termination);
} else {
LOG_E(TAG, "Failed to retrieve version and/or status");
}
}
// endregion

View File

@ -0,0 +1,38 @@
#pragma once
#include <Tactility/hal/i2c/I2cDevice.h>
#define BQ24295_ADDRESS 0x6BU
class Bq24295 final : public tt::hal::i2c::I2cDevice {
bool readChargeTermination(uint8_t& out) const;
public:
std::string getName() const final { return "BQ24295"; }
std::string getDescription() const final { return "I2C-controlled single cell USB charger"; }
enum class WatchDogTimer {
Disabled = 0b000000,
Enabled40s = 0b010000,
Enabled80s = 0b100000,
Enabled160s = 0b110000
};
explicit Bq24295(::Device* controller) : I2cDevice(controller, BQ24295_ADDRESS) {}
bool getWatchDogTimer(WatchDogTimer& out) const;
bool setWatchDogTimer(WatchDogTimer in) const;
bool isUsbPowerConnected() const;
bool setBatFetOn(bool on) const;
bool getStatus(uint8_t& value) const;
bool getVersion(uint8_t& value) const;
void printInfo() const;
};

View File

@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(bq24295-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel
)

View File

@ -1,195 +0,0 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,7 +0,0 @@
# BQ24295 USB charger
A driver for the TI `BQ24295` single-cell Li-ion USB battery charger: watchdog timer
control, BATFET (battery FET) enable/disable for shipping mode, USB power-good detection,
and status/version readback.
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -1,5 +0,0 @@
description: TI BQ24295 single-cell USB battery charger
include: ["i2c-device.yaml"]
compatible: "ti,bq24295"

View File

@ -1,3 +0,0 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -1,15 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/bq24295.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(bq24295, struct Bq24295Config)
#ifdef __cplusplus
}
#endif

View File

@ -1,14 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module bq24295_module;
#ifdef __cplusplus
}
#endif

View File

@ -1,42 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <tactility/error.h>
struct Device;
#ifdef __cplusplus
extern "C" {
#endif
struct Bq24295Config {
/** Address on bus */
uint8_t address;
};
enum Bq24295WatchDogTimer {
BQ24295_WATCHDOG_DISABLED = 0b000000,
BQ24295_WATCHDOG_ENABLED_40S = 0b010000,
BQ24295_WATCHDOG_ENABLED_80S = 0b100000,
BQ24295_WATCHDOG_ENABLED_160S = 0b110000
};
error_t bq24295_get_watchdog_timer(struct Device* device, enum Bq24295WatchDogTimer* out);
error_t bq24295_set_watchdog_timer(struct Device* device, enum Bq24295WatchDogTimer in);
error_t bq24295_is_usb_power_connected(struct Device* device, bool* connected);
/** BATFET (battery FET): off disconnects the battery from the system (shipping mode). */
error_t bq24295_set_bat_fet_on(struct Device* device, bool on);
error_t bq24295_get_status(struct Device* device, uint8_t* value);
error_t bq24295_get_version(struct Device* device, uint8_t* value);
void bq24295_print_info(struct Device* device);
#ifdef __cplusplus
}
#endif

View File

@ -1,270 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/bq24295.h>
#include <bq24295_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/log.h>
#include <new>
#define TAG "BQ24295"
#define GET_CONFIG(device) (static_cast<const Bq24295Config*>((device)->config))
/** Reference:
* https://www.ti.com/lit/ds/symlink/bq24295.pdf
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
*/
static constexpr uint8_t REG_CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
static constexpr uint8_t REG_OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
static constexpr uint8_t REG_STATUS = 0x08U; // Datasheet page 38: System status
static constexpr uint8_t REG_VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50);
extern "C" {
extern Module bq24295_module;
static error_t read_charge_termination(Device* device, uint8_t* out) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
return i2c_controller_register8_get(parent, address, REG_CHARGE_TERMINATION, out, TIMEOUT);
}
error_t bq24295_get_watchdog_timer(Device* device, Bq24295WatchDogTimer* out) {
uint8_t value;
error_t err = read_charge_termination(device, &value);
if (err != ERROR_NONE) {
return err;
}
switch (value & 0b00110000) {
case BQ24295_WATCHDOG_DISABLED:
*out = BQ24295_WATCHDOG_DISABLED;
return ERROR_NONE;
case BQ24295_WATCHDOG_ENABLED_40S:
*out = BQ24295_WATCHDOG_ENABLED_40S;
return ERROR_NONE;
case BQ24295_WATCHDOG_ENABLED_80S:
*out = BQ24295_WATCHDOG_ENABLED_80S;
return ERROR_NONE;
case BQ24295_WATCHDOG_ENABLED_160S:
*out = BQ24295_WATCHDOG_ENABLED_160S;
return ERROR_NONE;
default:
return ERROR_RESOURCE;
}
}
error_t bq24295_set_watchdog_timer(Device* device, Bq24295WatchDogTimer in) {
uint8_t value;
error_t err = read_charge_termination(device, &value);
if (err != ERROR_NONE) {
return err;
}
uint8_t bits_to_set = 0b00110000 & static_cast<uint8_t>(in);
uint8_t value_cleared = value & 0b11001111;
uint8_t to_set = bits_to_set | value_cleared;
LOG_I(TAG, "WatchDogTimer: %02X -> %02X", value, to_set);
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
return i2c_controller_register8_set(parent, address, REG_CHARGE_TERMINATION, to_set, TIMEOUT);
}
error_t bq24295_set_bat_fet_on(Device* device, bool on) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
// bit 5 low means bat fet is on, bit 5 high means bat fet is off
if (on) {
return i2c_controller_register8_reset_bits(parent, address, REG_OPERATION_CONTROL, 1 << 5, TIMEOUT);
} else {
return i2c_controller_register8_set_bits(parent, address, REG_OPERATION_CONTROL, 1 << 5, TIMEOUT);
}
}
error_t bq24295_get_status(Device* device, uint8_t* value) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
return i2c_controller_register8_get(parent, address, REG_STATUS, value, TIMEOUT);
}
error_t bq24295_is_usb_power_connected(Device* device, bool* connected) {
uint8_t status;
error_t err = bq24295_get_status(device, &status);
if (err != ERROR_NONE) {
return err;
}
*connected = (status & (1 << 2)) != 0U;
return ERROR_NONE;
}
error_t bq24295_get_version(Device* device, uint8_t* value) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
return i2c_controller_register8_get(parent, address, REG_VERSION, value, TIMEOUT);
}
void bq24295_print_info(Device* device) {
uint8_t version, status, charge_termination;
if (bq24295_get_status(device, &status) == ERROR_NONE &&
bq24295_get_version(device, &version) == ERROR_NONE &&
read_charge_termination(device, &charge_termination) == ERROR_NONE) {
LOG_I(TAG, "Version %d, status %02X, charge termination %02X", version, status, charge_termination);
} else {
LOG_E(TAG, "Failed to retrieve version and/or status");
}
}
// region Power supply child device
static bool ps_supports_property(Device*, PowerSupplyProperty property) {
return property == POWER_SUPPLY_PROP_IS_CHARGING;
}
static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) {
if (property != POWER_SUPPLY_PROP_IS_CHARGING) {
return ERROR_NOT_SUPPORTED;
}
// device_get_parent() here is the bq24295 device itself (this child's parent), not the I2C bus.
uint8_t status;
error_t error = bq24295_get_status(device_get_parent(device), &status);
if (error != ERROR_NONE) {
return error;
}
// Datasheet REG08 bits[5:4] (CHRG_STAT): 0 = not charging, 1 = pre-charge,
// 2 = fast charge, 3 = charge termination done. Only 1|2 count as actively charging.
uint8_t charge_status = (status >> 4) & 0x03;
out_value->int_value = (charge_status == 1 || charge_status == 2) ? 1 : 0;
return ERROR_NONE;
}
static bool ps_supports_charge_control(Device*) { return false; }
static bool ps_is_allowed_to_charge(Device*) { return false; }
static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_quick_charge(Device*) { return false; }
static bool ps_is_quick_charge_enabled(Device*) { return false; }
static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_power_off(Device*) { return false; }
static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; }
static constexpr PowerSupplyApi BQ24295_POWER_SUPPLY_API = {
.supports_property = ps_supports_property,
.get_property = ps_get_property,
.supports_charge_control = ps_supports_charge_control,
.is_allowed_to_charge = ps_is_allowed_to_charge,
.set_allowed_to_charge = ps_set_allowed_to_charge,
.supports_quick_charge = ps_supports_quick_charge,
.is_quick_charge_enabled = ps_is_quick_charge_enabled,
.set_quick_charge_enabled = ps_set_quick_charge_enabled,
.supports_power_off = ps_supports_power_off,
.power_off = ps_power_off,
};
// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal,
// but never matched against a devicetree node: bq24295_driver wires it up directly by pointer.
Driver bq24295_power_supply_driver = {
.name = "bq24295-power-supply",
.compatible = (const char*[]) { "bq24295-power-supply", nullptr },
.start_device = nullptr,
.stop_device = nullptr,
.api = &BQ24295_POWER_SUPPLY_API,
.device_type = &POWER_SUPPLY_TYPE,
.owner = &bq24295_module,
.internal = nullptr
};
struct Bq24295Internal {
Device* power_supply_device = nullptr;
};
static error_t create_power_supply_child(Device* parent, Device*& out_child) {
auto* child = new(std::nothrow) Device { .address = 0, .name = "bq24295-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr };
if (child == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t error = device_construct(child);
if (error != ERROR_NONE) {
delete child;
return error;
}
device_set_parent(child, parent);
device_set_driver(child, &bq24295_power_supply_driver);
error = device_add(child);
if (error != ERROR_NONE) {
device_destruct(child);
delete child;
return error;
}
error = device_start(child);
if (error != ERROR_NONE) {
device_remove(child);
device_destruct(child);
delete child;
return error;
}
out_child = child;
return ERROR_NONE;
}
static void destroy_power_supply_child(Device* child) {
check(device_stop(child) == ERROR_NONE);
check(device_remove(child) == ERROR_NONE);
check(device_destruct(child) == ERROR_NONE);
delete child;
}
// endregion
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
auto* internal = new(std::nothrow) Bq24295Internal();
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t error = create_power_supply_child(device, internal->power_supply_device);
if (error != ERROR_NONE) {
delete internal;
return error;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Bq24295Internal*>(device_get_driver_data(device));
destroy_power_supply_child(internal->power_supply_device);
device_set_driver_data(device, nullptr);
delete internal;
return ERROR_NONE;
}
Driver bq24295_driver = {
.name = "bq24295",
.compatible = (const char*[]) { "ti,bq24295", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &bq24295_module,
.internal = nullptr
};
}

View File

@ -1,35 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver bq24295_driver;
extern Driver bq24295_power_supply_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&bq24295_driver) == ERROR_NONE);
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 = {
.name = "bq24295",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -208,7 +208,7 @@ static error_t start(Device* device) {
const auto* spi_config = static_cast<const Esp32SpiConfig*>(parent->config);
const auto* config = GET_CONFIG(device);
GpioPinSpec cs_pin;
struct GpioPinSpec cs_pin;
if (esp32_spi_get_cs_pin(device, &cs_pin) != ERROR_NONE) {
LOG_E(TAG, "Failed to resolve CS pin");
return ERROR_RESOURCE;
@ -251,13 +251,14 @@ static error_t start(Device* device) {
.queue_size = 1,
};
if (spi_bus_add_device(spi_config->host, &device_config, &internal->spi_handle) != ESP_OK) {
if (spi_bus_add_device((spi_host_device_t)spi_config->host, &device_config, &internal->spi_handle) != ESP_OK) {
LOG_E(TAG, "Failed to add SPI device");
free(internal);
return ERROR_RESOURCE;
}
// Hardware reset, in addition to the SWRESET the bring-up command list sends
// Hardware reset, in addition to the SWRESET the bring-up command list sends - matches the
// deprecated HAL's Hx8357Display::start(), which did both.
int reset_pin = pin_or_unused(config->pin_reset);
if (reset_pin != -1) {
gpio_config_t reset_config = {

View File

@ -3,10 +3,10 @@
#include <tactility/driver.h>
#include <tactility/module.h>
extern Driver hx8357_driver;
extern "C" {
extern Driver hx8357_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */

View File

@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(rgb-display-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd driver
)

View File

@ -1,195 +0,0 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,5 +0,0 @@
# RGB Display Driver
A kernel driver for ESP32 RGB (parallel timing-driven) LCD panels, built on ESP-IDF's `esp_lcd_rgb_panel` component. Unlike SPI/i8080 panels, an RGB panel owns its GPIOs directly and has no command interface, so it doesn't sit behind a shared bus controller device - kernel driver equivalent of the deprecated HAL's `RgbDisplay`.
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -1,224 +0,0 @@
description: >
ESP32 RGB (parallel timing-driven) LCD panel, built on ESP-IDF's esp_lcd_rgb_panel
component. Unlike SPI/i8080 panels, an RGB panel has no command interface and owns its
GPIOs directly rather than sitting behind a shared bus controller device.
compatible: "espressif,esp32-rgb-display"
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
pixel-clock-hz:
type: int
required: true
description: Pixel clock frequency in Hz
hsync-pulse-width:
type: int
required: true
description: Horizontal sync width, in PCLK periods
hsync-back-porch:
type: int
required: true
description: Number of PCLK periods between hsync and the start of line active data
hsync-front-porch:
type: int
required: true
description: Number of PCLK periods between the end of active data and the next hsync
vsync-pulse-width:
type: int
required: true
description: Vertical sync width, in lines
vsync-back-porch:
type: int
required: true
description: Number of invalid lines between vsync and the start of the frame
vsync-front-porch:
type: int
required: true
description: Number of invalid lines between the end of the frame and the next vsync
hsync-idle-low:
type: boolean
default: false
description: The hsync signal is low in the idle state
vsync-idle-low:
type: boolean
default: false
description: The vsync signal is low in the idle state
de-idle-high:
type: boolean
default: false
description: The DE signal is high in the idle state
pclk-active-neg:
type: boolean
default: false
description: Whether display data is clocked out on the falling edge of PCLK
pclk-idle-high:
type: boolean
default: false
description: The PCLK signal stays high in the idle phase
data-width:
type: int
default: 16
description: Number of parallel data lines wired (8 or 16)
bits-per-pixel:
type: int
default: 0
description: Frame buffer color depth in bpp. 0 defaults to data-width.
num-fbs:
type: int
default: 1
description: Number of screen-sized frame buffers to allocate (0 or 1 = single-buffered)
bounce-buffer-size-px:
type: int
default: 0
description: Non-zero enables the DRAM bounce-buffer DMA path, sized in pixels
sram-trans-align:
type: int
default: 8
description: Alignment of buffers allocated in internal SRAM
psram-trans-align:
type: int
default: 64
description: Alignment of buffers allocated in PSRAM
pin-hsync:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: HSYNC GPIO pin, optional for panels that only need DE (data-enable) sync
pin-vsync:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: VSYNC GPIO pin, optional for panels that only need DE (data-enable) sync
pin-de:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data-enable GPIO pin, optional
pin-pclk:
type: phandles
required: true
description: Pixel-clock GPIO pin
pin-disp:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Optional display-enable control GPIO pin
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Optional hardware reset pin for the panel's own driver IC, pulsed once at start()
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
pin-data0:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 0 GPIO pin
pin-data1:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 1 GPIO pin
pin-data2:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 2 GPIO pin
pin-data3:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 3 GPIO pin
pin-data4:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 4 GPIO pin
pin-data5:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 5 GPIO pin
pin-data6:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 6 GPIO pin
pin-data7:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 7 GPIO pin
pin-data8:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 8 GPIO pin
pin-data9:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 9 GPIO pin
pin-data10:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 10 GPIO pin
pin-data11:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 11 GPIO pin
pin-data12:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 12 GPIO pin
pin-data13:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 13 GPIO pin
pin-data14:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 14 GPIO pin
pin-data15:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 15 GPIO pin
disp-active-low:
type: boolean
default: false
description: A low level on the display-enable control signal turns the screen on
refresh-on-demand:
type: boolean
default: false
description: Only refresh the frame buffer on explicit draw_bitmap calls
fb-in-psram:
type: boolean
default: true
description: Allocate the frame buffer from PSRAM, preferentially
double-fb:
type: boolean
default: false
description: Allocate two screen-sized frame buffers (equivalent to num-fbs = 2)
no-fb:
type: boolean
default: false
description: Don't allocate a frame buffer; bounce buffer must be filled manually
bb-invalidate-cache:
type: boolean
default: false
description: Invalidate cache on bounce-buffer reads (can be dangerous with multi-core writers)
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
invert-color:
type: boolean
default: false
description: Invert the panel's color output
backlight:
type: phandle
default: "NULL"
description: Optional reference to this display's backlight device

View File

@ -1,3 +0,0 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -1,10 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/rgb_display.h>
// The devicetree compiler derives the expected config typedef name from the compatible
// string's suffix (e.g. "espressif,esp32-rgb-display" -> esp32_rgb_display_config_dt), not
// from the node name or driver name, so the tag here must match that exactly.
DEFINE_DEVICETREE(esp32_rgb_display, struct RgbDisplayConfig)

View File

@ -1,91 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/device.h>
#include <tactility/drivers/gpio.h>
struct RgbDisplayConfig {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
uint32_t pixel_clock_hz;
uint32_t hsync_pulse_width;
uint32_t hsync_back_porch;
uint32_t hsync_front_porch;
uint32_t vsync_pulse_width;
uint32_t vsync_back_porch;
uint32_t vsync_front_porch;
bool hsync_idle_low;
bool vsync_idle_low;
bool de_idle_high;
bool pclk_active_neg;
bool pclk_idle_high;
// Number of parallel data lines actually wired (8 or 16). Only the first data_width
// pin-dataN fields below are used; the rest are ignored.
uint8_t data_width;
// Frame buffer color depth in bpp. 0 defaults to data_width.
uint8_t bits_per_pixel;
// Number of screen-sized frame buffers the driver allocates (0 or 1 = single-buffered).
uint8_t num_fbs;
// Non-zero enables the DRAM bounce-buffer DMA path, sized in pixels.
uint32_t bounce_buffer_size_px;
uint32_t sram_trans_align;
uint32_t psram_trans_align;
struct GpioPinSpec pin_hsync;
struct GpioPinSpec pin_vsync;
struct GpioPinSpec pin_de;
struct GpioPinSpec pin_pclk;
// Optional display-enable control pin, GPIO_PIN_SPEC_NONE if unused.
struct GpioPinSpec pin_disp;
// Optional hardware reset pin for the panel's own driver IC, pulsed once at start() before
// the RGB peripheral is brought up. GPIO_PIN_SPEC_NONE if the panel has none (esp_lcd_rgb_panel
// itself has no reset concept - some RGB panels still have a separate reset line on their
// internal driver IC even though pixel data is fed over the RGB bus).
struct GpioPinSpec pin_reset;
bool reset_active_high;
struct GpioPinSpec pin_data0;
struct GpioPinSpec pin_data1;
struct GpioPinSpec pin_data2;
struct GpioPinSpec pin_data3;
struct GpioPinSpec pin_data4;
struct GpioPinSpec pin_data5;
struct GpioPinSpec pin_data6;
struct GpioPinSpec pin_data7;
struct GpioPinSpec pin_data8;
struct GpioPinSpec pin_data9;
struct GpioPinSpec pin_data10;
struct GpioPinSpec pin_data11;
struct GpioPinSpec pin_data12;
struct GpioPinSpec pin_data13;
struct GpioPinSpec pin_data14;
struct GpioPinSpec pin_data15;
bool disp_active_low;
bool refresh_on_demand;
bool fb_in_psram;
bool double_fb;
bool no_fb;
bool bb_invalidate_cache;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool invert_color;
// Optional reference to this display's backlight device, NULL if none.
struct Device* backlight;
};
#ifdef __cplusplus
}
#endif

View File

@ -1,14 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module rgb_display_module;
#ifdef __cplusplus
}
#endif

View File

@ -1,32 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver rgb_display_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&rgb_display_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(&rgb_display_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module rgb_display_module = {
.name = "rgb_display",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

@ -1,413 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <soc/soc_caps.h>
#if SOC_LCD_RGB_SUPPORTED
#include <drivers/rgb_display.h>
#include <rgb_display_module.h>
#include <tactility/delay.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_panel_rgb.h>
#include <esp_lcd_panel_ops.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <cstdlib>
#define TAG "RgbDisplay"
#define GET_CONFIG(device) (static_cast<const RgbDisplayConfig*>((device)->config))
// Generic lvgl-module display glue (Modules/lvgl-module/source/lvgl_display.c) only ever asks
// for frame buffer index 0 and 1, so caching more than that would be dead weight.
constexpr size_t MAX_CACHED_FRAME_BUFFERS = 2;
struct RgbDisplayInternal {
esp_lcd_panel_handle_t panel_handle;
void* frame_buffers[MAX_CACHED_FRAME_BUFFERS];
uint8_t frame_buffer_count;
// Signaled by on_frame_buf_complete once per real DMA scan-out of a whole frame. Only
// waited on in draw_bitmap() when frame_buffer_count > 0 - see the comment there for why.
SemaphoreHandle_t frame_complete_semaphore;
};
// esp_lcd_rgb_panel's draw_bitmap() has a zero-copy path when color_data is one of the panel's
// own frame buffers (as returned by esp_lcd_rgb_panel_get_frame_buffer()): it just repoints which
// buffer is scanned out and returns almost instantly - well before the RGB peripheral's DMA has
// actually finished scanning out the *previous* buffer, let alone started on this one. Callers in
// full/direct LVGL render mode render straight into these real frame buffers, so if draw_bitmap()
// returned that quickly, LVGL would be free to start overwriting the *other* buffer - which may
// still be mid-scanout - producing visible tearing/flashing. on_frame_buf_complete fires once per
// actual whole-frame DMA completion (continuously, at the panel's refresh rate, independent of
// draw_bitmap calls), so waiting for the next occurrence after each draw_bitmap() genuinely
// blocks until it's safe to start writing into the frame buffers again.
static bool IRAM_ATTR on_frame_buf_complete(esp_lcd_panel_handle_t, const esp_lcd_rgb_panel_event_data_t*, void* user_ctx) {
auto* internal = static_cast<RgbDisplayInternal*>(user_ctx);
BaseType_t high_task_woken = pdFALSE;
xSemaphoreGiveFromISR(internal->frame_complete_semaphore, &high_task_woken);
return high_task_woken == pdTRUE;
}
static int pin_or_unused(const GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
}
// Pulses the panel's own driver-IC reset line, if configured. Transient: the descriptor is
// released immediately after, since nothing else needs to touch this pin afterward.
static error_t perform_hardware_reset(const RgbDisplayConfig* config) {
if (config->pin_reset.gpio_controller == nullptr) {
return ERROR_NONE;
}
auto* descriptor = gpio_descriptor_acquire(config->pin_reset.gpio_controller, config->pin_reset.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire reset GPIO descriptor");
return ERROR_RESOURCE;
}
bool ok = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE;
ok = ok && gpio_descriptor_set_level(descriptor, config->reset_active_high) == ERROR_NONE;
if (ok) {
delay_millis(100);
ok = gpio_descriptor_set_level(descriptor, !config->reset_active_high) == ERROR_NONE;
delay_millis(10);
}
gpio_descriptor_release(descriptor);
return ok ? ERROR_NONE : ERROR_RESOURCE;
}
// region Driver lifecycle
static error_t cache_frame_buffers(RgbDisplayInternal* internal, const RgbDisplayConfig* config) {
internal->frame_buffer_count = 0;
if (config->num_fbs == 0) {
return ERROR_NONE;
}
// esp_lcd_rgb_panel_get_frame_buffer() is variadic: the number of out-pointer arguments
// passed must match fb_num exactly, so this can't be a loop.
size_t fb_num = config->num_fbs < MAX_CACHED_FRAME_BUFFERS ? config->num_fbs : MAX_CACHED_FRAME_BUFFERS;
esp_err_t ret;
switch (fb_num) {
case 1:
ret = esp_lcd_rgb_panel_get_frame_buffer(internal->panel_handle, 1, &internal->frame_buffers[0]);
break;
case 2:
ret = esp_lcd_rgb_panel_get_frame_buffer(internal->panel_handle, 2, &internal->frame_buffers[0], &internal->frame_buffers[1]);
break;
default:
return ERROR_NONE;
}
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to get frame buffer(s): %s", esp_err_to_name(ret));
return ERROR_RESOURCE;
}
internal->frame_buffer_count = (uint8_t)fb_num;
return ERROR_NONE;
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<RgbDisplayInternal*>(malloc(sizeof(RgbDisplayInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t reset_error = perform_hardware_reset(config);
if (reset_error != ERROR_NONE) {
LOG_E(TAG, "Failed to reset panel");
free(internal);
return reset_error;
}
esp_lcd_rgb_panel_config_t panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = config->pixel_clock_hz,
.h_res = config->horizontal_resolution,
.v_res = config->vertical_resolution,
.hsync_pulse_width = config->hsync_pulse_width,
.hsync_back_porch = config->hsync_back_porch,
.hsync_front_porch = config->hsync_front_porch,
.vsync_pulse_width = config->vsync_pulse_width,
.vsync_back_porch = config->vsync_back_porch,
.vsync_front_porch = config->vsync_front_porch,
.flags = {
.hsync_idle_low = config->hsync_idle_low,
.vsync_idle_low = config->vsync_idle_low,
.de_idle_high = config->de_idle_high,
.pclk_active_neg = config->pclk_active_neg,
.pclk_idle_high = config->pclk_idle_high,
}
},
.data_width = config->data_width,
.bits_per_pixel = config->bits_per_pixel,
.num_fbs = config->num_fbs,
.bounce_buffer_size_px = config->bounce_buffer_size_px,
.sram_trans_align = config->sram_trans_align,
.psram_trans_align = config->psram_trans_align,
.hsync_gpio_num = pin_or_unused(config->pin_hsync),
.vsync_gpio_num = pin_or_unused(config->pin_vsync),
.de_gpio_num = pin_or_unused(config->pin_de),
.pclk_gpio_num = pin_or_unused(config->pin_pclk),
.disp_gpio_num = pin_or_unused(config->pin_disp),
.data_gpio_nums = {
pin_or_unused(config->pin_data0),
pin_or_unused(config->pin_data1),
pin_or_unused(config->pin_data2),
pin_or_unused(config->pin_data3),
pin_or_unused(config->pin_data4),
pin_or_unused(config->pin_data5),
pin_or_unused(config->pin_data6),
pin_or_unused(config->pin_data7),
pin_or_unused(config->pin_data8),
pin_or_unused(config->pin_data9),
pin_or_unused(config->pin_data10),
pin_or_unused(config->pin_data11),
pin_or_unused(config->pin_data12),
pin_or_unused(config->pin_data13),
pin_or_unused(config->pin_data14),
pin_or_unused(config->pin_data15),
},
.flags = {
.disp_active_low = config->disp_active_low,
.refresh_on_demand = config->refresh_on_demand,
.fb_in_psram = config->fb_in_psram,
.double_fb = config->double_fb,
.no_fb = config->no_fb,
.bb_invalidate_cache = config->bb_invalidate_cache,
}
};
// This Config struct only exposes 16 named data pins, so on chips whose RGB peripheral has
// more data lines than that (e.g. ESP32-P4's 24), the tail of the array must be explicitly
// marked unused rather than left as the aggregate-init default of 0 (which would look like
// "GPIO0 is wired to this line").
for (size_t i = 16; i < sizeof(panel_config.data_gpio_nums) / sizeof(panel_config.data_gpio_nums[0]); i++) {
panel_config.data_gpio_nums[i] = -1;
}
esp_err_t ret = esp_lcd_new_rgb_panel(&panel_config, &internal->panel_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret));
free(internal);
return ERROR_RESOURCE;
}
bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_swap_xy(internal->panel_handle, config->swap_xy) == ESP_OK &&
esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK &&
esp_lcd_panel_invert_color(internal->panel_handle, config->invert_color) == ESP_OK;
if (!ok) {
LOG_E(TAG, "Failed to bring up panel");
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_RESOURCE;
}
error_t error = cache_frame_buffers(internal, config);
if (error != ERROR_NONE) {
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return error;
}
internal->frame_complete_semaphore = xSemaphoreCreateBinary();
if (internal->frame_complete_semaphore == nullptr) {
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_OUT_OF_MEMORY;
}
esp_lcd_rgb_panel_event_callbacks_t callbacks = {};
callbacks.on_frame_buf_complete = on_frame_buf_complete;
if (esp_lcd_rgb_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
LOG_E(TAG, "Failed to register panel event callbacks");
vSemaphoreDelete(internal->frame_complete_semaphore);
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
vSemaphoreDelete(internal->frame_complete_semaphore);
free(internal);
return ERROR_RESOURCE;
}
vSemaphoreDelete(internal->frame_complete_semaphore);
free(internal);
return ERROR_NONE;
}
// endregion
// region DisplayApi
static error_t rgb_display_reset(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_init(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
// Only block for scan-out completion when the caller could be writing straight into one of
// the panel's own frame buffers (see on_frame_buf_complete's comment above for why that
// matters). With no real frame buffer of ours involved (frame_buffer_count == 0, e.g. LVGL
// partial-render mode with its own separate buffer), draw_bitmap does a real memcpy into the
// panel's buffer and there's no reuse race to guard against, so don't pay the up-to-one-frame
// latency cost for every small partial update.
bool wait_for_scanout = internal->frame_buffer_count > 0;
if (wait_for_scanout) {
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
}
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
return ERROR_RESOURCE;
}
if (wait_for_scanout) {
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
}
return ERROR_NONE;
}
static error_t rgb_display_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static bool rgb_display_get_swap_xy(Device* device) {
return GET_CONFIG(device)->swap_xy;
}
static bool rgb_display_get_mirror_x(Device* device) {
return GET_CONFIG(device)->mirror_x;
}
static bool rgb_display_get_mirror_y(Device* device) {
return GET_CONFIG(device)->mirror_y;
}
// RGB panels are raw scan-out framebuffers with no addressable-window concept the way MIPI/SPI
// panels have, so there's no gap to set.
static error_t rgb_display_set_gap(Device*, int32_t, int32_t) {
return ERROR_NOT_SUPPORTED;
}
static error_t rgb_display_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// RGB panels have no MIPI DCS command interface, so there's no sleep mode to enter.
static error_t rgb_display_disp_sleep(Device*, bool) {
return ERROR_NOT_SUPPORTED;
}
static enum DisplayColorFormat rgb_display_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565;
}
static uint16_t rgb_display_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t rgb_display_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void rgb_display_get_frame_buffer(Device* device, uint8_t index, void** out_buffer) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
*out_buffer = index < internal->frame_buffer_count ? internal->frame_buffers[index] : nullptr;
}
static uint8_t rgb_display_get_frame_buffer_count(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return internal->frame_buffer_count;
}
static error_t rgb_display_get_backlight(Device* device, Device** backlight) {
auto* configured_backlight = GET_CONFIG(device)->backlight;
if (configured_backlight == nullptr) {
return ERROR_NOT_SUPPORTED;
}
*backlight = configured_backlight;
return ERROR_NONE;
}
// endregion
static const DisplayApi rgb_display_api = {
.reset = rgb_display_reset,
.init = rgb_display_init,
.draw_bitmap = rgb_display_draw_bitmap,
.mirror = rgb_display_mirror,
.swap_xy = rgb_display_swap_xy,
.get_swap_xy = rgb_display_get_swap_xy,
.get_mirror_x = rgb_display_get_mirror_x,
.get_mirror_y = rgb_display_get_mirror_y,
.set_gap = rgb_display_set_gap,
.invert_color = rgb_display_invert_color,
.disp_on_off = rgb_display_disp_on_off,
.disp_sleep = rgb_display_disp_sleep,
.get_color_format = rgb_display_get_color_format,
.get_resolution_x = rgb_display_get_resolution_x,
.get_resolution_y = rgb_display_get_resolution_y,
.get_frame_buffer = rgb_display_get_frame_buffer,
.get_frame_buffer_count = rgb_display_get_frame_buffer_count,
.get_backlight = rgb_display_get_backlight,
};
Driver rgb_display_driver = {
.name = "rgb_display",
.compatible = (const char*[]) { "espressif,esp32-rgb-display", nullptr },
.start_device = start,
.stop_device = stop,
.api = &rgb_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &rgb_display_module,
.internal = nullptr
};
#endif // SOC_LCD_RGB_SUPPORTED

View File

@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(tca95xx-16bit-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel
)

View File

@ -1,195 +0,0 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,11 +0,0 @@
# TCA9535 / TCA9539 I/O expander
A driver for the TI `TCA9535` and `TCA9539` 16-bit I2C-bus I/O expanders. Both parts
share the same PCA9555-compatible register map (differing only in package and interrupt
pin): two 8-bit ports for input, output, polarity inversion, and direction, addressed as
pins 0-15 (port 0 = pins 0-7, port 1 = pins 8-15).
It does not support pull-up/down resistors or high-impedance outputs; requesting those
flags returns `ERROR_NOT_SUPPORTED`.
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -1,5 +0,0 @@
description: TI TCA9535 16-bit I2C-bus I/O expander (PCA9555-register-compatible)
include: ["i2c-device.yaml"]
compatible: "ti,tca9535"

View File

@ -1,5 +0,0 @@
description: TI TCA9539 16-bit I2C-bus I/O expander (PCA9555-register-compatible)
include: ["i2c-device.yaml"]
compatible: "ti,tca9539"

View File

@ -1,3 +0,0 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -1,21 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/tca95xx.h>
#ifdef __cplusplus
extern "C" {
#endif
// One DEFINE_DEVICETREE per compatible string -- the devicetree compiler derives the
// expected config typedef name from the compatible string's suffix (e.g. "ti,tca9535"
// -> tca9535_config_dt), not from a name we choose, so each supported chip needs its own
// typedef even though they share the same underlying config layout (see dummy-i2s-amp-module's
// bindings header for the same pattern).
DEFINE_DEVICETREE(tca9535, struct Tca95xxConfig)
DEFINE_DEVICETREE(tca9539, struct Tca95xxConfig)
#ifdef __cplusplus
}
#endif

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Tca95xxConfig {
/** Address on bus */
uint8_t address;
};
#ifdef __cplusplus
}
#endif

View File

@ -1,16 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
// The devicetree compiler derives the expected module symbol name from this component's
// folder name ("tca95xx-16bit-module" -> tca95xx_16bit_module), not from a name we choose.
extern struct Module tca95xx_16bit_module;
#ifdef __cplusplus
}
#endif

View File

@ -1,32 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver tca95xx_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&tca95xx_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(&tca95xx_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module tca95xx_16bit_module = {
.name = "tca95xx",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -1,194 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/tca95xx.h>
#include <tca95xx_module.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#define TAG "TCA95XX"
#define GET_CONFIG(device) (static_cast<const Tca95xxConfig*>((device)->config))
// PCA9555-compatible register map: one register per function per 8-pin port.
constexpr auto TCA95XX_REGISTER_INPUT_PORT0 = 0x00;
constexpr auto TCA95XX_REGISTER_OUTPUT_PORT0 = 0x02;
constexpr auto TCA95XX_REGISTER_POLARITY_PORT0 = 0x04;
constexpr auto TCA95XX_REGISTER_CONFIG_PORT0 = 0x06;
static inline uint8_t port_of(GpioDescriptor* descriptor) {
return descriptor->pin >> 3;
}
static inline uint8_t bit_of(GpioDescriptor* descriptor) {
return 1 << (descriptor->pin & 0x7);
}
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
return gpio_controller_init_descriptors(device, 16, nullptr);
}
static error_t stop(Device* device) {
check(gpio_controller_deinit_descriptors(device) == ERROR_NONE);
return ERROR_NONE;
}
extern "C" {
static error_t set_level(GpioDescriptor* descriptor, bool high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto reg = static_cast<uint8_t>(TCA95XX_REGISTER_OUTPUT_PORT0 + port_of(descriptor));
auto bit = bit_of(descriptor);
// i2c_controller_register8_{set,reset}_bits() do a separate read then
// write; without this lock, concurrent updates to different pins on the
// same output port register can clobber each other.
device_lock(device);
error_t err = high
? i2c_controller_register8_set_bits(parent, address, reg, bit, portMAX_DELAY)
: i2c_controller_register8_reset_bits(parent, address, reg, bit, portMAX_DELAY);
device_unlock(device);
return err;
}
static error_t get_level(GpioDescriptor* descriptor, bool* high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto reg = static_cast<uint8_t>(TCA95XX_REGISTER_INPUT_PORT0 + port_of(descriptor));
uint8_t bits;
error_t err = i2c_controller_register8_get(parent, address, reg, &bits, portMAX_DELAY);
if (err != ERROR_NONE) {
return err;
}
*high = (bits & bit_of(descriptor)) != 0;
return ERROR_NONE;
}
static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
// The TCA95xx only supports direction and polarity inversion. Pull-up/down and
// high-impedance are not present in its PCA9555-compatible register map.
if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN | GPIO_FLAG_HIGH_IMPEDANCE)) {
return ERROR_NOT_SUPPORTED;
}
// The polarity register only inverts what's read back from an input pin;
// set_level() still drives outputs at the raw level. Accepting ACTIVE_LOW
// on an output would silently not do what it implies.
if ((flags & GPIO_FLAG_ACTIVE_LOW) && (flags & GPIO_FLAG_DIRECTION_OUTPUT)) {
return ERROR_NOT_SUPPORTED;
}
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto config_reg = static_cast<uint8_t>(TCA95XX_REGISTER_CONFIG_PORT0 + port_of(descriptor));
auto polarity_reg = static_cast<uint8_t>(TCA95XX_REGISTER_POLARITY_PORT0 + port_of(descriptor));
auto bit = bit_of(descriptor);
error_t err;
// Locked as a whole: direction and polarity are two separate RMW register
// writes, and both should apply atomically with respect to other set_flags
// / set_level calls on this device.
device_lock(device);
// Direction: configuration bit is 1 for input, 0 for output.
if (flags & GPIO_FLAG_DIRECTION_OUTPUT) {
err = i2c_controller_register8_reset_bits(parent, address, config_reg, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_set_bits(parent, address, config_reg, bit, portMAX_DELAY);
}
if (err != ERROR_NONE) {
device_unlock(device);
return err;
}
// Polarity inversion (mainly relevant for active-low inputs).
if (flags & GPIO_FLAG_ACTIVE_LOW) {
err = i2c_controller_register8_set_bits(parent, address, polarity_reg, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_reset_bits(parent, address, polarity_reg, bit, portMAX_DELAY);
}
device_unlock(device);
return err;
}
static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto config_reg = static_cast<uint8_t>(TCA95XX_REGISTER_CONFIG_PORT0 + port_of(descriptor));
auto polarity_reg = static_cast<uint8_t>(TCA95XX_REGISTER_POLARITY_PORT0 + port_of(descriptor));
auto bit = bit_of(descriptor);
uint8_t val;
error_t err;
gpio_flags_t f = GPIO_FLAG_NONE;
err = i2c_controller_register8_get(parent, address, config_reg, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
f |= (val & bit) ? GPIO_FLAG_DIRECTION_INPUT : GPIO_FLAG_DIRECTION_OUTPUT;
err = i2c_controller_register8_get(parent, address, polarity_reg, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
f |= (val & bit) ? GPIO_FLAG_ACTIVE_LOW : GPIO_FLAG_ACTIVE_HIGH;
*flags = f;
return ERROR_NONE;
}
static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) {
return ERROR_NOT_SUPPORTED;
}
static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) {
return ERROR_NOT_SUPPORTED;
}
static error_t remove_callback(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
static error_t enable_interrupt(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
static error_t disable_interrupt(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
const static GpioControllerApi tca95xx_gpio_api = {
.set_level = set_level,
.get_level = get_level,
.set_flags = set_flags,
.get_flags = get_flags,
.get_native_pin_number = get_native_pin_number,
.add_callback = add_callback,
.remove_callback = remove_callback,
.enable_interrupt = enable_interrupt,
.disable_interrupt = disable_interrupt
};
Driver tca95xx_driver = {
.name = "tca95xx",
.compatible = (const char*[]) { "ti,tca9535", "ti,tca9539", nullptr },
.start_device = start,
.stop_device = stop,
.api = static_cast<const void*>(&tca95xx_gpio_api),
.device_type = &GPIO_CONTROLLER_TYPE,
.owner = &tca95xx_16bit_module,
.internal = nullptr
};
}

View File

@ -25,11 +25,3 @@ properties:
type: boolean
default: false
description: Mirror the Y axis
power-supply:
type: boolean
default: false
description: Expose a power-supply device that reports battery voltage/capacity read from the XPT2046's v-bat input
power-supply-reference-voltage-mv:
type: int
default: 4200
description: Battery voltage (in mV) considered 100% capacity, used to compute POWER_SUPPLY_PROP_CAPACITY

View File

@ -14,10 +14,6 @@ struct Xpt2046Config {
bool swap_xy;
bool mirror_x;
bool mirror_y;
/** Expose a power-supply child device that reads battery voltage/capacity off the chip's v-bat input */
bool power_supply;
/** Battery voltage (mV) considered 100% capacity, used to derive POWER_SUPPLY_PROP_CAPACITY */
uint32_t power_supply_reference_voltage_mv;
};
#ifdef __cplusplus

View File

@ -6,20 +6,17 @@
extern "C" {
extern Driver xpt2046_driver;
extern Driver xpt2046_power_supply_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&xpt2046_driver) == ERROR_NONE);
check(driver_construct_add(&xpt2046_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(&xpt2046_power_supply_driver) == ERROR_NONE);
check(driver_remove_destruct(&xpt2046_driver) == ERROR_NONE);
return ERROR_NONE;
}

View File

@ -7,7 +7,6 @@
#include <tactility/driver.h>
#include <tactility/drivers/esp32_spi.h>
#include <tactility/drivers/pointer.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/drivers/spi_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
@ -18,149 +17,15 @@
#include <esp_lcd_touch_xpt2046.h>
#include <cstdlib>
#include <new>
#define TAG "XPT2046"
#define GET_CONFIG(device) (static_cast<const Xpt2046Config*>((device)->config))
// Rough LiPo discharge curve floor, used together with the configured reference voltage
// (the 100% point) to estimate a charge percentage from the sensed v-bat voltage.
#define POWER_SUPPLY_MIN_MV 3200
struct Xpt2046Internal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_touch_handle_t touch_handle;
Device* power_supply_device;
};
// region Power supply
static bool ps_supports_property(Device*, PowerSupplyProperty property) {
return property == POWER_SUPPLY_PROP_VOLTAGE || property == POWER_SUPPLY_PROP_CAPACITY;
}
static int estimate_capacity_from_mv(int battery_mv, uint32_t reference_mv) {
if (battery_mv <= POWER_SUPPLY_MIN_MV) return 0;
if ((uint32_t)battery_mv >= reference_mv) return 100;
return (battery_mv - POWER_SUPPLY_MIN_MV) * 100 / ((int)reference_mv - POWER_SUPPLY_MIN_MV);
}
// The v-bat reading is noisy (shared ADC, no dedicated sample/hold), so it's smoothed by
// averaging over several samples rather than trusting a single conversion.
#define POWER_SUPPLY_SAMPLE_COUNT 20
static error_t read_battery_mv(esp_lcd_touch_handle_t touch_handle, int* out_mv) {
float volts_sum = 0.0f;
for (int i = 0; i < POWER_SUPPLY_SAMPLE_COUNT; i++) {
float volts;
if (esp_lcd_touch_xpt2046_read_battery_level(touch_handle, &volts) != ESP_OK) {
return ERROR_RESOURCE;
}
volts_sum += volts;
}
*out_mv = (int)((volts_sum / POWER_SUPPLY_SAMPLE_COUNT) * 1000.0f);
return ERROR_NONE;
}
static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) {
if (property != POWER_SUPPLY_PROP_VOLTAGE && property != POWER_SUPPLY_PROP_CAPACITY) {
return ERROR_NOT_SUPPORTED;
}
auto* parent = device_get_parent(device);
const auto* parent_config = GET_CONFIG(parent);
auto* parent_internal = static_cast<Xpt2046Internal*>(device_get_driver_data(parent));
int battery_mv;
error_t error = read_battery_mv(parent_internal->touch_handle, &battery_mv);
if (error != ERROR_NONE) {
return error;
}
out_value->int_value = (property == POWER_SUPPLY_PROP_VOLTAGE) ? battery_mv : estimate_capacity_from_mv(battery_mv, parent_config->power_supply_reference_voltage_mv);
return ERROR_NONE;
}
static bool ps_supports_charge_control(Device*) { return false; }
static bool ps_is_allowed_to_charge(Device*) { return false; }
static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_quick_charge(Device*) { return false; }
static bool ps_is_quick_charge_enabled(Device*) { return false; }
static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_power_off(Device*) { return false; }
static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; }
static constexpr PowerSupplyApi XPT2046_POWER_SUPPLY_API = {
.supports_property = ps_supports_property,
.get_property = ps_get_property,
.supports_charge_control = ps_supports_charge_control,
.is_allowed_to_charge = ps_is_allowed_to_charge,
.set_allowed_to_charge = ps_set_allowed_to_charge,
.supports_quick_charge = ps_supports_quick_charge,
.is_quick_charge_enabled = ps_is_quick_charge_enabled,
.set_quick_charge_enabled = ps_set_quick_charge_enabled,
.supports_power_off = ps_supports_power_off,
.power_off = ps_power_off,
};
// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal,
// but never matched against a devicetree node: xpt2046 wires it up directly by pointer.
Driver xpt2046_power_supply_driver = {
.name = "xpt2046-power-supply",
.compatible = (const char*[]) { "xpt2046-power-supply", nullptr },
.start_device = nullptr,
.stop_device = nullptr,
.api = &XPT2046_POWER_SUPPLY_API,
.device_type = &POWER_SUPPLY_TYPE,
.owner = &xpt2046_module,
.internal = nullptr
};
static error_t create_power_supply_child(Device* parent, Device*& out_child) {
auto* child = new(std::nothrow) Device { .address = 0, .name = "xpt2046-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr };
if (child == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t error = device_construct(child);
if (error != ERROR_NONE) {
delete child;
return error;
}
device_set_parent(child, parent);
device_set_driver(child, &xpt2046_power_supply_driver);
error = device_add(child);
if (error != ERROR_NONE) {
device_destruct(child);
delete child;
return error;
}
error = device_start(child);
if (error != ERROR_NONE) {
device_remove(child);
device_destruct(child);
delete child;
return error;
}
out_child = child;
return ERROR_NONE;
}
static void destroy_power_supply_child(Device* child) {
check(device_stop(child) == ERROR_NONE);
check(device_remove(child) == ERROR_NONE);
check(device_destruct(child) == ERROR_NONE);
delete child;
}
// endregion
// region Driver lifecycle
static error_t start(Device* device) {
@ -217,30 +82,13 @@ static error_t start(Device* device) {
return ERROR_RESOURCE;
}
internal->power_supply_device = nullptr;
device_set_driver_data(device, internal);
if (config->power_supply) {
error_t error = create_power_supply_child(device, internal->power_supply_device);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to create power-supply device");
esp_lcd_touch_del(internal->touch_handle);
esp_lcd_panel_io_del(internal->io_handle);
free(internal);
return error;
}
}
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device));
if (internal->power_supply_device != nullptr) {
destroy_power_supply_child(internal->power_supply_device);
}
bool ok = true;
// esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned

View File

@ -42,11 +42,3 @@ properties:
type: boolean
default: false
description: Mirror the Y axis
power-supply:
type: boolean
default: false
description: Expose a power-supply device that reports battery voltage/capacity read from the XPT2046's v-bat input
power-supply-reference-voltage-mv:
type: int
default: 4200
description: Battery voltage (in mV) considered 100% capacity, used to compute POWER_SUPPLY_PROP_CAPACITY

View File

@ -20,10 +20,6 @@ struct Xpt2046SoftSpiConfig {
bool swap_xy;
bool mirror_x;
bool mirror_y;
/** Expose a power-supply child device that reads battery voltage/capacity off the chip's v-bat input */
bool power_supply;
/** Battery voltage (mV) considered 100% capacity, used to derive POWER_SUPPLY_PROP_CAPACITY */
uint32_t power_supply_reference_voltage_mv;
};
#ifdef __cplusplus

View File

@ -6,20 +6,17 @@
extern "C" {
extern Driver xpt2046_softspi_driver;
extern Driver xpt2046_softspi_power_supply_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&xpt2046_softspi_driver) == ERROR_NONE);
check(driver_construct_add(&xpt2046_softspi_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(&xpt2046_softspi_power_supply_driver) == ERROR_NONE);
check(driver_remove_destruct(&xpt2046_softspi_driver) == ERROR_NONE);
return ERROR_NONE;
}

View File

@ -2,33 +2,23 @@
#include <drivers/xpt2046_softspi.h>
#include <xpt2046_softspi_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/pointer.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/delay.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <cstdlib>
#include <new>
#define TAG "XPT2046SoftSPI"
#define GET_CONFIG(device) (static_cast<const Xpt2046SoftSpiConfig*>((device)->config))
// Rough LiPo discharge curve floor, used together with the configured reference voltage
// (the 100% point) to estimate a charge percentage from the sensed v-bat voltage.
#define POWER_SUPPLY_MIN_MV 3200
namespace {
constexpr uint8_t CMD_READ_X = 0xD0;
constexpr uint8_t CMD_READ_Y = 0x90;
// BATTERY register (start=1, addr=010, 12-bit mode, single-ended, PD1=0/PD0=1 i.e. IRQ disabled
// between conversions), same encoding as the hardware-SPI XPT2046 driver's default mode.
constexpr uint8_t CMD_READ_BATTERY = 0xA7;
constexpr int RAW_MIN_DEFAULT = 100;
constexpr int RAW_MAX_DEFAULT = 1900;
@ -49,12 +39,8 @@ struct Xpt2046SoftSpiInternal {
bool touched;
uint16_t x;
uint16_t y;
Device* power_supply_device;
};
static error_t create_power_supply_child(Device* parent, Device*& out_child);
static void destroy_power_supply_child(Device* child);
// region Driver lifecycle
static void release_descriptors(Xpt2046SoftSpiInternal* internal) {
@ -109,27 +95,11 @@ static error_t start(Device* device) {
internal->mirror_y = config->mirror_y;
device_set_driver_data(device, internal);
if (config->power_supply) {
error_t error = create_power_supply_child(device, internal->power_supply_device);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to create power-supply device");
release_descriptors(internal);
free(internal);
return error;
}
}
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(device));
if (internal->power_supply_device != nullptr) {
destroy_power_supply_child(internal->power_supply_device);
}
release_descriptors(internal);
free(internal);
return ERROR_NONE;
@ -176,127 +146,6 @@ static int read_spi_command(Xpt2046SoftSpiInternal* internal, uint8_t command) {
// endregion
// region Power supply
// The v-bat reading is noisy (bit-banged SPI, no dedicated sample/hold), so it's smoothed by
// averaging over several samples rather than trusting a single conversion.
#define POWER_SUPPLY_SAMPLE_COUNT 20
// Same scaling as the hardware-SPI XPT2046 driver's esp_lcd_touch_xpt2046_read_battery_level():
// the chip halves the v-bat voltage internally, and the raw code is relative to its internal
// 2.507V reference over 12 bits.
static int read_battery_mv(Xpt2046SoftSpiInternal* internal) {
int64_t raw_sum = 0;
for (int i = 0; i < POWER_SUPPLY_SAMPLE_COUNT; i++) {
raw_sum += read_spi_command(internal, CMD_READ_BATTERY);
}
int64_t raw_avg = raw_sum / POWER_SUPPLY_SAMPLE_COUNT;
return (int)((raw_avg * 4 * 2507) / 4096);
}
static bool ps_supports_property(Device*, PowerSupplyProperty property) {
return property == POWER_SUPPLY_PROP_VOLTAGE || property == POWER_SUPPLY_PROP_CAPACITY;
}
static int estimate_capacity_from_mv(int battery_mv, uint32_t reference_mv) {
if (battery_mv <= POWER_SUPPLY_MIN_MV) return 0;
if ((uint32_t)battery_mv >= reference_mv) return 100;
return (battery_mv - POWER_SUPPLY_MIN_MV) * 100 / ((int)reference_mv - POWER_SUPPLY_MIN_MV);
}
static error_t ps_get_property(Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* out_value) {
if (property != POWER_SUPPLY_PROP_VOLTAGE && property != POWER_SUPPLY_PROP_CAPACITY) {
return ERROR_NOT_SUPPORTED;
}
auto* parent = device_get_parent(device);
const auto* parent_config = GET_CONFIG(parent);
auto* parent_internal = static_cast<Xpt2046SoftSpiInternal*>(device_get_driver_data(parent));
int battery_mv = read_battery_mv(parent_internal);
out_value->int_value = (property == POWER_SUPPLY_PROP_VOLTAGE) ? battery_mv : estimate_capacity_from_mv(battery_mv, parent_config->power_supply_reference_voltage_mv);
return ERROR_NONE;
}
static bool ps_supports_charge_control(Device*) { return false; }
static bool ps_is_allowed_to_charge(Device*) { return false; }
static error_t ps_set_allowed_to_charge(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_quick_charge(Device*) { return false; }
static bool ps_is_quick_charge_enabled(Device*) { return false; }
static error_t ps_set_quick_charge_enabled(Device*, bool) { return ERROR_NOT_SUPPORTED; }
static bool ps_supports_power_off(Device*) { return false; }
static error_t ps_power_off(Device*) { return ERROR_NOT_SUPPORTED; }
static constexpr PowerSupplyApi XPT2046_SOFTSPI_POWER_SUPPLY_API = {
.supports_property = ps_supports_property,
.get_property = ps_get_property,
.supports_charge_control = ps_supports_charge_control,
.is_allowed_to_charge = ps_is_allowed_to_charge,
.set_allowed_to_charge = ps_set_allowed_to_charge,
.supports_quick_charge = ps_supports_quick_charge,
.is_quick_charge_enabled = ps_is_quick_charge_enabled,
.set_quick_charge_enabled = ps_set_quick_charge_enabled,
.supports_power_off = ps_supports_power_off,
.power_off = ps_power_off,
};
// Registered (driver_construct_add() in module.cpp) so driver_bind() has a valid ->internal,
// but never matched against a devicetree node: xpt2046_softspi wires it up directly by pointer.
Driver xpt2046_softspi_power_supply_driver = {
.name = "xpt2046-softspi-power-supply",
.compatible = (const char*[]) { "xpt2046-softspi-power-supply", nullptr },
.start_device = nullptr,
.stop_device = nullptr,
.api = &XPT2046_SOFTSPI_POWER_SUPPLY_API,
.device_type = &POWER_SUPPLY_TYPE,
.owner = &xpt2046_softspi_module,
.internal = nullptr
};
static error_t create_power_supply_child(Device* parent, Device*& out_child) {
auto* child = new(std::nothrow) Device { .address = 0, .name = "xpt2046-softspi-power-supply", .config = nullptr, .parent = nullptr, .internal = nullptr };
if (child == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t error = device_construct(child);
if (error != ERROR_NONE) {
delete child;
return error;
}
device_set_parent(child, parent);
device_set_driver(child, &xpt2046_softspi_power_supply_driver);
error = device_add(child);
if (error != ERROR_NONE) {
device_destruct(child);
delete child;
return error;
}
error = device_start(child);
if (error != ERROR_NONE) {
device_remove(child);
device_destruct(child);
delete child;
return error;
}
out_child = child;
return ERROR_NONE;
}
static void destroy_power_supply_child(Device* child) {
check(device_stop(child) == ERROR_NONE);
check(device_remove(child) == ERROR_NONE);
check(device_destruct(child) == ERROR_NONE);
delete child;
}
// endregion
// region PointerApi
static error_t xpt2046_softspi_enter_sleep(Device*) {

View File

@ -32,6 +32,8 @@ dependencies:
espressif/esp_lcd_touch_cst816s: "1.0.3"
espressif/esp_lcd_touch_gt911: "1.1.3"
espressif/esp_lcd_touch_ft5x06: "1.0.6~1"
espressif/esp_io_expander: "1.0.1"
espressif/esp_io_expander_tca95xx_16bit: "1.0.1"
espressif/esp_lcd_axs15231b: "2.0.2"
lambage/esp_lcd_touch_ft6336u: "1.0.8"
espressif/esp_lcd_st7701:

View File

@ -5,8 +5,6 @@
#include <stdlib.h>
#define TAG "lvgl_pointer"
struct LvglPointerCtx {
struct Device* device;
bool calibration_enabled;
@ -51,56 +49,40 @@ static void lvgl_pointer_calibration_apply(
if (mapped_y < 0) mapped_y = 0;
if (mapped_y > target_y_max) mapped_y = target_y_max;
LOG_I(TAG, "Calibration mapping: %d,%d -> %d,%d", *x, *y, (int)mapped_x, (int)mapped_y);
*x = (uint16_t)mapped_x;
*y = (uint16_t)mapped_y;
}
// Reads the touch controller and applies calibration entirely in the graphics driver's own
// native (LV_DISPLAY_ROTATION_0) coordinate space - native_x_max/native_y_max are just the panel's
// fixed pixel dimensions, not a rotation. This function has no notion of LVGL rotation at all:
// calibration corrects the raw sensor's fixed physical mapping, which never changes with on-screen
// orientation, so it doesn't belong anywhere near rotation math.
static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
return false;
}
uint8_t point_count = 0;
if (!pointer_get_touched_points(ctx->device, x, y, NULL, &point_count, 1) || point_count == 0) {
return false;
}
if (ctx->calibration_enabled && native_x_max > 0 && native_y_max > 0) {
lvgl_pointer_calibration_apply(&ctx->calibration, native_x_max, native_y_max, x, y);
}
return true;
}
// The actual LVGL indev read callback: wraps lvgl_pointer_read_calibrated() and, only here, applies
// the rotation needed to place the (still native-space) point into the currently active LVGL
// logical space - unconditionally, since native-space coordinates always need this regardless of
// whether calibration is enabled.
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
lv_display_t* display = lv_indev_get_display(indev);
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
// unaffected by the display's current rotation - no rotation lookup needed to get it.
int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
uint16_t x = 0;
uint16_t y = 0;
if (!lvgl_pointer_read_calibrated(ctx, native_x_max, native_y_max, &x, &y)) {
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
data->state = LV_INDEV_STATE_RELEASED;
return;
}
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
uint16_t x = 0;
uint16_t y = 0;
uint8_t point_count = 0;
bool touched = pointer_get_touched_points(ctx->device, &x, &y, NULL, &point_count, 1);
if (touched && point_count > 0) {
if (ctx->calibration_enabled) {
lv_display_t* display = lv_indev_get_display(indev);
if (display != NULL) {
int32_t target_x_max = lv_display_get_horizontal_resolution(display) - 1;
int32_t target_y_max = lv_display_get_vertical_resolution(display) - 1;
if (target_x_max > 0 && target_y_max > 0) {
lvgl_pointer_calibration_apply(&ctx->calibration, target_x_max, target_y_max, &x, &y);
}
}
}
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
@ -111,7 +93,7 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_
return ERROR_INVALID_ARGUMENT;
}
struct LvglPointerCtx* ctx = calloc(1, sizeof(struct LvglPointerCtx));
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)calloc(1, sizeof(struct LvglPointerCtx));
if (ctx == NULL) {
return ERROR_OUT_OF_MEMORY;
}
@ -165,7 +147,7 @@ bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibrati
if (indev == NULL || out_calibration == NULL) {
return false;
}
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
if (!ctx->calibration_enabled) {
return false;
}
@ -178,7 +160,7 @@ void lvgl_pointer_remove(lv_indev_t* indev) {
return;
}
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
if (default_pointer_indev == indev) {
default_pointer_indev = NULL;
}

View File

@ -1,16 +0,0 @@
description: >
Simple GPIO-driven on/off backlight, for panels whose backlight is a single digital
enable pin rather than a PWM-dimmable one. Brightness is treated as boolean: any
value greater than 0 turns the backlight on, 0 turns it off.
compatible: "espressif,esp32-gpio-backlight"
properties:
pin-backlight:
type: phandles
required: true
description: Backlight enable output pin
default-on:
type: boolean
default: true
description: Whether the backlight is turned on by set_brightness_default()

View File

@ -41,11 +41,3 @@ properties:
element-type: "struct GpioPinSpec"
default: "{ }"
description: Null-terminated array of chip select GPIO pin specs for peripherals on this bus
miso-pull-up:
type: boolean
default: false
description: >
Enables a weak internal pull-up on MISO, which floats between transactions/while another
device on the bus is selected. Helps some SPI peripherals (a garbled/invalid response on
CMD8/if_cond has been observed for SD-over-SPI on some boards) but has been observed to
break others (prevents the SD card from responding to CMD0 at all) - opt in per board.

View File

@ -1,15 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_gpio_backlight.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_gpio_backlight, struct Esp32GpioBacklightConfig)
#ifdef __cplusplus
}
#endif

View File

@ -1,18 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/gpio.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32GpioBacklightConfig {
struct GpioPinSpec pin_backlight;
bool default_on;
};
#ifdef __cplusplus
}
#endif

View File

@ -27,14 +27,6 @@ struct Esp32SpiConfig {
struct GpioPinSpec* cs_gpios;
/** The item count of cs_gpios */
uint8_t cs_gpios_count;
/**
* Enables a weak internal pull-up on MISO, which floats between transactions/while
* another device on the bus is selected. Helps some SPI peripherals (a garbled/invalid
* response on CMD8/if_cond has been observed for SD-over-SPI on some boards) but has been
* observed to break others (e.g. prevents the SD card from responding to CMD0 at all on
* some boards) - opt in per board rather than defaulting it on for everyone.
*/
bool miso_pull_up;
};
/**

View File

@ -1,119 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/esp32_gpio_backlight.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/log.h>
#include <cstdlib>
#define TAG "Esp32GpioBacklight"
#define GET_CONFIG(device) (static_cast<const Esp32GpioBacklightConfig*>((device)->config))
struct Esp32GpioBacklightInternal {
GpioDescriptor* descriptor;
uint8_t brightness;
};
// region Driver lifecycle
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* descriptor = gpio_descriptor_acquire(config->pin_backlight.gpio_controller, config->pin_backlight.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptor");
return ERROR_RESOURCE;
}
if (gpio_descriptor_set_flags(descriptor, config->pin_backlight.flags | GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure backlight pin as output");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
auto* internal = static_cast<Esp32GpioBacklightInternal*>(malloc(sizeof(Esp32GpioBacklightInternal)));
if (internal == nullptr) {
gpio_descriptor_release(descriptor);
return ERROR_OUT_OF_MEMORY;
}
internal->descriptor = descriptor;
internal->brightness = 0;
device_set_driver_data(device, internal);
backlight_set_brightness_default(device); // Allowed to fail, we don't care about the result
return ERROR_NONE;
}
static error_t stop(Device* device) {
backlight_set_brightness(device, 0); // Allowed to fail, we don't care about the result
auto* internal = static_cast<Esp32GpioBacklightInternal*>(device_get_driver_data(device));
gpio_descriptor_release(internal->descriptor);
free(internal);
return ERROR_NONE;
}
// endregion
// region BacklightApi
static error_t esp32_gpio_backlight_set_brightness(Device* device, uint8_t brightness) {
auto* internal = static_cast<Esp32GpioBacklightInternal*>(device_get_driver_data(device));
error_t error = gpio_descriptor_set_level(internal->descriptor, brightness > 0);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to set backlight level");
return error;
}
internal->brightness = brightness;
return ERROR_NONE;
}
static error_t esp32_gpio_backlight_set_brightness_default(Device* device) {
return esp32_gpio_backlight_set_brightness(device, GET_CONFIG(device)->default_on ? 1 : 0);
}
static error_t esp32_gpio_backlight_get_brightness(Device* device, uint8_t* out_brightness) {
auto* internal = static_cast<Esp32GpioBacklightInternal*>(device_get_driver_data(device));
*out_brightness = internal->brightness;
return ERROR_NONE;
}
static uint8_t esp32_gpio_backlight_get_min_brightness(Device*) {
return 0;
}
static uint8_t esp32_gpio_backlight_get_max_brightness(Device*) {
return 1;
}
// endregion
static const BacklightApi esp32_gpio_backlight_api = {
.set_brightness = esp32_gpio_backlight_set_brightness,
.set_brightness_default = esp32_gpio_backlight_set_brightness_default,
.get_brightness = esp32_gpio_backlight_get_brightness,
.get_min_brightness = esp32_gpio_backlight_get_min_brightness,
.get_max_brightness = esp32_gpio_backlight_get_max_brightness,
};
extern Module platform_esp32_module;
Driver esp32_gpio_backlight_driver = {
.name = "esp32_gpio_backlight",
.compatible = (const char*[]) { "espressif,esp32-gpio-backlight", nullptr },
.start_device = start,
.stop_device = stop,
.api = &esp32_gpio_backlight_api,
.device_type = &BACKLIGHT_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};

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