mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-19 00:15:05 +00:00
WIP kernel drivers for T-Deck
This commit is contained in:
parent
5292901092
commit
77e6872191
@ -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()
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
extern "C" {
|
||||
|
||||
extern void register_device_drivers() {
|
||||
/* NO-OP */
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
7
Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h
Normal file
7
Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h
Normal 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)
|
||||
@ -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)
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
@ -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();
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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; }
|
||||
};
|
||||
16
Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h
Normal file
16
Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h
Normal 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
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
101
Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp
Normal file
101
Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp
Normal 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
|
||||
};
|
||||
122
Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp
Normal file
122
Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp
Normal 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
|
||||
};
|
||||
@ -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.
|
||||
7
Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml
Normal file
7
Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
description: LilyGO T-Deck onboard keyboard controller
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "lilygo,tdeck-keyboard"
|
||||
|
||||
bus: i2c
|
||||
@ -1,3 +1,6 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/st7789-module
|
||||
- Drivers/gt911-module
|
||||
bindings: bindings
|
||||
dts: lilygo,tdeck.dts
|
||||
|
||||
@ -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>;
|
||||
};
|
||||
};
|
||||
|
||||
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
11
Drivers/gt911-module/CMakeLists.txt
Normal file
11
Drivers/gt911-module/CMakeLists.txt
Normal 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
|
||||
)
|
||||
45
Drivers/gt911-module/bindings/goodix,gt911.yaml
Normal file
45
Drivers/gt911-module/bindings/goodix,gt911.yaml
Normal 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
|
||||
3
Drivers/gt911-module/devicetree.yaml
Normal file
3
Drivers/gt911-module/devicetree.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
7
Drivers/gt911-module/include/bindings/gt911.h
Normal file
7
Drivers/gt911-module/include/bindings/gt911.h
Normal 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)
|
||||
31
Drivers/gt911-module/include/drivers/gt911.h
Normal file
31
Drivers/gt911-module/include/drivers/gt911.h
Normal 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
|
||||
14
Drivers/gt911-module/include/gt911_module.h
Normal file
14
Drivers/gt911-module/include/gt911_module.h
Normal 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
|
||||
205
Drivers/gt911-module/source/gt911.cpp
Normal file
205
Drivers/gt911-module/source/gt911.cpp
Normal 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 = >911_pointer_api,
|
||||
.device_type = &POINTER_TYPE,
|
||||
.owner = >911_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
32
Drivers/gt911-module/source/module.cpp
Normal file
32
Drivers/gt911-module/source/module.cpp
Normal 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(>911_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(>911_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module gt911_module = {
|
||||
.name = "gt911",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
11
Drivers/st7789-module/CMakeLists.txt
Normal file
11
Drivers/st7789-module/CMakeLists.txt
Normal 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
|
||||
)
|
||||
71
Drivers/st7789-module/bindings/sitronix,st7789.yaml
Normal file
71
Drivers/st7789-module/bindings/sitronix,st7789.yaml
Normal 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
|
||||
3
Drivers/st7789-module/devicetree.yaml
Normal file
3
Drivers/st7789-module/devicetree.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
7
Drivers/st7789-module/include/bindings/st7789.h
Normal file
7
Drivers/st7789-module/include/bindings/st7789.h
Normal 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)
|
||||
36
Drivers/st7789-module/include/drivers/st7789.h
Normal file
36
Drivers/st7789-module/include/drivers/st7789.h
Normal 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
|
||||
14
Drivers/st7789-module/include/st7789_module.h
Normal file
14
Drivers/st7789-module/include/st7789_module.h
Normal 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
|
||||
32
Drivers/st7789-module/source/module.cpp
Normal file
32
Drivers/st7789-module/source/module.cpp
Normal 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"
|
||||
278
Drivers/st7789-module/source/st7789.cpp
Normal file
278
Drivers/st7789-module/source/st7789.cpp
Normal 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
|
||||
};
|
||||
61
Modules/lvgl-module/include/tactility/lvgl_display.h
Normal file
61
Modules/lvgl-module/include/tactility/lvgl_display.h
Normal 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
|
||||
37
Modules/lvgl-module/include/tactility/lvgl_keyboard.h
Normal file
37
Modules/lvgl-module/include/tactility/lvgl_keyboard.h
Normal 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
|
||||
37
Modules/lvgl-module/include/tactility/lvgl_pointer.h
Normal file
37
Modules/lvgl-module/include/tactility/lvgl_pointer.h
Normal 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
|
||||
@ -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();
|
||||
|
||||
54
Modules/lvgl-module/source/lvgl_devices.c
Normal file
54
Modules/lvgl-module/source/lvgl_devices.c
Normal 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);
|
||||
}
|
||||
}
|
||||
215
Modules/lvgl-module/source/lvgl_display.c
Normal file
215
Modules/lvgl-module/source/lvgl_display.c
Normal 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);
|
||||
}
|
||||
}
|
||||
67
Modules/lvgl-module/source/lvgl_keyboard.c
Normal file
67
Modules/lvgl-module/source/lvgl_keyboard.c
Normal 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);
|
||||
}
|
||||
76
Modules/lvgl-module/source/lvgl_pointer.c
Normal file
76
Modules/lvgl-module/source/lvgl_pointer.c
Normal 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);
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
133
Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp
Normal file
133
Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp
Normal 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
|
||||
};
|
||||
@ -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
|
||||
|
||||
@ -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 {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
284
Tactility/Source/app/kerneldisplay/KernelDisplay.cpp
Normal file
284
Tactility/Source/app/kerneldisplay/KernelDisplay.cpp
Normal 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
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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*/) {
|
||||
|
||||
172
Tactility/Source/hal/power/PowerDevice.cpp
Normal file
172
Tactility/Source/hal/power/PowerDevice.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
5
TactilityKernel/bindings/pointer-placeholder.yaml
Normal file
5
TactilityKernel/bindings/pointer-placeholder.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
description: Pointer placeholder
|
||||
|
||||
compatible: "pointer-placeholder"
|
||||
|
||||
properties: {}
|
||||
@ -1,5 +0,0 @@
|
||||
description: Touch placeholder
|
||||
|
||||
compatible: "touch-placeholder"
|
||||
|
||||
properties: {}
|
||||
@ -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
|
||||
}
|
||||
96
TactilityKernel/include/tactility/drivers/backlight.h
Normal file
96
TactilityKernel/include/tactility/drivers/backlight.h
Normal 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
|
||||
@ -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
|
||||
|
||||
51
TactilityKernel/include/tactility/drivers/keyboard.h
Normal file
51
TactilityKernel/include/tactility/drivers/keyboard.h
Normal 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
|
||||
@ -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
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct TouchPlaceholderConfig {
|
||||
struct PointerPlaceholderConfig {
|
||||
uint8_t _unused;
|
||||
};
|
||||
|
||||
163
TactilityKernel/include/tactility/drivers/power_supply.h
Normal file
163
TactilityKernel/include/tactility/drivers/power_supply.h
Normal 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
|
||||
38
TactilityKernel/source/drivers/backlight.cpp
Normal file
38
TactilityKernel/source/drivers/backlight.cpp
Normal 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"
|
||||
};
|
||||
|
||||
}
|
||||
@ -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"
|
||||
};
|
||||
|
||||
@ -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"
|
||||
};
|
||||
|
||||
|
||||
18
TactilityKernel/source/drivers/keyboard.cpp
Normal file
18
TactilityKernel/source/drivers/keyboard.cpp
Normal 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"
|
||||
};
|
||||
|
||||
}
|
||||
63
TactilityKernel/source/drivers/pointer.cpp
Normal file
63
TactilityKernel/source/drivers/pointer.cpp
Normal 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"
|
||||
};
|
||||
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
63
TactilityKernel/source/drivers/power_supply.cpp
Normal file
63
TactilityKernel/source/drivers/power_supply.cpp
Normal 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"
|
||||
};
|
||||
|
||||
}
|
||||
@ -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"
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType SDCARD_TYPE {
|
||||
const DeviceType SDCARD_TYPE {
|
||||
.name = "sdcard"
|
||||
};
|
||||
|
||||
|
||||
@ -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"
|
||||
};
|
||||
|
||||
|
||||
@ -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"
|
||||
};
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user