WIP kernel drivers for T-Deck

This commit is contained in:
Ken Van Hoeylandt 2026-07-11 01:03:50 +02:00
parent 5292901092
commit 77e6872191
97 changed files with 3271 additions and 770 deletions

View File

@ -79,6 +79,11 @@ def property_to_string(property: DeviceProperty, devices: list[Device]) -> str:
value_list.append(str(item))
return "{ " + ",".join(value_list) + " }"
elif type == "phandle":
# Mirrors the string-passthrough convention "phandles" already uses for sentinel defaults
# like GPIO_PIN_SPEC_NONE: a binding default of "NULL" represents "no device referenced",
# not an actual node name to resolve.
if isinstance(property.value, str) and property.value == "NULL":
return "NULL"
return find_phandle(devices, property.value)
elif type == "phandles":
value_list = list()

View File

@ -6,7 +6,7 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_uart.h>
/ {
@ -37,7 +37,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -46,7 +46,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -46,7 +46,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -6,7 +6,7 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -36,7 +36,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -7,7 +7,7 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -45,7 +45,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -47,7 +47,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -47,7 +47,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat ST7789 GT911 PwmBacklight EstimatedPower driver
REQUIRES Tactility EstimatedPower driver
)

View File

@ -1,30 +1,19 @@
#include "devices/Display.h"
#include "devices/KeyboardBacklight.h"
#include "devices/Power.h"
#include "devices/TdeckKeyboard.h"
#include "devices/TrackballDevice.h"
#include <Tactility/hal/Configuration.h>
#include <tactility/check.h>
#include <tactility/device.h>
bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
auto* i2c_internal = device_find_by_name("i2c0");
check(i2c_internal);
return {
createPower(),
createDisplay(),
std::make_shared<TdeckKeyboard>(i2c_internal),
std::make_shared<KeyboardBacklightDevice>(),
std::make_shared<TrackballDevice>(),
createPower()
// std::make_shared<TrackballDevice>(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};

View File

@ -1,7 +0,0 @@
extern "C" {
extern void register_device_drivers() {
/* NO-OP */
}
}

View File

@ -1,94 +0,0 @@
#include "PwmBacklight.h"
#include "devices/KeyboardBacklight.h"
#include "devices/TrackballDevice.h"
#include <Tactility/SystemEvents.h>
#include <Tactility/LogMessages.h>
#include <Tactility/hal/gps/GpsConfiguration.h>
#include <Tactility/kernel/Kernel.h>
#include <Tactility/service/gps/GpsService.h>
#include <tactility/log.h>
constexpr auto* TAG = "T-Deck";
constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10;
static bool powerOn() {
gpio_config_t device_power_signal_config = {
.pin_bit_mask = BIT64(TDECK_POWERON_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&device_power_signal_config) != ESP_OK) {
return false;
}
if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) {
return false;
}
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
tt::kernel::delayMillis(100);
return true;
}
bool initBoot() {
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
if (!powerOn()) {
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
return false;
}
/* 32 Khz and higher gives an issue where the screen starts dimming again above 80% brightness
* when moving the brightness slider rapidly from a lower setting to 100%.
* This is not a slider bug (data was debug-traced) */
if (!driver::pwmbacklight::init(GPIO_NUM_42, 30000)) {
LOG_E(TAG, "Backlight init failed");
return false;
}
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
auto gps_service = tt::service::gps::findGpsService();
if (gps_service != nullptr) {
std::vector<tt::hal::gps::GpsConfiguration> gps_configurations;
gps_service->getGpsConfigurations(gps_configurations);
if (gps_configurations.empty()) {
if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) {
LOG_I(TAG, "Configured internal GPS");
} else {
LOG_E(TAG, "Failed to configure internal GPS");
}
}
}
});
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
auto kbBacklight = tt::hal::findDevice("Keyboard Backlight");
if (kbBacklight != nullptr) {
LOG_I(TAG, "%s starting", kbBacklight->getName().c_str());
auto kbDevice = std::static_pointer_cast<KeyboardBacklightDevice>(kbBacklight);
if (kbDevice->start()) {
LOG_I(TAG, "%s started", kbBacklight->getName().c_str());
} else {
LOG_E(TAG, "%s start failed", kbBacklight->getName().c_str());
}
}
auto trackball = tt::hal::findDevice("Trackball");
if (trackball != nullptr) {
LOG_I(TAG, "%s starting", trackball->getName().c_str());
auto tbDevice = std::static_pointer_cast<TrackballDevice>(trackball);
if (tbDevice->start()) {
LOG_I(TAG, "%s started", trackball->getName().c_str());
} else {
LOG_E(TAG, "%s start failed", trackball->getName().c_str());
}
}
});
return true;
}

View File

@ -1,111 +0,0 @@
#include "KeyboardBacklight.h"
#include <cstring>
#include <esp_log.h>
#include <tactility/log.h>
constexpr auto* TAG = "KeyboardBacklight";
namespace keyboardbacklight {
static const uint8_t CMD_BRIGHTNESS = 0x01;
static const uint8_t CMD_DEFAULT_BRIGHTNESS = 0x02;
static i2c_port_t g_i2cPort = I2C_NUM_MAX;
static uint8_t g_slaveAddress = 0x55;
static uint8_t g_currentBrightness = 127;
// TODO: Umm...something. Calls xxxBrightness, ignores return values.
bool init(i2c_port_t i2cPort, uint8_t slaveAddress) {
g_i2cPort = i2cPort;
g_slaveAddress = slaveAddress;
LOG_I(TAG, "Initialized on I2C port %d, address 0x%02X", static_cast<int>(g_i2cPort), (unsigned)g_slaveAddress);
// Set a reasonable default brightness
if (!setDefaultBrightness(127)) {
LOG_E(TAG, "Failed to set default brightness");
return false;
}
if (!setBrightness(127)) {
LOG_E(TAG, "Failed to set brightness");
return false;
}
return true;
}
bool setBrightness(uint8_t brightness) {
if (g_i2cPort >= I2C_NUM_MAX) {
LOG_E(TAG, "Not initialized");
return false;
}
// Skip if brightness is already at target value (avoid I2C spam on every keypress)
if (brightness == g_currentBrightness) {
return true;
}
LOG_I(TAG, "Setting brightness to %d on I2C port %d, address 0x%02X", brightness, static_cast<int>(g_i2cPort), (unsigned)g_slaveAddress);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, CMD_BRIGHTNESS, true);
i2c_master_write_byte(cmd, brightness, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
g_currentBrightness = brightness;
LOG_I(TAG, "Successfully set brightness to %d", brightness);
return true;
} else {
LOG_E(TAG, "Failed to set brightness: %s (0x%02X)", esp_err_to_name(ret), (unsigned)ret);
return false;
}
}
bool setDefaultBrightness(uint8_t brightness) {
if (g_i2cPort >= I2C_NUM_MAX) {
LOG_E(TAG, "Not initialized");
return false;
}
// Clamp to valid range for default brightness
if (brightness < 30) {
brightness = 30;
}
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, CMD_DEFAULT_BRIGHTNESS, true);
i2c_master_write_byte(cmd, brightness, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
LOG_D(TAG, "Set default brightness to %d", brightness);
return true;
} else {
LOG_E(TAG, "Failed to set default brightness: %s", esp_err_to_name(ret));
return false;
}
}
uint8_t getBrightness() {
if (g_i2cPort >= I2C_NUM_MAX) {
LOG_E(TAG, "Not initialized");
return 0;
}
return g_currentBrightness;
}
}

View File

@ -1,36 +0,0 @@
#pragma once
#include <driver/i2c.h>
#include <cstdint>
namespace keyboardbacklight {
/**
* @brief Initialize keyboard backlight control
* @param i2cPort I2C port number (I2C_NUM_0 or I2C_NUM_1)
* @param slaveAddress I2C slave address (default 0x55 for T-Deck keyboard)
* @return true if initialization succeeded
*/
bool init(i2c_port_t i2cPort, uint8_t slaveAddress = 0x55);
/**
* @brief Set keyboard backlight brightness
* @param brightness Brightness level (0-255, 0=off, 255=max)
* @return true if command succeeded
*/
bool setBrightness(uint8_t brightness);
/**
* @brief Set default keyboard backlight brightness for ALT+B toggle
* @param brightness Default brightness level (30-255)
* @return true if command succeeded
*/
bool setDefaultBrightness(uint8_t brightness);
/**
* @brief Get current keyboard backlight brightness
* @return Current brightness level (0-255)
*/
uint8_t getBrightness();
}

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/tdeck_keyboard.h>
DEFINE_DEVICETREE(tdeck_keyboard, struct TdeckKeyboardConfig)

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/tdeck_keyboard_backlight.h>
DEFINE_DEVICETREE(tdeck_keyboard_backlight, struct TdeckKeyboardBacklightConfig)

View File

@ -1,53 +0,0 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <St7789Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
240,
320,
true,
true,
false,
GPIO_NUM_NC,
GPIO_NUM_16
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = 320,
.verticalResolution = 240,
.gapX = 0,
.gapY = 0,
.swapXY = true,
.mirrorX = true,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.lvglSwapBytes = false,
.buffSpiram = false // Enabling leads to crashes when refreshing App Hub list
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}

View File

@ -1,16 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_12;
constexpr auto LCD_PIN_DC = GPIO_NUM_11; // RS
constexpr auto LCD_HORIZONTAL_RESOLUTION = 320;
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
constexpr auto LCD_BUFFER_HEIGHT = (LCD_VERTICAL_RESOLUTION / 10);
constexpr auto LCD_BUFFER_SIZE = (LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT);
constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -1,51 +0,0 @@
#include "KeyboardBacklight.h"
#include <KeyboardBacklight/KeyboardBacklight.h> // Driver
#include <Tactility/settings/KeyboardSettings.h>
// TODO: Add Mutex and consider refactoring into a class
bool KeyboardBacklightDevice::start() {
if (initialized) {
return true;
}
// T-Deck uses I2C_NUM_0 for internal peripherals
initialized = keyboardbacklight::init(I2C_NUM_0);
if (!initialized) {
return false;
}
// Backlight doesn't seem to turn on until toggled on and off from keyboard settings...
// Or let the display and backlight sleep then wake it up.
// Then it works fine...until reboot, then you need to toggle again.
// The current keyboard firmware sets backlight duty to 0 on boot.
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/firmware/T-Keyboard_Keyboard_ESP32C3_250620.bin
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L25
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L217
auto kbSettings = tt::settings::keyboard::loadOrGetDefault();
keyboardbacklight::setBrightness(kbSettings.backlightEnabled ? kbSettings.backlightBrightness : 0);
return true;
}
bool KeyboardBacklightDevice::stop() {
if (initialized) {
// Turn off backlight on shutdown
keyboardbacklight::setBrightness(0);
initialized = false;
}
return true;
}
bool KeyboardBacklightDevice::setBrightness(uint8_t brightness) {
if (!initialized) {
return false;
}
return keyboardbacklight::setBrightness(brightness);
}
uint8_t KeyboardBacklightDevice::getBrightness() const {
if (!initialized) {
return 0;
}
return keyboardbacklight::getBrightness();
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <tactility/hal/Device.h>
#include <Tactility/TactilityCore.h>
class KeyboardBacklightDevice final : public tt::hal::Device {
bool initialized = false;
public:
tt::hal::Device::Type getType() const override { return tt::hal::Device::Type::I2c; }
std::string getName() const override { return "Keyboard Backlight"; }
std::string getDescription() const override { return "T-Deck keyboard backlight control"; }
bool start();
bool stop();
bool isAttached() const { return initialized; }
/**
* Set keyboard backlight brightness
* @param brightness 0-255 (0=off, 255=max)
*/
bool setBrightness(uint8_t brightness);
/**
* Get current brightness
* @return 0-255
*/
uint8_t getBrightness() const;
};

View File

@ -1,86 +0,0 @@
#include "TdeckKeyboard.h"
#include <KeyboardBacklight/KeyboardBacklight.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/settings/DisplaySettings.h>
#include <Tactility/settings/KeyboardSettings.h>
#include <lvgl.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
using tt::hal::findFirstDevice;
constexpr auto* TAG = "TdeckKeyboard";
constexpr uint8_t TDECK_KEYBOARD_SLAVE_ADDRESS = 0x55;
/**
* The callback simulates press and release events, because the T-Deck
* keyboard only publishes press events on I2C.
* LVGL currently works without those extra release events, but they
* are implemented for correctness and future compatibility.
*
* @param indev_drv
* @param data
*/
static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) {
static uint8_t last_buffer = 0x00;
uint8_t read_buffer = 0x00;
// Defaults
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
auto* keyboard = static_cast<TdeckKeyboard*>(lv_indev_get_user_data(indev));
if (i2c_controller_read(keyboard->getI2cController(), TDECK_KEYBOARD_SLAVE_ADDRESS, &read_buffer, 1, 100 / portTICK_PERIOD_MS) == ERROR_NONE) {
if (read_buffer == 0 && read_buffer != last_buffer) {
LOG_D(TAG, "Released %d", last_buffer);
data->key = last_buffer;
data->state = LV_INDEV_STATE_RELEASED;
} else if (read_buffer != 0) {
LOG_D(TAG, "Pressed %d", read_buffer);
data->key = read_buffer;
data->state = LV_INDEV_STATE_PRESSED;
// TODO: Avoid performance hit by calling loadOrGetDefault() on each key press
// Ensure LVGL activity is triggered so idle services can wake the display
lv_display_trigger_activity(nullptr);
// Actively wake display/backlights immediately on key press (independent of idle tick)
// Restore display backlight if off (we assume duty 0 means dimmed)
auto display = findFirstDevice<tt::hal::display::DisplayDevice>(tt::hal::Device::Type::Display);
if (display && display->supportsBacklightDuty()) {
// Load display settings for target duty
auto dsettings = tt::settings::display::loadOrGetDefault();
// Always set duty, harmless if already on
display->setBacklightDuty(dsettings.backlightDuty);
}
// Restore keyboard backlight if enabled in settings
auto ksettings = tt::settings::keyboard::loadOrGetDefault();
if (ksettings.backlightEnabled) {
keyboardbacklight::setBrightness(ksettings.backlightBrightness);
}
}
}
last_buffer = read_buffer;
}
bool TdeckKeyboard::startLvgl(lv_display_t* display) {
deviceHandle = lv_indev_create();
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(deviceHandle, &keyboard_read_callback);
lv_indev_set_display(deviceHandle, display);
lv_indev_set_user_data(deviceHandle, this);
return true;
}
bool TdeckKeyboard::stopLvgl() {
lv_indev_delete(deviceHandle);
deviceHandle = nullptr;
return true;
}
bool TdeckKeyboard::isAttached() const {
return i2c_controller_has_device_at_address(i2cController, TDECK_KEYBOARD_SLAVE_ADDRESS, 100) == ERROR_NONE;
}

View File

@ -1,25 +0,0 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
struct Device;
class TdeckKeyboard final : public tt::hal::keyboard::KeyboardDevice {
::Device* i2cController;
lv_indev_t* deviceHandle = nullptr;
public:
explicit TdeckKeyboard(::Device* i2cController) : i2cController(i2cController) {}
std::string getName() const override { return "T-Deck Keyboard"; }
std::string getDescription() const override { return "I2C keyboard"; }
::Device* getI2cController() const { return i2cController; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return deviceHandle; }
};

View File

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

View File

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

View File

