mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-21 09:25:06 +00:00
Compare commits
14 Commits
80c716d97f
...
139d35cb69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
139d35cb69 | ||
|
|
9f92c6daaf | ||
|
|
7fef42426f | ||
|
|
4d41d8497a | ||
|
|
44f3eec365 | ||
|
|
ceed6f1d27 | ||
|
|
daded517aa | ||
|
|
f1e96d2ef5 | ||
|
|
b02663b4bf | ||
|
|
1069032148 | ||
|
|
49af1d42f8 | ||
|
|
d2889b2d3d | ||
|
|
f31103882d | ||
|
|
8f5e92d6e4 |
@ -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
|
||||
)
|
||||
|
||||
31
Devices/btt-panda-touch/Source/Configuration.cpp
Normal file
31
Devices/btt-panda-touch/Source/Configuration.cpp
Normal 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
|
||||
};
|
||||
107
Devices/btt-panda-touch/Source/devices/Display.cpp
Normal file
107
Devices/btt-panda-touch/Source/devices/Display.cpp
Normal 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));
|
||||
}
|
||||
5
Devices/btt-panda-touch/Source/devices/Display.h
Normal file
5
Devices/btt-panda-touch/Source/devices/Display.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -12,8 +12,6 @@ hardware.esptoolFlashFreq=120M
|
||||
hardware.bluetooth=true
|
||||
hardware.usbHostEnabled=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=Internal
|
||||
|
||||
display.size=5"
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/rgb-display-module
|
||||
- Drivers/gt911-module
|
||||
dts: bigtreetech,panda-touch.dts
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
22
Devices/elecrow-crowpanel-basic-50/Source/Configuration.cpp
Normal file
22
Devices/elecrow-crowpanel-basic-50/Source/Configuration.cpp
Normal 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
|
||||
};
|
||||
109
Devices/elecrow-crowpanel-basic-50/Source/devices/Display.cpp
Normal file
109
Devices/elecrow-crowpanel-basic-50/Source/devices/Display.cpp
Normal 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);
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -12,8 +12,6 @@ hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=120M
|
||||
hardware.bluetooth=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=5.0"
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/rgb-display-module
|
||||
- Drivers/gt911-module
|
||||
dts: elecrow,crowpanel-basic-50.dts
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
7
Devices/unphone/Source/Configuration.cpp
Normal file
7
Devices/unphone/Source/Configuration.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
bool initBoot();
|
||||
|
||||
extern const tt::hal::Configuration hardwareConfiguration = {
|
||||
.initBoot = initBoot
|
||||
};
|
||||
201
Devices/unphone/Source/InitBoot.cpp
Normal file
201
Devices/unphone/Source/InitBoot.cpp
Normal 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;
|
||||
}
|
||||
306
Devices/unphone/Source/UnPhoneFeatures.cpp
Normal file
306
Devices/unphone/Source/UnPhoneFeatures.cpp
Normal 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();
|
||||
}
|
||||
55
Devices/unphone/Source/UnPhoneFeatures.h
Normal file
55
Devices/unphone/Source/UnPhoneFeatures.h
Normal 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;
|
||||
};
|
||||
23
Devices/unphone/Source/module.cpp
Normal file
23
Devices/unphone/Source/module.cpp
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -10,8 +10,6 @@ hardware.spiRamMode=OCT
|
||||
hardware.spiRamSpeed=80M
|
||||
hardware.bluetooth=true
|
||||
|
||||
dependencies.useDeprecatedHal=false
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=3.5"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
5
Drivers/BQ24295/CMakeLists.txt
Normal file
5
Drivers/BQ24295/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility
|
||||
)
|
||||
5
Drivers/BQ24295/README.md
Normal file
5
Drivers/BQ24295/README.md
Normal 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)
|
||||
105
Drivers/BQ24295/Source/Bq24295.cpp
Normal file
105
Drivers/BQ24295/Source/Bq24295.cpp
Normal 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
|
||||
38
Drivers/BQ24295/Source/Bq24295.h
Normal file
38
Drivers/BQ24295/Source/Bq24295.h
Normal 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;
|
||||
};
|
||||
@ -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
|
||||
)
|
||||
@ -1,195 +0,0 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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)
|
||||
@ -1,5 +0,0 @@
|
||||
description: TI BQ24295 single-cell USB battery charger
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "ti,bq24295"
|
||||
@ -1,3 +0,0 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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 = {
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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
|
||||
)
|
||||
@ -1,195 +0,0 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -1,3 +0,0 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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"
|
||||
@ -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
|
||||
@ -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
|
||||
)
|
||||
@ -1,195 +0,0 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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)
|
||||
@ -1,5 +0,0 @@
|
||||
description: TI TCA9535 16-bit I2C-bus I/O expander (PCA9555-register-compatible)
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "ti,tca9535"
|
||||
@ -1,5 +0,0 @@
|
||||
description: TI TCA9539 16-bit I2C-bus I/O expander (PCA9555-register-compatible)
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "ti,tca9539"
|
||||
@ -1,3 +0,0 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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*) {
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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()
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user