@ -1,14 +1,113 @@
#include <tactility/module.h>
#include <tactility/driver.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/LogMessages.h>
#include <Tactility/hal/gps/GpsConfiguration.h>
#include <Tactility/kernel/Kernel.h>
#include <Tactility/service/gps/GpsService.h>
#include "devices/TrackballDevice.h"
#include <driver/gpio.h>
constexpr auto* TAG = "T-Deck";
constexpr auto TDECK_POWERON_GPIO = GPIO_NUM_10;
extern "C" {
extern Driver tdeck_keyboard_driver;
extern Driver tdeck_keyboard_backlight_driver;
static bool power_on() {
gpio_config_t device_power_signal_config = {
.pin_bit_mask = BIT64(TDECK_POWERON_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&device_power_signal_config) != ESP_OK) {
return false;
}
if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) {
return false;
}
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
tt::kernel::delayMillis(100);
return true;
}
void subscribe_events() {
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
auto gps_service = tt::service::gps::findGpsService();
if (gps_service != nullptr) {
std::vector<tt::hal::gps::GpsConfiguration> gps_configurations;
gps_service->getGpsConfigurations(gps_configurations);
if (gps_configurations.empty()) {
if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) {
LOG_I(TAG, "Configured internal GPS");
} else {
LOG_E(TAG, "Failed to configure internal GPS");
}
}
}
});
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
auto trackball = tt::hal::findDevice("Trackball");
if (trackball != nullptr) {
LOG_I(TAG, "%s starting", trackball->getName().c_str());
auto tbDevice = std::static_pointer_cast<TrackballDevice>(trackball);
if (tbDevice->start()) {
LOG_I(TAG, "%s started", trackball->getName().c_str());
} else {
LOG_E(TAG, "%s start failed", trackball->getName().c_str());
}
}
});
}
static error_t start() {
// Empty for now
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
if (!power_on()) {
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
return ERROR_RESOURCE;
}
if (driver_construct_add(&tdeck_keyboard_driver) != ERROR_NONE) {
LOG_E(TAG, "Failed to register keyboard driver");
return ERROR_RESOURCE;
}
if (driver_construct_add(&tdeck_keyboard_backlight_driver) != ERROR_NONE) {
LOG_E(TAG, "Failed to register keyboard backlight driver");
return ERROR_RESOURCE;
}
subscribe_events();
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
// We never stop this module, but keep driver registration symmetric in case that changes.
if (driver_remove_destruct(&tdeck_keyboard_driver) != ERROR_NONE) {
LOG_E(TAG, "Failed to unregister keyboard driver");
return ERROR_RESOURCE;
}
if (driver_remove_destruct(&tdeck_keyboard_backlight_driver) != ERROR_NONE) {
LOG_E(TAG, "Failed to unregister keyboard backlight driver");
return ERROR_RESOURCE;
}
return ERROR_NONE;
}

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/tdeck_keyboard.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/log.h>
#include <cstdlib>
#define TAG "TdeckKeyboard"
#define GET_CONFIG(device) (static_cast<const TdeckKeyboardConfig*>((device)->config))
static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(100);
struct TdeckKeyboardInternal {
// The T-Deck keyboard controller only ever reports the currently pressed key (0x00 = none) and
// never a release event, so release events are synthesized by comparing against this.
uint8_t last_key;
};
// region Driver lifecycle
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
auto address = GET_CONFIG(device)->address;
if (i2c_controller_has_device_at_address(parent, address, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "No device found on I2C bus at address 0x%02X", address);
return ERROR_RESOURCE;
}
auto* internal = static_cast<TdeckKeyboardInternal*>(malloc(sizeof(TdeckKeyboardInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->last_key = 0;
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<TdeckKeyboardInternal*>(device_get_driver_data(device));
free(internal);
return ERROR_NONE;
}
// endregion
// region KeyboardApi
static error_t tdeck_keyboard_read_key(Device* device, KeyboardKeyData* data) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto* internal = static_cast<TdeckKeyboardInternal*>(device_get_driver_data(device));
uint8_t read_buffer = 0;
if (i2c_controller_read(parent, address, &read_buffer, 1, I2C_TIMEOUT) != ERROR_NONE) {
return ERROR_RESOURCE;
}
// This controller never buffers more than one key event.
data->continue_reading = false;
if (read_buffer == 0 && internal->last_key != 0) {
data->key = internal->last_key;
data->pressed = false;
} else if (read_buffer != 0) {
data->key = read_buffer;
data->pressed = true;
} else {
data->key = 0;
data->pressed = false;
}
internal->last_key = read_buffer;
return ERROR_NONE;
}
// endregion
static const KeyboardApi tdeck_keyboard_api = {
.read_key = tdeck_keyboard_read_key,
};
extern struct Module lilygo_tdeck_module;
Driver tdeck_keyboard_driver = {
.name = "tdeck_keyboard",
.compatible = (const char*[]) { "lilygo,tdeck-keyboard", nullptr },
.start_device = start,
.stop_device = stop,
.api = &tdeck_keyboard_api,
.device_type = &KEYBOARD_TYPE,
.owner = &lilygo_tdeck_module,
.internal = nullptr
};

View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/tdeck_keyboard_backlight.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <cstdlib>
#define TAG "TdeckKeyboardBacklight"
#define GET_CONFIG(device) (static_cast<const TdeckKeyboardBacklightConfig*>((device)->config))
static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(100);
static constexpr uint8_t CMD_BRIGHTNESS = 0x01;
static constexpr uint8_t CMD_DEFAULT_BRIGHTNESS = 0x02;
struct TdeckKeyboardBacklightInternal {
// The controller is write-only for brightness, so the current value is cached here.
uint8_t current_brightness;
};
// region Driver lifecycle
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
auto address = GET_CONFIG(device)->address;
if (i2c_controller_has_device_at_address(parent, address, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "No device found on I2C bus at address 0x%02X", address);
return ERROR_RESOURCE;
}
auto* internal = static_cast<TdeckKeyboardBacklightInternal*>(malloc(sizeof(TdeckKeyboardBacklightInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->current_brightness = 0;
device_set_driver_data(device, internal);
auto brightness_default = GET_CONFIG(device)->brightness_default;
// Configures the keyboard controller's own persisted default, used by its onboard ALT+B toggle.
if (i2c_controller_write_register(parent, address, CMD_DEFAULT_BRIGHTNESS, &brightness_default, 1, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to set default brightness");
}
if (i2c_controller_write_register(parent, address, CMD_BRIGHTNESS, &brightness_default, 1, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to set initial brightness");
} else {
internal->current_brightness = brightness_default;
}
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<TdeckKeyboardBacklightInternal*>(device_get_driver_data(device));
free(internal);
return ERROR_NONE;
}
// endregion
// region BacklightApi
static error_t tdeck_keyboard_backlight_set_brightness(Device* device, uint8_t brightness) {
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
auto* internal = static_cast<TdeckKeyboardBacklightInternal*>(device_get_driver_data(device));
if (i2c_controller_write_register(parent, address, CMD_BRIGHTNESS, &brightness, 1, I2C_TIMEOUT) != ERROR_NONE) {
return ERROR_RESOURCE;
}
internal->current_brightness = brightness;
return ERROR_NONE;
}
static error_t tdeck_keyboard_backlight_set_brightness_default(Device* device) {
return tdeck_keyboard_backlight_set_brightness(device, GET_CONFIG(device)->brightness_default);
}
static error_t tdeck_keyboard_backlight_get_brightness(Device* device, uint8_t* out_brightness) {
auto* internal = static_cast<TdeckKeyboardBacklightInternal*>(device_get_driver_data(device));
*out_brightness = internal->current_brightness;
return ERROR_NONE;
}
static uint8_t tdeck_keyboard_backlight_get_min_brightness(Device*) {
return 0;
}
static uint8_t tdeck_keyboard_backlight_get_max_brightness(Device*) {
return 255;
}
// endregion
static const BacklightApi tdeck_keyboard_backlight_api = {
.set_brightness = tdeck_keyboard_backlight_set_brightness,
.set_brightness_default = tdeck_keyboard_backlight_set_brightness_default,
.get_brightness = tdeck_keyboard_backlight_get_brightness,
.get_min_brightness = tdeck_keyboard_backlight_get_min_brightness,
.get_max_brightness = tdeck_keyboard_backlight_get_max_brightness,
};
extern struct Module lilygo_tdeck_module;
Driver tdeck_keyboard_backlight_driver = {
.name = "tdeck_keyboard_backlight",
.compatible = (const char*[]) { "lilygo,tdeck-keyboard-backlight", nullptr },
.start_device = start,
.stop_device = stop,
.api = &tdeck_keyboard_backlight_api,
.device_type = &BACKLIGHT_TYPE,
.owner = &lilygo_tdeck_module,
.internal = nullptr
};

View File

@ -0,0 +1,13 @@
description: LilyGO T-Deck onboard keyboard backlight controller
include: ["i2c-device.yaml"]
compatible: "lilygo,tdeck-keyboard-backlight"
bus: i2c
properties:
brightness-default:
type: int
default: 127
description: Default brightness level, applied on start and by set_brightness_default(). Range 0-255.

View File

@ -0,0 +1,7 @@
description: LilyGO T-Deck onboard keyboard controller
include: ["i2c-device.yaml"]
compatible: "lilygo,tdeck-keyboard"
bus: i2c

View File

@ -1,3 +1,6 @@
dependencies:
- Platforms/platform-esp32
- Drivers/st7789-module
- Drivers/gt911-module
bindings: bindings
dts: lilygo,tdeck.dts

View File

@ -7,10 +7,14 @@
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/gt911.h>
#include <bindings/tdeck_keyboard.h>
#include <bindings/tdeck_keyboard_backlight.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <bindings/st7789.h>
// Reference: https://wiki.lilygo.cc/get_started/en/Wearable/T-Deck-Plus/T-Deck-Plus.html#Pin-Overview
/ {
@ -38,6 +42,26 @@
clock-frequency = <100000>;
pin-sda = <&gpio0 18 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
touch {
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <240>;
y-max = <320>;
swap-xy;
mirror-x;
pin-interrupt = <&gpio0 16 GPIO_FLAG_NONE>;
};
keyboard {
compatible = "lilygo,tdeck-keyboard";
reg = <0x55>;
};
keyboard_backlight {
compatible = "lilygo,tdeck-keyboard-backlight";
reg = <0x55>;
};
};
i2s0 {
@ -48,6 +72,17 @@
pin-data-out = <&gpio0 6 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so dispaly power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 42 GPIO_FLAG_NONE>;
// 32 KHz and higher causes the screen to start dimming again above 80% brightness
// when moving the brightness slider rapidly from a lower setting to 100% (debug-traced, not a slider bug).
frequency-hz = <30000>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
@ -59,12 +94,21 @@
pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>;
display@0 {
compatible = "display-placeholder";
compatible = "sitronix,st7789";
horizontal-resolution = <320>;
vertical-resolution = <240>;
swap-xy;
mirror-x;
invert-color;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 11 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
// Must be started after display
sdcard@1 {
compatible = "espressif,esp32-sdspi";
status = "disabled"; // Must be started after display
status = "disabled";
frequency-khz = <20000>;
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -43,7 +43,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
};

View File

@ -8,7 +8,7 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/touch_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
/ {
compatible = "root";
@ -53,7 +53,7 @@
};
touch@1 {
compatible = "touch-placeholder";
compatible = "pointer-placeholder";
};
sdcard@2 {

View File

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

View File

@ -0,0 +1,45 @@
description: Goodix GT911 capacitive touch controller
include: ["i2c-device.yaml"]
compatible: "goodix,gt911"
bus: i2c
properties:
x-max:
type: int
required: true
description: Maximum X coordinate reported by the controller (typically the panel's horizontal resolution)
y-max:
type: int
required: true
description: Maximum Y coordinate reported by the controller (typically the panel's vertical resolution)
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
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
pin-interrupt:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Interrupt GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
interrupt-active-high:
type: boolean
default: false
description: Whether the interrupt pin is active high

View File

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

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/gt911.h>
DEFINE_DEVICETREE(gt911, struct Gt911Config)

View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/drivers/gpio.h>
struct Gt911Config {
// Devicetree address hint. The driver auto-probes both known GT911 addresses
// (0x5D primary, 0x14 backup) regardless, since the effective address depends on
// the controller's INT pin strapping level at power-up, not a fixed board wiring choice.
uint8_t address;
uint16_t x_max;
uint16_t y_max;
bool swap_xy;
bool mirror_x;
bool mirror_y;
struct GpioPinSpec pin_reset;
struct GpioPinSpec pin_interrupt;
bool reset_active_high;
bool interrupt_active_high;
};
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,205 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/gt911.h>
#include <gt911_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_i2c.h>
#include <tactility/drivers/esp32_i2c_master.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_io_i2c.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_touch.h>
#include <esp_lcd_touch_gt911.h>
#include <cstdlib>
#define TAG "GT911"
#define GET_CONFIG(device) (static_cast<const Gt911Config*>((device)->config))
struct Gt911Internal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_touch_handle_t touch_handle;
};
static inline gpio_num_t pin_or_nc(const struct GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? GPIO_NUM_NC : static_cast<gpio_num_t>(pin.pin);
}
// region Driver lifecycle
// GT911's I2C address depends on the controller's INT pin level at power-up (board-strapped, not
// devicetree-fixed), so both known addresses are probed regardless of the devicetree "reg" hint.
static esp_err_t create_io_handle(Device* parent, esp_lcd_panel_io_handle_t* out_handle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
if (i2c_controller_has_device_at_address(parent, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS;
} else if (i2c_controller_has_device_at_address(parent, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, pdMS_TO_TICKS(10)) == ERROR_NONE) {
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
} else {
LOG_E(TAG, "No GT911 found on I2C bus");
return ESP_ERR_NOT_FOUND;
}
auto* parent_driver = device_get_driver(parent);
if (driver_is_compatible(parent_driver, "espressif,esp32-i2c")) {
auto port = static_cast<const Esp32I2cConfig*>(parent->config)->port;
return esp_lcd_new_panel_io_i2c_v1(port, &io_config, out_handle);
}
if (driver_is_compatible(parent_driver, "espressif,esp32-i2c-master")) {
auto bus = esp32_i2c_master_get_bus_handle(parent);
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(parent);
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, out_handle);
}
LOG_E(TAG, "Unsupported I2C driver");
return ESP_ERR_NOT_SUPPORTED;
}
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<Gt911Internal*>(malloc(sizeof(Gt911Internal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
esp_err_t ret = create_io_handle(parent, &internal->io_handle);
if (ret != ESP_OK) {
free(internal);
return ERROR_RESOURCE;
}
esp_lcd_touch_config_t touch_config = {
.x_max = config->x_max,
.y_max = config->y_max,
.rst_gpio_num = pin_or_nc(config->pin_reset),
.int_gpio_num = pin_or_nc(config->pin_interrupt),
.levels = {
.reset = config->reset_active_high ? 1u : 0u,
.interrupt = config->interrupt_active_high ? 1u : 0u,
},
.flags = {
.swap_xy = config->swap_xy ? 1u : 0u,
.mirror_x = config->mirror_x ? 1u : 0u,
.mirror_y = config->mirror_y ? 1u : 0u,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr,
};
ret = esp_lcd_touch_new_i2c_gt911(internal->io_handle, &touch_config, &internal->touch_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create touch handle: %s", esp_err_to_name(ret));
esp_lcd_panel_io_del(internal->io_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<Gt911Internal*>(device_get_driver_data(device));
if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete touch handle");
return ERROR_RESOURCE;
}
free(internal);
return ERROR_NONE;
}
// endregion
// region PointerApi
static error_t gt911_enter_sleep(Device* device) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_enter_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_exit_sleep(Device* device) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_exit_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_read_data(Device* device, TickType_t timeout) {
(void)timeout; // esp_lcd_touch_read_data() has no timeout parameter
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_read_data(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static bool gt911_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_get_coordinates(internal->touch_handle, x, y, strength, point_count, max_point_count);
}
static error_t gt911_set_swap_xy(Device* device, bool swap) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_set_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_get_swap_xy(Device* device, bool* swap) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_get_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_set_mirror_x(Device* device, bool mirror) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_set_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_get_mirror_x(Device* device, bool* mirror) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_get_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_set_mirror_y(Device* device, bool mirror) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_set_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t gt911_get_mirror_y(Device* device, bool* mirror) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));
return esp_lcd_touch_get_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// endregion
static const PointerApi gt911_pointer_api = {
.enter_sleep = gt911_enter_sleep,
.exit_sleep = gt911_exit_sleep,
.read_data = gt911_read_data,
.get_touched_points = gt911_get_touched_points,
.set_swap_xy = gt911_set_swap_xy,
.get_swap_xy = gt911_get_swap_xy,
.set_mirror_x = gt911_set_mirror_x,
.get_mirror_x = gt911_get_mirror_x,
.set_mirror_y = gt911_set_mirror_y,
.get_mirror_y = gt911_get_mirror_y,
};
Driver gt911_driver = {
.name = "gt911",
.compatible = (const char*[]) { "goodix,gt911", nullptr },
.start_device = start,
.stop_device = stop,
.api = &gt911_pointer_api,
.device_type = &POINTER_TYPE,
.owner = &gt911_module,
.internal = nullptr
};

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver gt911_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(&gt911_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(&gt911_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module gt911_module = {
.name = "gt911",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

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

View File

@ -0,0 +1,71 @@
description: Sitronix ST7789 display panel
compatible: "sitronix,st7789"
bus: spi
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
gap-x:
type: int
default: 0
description: X offset applied to all draw operations
gap-y:
type: int
default: 0
description: Y offset applied to all draw operations
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
bgr-order:
type: boolean
default: false
description: Use BGR element order instead of RGB
bits-per-pixel:
type: int
default: 16
description: Color depth in bits per pixel
pixel-clock-hz:
type: int
default: 80000000
description: SPI pixel clock frequency in Hz
transaction-queue-depth:
type: int
default: 10
description: Size of the internal SPI transaction queue
pin-dc:
type: phandles
required: true
description: Data/Command GPIO pin
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
backlight:
type: phandle
default: NULL
description: Optional reference to this display's backlight device

View File

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

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/st7789.h>
DEFINE_DEVICETREE(st7789, struct St7789Config)

View File

@ -0,0 +1,36 @@
// 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 St7789Config {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
int32_t gap_x;
int32_t gap_y;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool invert_color;
bool bgr_order;
uint32_t bits_per_pixel;
uint32_t pixel_clock_hz;
uint8_t transaction_queue_depth;
struct GpioPinSpec pin_dc;
struct GpioPinSpec pin_reset;
bool reset_active_high;
// Optional reference to this display's backlight device, NULL if none.
struct Device* backlight;
};
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver st7789_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(&st7789_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(&st7789_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module st7789_module = {
.name = "st7789",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

@ -0,0 +1,278 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/st7789.h>
#include <st7789_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/esp32_spi.h>
#include <tactility/drivers/spi_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_io_spi.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_st7789.h>
#include <cstdlib>
#define TAG "ST7789"
#define GET_CONFIG(device) (static_cast<const St7789Config*>((device)->config))
struct St7789Internal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle;
};
static int pin_or_unused(const struct GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
}
// region Driver lifecycle
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &SPI_CONTROLLER_TYPE);
const auto* spi_config = static_cast<const Esp32SpiConfig*>(parent->config);
const auto* config = GET_CONFIG(device);
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;
}
auto* internal = static_cast<St7789Internal*>(malloc(sizeof(St7789Internal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = pin_or_unused(cs_pin),
.dc_gpio_num = pin_or_unused(config->pin_dc),
.spi_mode = 0,
.pclk_hz = config->pixel_clock_hz,
.trans_queue_depth = config->transaction_queue_depth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = {
.dc_high_on_cmd = 0,
.dc_low_on_data = 0,
.dc_low_on_param = 0,
.octal_mode = 0,
.quad_mode = 0,
.sio_mode = 1,
.lsb_first = 0,
.cs_high_active = 0,
},
};
esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_config->host, &io_config, &internal->io_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret));
free(internal);
return ERROR_RESOURCE;
}
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = pin_or_unused(config->pin_reset),
.rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = config->bits_per_pixel,
.flags = { .reset_active_high = config->reset_active_high },
.vendor_config = nullptr,
};
ret = esp_lcd_new_panel_st7789(internal->io_handle, &panel_config, &internal->panel_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret));
esp_lcd_panel_io_del(internal->io_handle);
free(internal);
return ERROR_RESOURCE;
}
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7789 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
if (ok) {
int gap_x = config->swap_xy ? config->gap_y : config->gap_x;
int gap_y = config->swap_xy ? config->gap_x : config->gap_y;
ok = (gap_x == 0 && gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, gap_x, gap_y) == ESP_OK;
}
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
if (!ok) {
LOG_E(TAG, "Failed to bring up panel");
esp_lcd_panel_del(internal->panel_handle);
esp_lcd_panel_io_del(internal->io_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<St7789Internal*>(device_get_driver_data(device));
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
return ERROR_RESOURCE;
}
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO");
return ERROR_RESOURCE;
}
free(internal);
return ERROR_NONE;
}
// endregion
// region DisplayApi
static error_t st7789_reset(Device* device) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_init(Device* device) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_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<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<St7789Internal*>(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 st7789_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
// start_device() (e.g. by an LVGL rotation binding) intentionally don't change what "rotation 0" means here.
static bool st7789_get_swap_xy(Device* device) {
return GET_CONFIG(device)->swap_xy;
}
static bool st7789_get_mirror_x(Device* device) {
return GET_CONFIG(device)->mirror_x;
}
static bool st7789_get_mirror_y(Device* device) {
return GET_CONFIG(device)->mirror_y;
}
static error_t st7789_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<St7789Internal*>(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 st7789_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t st7789_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// SPI panels have no direct-mapped frame buffer; every draw is an SPI transaction, so the color
// format here is derived purely from the devicetree config (bgr_order), not queried from hardware.
// Only 16bpp (RGB565/BGR565) is representable in DisplayColorFormat; other bit depths are not
// distinguishable and fall back to the 16bpp mapping.
static enum DisplayColorFormat st7789_get_color_format(Device* device) {
const auto* config = GET_CONFIG(device);
return config->bgr_order ? DISPLAY_COLOR_FORMAT_BGR565 : DISPLAY_COLOR_FORMAT_RGB565;
}
static uint16_t st7789_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t st7789_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void st7789_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
*out_buffer = nullptr;
}
static uint8_t st7789_get_frame_buffer_count(Device*) {
return 0;
}
static error_t st7789_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 st7789_display_api = {
.reset = st7789_reset,
.init = st7789_init,
.draw_bitmap = st7789_draw_bitmap,
.mirror = st7789_mirror,
.swap_xy = st7789_swap_xy,
.get_swap_xy = st7789_get_swap_xy,
.get_mirror_x = st7789_get_mirror_x,
.get_mirror_y = st7789_get_mirror_y,
.set_gap = st7789_set_gap,
.invert_color = st7789_invert_color,
.disp_on_off = st7789_disp_on_off,
.disp_sleep = st7789_disp_sleep,
.get_color_format = st7789_get_color_format,
.get_resolution_x = st7789_get_resolution_x,
.get_resolution_y = st7789_get_resolution_y,
.get_frame_buffer = st7789_get_frame_buffer,
.get_frame_buffer_count = st7789_get_frame_buffer_count,
.get_backlight = st7789_get_backlight,
};
Driver st7789_driver = {
.name = "st7789",
.compatible = (const char*[]) { "sitronix,st7789", nullptr },
.start_device = start,
.stop_device = stop,
.api = &st7789_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &st7789_module,
.internal = nullptr
};

View File

@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <lvgl.h>
#include <tactility/device.h>
#include <tactility/error.h>
/**
* @brief Configuration for binding a kernel DisplayApi device to an lv_display_t.
*/
struct LvglDisplayConfig {
/**
* Number of horizontal lines per draw buffer. 0 means the full vertical resolution.
* Ignored when the device exposes its own frame buffer(s) (display_get_frame_buffer_count() > 0).
*/
uint16_t buffer_height;
/**
* Allocates a second draw buffer for double buffering.
* Ignored when the device exposes its own frame buffer(s).
*/
bool double_buffer;
};
/**
* @brief Creates an lv_display_t bound to the given DISPLAY_TYPE device and registers a flush callback
* that draws through the device's DisplayApi.
*
* The device's swap_xy/mirror_x/mirror_y state at the time of this call (via display_get_swap_xy() etc.)
* is treated as the LV_DISPLAY_ROTATION_0 baseline; LVGL-driven rotation changes are applied relative to it.
*
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) call this from
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
*
* @param[in] device a device of type DISPLAY_TYPE
* @param[in] config binding configuration
* @param[out] out_display the created display, valid only when ERROR_NONE is returned
* @retval ERROR_NONE on success
* @retval ERROR_INVALID_ARGUMENT if device, config or out_display is NULL, or device is not of type DISPLAY_TYPE
* @retval ERROR_NOT_SUPPORTED if the device's color format has no LVGL equivalent
* @retval ERROR_OUT_OF_MEMORY if buffer or lv_display_t allocation failed
*/
error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* config, lv_display_t** out_display);
/**
* @brief Removes a display previously created with lvgl_display_add(), freeing any buffers it owns.
* @warning Caller must hold the LVGL lock.
*/
void lvgl_display_remove(lv_display_t* display);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <lvgl.h>
#include <tactility/device.h>
#include <tactility/error.h>
/**
* @brief Creates an lv_indev_t bound to the given KEYBOARD_TYPE device and registers a read callback
* that polls the device through its KeyboardApi.
*
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) call this from
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
*
* @param[in] device a device of type KEYBOARD_TYPE
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
* @param[out] out_indev the created indev, valid only when ERROR_NONE is returned
* @retval ERROR_NONE on success
* @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type KEYBOARD_TYPE
* @retval ERROR_OUT_OF_MEMORY if allocation failed
*/
error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev);
/**
* @brief Removes an indev previously created with lvgl_keyboard_add().
* @warning Caller must hold the LVGL lock.
*/
void lvgl_keyboard_remove(lv_indev_t* indev);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <lvgl.h>
#include <tactility/device.h>
#include <tactility/error.h>
/**
* @brief Creates an lv_indev_t bound to the given POINTER_TYPE device and registers a read callback
* that polls the device through its PointerApi.
*
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) call this from
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
*
* @param[in] device a device of type POINTER_TYPE
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
* @param[out] out_indev the created indev, valid only when ERROR_NONE is returned
* @retval ERROR_NONE on success
* @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type POINTER_TYPE
* @retval ERROR_OUT_OF_MEMORY if allocation failed
*/
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev);
/**
* @brief Removes an indev previously created with lvgl_pointer_add().
* @warning Caller must hold the LVGL lock.
*/
void lvgl_pointer_remove(lv_indev_t* indev);
#ifdef __cplusplus
}
#endif

View File

@ -8,6 +8,8 @@
#include <tactility/lvgl_module.h>
extern struct LvglModuleConfig lvgl_module_config;
extern void lvgl_devices_attach();
extern void lvgl_devices_detach();
static bool initialized = false;
@ -44,6 +46,8 @@ error_t lvgl_arch_start() {
// devices and services. The latter might start adding widgets immediately.
initialized = true;
lvgl_devices_attach();
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
return ERROR_NONE;
@ -52,6 +56,8 @@ error_t lvgl_arch_start() {
error_t lvgl_arch_stop() {
if (lvgl_module_config.on_stop) lvgl_module_config.on_stop();
lvgl_devices_detach();
if (lvgl_port_deinit() != ESP_OK) {
// Call on_start again to recover
if (lvgl_module_config.on_start) lvgl_module_config.on_start();

View File

@ -0,0 +1,54 @@
#include <tactility/device.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>
#include <tactility/lvgl_display.h>
#include <tactility/lvgl_keyboard.h>
#include <tactility/lvgl_pointer.h>
#include <lvgl.h>
#define TAG "lvgl"
void lvgl_devices_attach() {
lv_disp_t* lvgl_display = NULL;
struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE);
if (kernel_display_device != NULL) {
struct LvglDisplayConfig lvgl_display_config = {};
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
}
}
struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE);
lv_indev_t* lvgl_pointer_device;
if (kernel_pointer_device != NULL) {
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
}
}
struct Device* kernel_keyboard_device = device_find_first_by_type(&KEYBOARD_TYPE);
lv_indev_t* lvgl_keyboard_device;
if (kernel_keyboard_device != NULL) {
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
}
}
}
void lvgl_devices_detach() {
lv_indev_t* device = lv_indev_get_next(NULL);
while (device != NULL) {
lv_indev_delete(device);
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
device = lv_indev_get_next(NULL);
}
}

View File

@ -0,0 +1,215 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/lvgl_display.h>
#include <tactility/drivers/display.h>
#include <tactility/log.h>
#include <stdlib.h>
#ifdef ESP_PLATFORM
#include <esp_heap_caps.h>
#endif
#define TAG "lvgl_display"
struct LvglDisplayCtx {
struct Device* device;
void* buf1;
void* buf2;
bool owns_buffers; // false when buf1/buf2 point at the device's own frame buffer(s)
// The device's swap_xy/mirror_x/mirror_y at bind time, queried once and treated as the LV_DISPLAY_ROTATION_0 baseline.
bool base_swap_xy;
bool base_mirror_x;
bool base_mirror_y;
};
static void* lvgl_display_alloc_buffer(size_t size_bytes) {
#ifdef ESP_PLATFORM
void* buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
if (buf == NULL) {
buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DEFAULT);
}
return buf;
#else
return malloc(size_bytes);
#endif
}
static void lvgl_display_free_buffer(void* buf) {
#ifdef ESP_PLATFORM
heap_caps_free(buf);
#else
free(buf);
#endif
}
static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_format_t* out) {
switch (in) {
case DISPLAY_COLOR_FORMAT_RGB565:
*out = LV_COLOR_FORMAT_RGB565;
return true;
case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED:
*out = LV_COLOR_FORMAT_RGB565_SWAPPED;
return true;
case DISPLAY_COLOR_FORMAT_RGB888:
*out = LV_COLOR_FORMAT_RGB888;
return true;
default:
// DISPLAY_COLOR_FORMAT_BGR565/_SWAPPED: no LVGL equivalent (channel order, not byte order).
// DISPLAY_COLOR_FORMAT_MONOCHROME: unsupported for now, no 1bpp conversion buffer implemented.
return false;
}
}
static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_rotation_t rotation) {
bool swap_xy = ctx->base_swap_xy;
bool mirror_x = ctx->base_mirror_x;
bool mirror_y = ctx->base_mirror_y;
switch (rotation) {
case LV_DISPLAY_ROTATION_0:
break;
case LV_DISPLAY_ROTATION_90:
swap_xy = !ctx->base_swap_xy;
if (ctx->base_swap_xy) {
mirror_x = !ctx->base_mirror_x;
} else {
mirror_y = !ctx->base_mirror_y;
}
break;
case LV_DISPLAY_ROTATION_180:
mirror_x = !ctx->base_mirror_x;
mirror_y = !ctx->base_mirror_y;
break;
case LV_DISPLAY_ROTATION_270:
swap_xy = !ctx->base_swap_xy;
if (ctx->base_swap_xy) {
mirror_y = !ctx->base_mirror_y;
} else {
mirror_x = !ctx->base_mirror_x;
}
break;
}
display_swap_xy(ctx->device, swap_xy);
display_mirror(ctx->device, mirror_x, mirror_y);
}
static void lvgl_display_rotation_event_cb(lv_event_t* e) {
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_event_get_user_data(e);
lv_display_t* disp = (lv_display_t*)lv_event_get_current_target(e);
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
}
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
// LVGL's area is inclusive; DisplayApi's draw_bitmap wants an exclusive end.
display_draw_bitmap(ctx->device, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
// DisplayApi has no async completion callback, so draw_bitmap is synchronous.
lv_display_flush_ready(disp);
}
error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* config, lv_display_t** out_display) {
if (device == NULL || config == NULL || out_display == NULL) {
return ERROR_INVALID_ARGUMENT;
}
if (device_get_type(device) != &DISPLAY_TYPE) {
return ERROR_INVALID_ARGUMENT;
}
lv_color_format_t lv_color_format;
enum DisplayColorFormat kernel_color_format = display_get_color_format(device);
if (!lvgl_display_map_color_format(kernel_color_format, &lv_color_format)) {
LOG_E(TAG, "Unsupported color format %d (no LVGL equivalent)", (int)kernel_color_format);
return ERROR_NOT_SUPPORTED;
}
uint16_t hres = display_get_resolution_x(device);
uint16_t vres = display_get_resolution_y(device);
uint8_t fb_count = display_get_frame_buffer_count(device);
uint8_t bpp = lv_color_format_get_size(lv_color_format);
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)calloc(1, sizeof(struct LvglDisplayCtx));
if (ctx == NULL) {
return ERROR_OUT_OF_MEMORY;
}
ctx->device = device;
ctx->base_swap_xy = display_get_swap_xy(device);
ctx->base_mirror_x = display_get_mirror_x(device);
ctx->base_mirror_y = display_get_mirror_y(device);
lv_display_render_mode_t render_mode;
size_t buf_size_bytes;
if (fb_count > 0) {
display_get_frame_buffer(device, 0, &ctx->buf1);
if (fb_count > 1) {
display_get_frame_buffer(device, 1, &ctx->buf2);
}
ctx->owns_buffers = false;
render_mode = LV_DISPLAY_RENDER_MODE_FULL;
buf_size_bytes = (size_t)hres * vres * bpp;
} else {
uint16_t buf_height = config->buffer_height > 0 ? config->buffer_height : vres;
buf_size_bytes = (size_t)hres * buf_height * bpp;
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
if (ctx->buf1 == NULL) {
free(ctx);
return ERROR_OUT_OF_MEMORY;
}
if (config->double_buffer) {
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes);
if (ctx->buf2 == NULL) {
lvgl_display_free_buffer(ctx->buf1);
free(ctx);
return ERROR_OUT_OF_MEMORY;
}
}
ctx->owns_buffers = true;
render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL;
}
lv_display_t* disp = lv_display_create(hres, vres);
if (disp == NULL) {
if (ctx->owns_buffers) {
lvgl_display_free_buffer(ctx->buf1);
lvgl_display_free_buffer(ctx->buf2);
}
free(ctx);
return ERROR_OUT_OF_MEMORY;
}
lv_display_set_color_format(disp, lv_color_format);
lv_display_set_buffers(disp, ctx->buf1, ctx->buf2, buf_size_bytes, render_mode);
lv_display_set_flush_cb(disp, lvgl_display_flush_cb);
lv_display_set_driver_data(disp, ctx);
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, ctx);
// Apply once explicitly, independent of whether LV_EVENT_RESOLUTION_CHANGED fires on creation.
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
*out_display = disp;
return ERROR_NONE;
}
void lvgl_display_remove(lv_display_t* display) {
if (display == NULL) {
return;
}
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(display);
lv_display_delete(display);
if (ctx != NULL) {
if (ctx->owns_buffers) {
if (ctx->buf1 != NULL) {
lvgl_display_free_buffer(ctx->buf1);
}
if (ctx->buf2 != NULL) {
lvgl_display_free_buffer(ctx->buf2);
}
}
free(ctx);
}
}

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/lvgl_keyboard.h>
#include <tactility/drivers/keyboard.h>
#include <stdlib.h>
struct LvglKeyboardCtx {
struct Device* device;
};
static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
struct KeyboardKeyData key_data = {0};
if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) {
data->state = LV_INDEV_STATE_RELEASED;
data->continue_reading = false;
return;
}
// KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed.
data->key = key_data.key;
data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
data->continue_reading = key_data.continue_reading;
}
error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
if (device == NULL || out_indev == NULL) {
return ERROR_INVALID_ARGUMENT;
}
if (device_get_type(device) != &KEYBOARD_TYPE) {
return ERROR_INVALID_ARGUMENT;
}
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)malloc(sizeof(struct LvglKeyboardCtx));
if (ctx == NULL) {
return ERROR_OUT_OF_MEMORY;
}
ctx->device = device;
lv_indev_t* indev = lv_indev_create();
if (indev == NULL) {
free(ctx);
return ERROR_OUT_OF_MEMORY;
}
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb);
lv_indev_set_driver_data(indev, ctx);
if (display != NULL) {
lv_indev_set_display(indev, display);
}
*out_indev = indev;
return ERROR_NONE;
}
void lvgl_keyboard_remove(lv_indev_t* indev) {
if (indev == NULL) {
return;
}
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
lv_indev_delete(indev);
free(ctx);
}

View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/lvgl_pointer.h>
#include <tactility/drivers/pointer.h>
#include <stdlib.h>
struct LvglPointerCtx {
struct Device* device;
};
// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll.
static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10);
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);
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
data->state = LV_INDEV_STATE_RELEASED;
return;
}
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) {
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) {
if (device == NULL || out_indev == NULL) {
return ERROR_INVALID_ARGUMENT;
}
if (device_get_type(device) != &POINTER_TYPE) {
return ERROR_INVALID_ARGUMENT;
}
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)malloc(sizeof(struct LvglPointerCtx));
if (ctx == NULL) {
return ERROR_OUT_OF_MEMORY;
}
ctx->device = device;
lv_indev_t* indev = lv_indev_create();
if (indev == NULL) {
free(ctx);
return ERROR_OUT_OF_MEMORY;
}
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
lv_indev_set_driver_data(indev, ctx);
if (display != NULL) {
lv_indev_set_display(indev, display);
}
*out_indev = indev;
return ERROR_NONE;
}
void lvgl_pointer_remove(lv_indev_t* indev) {
if (indev == NULL) {
return;
}
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
lv_indev_delete(indev);
free(ctx);
}

View File

@ -0,0 +1,29 @@
description: ESP32 LEDC-backed PWM backlight
compatible: "espressif,esp32-ledc-backlight"
properties:
pin-backlight:
type: phandles
required: true
description: Backlight PWM output pin
frequency-hz:
type: int
default: 30000
description: PWM frequency in Hz
brightness-level-range:
type: values
default: [0, 255]
description: Inclusive [min,max] brightness range. The minimum value turns the backlight off.
brightness-default:
type: int
default: 200
description: Default brightness level, applied by set_brightness_default(). Should fall within brightness-level-range.
ledc-timer:
type: int
default: 0
description: LEDC timer index, defined by ledc_timer_t
ledc-channel:
type: int
default: 0
description: LEDC channel index, defined by ledc_channel_t

View File

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

View File

@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <driver/ledc.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/gpio.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32LedcBacklightConfig {
struct GpioPinSpec pin_backlight;
uint32_t frequency_hz;
struct BrightnessLevelRange brightness_range;
uint8_t brightness_default;
ledc_timer_t ledc_timer;
ledc_channel_t ledc_channel;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,133 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/esp32_ledc_backlight.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/backlight.h>
#include <tactility/log.h>
#include <driver/ledc.h>
#include <esp_err.h>
#include <cstdlib>
#define TAG "Esp32LedcBacklight"
#define GET_CONFIG(device) (static_cast<const Esp32LedcBacklightConfig*>((device)->config))
struct Esp32LedcBacklightInternal {
uint8_t brightness;
};
// region Driver lifecycle
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
ledc_timer_config_t timer_config = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_8_BIT,
.timer_num = config->ledc_timer,
.freq_hz = config->frequency_hz,
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false,
};
if (ledc_timer_config(&timer_config) != ESP_OK) {
LOG_E(TAG, "Failed to configure LEDC timer");
return ERROR_RESOURCE;
}
ledc_channel_config_t channel_config = {
.gpio_num = (int)config->pin_backlight.pin,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = config->ledc_channel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = config->ledc_timer,
.duty = config->brightness_range.min,
.hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = {
.output_invert = 0,
},
};
if (ledc_channel_config(&channel_config) != ESP_OK) {
LOG_E(TAG, "Failed to configure LEDC channel");
return ERROR_RESOURCE;
}
auto* internal = static_cast<Esp32LedcBacklightInternal*>(malloc(sizeof(Esp32LedcBacklightInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->brightness = config->brightness_range.min;
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Esp32LedcBacklightInternal*>(device_get_driver_data(device));
free(internal);
return ERROR_NONE;
}
// endregion
// region BacklightApi
static error_t esp32_ledc_backlight_set_brightness(Device* device, uint8_t brightness) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<Esp32LedcBacklightInternal*>(device_get_driver_data(device));
esp_err_t ret = ledc_set_duty(LEDC_LOW_SPEED_MODE, config->ledc_channel, brightness);
if (ret == ESP_OK) {
ret = ledc_update_duty(LEDC_LOW_SPEED_MODE, config->ledc_channel);
}
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to set brightness: %s", esp_err_to_name(ret));
return ERROR_RESOURCE;
}
internal->brightness = brightness;
return ERROR_NONE;
}
static error_t esp32_ledc_backlight_set_brightness_default(Device* device) {
return esp32_ledc_backlight_set_brightness(device, GET_CONFIG(device)->brightness_default);
}
static error_t esp32_ledc_backlight_get_brightness(Device* device, uint8_t* out_brightness) {
auto* internal = static_cast<Esp32LedcBacklightInternal*>(device_get_driver_data(device));
*out_brightness = internal->brightness;
return ERROR_NONE;
}
static uint8_t esp32_ledc_backlight_get_min_brightness(Device* device) {
return GET_CONFIG(device)->brightness_range.min;
}
static uint8_t esp32_ledc_backlight_get_max_brightness(Device* device) {
return GET_CONFIG(device)->brightness_range.max;
}
// endregion
static const BacklightApi esp32_ledc_backlight_api = {
.set_brightness = esp32_ledc_backlight_set_brightness,
.set_brightness_default = esp32_ledc_backlight_set_brightness_default,
.get_brightness = esp32_ledc_backlight_get_brightness,
.get_min_brightness = esp32_ledc_backlight_get_min_brightness,
.get_max_brightness = esp32_ledc_backlight_get_max_brightness,
};
extern Module platform_esp32_module;
Driver esp32_ledc_backlight_driver = {
.name = "esp32_ledc_backlight",
.compatible = (const char*[]) { "espressif,esp32-ledc-backlight", nullptr },
.start_device = start,
.stop_device = stop,
.api = &esp32_ledc_backlight_api,
.device_type = &BACKLIGHT_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};

View File

@ -15,6 +15,7 @@ extern Driver esp32_gpio_driver;
extern Driver esp32_i2c_driver;
extern Driver esp32_i2c_master_driver;
extern Driver esp32_i2s_driver;
extern Driver esp32_ledc_backlight_driver;
#if SOC_SDMMC_HOST_SUPPORTED
extern Driver esp32_sdmmc_driver;
#endif
@ -46,6 +47,7 @@ static error_t start() {
check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_i2c_master_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_ledc_backlight_driver) == ERROR_NONE);
#if SOC_SDMMC_HOST_SUPPORTED
check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE);
#endif
@ -95,6 +97,7 @@ static error_t stop() {
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_i2c_master_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_ledc_backlight_driver) == ERROR_NONE);
#if SOC_SDMMC_HOST_SUPPORTED
check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE);
#endif

View File

@ -2,6 +2,7 @@
#include <tactility/hal/Device.h>
#include <cstdint>
#include <string>
namespace tt::hal::power {
@ -9,8 +10,8 @@ class PowerDevice : public Device {
public:
PowerDevice() = default;
~PowerDevice() override = default;
PowerDevice();
~PowerDevice() override;
Type getType() const override { return Type::Power; }
@ -46,6 +47,17 @@ public:
virtual bool supportsPowerOff() const { return false; }
virtual void powerOff() { /* NO-OP*/ }
private:
/** Creates the kernel-level power_supply device that exposes this instance to TactilityKernel. */
void createPowerSupplyDevice();
/** Destroys the kernel-level power_supply device created by createPowerSupplyDevice(). */
void destroyPowerSupplyDevice();
std::string kernelDeviceName;
KernelDevice kernelDevice {};
};
}

View File

@ -7,7 +7,7 @@
namespace tt::hal::touch {
/** Wraps a TactilityKernel Device of type TOUCH_TYPE as a TouchDriver. */
/** Wraps a TactilityKernel Device of type POINTER_TYPE as a TouchDriver. */
class KernelTouchDriver final : public TouchDriver {
::Device* device;

View File

@ -23,7 +23,9 @@
#include <tactility/concurrent/thread.h>
#include <tactility/crypt_module.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/grove.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/drivers/rtc.h>
#include <tactility/drivers/uart_controller.h>
#include <tactility/filesystem/file_system.h>
@ -112,6 +114,7 @@ namespace app {
namespace boot { extern const AppManifest manifest; }
namespace development { extern const AppManifest manifest; }
namespace display { extern const AppManifest manifest; }
namespace kerneldisplay { extern const AppManifest manifest; }
namespace files { extern const AppManifest manifest; }
namespace fileselection { extern const AppManifest manifest; }
namespace gpssettings { extern const AppManifest manifest; }
@ -169,7 +172,11 @@ static void registerInternalApps() {
addAppManifest(app::apphubdetails::manifest);
addAppManifest(app::applist::manifest);
addAppManifest(app::appsettings::manifest);
addAppManifest(app::display::manifest);
if (device_exists_of_type(&DISPLAY_TYPE)) {
addAppManifest(app::kerneldisplay::manifest);
} else if (hal::hasDevice(hal::Device::Type::Display)) {
addAppManifest(app::display::manifest);
}
addAppManifest(app::files::manifest);
addAppManifest(app::fileselection::manifest);
addAppManifest(app::i2cscanner::manifest);
@ -178,7 +185,9 @@ static void registerInternalApps() {
addAppManifest(app::launcher::manifest);
addAppManifest(app::localesettings::manifest);
addAppManifest(app::notes::manifest);
addAppManifest(app::poweroff::manifest);
if (device_exists_of_type(&POWER_SUPPLY_TYPE)) {
addAppManifest(app::poweroff::manifest);
}
addAppManifest(app::settings::manifest);
addAppManifest(app::selectiondialog::manifest);
addAppManifest(app::setup::manifest);

View File

@ -1,8 +1,10 @@
#include "Tactility/lvgl/Lvgl.h"
#include "tactility/drivers/backlight.h"
#include "tactility/drivers/display.h"
#include <Tactility/SystemEvents.h>
#include <Tactility/CpuAffinity.h>
#include <Tactility/Paths.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/TactilityPrivate.h>
#include <Tactility/app/AppContext.h>
#include <Tactility/app/AppPaths.h>
@ -56,7 +58,7 @@ class BootApp : public App {
getCpuAffinityConfiguration().system
);
static void setupDisplay() {
static void setupHalDisplay() {
const auto hal_display = getHalDisplay();
if (hal_display == nullptr) {
return;
@ -80,6 +82,36 @@ class BootApp : public App {
}
}
static void setupKernelDisplay() {
auto* display = device_find_first_by_type(&DISPLAY_TYPE);
if (display != nullptr) {
Device* backlight;
if (display_get_backlight(display, &backlight) == ERROR_NONE) {
if (!device_is_ready(backlight)) {
if (device_start(backlight) != ERROR_NONE) {
LOG_E(TAG, "Failed to start %s", backlight->name);
}
}
settings::display::DisplaySettings settings;
if (settings::display::load(settings)) {
} else {
settings = settings::display::getDefault();
}
if (backlight_set_brightness(backlight, settings.backlightDuty) == ERROR_NONE) {
LOG_I(TAG, "Backlight for %s set to %d", display->name, settings.backlightDuty);
} else {
LOG_E(TAG, "Failed to set brightness of %s", backlight->name);
}
} else {
LOG_I(TAG, "No backlight for %s", display->name);
}
} else {
LOG_I(TAG, "No kernel display");
}
}
static bool setupUsbBootMode() {
if (!hal::usb::isUsbBootMode()) {
return false;
@ -124,7 +156,8 @@ class BootApp : public App {
// TODO: Support for multiple displays
LOG_I(TAG, "Setup display");
setupDisplay(); // Set backlight
setupHalDisplay();
setupKernelDisplay();
prepareFileSystems();
#ifdef CONFIG_TT_USER_DATA_LOCATION_SD

View File

@ -34,7 +34,7 @@ static bool hasCalibratableTouchDevice() {
return false;
}
class DisplayApp final : public App {
class HalDisplayApp final : public App {
settings::display::DisplaySettings displaySettings;
bool displaySettingsUpdated = false;
@ -44,7 +44,7 @@ class DisplayApp final : public App {
static void onBacklightSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto hal_display = getHalDisplay();
assert(hal_display != nullptr);
@ -59,7 +59,7 @@ class DisplayApp final : public App {
static void onGammaSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
auto hal_display = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
assert(hal_display != nullptr);
if (hal_display->getGammaCurveCount() > 0) {
@ -71,7 +71,7 @@ class DisplayApp final : public App {
}
static void onOrientationSet(lv_event_t* event) {
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t selected_index = lv_dropdown_get_selected(dropdown);
LOG_I(TAG, "Selected %u", (unsigned)selected_index);
@ -84,7 +84,7 @@ class DisplayApp final : public App {
}
static void onTimeoutSwitch(lv_event_t* event) {
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED);
app->displaySettings.backlightTimeoutEnabled = enabled;
@ -105,7 +105,7 @@ class DisplayApp final : public App {
}
static void onTimeoutChanged(lv_event_t* event) {
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t idx = lv_dropdown_get_selected(dropdown);
// Map dropdown index to ms: 0=15s,1=30s,2=1m,3=2m,4=5m,5=Never
@ -117,7 +117,7 @@ class DisplayApp final : public App {
}
static void onScreensaverChanged(lv_event_t* event) {
auto* app = static_cast<DisplayApp*>(lv_event_get_user_data(event));
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t idx = lv_dropdown_get_selected(dropdown);
// Validate index bounds before casting to enum
@ -338,7 +338,7 @@ extern const AppManifest manifest = {
.appName = "Display",
.appIcon = LVGL_ICON_SHARED_DISPLAY_SETTINGS,
.appCategory = Category::Settings,
.createApp = create<DisplayApp>
.createApp = create<HalDisplayApp>
};
} // namespace

View File

@ -0,0 +1,284 @@
#include "../../../../TactilityKernel/include/tactility/drivers/display.h"
#include "../../../../TactilityKernel/include/tactility/error.h"
#include <Tactility/Tactility.h>
#include <tactility/lvgl_icon_shared.h>
#ifdef ESP_PLATFORM
#include <Tactility/service/displayidle/DisplayIdleService.h>
#endif
#include <Tactility/app/App.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/settings/DisplaySettings.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/log.h>
#include <tactility/lvgl_module.h>
#include <lvgl.h>
namespace tt::app::kerneldisplay {
constexpr auto* TAG = "KernelDisplay";
static Device* getBacklightDevice() {
Device* display = device_find_first_by_type(&DISPLAY_TYPE);
check(display);
Device* backlight = nullptr;
return display_get_backlight(display, &backlight) == ERROR_NONE ? backlight : nullptr;
}
class KernelDisplayApp final : public App {
settings::display::DisplaySettings displaySettings;
bool displaySettingsUpdated = false;
lv_obj_t* timeoutSwitch = nullptr;
lv_obj_t* timeoutDropdown = nullptr;
lv_obj_t* screensaverDropdown = nullptr;
static void onBacklightSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
auto* backlight = getBacklightDevice();
assert(backlight != nullptr);
int32_t slider_value = lv_slider_get_value(slider);
app->displaySettings.backlightDuty = static_cast<uint8_t>(slider_value);
app->displaySettingsUpdated = true;
backlight_set_brightness(backlight, app->displaySettings.backlightDuty);
}
static void onOrientationSet(lv_event_t* event) {
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t selected_index = lv_dropdown_get_selected(dropdown);
LOG_I(TAG, "Selected %u", (unsigned)selected_index);
auto selected_orientation = static_cast<settings::display::Orientation>(selected_index);
if (selected_orientation != app->displaySettings.orientation) {
app->displaySettings.orientation = selected_orientation;
app->displaySettingsUpdated = true;
lv_display_set_rotation(lv_display_get_default(), settings::display::toLvglDisplayRotation(selected_orientation));
}
}
static void onTimeoutSwitch(lv_event_t* event) {
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED);
app->displaySettings.backlightTimeoutEnabled = enabled;
app->displaySettingsUpdated = true;
if (app->timeoutDropdown) {
if (enabled) {
lv_obj_clear_state(app->timeoutDropdown, LV_STATE_DISABLED);
if (app->screensaverDropdown) {
lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED);
}
} else {
lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED);
if (app->screensaverDropdown) {
lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED);
}
}
}
}
static void onTimeoutChanged(lv_event_t* event) {
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t idx = lv_dropdown_get_selected(dropdown);
// Map dropdown index to ms: 0=15s,1=30s,2=1m,3=2m,4=5m,5=Never
static const uint32_t values_ms[] = {15000, 30000, 60000, 120000, 300000, 0};
if (idx < (sizeof(values_ms)/sizeof(values_ms[0]))) {
app->displaySettings.backlightTimeoutMs = values_ms[idx];
app->displaySettingsUpdated = true;
}
}
static void onScreensaverChanged(lv_event_t* event) {
auto* app = static_cast<KernelDisplayApp*>(lv_event_get_user_data(event));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t idx = lv_dropdown_get_selected(dropdown);
// Validate index bounds before casting to enum
if (idx >= static_cast<uint32_t>(settings::display::ScreensaverType::Count)) {
return;
}
auto selected_type = static_cast<settings::display::ScreensaverType>(idx);
if (selected_type != app->displaySettings.screensaverType) {
app->displaySettings.screensaverType = selected_type;
app->displaySettingsUpdated = true;
}
}
public:
void onShow(AppContext& app, lv_obj_t* parent) override {
displaySettings = settings::display::loadOrGetDefault();
auto ui_density = lvgl_get_ui_density();
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
auto* backlight = getBacklightDevice();
lvgl::toolbar_create(parent, app);
auto* main_wrapper = lv_obj_create(parent);
lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(main_wrapper, LV_PCT(100));
lv_obj_set_flex_grow(main_wrapper, 1);
// Backlight slider
// Note: no gamma slider here - unlike HalDisplayApp (app/display/Display.cpp), the kernel
// DisplayApi has no gamma curve control yet.
if (backlight != nullptr) {
auto* brightness_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(brightness_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(brightness_wrapper, 0, LV_STATE_DEFAULT);
if (ui_density != LVGL_UI_DENSITY_COMPACT) {
lv_obj_set_style_pad_ver(brightness_wrapper, 4, LV_STATE_DEFAULT);
}
auto* brightness_label = lv_label_create(brightness_wrapper);
lv_label_set_text(brightness_label, "Brightness");
lv_obj_align(brightness_label, LV_ALIGN_LEFT_MID, 0, 0);
auto* brightness_slider = lv_slider_create(brightness_wrapper);
lv_obj_set_width(brightness_slider, LV_PCT(50));
lv_obj_align(brightness_slider, LV_ALIGN_RIGHT_MID, 0, 0);
lv_slider_set_range(brightness_slider, backlight_get_min_brightness(backlight), backlight_get_max_brightness(backlight));
lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, this);
lv_slider_set_value(brightness_slider, displaySettings.backlightDuty, LV_ANIM_OFF);
}
// Orientation
auto* orientation_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(orientation_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(orientation_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(orientation_wrapper, 0, LV_STATE_DEFAULT);
auto* orientation_label = lv_label_create(orientation_wrapper);
lv_label_set_text(orientation_label, "Orientation");
lv_obj_align(orientation_label, LV_ALIGN_LEFT_MID, 0, 0);
auto* orientation_dropdown = lv_dropdown_create(orientation_wrapper);
// Note: order correlates with settings::display::Orientation item order
lv_dropdown_set_options(orientation_dropdown, "Landscape\nPortrait Right\nLandscape Flipped\nPortrait Left");
lv_obj_align(orientation_dropdown, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, this);
// Set the dropdown to match current orientation enum
lv_dropdown_set_selected(orientation_dropdown, static_cast<uint16_t>(displaySettings.orientation));
// Screen timeout
// Note: DisplayIdleService doesn't act on these settings for kernel-driver displays yet
// (it only looks up the deprecated tt::hal::display::DisplayDevice), so these currently
// just get saved without taking effect. Kept for parity/forward-compatibility.
if (backlight != nullptr) {
auto* timeout_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(timeout_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(timeout_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(timeout_wrapper, 0, LV_STATE_DEFAULT);
auto* timeout_label = lv_label_create(timeout_wrapper);
lv_label_set_text(timeout_label, "Auto screen off");
lv_obj_align(timeout_label, LV_ALIGN_LEFT_MID, 0, 0);
timeoutSwitch = lv_switch_create(timeout_wrapper);
if (displaySettings.backlightTimeoutEnabled) {
lv_obj_add_state(timeoutSwitch, LV_STATE_CHECKED);
}
lv_obj_align(timeoutSwitch, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(timeoutSwitch, onTimeoutSwitch, LV_EVENT_VALUE_CHANGED, this);
auto* timeout_select_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(timeout_select_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(timeout_select_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(timeout_select_wrapper, 0, LV_STATE_DEFAULT);
auto* timeout_value_label = lv_label_create(timeout_select_wrapper);
lv_label_set_text(timeout_value_label, "Timeout");
lv_obj_align(timeout_value_label, LV_ALIGN_LEFT_MID, 0, 0);
timeoutDropdown = lv_dropdown_create(timeout_select_wrapper);
lv_dropdown_set_options(timeoutDropdown, "15 seconds\n30 seconds\n1 minute\n2 minutes\n5 minutes\nNever");
lv_obj_align(timeoutDropdown, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(timeoutDropdown, onTimeoutChanged, LV_EVENT_VALUE_CHANGED, this);
// Initialize dropdown selection from settings
uint32_t ms = displaySettings.backlightTimeoutMs;
uint32_t idx = 2; // default 1 minute
if (ms == 15000) idx = 0;
else if (ms == 30000)
idx = 1;
else if (ms == 60000)
idx = 2;
else if (ms == 120000)
idx = 3;
else if (ms == 300000)
idx = 4;
else if (ms == 0)
idx = 5;
lv_dropdown_set_selected(timeoutDropdown, idx);
if (!displaySettings.backlightTimeoutEnabled) {
lv_obj_add_state(timeoutDropdown, LV_STATE_DISABLED);
}
// Screensaver type
auto* screensaver_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(screensaver_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(screensaver_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(screensaver_wrapper, 0, LV_STATE_DEFAULT);
auto* screensaver_label = lv_label_create(screensaver_wrapper);
lv_label_set_text(screensaver_label, "Screensaver");
lv_obj_align(screensaver_label, LV_ALIGN_LEFT_MID, 0, 0);
screensaverDropdown = lv_dropdown_create(screensaver_wrapper);
// Note: order correlates with settings::display::ScreensaverType enum order
lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan");
lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this);
lv_dropdown_set_selected(screensaverDropdown, static_cast<uint16_t>(displaySettings.screensaverType));
if (!displaySettings.backlightTimeoutEnabled) {
lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED);
}
}
// Note: no touch calibration section here - unlike HalDisplayApp, the kernel PointerApi has
// no calibration support yet.
}
void onHide(AppContext& app) override {
if (displaySettingsUpdated) {
// Dispatch it, so file IO doesn't block the UI
const settings::display::DisplaySettings settings_to_save = displaySettings;
getMainDispatcher().dispatch([settings_to_save] {
settings::display::save(settings_to_save);
#ifdef ESP_PLATFORM
// Notify DisplayIdle service to reload settings
auto displayIdle = service::displayidle::findService();
if (displayIdle) {
displayIdle->reloadSettings();
}
#endif
});
}
}
};
extern const AppManifest manifest = {
.appId = "Display",
.appName = "Display",
.appIcon = LVGL_ICON_SHARED_DISPLAY_SETTINGS,
.appCategory = Category::Settings,
.createApp = create<KernelDisplayApp>
};
} // namespace

View File

@ -5,15 +5,12 @@
#include <Tactility/settings/KeyboardSettings.h>
#include <Tactility/lvgl/Toolbar.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/lvgl_icon_shared.h>
#include <lvgl.h>
// Forward declare driver functions
namespace keyboardbacklight {
bool setBrightness(uint8_t brightness);
}
namespace tt::app::keyboardsettings {
constexpr auto* TAG = "KeyboardSettings";
@ -30,7 +27,11 @@ static uint32_t timeoutMsToIndex(uint32_t ms) {
}
static void applyKeyboardBacklight(bool enabled, uint8_t brightness) {
keyboardbacklight::setBrightness(enabled ? brightness : 0);
// TODO: Get keyboard backlight from (optional) keyboard child device
Device* backlight = device_find_by_name("keyboard_backlight");
if (backlight != nullptr) {
backlight_set_brightness(backlight, enabled ? brightness : 0);
}
}
class KeyboardSettingsApp final : public App {

View File

@ -4,10 +4,10 @@
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/loader/Loader.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/Timer.h>
#include <tactility/hal/Device.h>
#include <tactility/device.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/lvgl_icon_shared.h>
#include <lvgl.h>
@ -34,7 +34,7 @@ class PowerApp : public App {
Timer update_timer = Timer(Timer::Type::Periodic, kernel::millisToTicks(1000),[]() { onTimer(); });
std::shared_ptr<hal::power::PowerDevice> power;
::Device* power = nullptr;
lv_obj_t* enableLabel = nullptr;
lv_obj_t* enableSwitch = nullptr;
@ -58,8 +58,8 @@ class PowerApp : public App {
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
if (power->isAllowedToCharge() != is_on) {
power->setAllowedToCharge(is_on);
if (power_supply_is_allowed_to_charge(power) != is_on) {
power_supply_set_allowed_to_charge(power, is_on);
updateUi();
}
}
@ -76,8 +76,8 @@ class PowerApp : public App {
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(qc_switch, LV_STATE_CHECKED);
if (power->isQuickChargeEnabled() != is_on) {
power->setQuickChargeEnabled(is_on);
if (power_supply_is_quick_charge_enabled(power) != is_on) {
power_supply_set_quick_charge_enabled(power, is_on);
updateUi();
}
}
@ -94,37 +94,37 @@ class PowerApp : public App {
}
const char* charge_state;
hal::power::PowerDevice::MetricData metric_data;
if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, metric_data)) {
charge_state = metric_data.valueAsBool ? "yes" : "no";
PowerSupplyPropertyValue property_value;
if (power_supply_get_property(power, POWER_SUPPLY_PROP_IS_CHARGING, &property_value) == ERROR_NONE) {
charge_state = property_value.int_value ? "yes" : "no";
} else {
charge_state = "N/A";
}
uint8_t charge_level;
int charge_level;
bool charge_level_scaled_set = false;
if (power->getMetric(hal::power::PowerDevice::MetricType::ChargeLevel, metric_data)) {
charge_level = metric_data.valueAsUint8;
if (power_supply_get_property(power, POWER_SUPPLY_PROP_CAPACITY, &property_value) == ERROR_NONE) {
charge_level = property_value.int_value;
charge_level_scaled_set = true;
}
bool charging_enabled_set = power->supportsChargeControl();
bool charging_enabled_and_allowed = power->supportsChargeControl() && power->isAllowedToCharge();
bool charging_enabled_set = power_supply_supports_charge_control(power);
bool charging_enabled_and_allowed = charging_enabled_set && power_supply_is_allowed_to_charge(power);
bool quick_charge_set = power->supportsQuickCharge();
bool quick_charge_enabled = power->supportsQuickCharge() && power->isQuickChargeEnabled();
bool quick_charge_set = power_supply_supports_quick_charge(power);
bool quick_charge_enabled = quick_charge_set && power_supply_is_quick_charge_enabled(power);
int32_t current;
int current;
bool current_set = false;
if (power->getMetric(hal::power::PowerDevice::MetricType::Current, metric_data)) {
current = metric_data.valueAsInt32;
if (power_supply_get_property(power, POWER_SUPPLY_PROP_CURRENT, &property_value) == ERROR_NONE) {
current = property_value.int_value;
current_set = true;
}
uint32_t battery_voltage;
int battery_voltage;
bool battery_voltage_set = false;
if (power->getMetric(hal::power::PowerDevice::MetricType::BatteryVoltage, metric_data)) {
battery_voltage = metric_data.valueAsUint32;
if (power_supply_get_property(power, POWER_SUPPLY_PROP_VOLTAGE, &property_value) == ERROR_NONE) {
battery_voltage = property_value.int_value;
battery_voltage_set = true;
}
@ -151,7 +151,7 @@ class PowerApp : public App {
lv_label_set_text_fmt(chargeStateLabel, "Charging: %s", charge_state);
if (battery_voltage_set) {
lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: %lu mV", battery_voltage);
lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: %d mV", battery_voltage);
} else {
lv_label_set_text_fmt(batteryVoltageLabel, "Battery voltage: N/A");
}
@ -163,7 +163,7 @@ class PowerApp : public App {
}
if (current_set) {
lv_label_set_text_fmt(currentLabel, "Current: %ld mA", current);
lv_label_set_text_fmt(currentLabel, "Current: %d mA", current);
} else {
lv_label_set_text_fmt(currentLabel, "Current: N/A");
}
@ -174,7 +174,7 @@ class PowerApp : public App {
public:
void onCreate(AppContext& app) override {
power = hal::findFirstDevice<hal::power::PowerDevice>(hal::Device::Type::Power);
power = device_find_first_active_by_type(&POWER_SUPPLY_TYPE);
}
void onShow(AppContext& app, lv_obj_t* parent) override {

View File

@ -1,10 +1,11 @@
#include <Tactility/app/AppContext.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/service/loader/Loader.h>
#include <lvgl.h>
#include <tactility/device.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/hal/Device.h>
#include <tactility/lvgl_fonts.h>
#include <tactility/lvgl_icon_shared.h>
@ -48,15 +49,15 @@ class PowerOffApp final : public App {
}
static void onYesPressed(lv_event_t* /*event*/) {
auto power = hal::findFirstDevice<hal::power::PowerDevice>(hal::Device::Type::Power);
if (power == nullptr || !power->supportsPowerOff()) {
auto* power = device_find_first_active_by_type(&POWER_SUPPLY_TYPE);
if (power == nullptr || !power_supply_supports_power_off(power)) {
return;
}
auto display = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
showPoweredOffScreenAndWait(display.get());
power->powerOff();
power_supply_power_off(power);
}
static void onNoPressed(lv_event_t* /*event*/) {

View File

@ -0,0 +1,172 @@
// SPDX-License-Identifier: Apache-2.0
#include <Tactility/hal/power/PowerDevice.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/power_supply.h>
#include <format>
namespace tt::hal::power {
#define GET_POWER_DEVICE(device) (static_cast<PowerDevice*>(device_get_driver_data(device)))
static PowerDevice::MetricType toMetricType(PowerSupplyProperty property) {
switch (property) {
case POWER_SUPPLY_PROP_IS_CHARGING:
return PowerDevice::MetricType::IsCharging;
case POWER_SUPPLY_PROP_CURRENT:
return PowerDevice::MetricType::Current;
case POWER_SUPPLY_PROP_VOLTAGE:
return PowerDevice::MetricType::BatteryVoltage;
case POWER_SUPPLY_PROP_CAPACITY:
default:
return PowerDevice::MetricType::ChargeLevel;
}
}
static int toIntValue(PowerDevice::MetricType type, const PowerDevice::MetricData& data) {
switch (type) {
case PowerDevice::MetricType::IsCharging:
return data.valueAsBool ? 1 : 0;
case PowerDevice::MetricType::Current:
return data.valueAsInt32;
case PowerDevice::MetricType::BatteryVoltage:
return static_cast<int>(data.valueAsUint32);
case PowerDevice::MetricType::ChargeLevel:
default:
return data.valueAsUint8;
}
}
static bool apiSupportsProperty(::Device* device, PowerSupplyProperty property) {
return GET_POWER_DEVICE(device)->supportsMetric(toMetricType(property));
}
static error_t apiGetProperty(::Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* outValue) {
auto* power_device = GET_POWER_DEVICE(device);
auto metric_type = toMetricType(property);
if (!power_device->supportsMetric(metric_type)) {
return ERROR_NOT_SUPPORTED;
}
PowerDevice::MetricData data;
if (!power_device->getMetric(metric_type, data)) {
return ERROR_NOT_FOUND;
}
outValue->int_value = toIntValue(metric_type, data);
return ERROR_NONE;
}
static bool apiSupportsChargeControl(::Device* device) {
return GET_POWER_DEVICE(device)->supportsChargeControl();
}
static bool apiIsAllowedToCharge(::Device* device) {
return GET_POWER_DEVICE(device)->isAllowedToCharge();
}
static error_t apiSetAllowedToCharge(::Device* device, bool allowed) {
auto* power_device = GET_POWER_DEVICE(device);
if (!power_device->supportsChargeControl()) {
return ERROR_NOT_SUPPORTED;
}
power_device->setAllowedToCharge(allowed);
return ERROR_NONE;
}
static bool apiSupportsQuickCharge(::Device* device) {
return GET_POWER_DEVICE(device)->supportsQuickCharge();
}
static bool apiIsQuickChargeEnabled(::Device* device) {
return GET_POWER_DEVICE(device)->isQuickChargeEnabled();
}
static error_t apiSetQuickChargeEnabled(::Device* device, bool enabled) {
auto* power_device = GET_POWER_DEVICE(device);
if (!power_device->supportsQuickCharge()) {
return ERROR_NOT_SUPPORTED;
}
power_device->setQuickChargeEnabled(enabled);
return ERROR_NONE;
}
static bool apiSupportsPowerOff(::Device* device) {
return GET_POWER_DEVICE(device)->supportsPowerOff();
}
static error_t apiPowerOff(::Device* device) {
auto* power_device = GET_POWER_DEVICE(device);
if (!power_device->supportsPowerOff()) {
return ERROR_NOT_SUPPORTED;
}
power_device->powerOff();
return ERROR_NONE;
}
static error_t startDevice(::Device*) { return ERROR_NONE; }
static error_t stopDevice(::Device*) { return ERROR_NONE; }
static PowerSupplyApi powerSupplyApi {
.supports_property = apiSupportsProperty,
.get_property = apiGetProperty,
.supports_charge_control = apiSupportsChargeControl,
.is_allowed_to_charge = apiIsAllowedToCharge,
.set_allowed_to_charge = apiSetAllowedToCharge,
.supports_quick_charge = apiSupportsQuickCharge,
.is_quick_charge_enabled = apiIsQuickChargeEnabled,
.set_quick_charge_enabled = apiSetQuickChargeEnabled,
.supports_power_off = apiSupportsPowerOff,
.power_off = apiPowerOff,
};
static const char* powerSupplyCompatible[] = { "hal-power-device", nullptr };
static Driver powerSupplyDriver {
.name = "hal-power-device",
.compatible = powerSupplyCompatible,
.start_device = startDevice,
.stop_device = stopDevice,
.api = &powerSupplyApi,
.device_type = &POWER_SUPPLY_TYPE,
.owner = nullptr,
.internal = nullptr,
};
/** Registers the "hal-power-device" driver with the kernel on first use. */
static Driver& getPowerSupplyDriver() {
static const bool registered = [] {
check(driver_construct_add(&powerSupplyDriver) == ERROR_NONE);
return true;
}();
(void)registered;
return powerSupplyDriver;
}
void PowerDevice::createPowerSupplyDevice() {
kernelDeviceName = std::format("power-supply-{}", getId());
kernelDevice.name = kernelDeviceName.c_str();
check(device_construct(&kernelDevice) == ERROR_NONE);
check(device_add(&kernelDevice) == ERROR_NONE);
device_set_driver(&kernelDevice, &getPowerSupplyDriver());
check(device_start(&kernelDevice) == ERROR_NONE);
device_set_driver_data(&kernelDevice, this);
}
void PowerDevice::destroyPowerSupplyDevice() {
device_set_driver_data(&kernelDevice, nullptr);
check(device_stop(&kernelDevice) == ERROR_NONE);
check(device_remove(&kernelDevice) == ERROR_NONE);
check(device_destruct(&kernelDevice) == ERROR_NONE);
}
PowerDevice::PowerDevice() {
createPowerSupplyDevice();
}
PowerDevice::~PowerDevice() {
destroyPowerSupplyDevice();
}
}

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#include <Tactility/hal/touch/KernelTouchDriver.h>
#include <tactility/drivers/touch.h>
#include <tactility/drivers/pointer.h>
#include <cassert>
@ -11,14 +11,14 @@ namespace tt::hal::touch {
static constexpr TickType_t READ_TIMEOUT = pdMS_TO_TICKS(10);
KernelTouchDriver::KernelTouchDriver(::Device* device) : device(device) {
assert(device_get_type(device) == &TOUCH_TYPE);
assert(device_get_type(device) == &POINTER_TYPE);
}
bool KernelTouchDriver::getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) {
if (touch_read_data(device, READ_TIMEOUT) != ERROR_NONE) {
if (pointer_read_data(device, READ_TIMEOUT) != ERROR_NONE) {
return false;
}
return touch_get_touched_points(device, x, y, strength, pointCount, maxPointCount);
return pointer_get_touched_points(device, x, y, strength, pointCount, maxPointCount);
}
}

View File

@ -1,7 +1,8 @@
#include "Tactility/lvgl/Keyboard.h"
#include "Tactility/service/gui/GuiService.h"
#include <Tactility/service/espnow/EspNowService.h>
#include <tactility/device.h>
#include <tactility/drivers/keyboard.h>
namespace tt::lvgl {
@ -46,7 +47,7 @@ void software_keyboard_deactivate() {
}
bool hardware_keyboard_is_available() {
return keyboard_device != nullptr;
return keyboard_device != nullptr || device_find_first_by_type(&KEYBOARD_TYPE) != nullptr;
}
void hardware_keyboard_set_indev(lv_indev_t* device) {

View File

@ -9,6 +9,8 @@
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/settings/DisplaySettings.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/log.h>
#include <tactility/lvgl_module.h>
#include <tactility/module.h>
@ -32,37 +34,36 @@ void attachDevices() {
// Start displays (their related touch devices start automatically within)
LOG_I(TAG, "Start displays");
auto displays = hal::findDevices<hal::display::DisplayDevice>(hal::Device::Type::Display);
for (const auto& display: displays) {
auto hal_displays= hal::findDevices<hal::display::DisplayDevice>(hal::Device::Type::Display);
for (const auto& display: hal_displays) {
if (display->supportsLvgl()) {
if (display->startLvgl()) {
LOG_I(TAG, "Started %s", display->getName().c_str());
auto lvgl_display = display->getLvglDisplay();
assert(lvgl_display != nullptr);
auto settings = settings::display::loadOrGetDefault();
lv_display_rotation_t rotation = settings::display::toLvglDisplayRotation(settings.orientation);
if (rotation != lv_display_get_rotation(lvgl_display)) {
lv_display_set_rotation(lvgl_display, rotation);
}
} else {
LOG_E(TAG, "Start failed for %s", display->getName().c_str());
}
}
}
auto* primary_lvgl_display = lv_disp_get_default();
if (primary_lvgl_display != nullptr) {
LOG_I(TAG, "Set default display rotation");
auto settings = settings::display::loadOrGetDefault();
lv_display_rotation_t rotation = settings::display::toLvglDisplayRotation(settings.orientation);
if (rotation != lv_display_get_rotation(primary_lvgl_display)) {
lv_display_set_rotation(primary_lvgl_display, rotation);
}
}
// Start touch
// TODO: Consider implementing support for multiple displays
auto primary_display = !displays.empty() ? displays[0] : nullptr;
// Start display-related peripherals
if (primary_display != nullptr) {
if (primary_lvgl_display != nullptr) {
LOG_I(TAG, "Start touch devices");
auto touch_devices = hal::findDevices<hal::touch::TouchDevice>(hal::Device::Type::Touch);
for (const auto& touch_device: touch_devices) {
// Start any touch devices that haven't been started yet
if (touch_device->supportsLvgl() && touch_device->getLvglIndev() == nullptr) {
if (touch_device->startLvgl(primary_display->getLvglDisplay())) {
if (touch_device->startLvgl(primary_lvgl_display)) {
LOG_I(TAG, "Started %s", touch_device->getName().c_str());
} else {
LOG_E(TAG, "Start failed for %s", touch_device->getName().c_str());
@ -75,7 +76,7 @@ void attachDevices() {
auto keyboards = hal::findDevices<hal::keyboard::KeyboardDevice>(hal::Device::Type::Keyboard);
for (const auto& keyboard: keyboards) {
if (keyboard->isAttached()) {
if (keyboard->startLvgl(primary_display->getLvglDisplay())) {
if (keyboard->startLvgl(primary_lvgl_display)) {
lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
hardware_keyboard_set_indev(keyboard_indev);
LOG_I(TAG, "Started %s", keyboard->getName().c_str());
@ -89,7 +90,7 @@ void attachDevices() {
LOG_I(TAG, "Start encoders");
auto encoders = hal::findDevices<hal::encoder::EncoderDevice>(hal::Device::Type::Encoder);
for (const auto& encoder: encoders) {
if (encoder->startLvgl(primary_display->getLvglDisplay())) {
if (encoder->startLvgl(primary_lvgl_display)) {
LOG_I(TAG, "Started %s", encoder->getName().c_str());
} else {
LOG_E(TAG, "Start failed for %s", encoder->getName().c_str());

View File

@ -9,9 +9,8 @@
#include <Tactility/settings/KeyboardSettings.h>
#include <Tactility/Timer.h>
namespace keyboardbacklight {
bool setBrightness(uint8_t brightness);
}
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
namespace tt::service::keyboardidle {
@ -25,6 +24,17 @@ class KeyboardIdleService final : public Service {
return hal::findFirstDevice<hal::keyboard::KeyboardDevice>(hal::Device::Type::Keyboard);
}
static Device* getKeyboardBacklight() {
return device_find_by_name("keyboard_backlight");
}
void setKeyboardBacklightBrightness(uint8_t brightness) {
Device* backlight = getKeyboardBacklight();
if (backlight != nullptr) {
backlight_set_brightness(backlight, brightness);
}
}
void tick() {
// Settings are now cached and event-driven (no file I/O in timer callback!)
// This prevents watchdog timeout from blocking the Timer Service task
@ -42,15 +52,15 @@ class KeyboardIdleService final : public Service {
// If timeout disabled, ensure backlight restored if we had dimmed it
if (!cachedKeyboardSettings.backlightTimeoutEnabled || cachedKeyboardSettings.backlightTimeoutMs == 0) {
if (keyboardDimmed) {
keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
keyboardDimmed = false;
}
} else {
if (!keyboardDimmed && inactive_ms >= cachedKeyboardSettings.backlightTimeoutMs) {
keyboardbacklight::setBrightness(0);
setKeyboardBacklightBrightness(0);
keyboardDimmed = true;
} else if (keyboardDimmed && inactive_ms < 100) {
keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
keyboardDimmed = false;
}
}
@ -62,7 +72,8 @@ public:
// Load settings once at startup and cache them
// This eliminates file I/O from timer callback (prevents watchdog timeout)
cachedKeyboardSettings = settings::keyboard::loadOrGetDefault();
setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
// Note: Settings changes require service restart to take effect
// TODO: Add KeyboardSettingsChanged events for dynamic updates
@ -80,7 +91,7 @@ public:
// Ensure keyboard restored on stop
auto keyboard = getKeyboard();
if (keyboard && keyboardDimmed) {
keyboardbacklight::setBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
keyboardDimmed = false;
}
}

View File

@ -0,0 +1,5 @@
description: Pointer placeholder
compatible: "pointer-placeholder"
properties: {}

View File

@ -1,5 +0,0 @@
description: Touch placeholder
compatible: "touch-placeholder"
properties: {}

View File

@ -2,13 +2,13 @@
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/touch_placeholder.h>
#include <tactility/drivers/pointer_placeholder.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(touch_placeholder, struct TouchPlaceholderConfig)
DEFINE_DEVICETREE(pointer_placeholder, struct PointerPlaceholderConfig)
#ifdef __cplusplus
}

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <tactility/device.h>
#include <tactility/error.h>
/**
* @brief Inclusive range of brightness levels accepted by a backlight driver.
*/
struct BrightnessLevelRange {
uint8_t min;
uint8_t max;
};
/**
* @brief API for backlight drivers.
*
* @note The minimum brightness level (see get_min_brightness()) turns the backlight fully off.
* There is no separate on/off control: to turn the backlight off, call set_brightness() with
* the minimum value; to turn it back on, set any value above the minimum.
*/
struct BacklightApi {
/**
* @brief Sets the backlight brightness.
* @param[in] device the backlight device
* @param[in] brightness the brightness level, expected to be within [get_min_brightness(), get_max_brightness()]
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_brightness)(struct Device* device, uint8_t brightness);
/**
* @brief Sets the backlight brightness to its devicetree-configured default level.
* @param[in] device the backlight device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_brightness_default)(struct Device* device);
/**
* @brief Gets the current backlight brightness.
* @param[in] device the backlight device
* @param[out] out_brightness the current brightness level
* @retval ERROR_NONE when the operation was successful
*/
error_t (*get_brightness)(struct Device* device, uint8_t* out_brightness);
/**
* @brief Gets the minimum brightness level. Setting the brightness to this value turns the backlight off.
* @param[in] device the backlight device
* @return the minimum brightness level
*/
uint8_t (*get_min_brightness)(struct Device* device);
/**
* @brief Gets the maximum (full-strength) brightness level.
* @param[in] device the backlight device
* @return the maximum brightness level
*/
uint8_t (*get_max_brightness)(struct Device* device);
};
/**
* @brief Sets the backlight brightness using the specified backlight device.
*/
error_t backlight_set_brightness(struct Device* device, uint8_t brightness);
/**
* @brief Sets the backlight brightness to its devicetree-configured default level using the specified backlight device.
*/
error_t backlight_set_brightness_default(struct Device* device);
/**
* @brief Gets the current backlight brightness using the specified backlight device.
*/
error_t backlight_get_brightness(struct Device* device, uint8_t* out_brightness);
/**
* @brief Gets the minimum brightness level using the specified backlight device. This level turns the backlight off.
*/
uint8_t backlight_get_min_brightness(struct Device* device);
/**
* @brief Gets the maximum brightness level using the specified backlight device.
*/
uint8_t backlight_get_max_brightness(struct Device* device);
extern const struct DeviceType BACKLIGHT_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -66,6 +66,27 @@ struct DisplayApi {
*/
error_t (*swap_xy)(struct Device* device, bool swap_axes);
/**
* @brief Gets whether the X and Y axes are currently swapped.
* @param[in] device the display device
* @return true if swapped
*/
bool (*get_swap_xy)(struct Device* device);
/**
* @brief Gets whether the X axis is currently mirrored.
* @param[in] device the display device
* @return true if mirrored
*/
bool (*get_mirror_x)(struct Device* device);
/**
* @brief Gets whether the Y axis is currently mirrored.
* @param[in] device the display device
* @return true if mirrored
*/
bool (*get_mirror_y)(struct Device* device);
/**
* @brief Sets a coordinate offset applied to all draw operations.
* @param[in] device the display device
@ -129,6 +150,15 @@ struct DisplayApi {
* @return the frame buffer count
*/
uint8_t (*get_frame_buffer_count)(struct Device* device);
/**
* @brief Gets the backlight device associated with this display, if any.
* @param[in] device the display device
* @param[out] backlight the associated backlight device
* @retval ERROR_NONE when a backlight is available and *backlight was set
* @retval ERROR_NOT_SUPPORTED when this display has no associated backlight
*/
error_t (*get_backlight)(struct Device* device, struct Device** backlight);
};
/**
@ -156,6 +186,21 @@ error_t display_mirror(struct Device* device, bool x_axis, bool y_axis);
*/
error_t display_swap_xy(struct Device* device, bool swap_axes);
/**
* @brief Gets whether the X and Y axes are currently swapped using the specified display.
*/
bool display_get_swap_xy(struct Device* device);
/**
* @brief Gets whether the X axis is currently mirrored using the specified display.
*/
bool display_get_mirror_x(struct Device* device);
/**
* @brief Gets whether the Y axis is currently mirrored using the specified display.
*/
bool display_get_mirror_y(struct Device* device);
/**
* @brief Sets a coordinate offset applied to all draw operations using the specified display.
*/
@ -201,6 +246,13 @@ void display_get_frame_buffer(struct Device* device, uint8_t index, void** out_b
*/
uint8_t display_get_frame_buffer_count(struct Device* device);
/**
* @brief Gets the backlight device associated with the specified display, if any.
* @retval ERROR_NONE when a backlight is available and *backlight was set
* @retval ERROR_NOT_SUPPORTED when the display has no associated backlight
*/
error_t display_get_backlight(struct Device* device, struct Device** backlight);
extern const struct DeviceType DISPLAY_TYPE;
#ifdef __cplusplus

View File

@ -0,0 +1,51 @@
// 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/error.h>
/**
* @brief A single key event read from a keyboard device.
*/
struct KeyboardKeyData {
/** @brief The key code. Driver-defined (e.g. ASCII/UTF-8 codepoint, scan code, or LVGL key code). */
uint32_t key;
/** @brief True if the key was pressed, false if released. */
bool pressed;
/**
* @brief True if another key event is already queued and read_key() should be called again
* immediately to drain it. False if this was the last pending event.
*/
bool continue_reading;
};
/**
* @brief API for keyboard drivers.
*/
struct KeyboardApi {
/**
* @brief Reads the next pending key event, if any.
* @param[in] device the keyboard device
* @param[out] data the key event data
* @retval ERROR_NONE when the operation was successful
*/
error_t (*read_key)(struct Device* device, struct KeyboardKeyData* data);
};
/**
* @brief Reads the next pending key event using the specified keyboard device.
*/
error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data);
extern const struct DeviceType KEYBOARD_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -12,19 +12,19 @@ extern "C" {
#include <tactility/freertos/freertos.h>
/**
* @brief API for touch controller drivers.
* @brief API for pointer controller drivers.
*/
struct TouchApi {
struct PointerApi {
/**
* @brief Puts the touch controller into its low-power sleep mode.
* @param[in] device the touch device
* @brief Puts the pointer controller into its low-power sleep mode.
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*enter_sleep)(struct Device* device);
/**
* @brief Wakes the touch controller from sleep mode.
* @param[in] device the touch device
* @brief Wakes the pointer controller from sleep mode.
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*exit_sleep)(struct Device* device);
@ -32,7 +32,7 @@ struct TouchApi {
/**
* @brief Performs a blocking read of the latest touch data from the controller into its internal state.
* Call this before get_touched_points().
* @param[in] device the touch device
* @param[in] device the pointer device
* @param[in] timeout the maximum time to wait for the operation to complete
* @retval ERROR_NONE when the read operation was successful
* @retval ERROR_TIMEOUT when the operation timed out
@ -41,7 +41,7 @@ struct TouchApi {
/**
* @brief Retrieves the coordinates most recently read by read_data().
* @param[in] device the touch device
* @param[in] device the pointer device
* @param[out] x array of X coordinates
* @param[out] y array of Y coordinates
* @param[out] strength optional array of touch strengths (nullable)
@ -53,98 +53,98 @@ struct TouchApi {
/**
* @brief Sets whether the X and Y axes should be swapped.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_swap_xy)(struct Device* device, bool swap);
/**
* @brief Gets whether the X and Y axes are swapped.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*get_swap_xy)(struct Device* device, bool* swap);
/**
* @brief Sets whether the X axis should be mirrored.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_mirror_x)(struct Device* device, bool mirror);
/**
* @brief Gets whether the X axis is mirrored.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*get_mirror_x)(struct Device* device, bool* mirror);
/**
* @brief Sets whether the Y axis should be mirrored.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_mirror_y)(struct Device* device, bool mirror);
/**
* @brief Gets whether the Y axis is mirrored.
* @param[in] device the touch device
* @param[in] device the pointer device
* @retval ERROR_NONE when the operation was successful
*/
error_t (*get_mirror_y)(struct Device* device, bool* mirror);
};
/**
* @brief Puts the touch controller into its low-power sleep mode using the specified touch device.
* @brief Puts the pointer controller into its low-power sleep mode using the specified pointer device.
*/
error_t touch_enter_sleep(struct Device* device);
error_t pointer_enter_sleep(struct Device* device);
/**
* @brief Wakes the touch controller from sleep mode using the specified touch device.
* @brief Wakes the pointer controller from sleep mode using the specified pointer device.
*/
error_t touch_exit_sleep(struct Device* device);
error_t pointer_exit_sleep(struct Device* device);
/**
* @brief Performs a blocking read of the latest touch data using the specified touch device.
* @brief Performs a blocking read of the latest touch data using the specified pointer device.
*/
error_t touch_read_data(struct Device* device, TickType_t timeout);
error_t pointer_read_data(struct Device* device, TickType_t timeout);
/**
* @brief Retrieves the coordinates most recently read using the specified touch device.
* @brief Retrieves the coordinates most recently read using the specified pointer device.
*/
bool touch_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count);
bool pointer_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count);
/**
* @brief Sets whether the X and Y axes should be swapped using the specified touch device.
* @brief Sets whether the X and Y axes should be swapped using the specified pointer device.
*/
error_t touch_set_swap_xy(struct Device* device, bool swap);
error_t pointer_set_swap_xy(struct Device* device, bool swap);
/**
* @brief Gets whether the X and Y axes are swapped using the specified touch device.
* @brief Gets whether the X and Y axes are swapped using the specified pointer device.
*/
error_t touch_get_swap_xy(struct Device* device, bool* swap);
error_t pointer_get_swap_xy(struct Device* device, bool* swap);
/**
* @brief Sets whether the X axis should be mirrored using the specified touch device.
* @brief Sets whether the X axis should be mirrored using the specified pointer device.
*/
error_t touch_set_mirror_x(struct Device* device, bool mirror);
error_t pointer_set_mirror_x(struct Device* device, bool mirror);
/**
* @brief Gets whether the X axis is mirrored using the specified touch device.
* @brief Gets whether the X axis is mirrored using the specified pointer device.
*/
error_t touch_get_mirror_x(struct Device* device, bool* mirror);
error_t pointer_get_mirror_x(struct Device* device, bool* mirror);
/**
* @brief Sets whether the Y axis should be mirrored using the specified touch device.
* @brief Sets whether the Y axis should be mirrored using the specified pointer device.
*/
error_t touch_set_mirror_y(struct Device* device, bool mirror);
error_t pointer_set_mirror_y(struct Device* device, bool mirror);
/**
* @brief Gets whether the Y axis is mirrored using the specified touch device.
* @brief Gets whether the Y axis is mirrored using the specified pointer device.
*/
error_t touch_get_mirror_y(struct Device* device, bool* mirror);
error_t pointer_get_mirror_y(struct Device* device, bool* mirror);
extern const struct DeviceType TOUCH_TYPE;
extern const struct DeviceType POINTER_TYPE;
#ifdef __cplusplus
}

View File

@ -7,7 +7,7 @@
extern "C" {
#endif
struct TouchPlaceholderConfig {
struct PointerPlaceholderConfig {
uint8_t _unused;
};

View File

@ -0,0 +1,163 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <tactility/device.h>
#include <tactility/error.h>
/**
* @brief Properties that a power supply device can report.
*/
enum PowerSupplyProperty {
/** [0, 1]: whether the battery is currently charging */
POWER_SUPPLY_PROP_IS_CHARGING,
/** mA: battery current, positive while charging, negative while discharging */
POWER_SUPPLY_PROP_CURRENT,
/** mV: battery voltage */
POWER_SUPPLY_PROP_VOLTAGE,
/** [0, 100]: battery charge level */
POWER_SUPPLY_PROP_CAPACITY,
};
/**
* @brief Holds the value of a single power supply property.
*/
union PowerSupplyPropertyValue {
int int_value;
};
/**
* @brief API for power supply drivers.
*
* @note supports_charge_control(), supports_quick_charge() and supports_power_off() gate
* their respective functions: when a capability is not supported, the driver's implementation
* of the related getter/setter is expected to be a NO-OP (getters returning false/0).
*/
struct PowerSupplyApi {
/**
* @brief Checks whether a property is supported by this device.
* @param[in] device the power supply device
* @param[in] property the property to check
* @return true if the property is supported
*/
bool (*supports_property)(struct Device* device, enum PowerSupplyProperty property);
/**
* @brief Gets the current value of a property.
* @param[in] device the power supply device
* @param[in] property the property to read
* @param[out] out_value the property value
* @retval ERROR_NOT_SUPPORTED when the property is not supported
* @retval ERROR_NOT_FOUND when the property is (temporarily) not available
* @retval ERROR_NONE when the operation was successful
*/
error_t (*get_property)(struct Device* device, enum PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value);
/**
* @brief Checks whether this device supports enabling/disabling charging.
*/
bool (*supports_charge_control)(struct Device* device);
/**
* @brief Checks whether charging is currently allowed.
*/
bool (*is_allowed_to_charge)(struct Device* device);
/**
* @brief Enables or disables charging.
* @retval ERROR_NOT_SUPPORTED when charge control is not supported
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_allowed_to_charge)(struct Device* device, bool allowed);
/**
* @brief Checks whether this device supports quick charging.
*/
bool (*supports_quick_charge)(struct Device* device);
/**
* @brief Checks whether quick charging is currently enabled.
*/
bool (*is_quick_charge_enabled)(struct Device* device);
/**
* @brief Enables or disables quick charging.
* @retval ERROR_NOT_SUPPORTED when quick charging is not supported
* @retval ERROR_NONE when the operation was successful
*/
error_t (*set_quick_charge_enabled)(struct Device* device, bool enabled);
/**
* @brief Checks whether this device can power off the system.
*/
bool (*supports_power_off)(struct Device* device);
/**
* @brief Powers off the system.
* @retval ERROR_NOT_SUPPORTED when power off is not supported
* @retval ERROR_NONE when the operation was successful (this call typically does not return)
*/
error_t (*power_off)(struct Device* device);
};
/**
* @brief Checks whether a property is supported using the specified power supply device.
*/
bool power_supply_supports_property(struct Device* device, enum PowerSupplyProperty property);
/**
* @brief Gets the current value of a property using the specified power supply device.
*/
error_t power_supply_get_property(struct Device* device, enum PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value);
/**
* @brief Checks whether the specified power supply device supports enabling/disabling charging.
*/
bool power_supply_supports_charge_control(struct Device* device);
/**
* @brief Checks whether charging is currently allowed on the specified power supply device.
*/
bool power_supply_is_allowed_to_charge(struct Device* device);
/**
* @brief Enables or disables charging on the specified power supply device.
*/
error_t power_supply_set_allowed_to_charge(struct Device* device, bool allowed);
/**
* @brief Checks whether the specified power supply device supports quick charging.
*/
bool power_supply_supports_quick_charge(struct Device* device);
/**
* @brief Checks whether quick charging is currently enabled on the specified power supply device.
*/
bool power_supply_is_quick_charge_enabled(struct Device* device);
/**
* @brief Enables or disables quick charging on the specified power supply device.
*/
error_t power_supply_set_quick_charge_enabled(struct Device* device, bool enabled);
/**
* @brief Checks whether the specified power supply device can power off the system.
*/
bool power_supply_supports_power_off(struct Device* device);
/**
* @brief Powers off the system using the specified power supply device.
*/
error_t power_supply_power_off(struct Device* device);
extern const struct DeviceType POWER_SUPPLY_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/backlight.h>
#include <tactility/device.h>
#define BACKLIGHT_DRIVER_API(driver) ((struct BacklightApi*)driver->api)
extern "C" {
error_t backlight_set_brightness(Device* device, uint8_t brightness) {
const auto* driver = device_get_driver(device);
return BACKLIGHT_DRIVER_API(driver)->set_brightness(device, brightness);
}
error_t backlight_set_brightness_default(Device* device) {
const auto* driver = device_get_driver(device);
return BACKLIGHT_DRIVER_API(driver)->set_brightness_default(device);
}
error_t backlight_get_brightness(Device* device, uint8_t* out_brightness) {
const auto* driver = device_get_driver(device);
return BACKLIGHT_DRIVER_API(driver)->get_brightness(device, out_brightness);
}
uint8_t backlight_get_min_brightness(Device* device) {
const auto* driver = device_get_driver(device);
return BACKLIGHT_DRIVER_API(driver)->get_min_brightness(device);
}
uint8_t backlight_get_max_brightness(Device* device) {
const auto* driver = device_get_driver(device);
return BACKLIGHT_DRIVER_API(driver)->get_max_brightness(device);
}
const DeviceType BACKLIGHT_TYPE {
.name = "backlight"
};
}

View File

@ -6,76 +6,96 @@
extern "C" {
error_t display_reset(struct Device* device) {
error_t display_reset(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->reset(device);
}
error_t display_init(struct Device* device) {
error_t display_init(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->init(device);
}
error_t display_draw_bitmap(struct Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
error_t 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) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->draw_bitmap(device, x_start, y_start, x_end, y_end, color_data);
}
error_t display_mirror(struct Device* device, bool x_axis, bool y_axis) {
error_t display_mirror(Device* device, bool x_axis, bool y_axis) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->mirror(device, x_axis, y_axis);
}
error_t display_swap_xy(struct Device* device, bool swap_axes) {
error_t display_swap_xy(Device* device, bool swap_axes) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->swap_xy(device, swap_axes);
}
error_t display_set_gap(struct Device* device, int32_t x_gap, int32_t y_gap) {
bool display_get_swap_xy(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_swap_xy(device);
}
bool display_get_mirror_x(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_mirror_x(device);
}
bool display_get_mirror_y(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_mirror_y(device);
}
error_t display_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->set_gap(device, x_gap, y_gap);
}
error_t display_invert_color(struct Device* device, bool invert_color_data) {
error_t display_invert_color(Device* device, bool invert_color_data) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->invert_color(device, invert_color_data);
}
error_t display_disp_on_off(struct Device* device, bool on_off) {
error_t display_disp_on_off(Device* device, bool on_off) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->disp_on_off(device, on_off);
}
error_t display_disp_sleep(struct Device* device, bool sleep) {
error_t display_disp_sleep(Device* device, bool sleep) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->disp_sleep(device, sleep);
}
enum DisplayColorFormat display_get_color_format(struct Device* device) {
enum DisplayColorFormat display_get_color_format(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_color_format(device);
}
uint16_t display_get_resolution_x(struct Device* device) {
uint16_t display_get_resolution_x(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_resolution_x(device);
}
uint16_t display_get_resolution_y(struct Device* device) {
uint16_t display_get_resolution_y(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_resolution_y(device);
}
void display_get_frame_buffer(struct Device* device, uint8_t index, void** out_buffer) {
void display_get_frame_buffer(Device* device, uint8_t index, void** out_buffer) {
const auto* driver = device_get_driver(device);
DISPLAY_DRIVER_API(driver)->get_frame_buffer(device, index, out_buffer);
}
uint8_t display_get_frame_buffer_count(struct Device* device) {
uint8_t display_get_frame_buffer_count(Device* device) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_frame_buffer_count(device);
}
error_t display_get_backlight(Device* device, Device** backlight) {
const auto* driver = device_get_driver(device);
return DISPLAY_DRIVER_API(driver)->get_backlight(device, backlight);
}
const struct DeviceType DISPLAY_TYPE {
.name = "display"
};

View File

@ -6,17 +6,17 @@
extern "C" {
error_t grove_set_mode(struct Device* device, enum GroveMode mode) {
error_t grove_set_mode(Device* device, GroveMode mode) {
const auto* driver = device_get_driver(device);
return GROVE_DRIVER_API(driver)->set_mode(device, mode);
}
error_t grove_get_mode(struct Device* device, enum GroveMode* mode) {
error_t grove_get_mode(Device* device, GroveMode* mode) {
const auto* driver = device_get_driver(device);
return GROVE_DRIVER_API(driver)->get_mode(device, mode);
}
const struct DeviceType GROVE_TYPE {
const DeviceType GROVE_TYPE {
.name = "grove"
};

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/keyboard.h>
#include <tactility/device.h>
#define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api)
extern "C" {
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
const auto* driver = device_get_driver(device);
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
}
const DeviceType KEYBOARD_TYPE {
.name = "keyboard"
};
}

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/pointer.h>
#include <tactility/device.h>
#define POINTER_DRIVER_API(driver) ((struct PointerApi*)driver->api)
extern "C" {
error_t pointer_enter_sleep(struct Device* device) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->enter_sleep(device);
}
error_t pointer_exit_sleep(Device* device) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->exit_sleep(device);
}
error_t pointer_read_data(Device* device, TickType_t timeout) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->read_data(device, timeout);
}
bool pointer_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->get_touched_points(device, x, y, strength, point_count, max_point_count);
}
error_t pointer_set_swap_xy(Device* device, bool swap) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->set_swap_xy(device, swap);
}
error_t pointer_get_swap_xy(Device* device, bool* swap) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->get_swap_xy(device, swap);
}
error_t pointer_set_mirror_x(Device* device, bool mirror) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->set_mirror_x(device, mirror);
}
error_t pointer_get_mirror_x(Device* device, bool* mirror) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->get_mirror_x(device, mirror);
}
error_t pointer_set_mirror_y(Device* device, bool mirror) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->set_mirror_y(device, mirror);
}
error_t pointer_get_mirror_y(Device* device, bool* mirror) {
const auto* driver = device_get_driver(device);
return POINTER_DRIVER_API(driver)->get_mirror_y(device, mirror);
}
const struct DeviceType POINTER_TYPE {
.name = "pointer"
};
}

View File

@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/touch_placeholder.h>
#include <tactility/drivers/pointer_placeholder.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/touch.h>
#include <tactility/drivers/pointer.h>
#include <tactility/module.h>
extern "C" {
@ -12,13 +12,13 @@ static error_t stop(Device*) { return ERROR_NONE; }
extern Module root_module;
Driver touch_placeholder_driver = {
.name = "touch_placeholder",
.compatible = (const char*[]) { "touch-placeholder", nullptr },
Driver pointer_placeholder_driver = {
.name = "pointer_placeholder",
.compatible = (const char*[]) { "pointer-placeholder", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = &TOUCH_TYPE,
.device_type = &POINTER_TYPE,
.owner = &root_module,
.internal = nullptr
};

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/power_supply.h>
#include <tactility/device.h>
#define POWER_SUPPLY_DRIVER_API(driver) ((PowerSupplyApi*)driver->api)
extern "C" {
bool power_supply_supports_property(Device* device, PowerSupplyProperty property) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->supports_property(device, property);
}
error_t power_supply_get_property(Device* device, PowerSupplyProperty property, union PowerSupplyPropertyValue* out_value) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->get_property(device, property, out_value);
}
bool power_supply_supports_charge_control(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->supports_charge_control(device);
}
bool power_supply_is_allowed_to_charge(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->is_allowed_to_charge(device);
}
error_t power_supply_set_allowed_to_charge(Device* device, bool allowed) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->set_allowed_to_charge(device, allowed);
}
bool power_supply_supports_quick_charge(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->supports_quick_charge(device);
}
bool power_supply_is_quick_charge_enabled(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->is_quick_charge_enabled(device);
}
error_t power_supply_set_quick_charge_enabled(Device* device, bool enabled) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->set_quick_charge_enabled(device, enabled);
}
bool power_supply_supports_power_off(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->supports_power_off(device);
}
error_t power_supply_power_off(Device* device) {
const auto* driver = device_get_driver(device);
return POWER_SUPPLY_DRIVER_API(driver)->power_off(device);
}
const DeviceType POWER_SUPPLY_TYPE {
.name = "power_supply"
};
}

View File

@ -6,17 +6,17 @@
extern "C" {
error_t rtc_get_time(struct Device* device, struct RtcDateTime* dt) {
error_t rtc_get_time(Device* device, RtcDateTime* dt) {
const auto* driver = device_get_driver(device);
return RTC_DRIVER_API(driver)->get_time(device, dt);
}
error_t rtc_set_time(struct Device* device, const struct RtcDateTime* dt) {
error_t rtc_set_time(Device* device, const RtcDateTime* dt) {
const auto* driver = device_get_driver(device);
return RTC_DRIVER_API(driver)->set_time(device, dt);
}
const struct DeviceType RTC_TYPE {
const DeviceType RTC_TYPE {
.name = "rtc"
};

View File

@ -3,7 +3,7 @@
extern "C" {
const struct DeviceType SDCARD_TYPE {
const DeviceType SDCARD_TYPE {
.name = "sdcard"
};

View File

@ -7,22 +7,22 @@
extern "C" {
error_t spi_controller_lock(struct Device* device) {
error_t spi_controller_lock(Device* device) {
const auto* driver = device_get_driver(device);
return SPI_DRIVER_API(driver)->lock(device);
}
error_t spi_controller_try_lock(struct Device* device, TickType_t timeout) {
error_t spi_controller_try_lock(Device* device, TickType_t timeout) {
const auto* driver = device_get_driver(device);
return SPI_DRIVER_API(driver)->try_lock(device, timeout);
}
error_t spi_controller_unlock(struct Device* device) {
error_t spi_controller_unlock(Device* device) {
const auto* driver = device_get_driver(device);
return SPI_DRIVER_API(driver)->unlock(device);
}
const struct DeviceType SPI_CONTROLLER_TYPE {
const DeviceType SPI_CONTROLLER_TYPE {
.name = "spi-controller"
};

View File

@ -1,63 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/touch.h>
#include <tactility/device.h>
#define TOUCH_DRIVER_API(driver) ((struct TouchApi*)driver->api)
extern "C" {
error_t touch_enter_sleep(struct Device* device) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->enter_sleep(device);
}
error_t touch_exit_sleep(struct Device* device) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->exit_sleep(device);
}
error_t touch_read_data(struct Device* device, TickType_t timeout) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->read_data(device, timeout);
}
bool touch_get_touched_points(struct Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->get_touched_points(device, x, y, strength, point_count, max_point_count);
}
error_t touch_set_swap_xy(struct Device* device, bool swap) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->set_swap_xy(device, swap);
}
error_t touch_get_swap_xy(struct Device* device, bool* swap) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->get_swap_xy(device, swap);
}
error_t touch_set_mirror_x(struct Device* device, bool mirror) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->set_mirror_x(device, mirror);
}
error_t touch_get_mirror_x(struct Device* device, bool* mirror) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->get_mirror_x(device, mirror);
}
error_t touch_set_mirror_y(struct Device* device, bool mirror) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->set_mirror_y(device, mirror);
}
error_t touch_get_mirror_y(struct Device* device, bool* mirror) {
const auto* driver = device_get_driver(device);
return TOUCH_DRIVER_API(driver)->get_mirror_y(device, mirror);
}
const struct DeviceType TOUCH_TYPE {
.name = "touch"
};
}

View File

@ -16,8 +16,8 @@ static error_t start() {
if (driver_construct_add(&root_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver display_placeholder_driver;
if (driver_construct_add(&display_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver touch_placeholder_driver;
if (driver_construct_add(&touch_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver pointer_placeholder_driver;
if (driver_construct_add(&pointer_placeholder_driver) != ERROR_NONE) return ERROR_RESOURCE;
extern Driver spi_peripheral_driver;
if (driver_construct_add(&spi_peripheral_driver) != ERROR_NONE) return ERROR_RESOURCE;
return ERROR_NONE;