Device migrations, drivers and fixes (#571)
Device migrations: - cyd-4848S040c - guition-jc1060p470ciwy - guition-jc2432w328c - guition-jc3248w535c (known issue with display and touch, Shadowtrance will look into it) - guition-jc8048w550c - heltec-wifi-lora-32-v3 - lilygo-tdeck-max (excluding graphics) - lilygo-tdisplay - lilygo-tdisplay-s3 - lilygo-tdongle-s3 - lilygo-tlora-pager Driver migrations: - Implemented haptic driver interface in kernel - AXS15231b - BQ25896 - BQ27220 - CST328 - CST6xx - DRV2605 - JD9165 - Custom LilyGO driver for T-Lora Pager - SSD1306 - ST7701 - ST7735 - SY6970 - TCA8418 Fixes/improvements: - Boot app: support for multiple power devices, improved UI - lvgl_devices and related code: fixes for mapping - Support for arrays in dts parser
This commit is contained in:
committed by
GitHub
parent
3b5a401594
commit
d896657bf9
@@ -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(cst66xx-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
description: >
|
||||
Hynitron CST66xx capacitive touch controller. Vendor docs for the LilyGO T-Deck Max label
|
||||
this chip "CST328", but the vendor HynTouch driver probes cst66xx first and that is what
|
||||
answers on that board. This is a minimal, single-touch port of the vendor's
|
||||
hyn_cst66xx.c protocol.
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "hynitron,cst66xx"
|
||||
|
||||
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
|
||||
required: true
|
||||
description: Reset GPIO pin. The chip only re-initializes into normal reporting mode after a clean reset pulse.
|
||||
reset-active-high:
|
||||
type: boolean
|
||||
default: false
|
||||
description: Whether the reset pin is active high
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <drivers/cst66xx.h>
|
||||
|
||||
DEFINE_DEVICETREE(cst66xx, struct Cst66xxConfig)
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module cst66xx_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 Cst66xxConfig {
|
||||
// Devicetree address hint (0x1A on the LilyGO T-Deck Max).
|
||||
uint8_t address;
|
||||
uint16_t x_max;
|
||||
uint16_t y_max;
|
||||
bool swap_xy;
|
||||
bool mirror_x;
|
||||
bool mirror_y;
|
||||
struct GpioPinSpec pin_reset;
|
||||
bool reset_active_high;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,266 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/cst66xx.h>
|
||||
#include <cst66xx_module.h>
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <tactility/drivers/gpio_descriptor.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#define TAG "CST66xx"
|
||||
#define GET_CONFIG(device) (static_cast<const Cst66xxConfig*>((device)->config))
|
||||
|
||||
static const TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(20);
|
||||
|
||||
// The protocol uses 4-byte register addresses, ported from the vendor's lib/HynTouch/src/hyn_cst66xx.c
|
||||
// (see Devices/lilygo-tdeck-max's former Source/devices/Cst66xxTouch.cpp for the pre-migration history).
|
||||
|
||||
// Normal-mode setup sequence (cst66xx_set_workmode, NOMAL_MODE).
|
||||
static constexpr uint8_t CMD_DISABLE_LP_I2C[4] = { 0xD0, 0x00, 0x04, 0x00 };
|
||||
static constexpr uint8_t CMD_NORMAL_A[4] = { 0xD0, 0x00, 0x00, 0x00 };
|
||||
static constexpr uint8_t CMD_NORMAL_B[4] = { 0xD0, 0x00, 0x0C, 0x00 };
|
||||
static constexpr uint8_t CMD_NORMAL_C[4] = { 0xD0, 0x00, 0x01, 0x00 };
|
||||
// Read-point register: write these 4 bytes, then read the touch frame.
|
||||
static constexpr uint8_t REG_READ_POINT[4] = { 0xD0, 0x07, 0x00, 0x00 };
|
||||
// Acknowledge/clear after each read.
|
||||
static constexpr uint8_t CMD_ACK[4] = { 0xD0, 0x00, 0x02, 0xAB };
|
||||
// Identity/config register (cst66xx_updata_tpinfo): read 50 bytes; buf[2]==0xCA && buf[3]==0xCA confirms a CST66xx.
|
||||
static constexpr uint8_t REG_INFO[4] = { 0xD0, 0x03, 0x00, 0x00 };
|
||||
|
||||
struct Cst66xxInternal {
|
||||
int16_t last_x;
|
||||
int16_t last_y;
|
||||
bool has_point;
|
||||
bool swap_xy;
|
||||
bool mirror_x;
|
||||
bool mirror_y;
|
||||
};
|
||||
|
||||
// region Driver lifecycle
|
||||
|
||||
static void set_normal_mode(Device* i2c, uint8_t address) {
|
||||
i2c_controller_write(i2c, address, CMD_DISABLE_LP_I2C, sizeof(CMD_DISABLE_LP_I2C), I2C_TIMEOUT);
|
||||
delay_millis(1);
|
||||
i2c_controller_write(i2c, address, CMD_DISABLE_LP_I2C, sizeof(CMD_DISABLE_LP_I2C), I2C_TIMEOUT);
|
||||
i2c_controller_write(i2c, address, CMD_NORMAL_A, sizeof(CMD_NORMAL_A), I2C_TIMEOUT);
|
||||
i2c_controller_write(i2c, address, CMD_NORMAL_B, sizeof(CMD_NORMAL_B), I2C_TIMEOUT);
|
||||
i2c_controller_write(i2c, address, CMD_NORMAL_C, sizeof(CMD_NORMAL_C), I2C_TIMEOUT);
|
||||
}
|
||||
|
||||
static void perform_hardware_reset(const Cst66xxConfig* config) {
|
||||
if (config->pin_reset.gpio_controller == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* rst = gpio_descriptor_acquire(config->pin_reset.gpio_controller, config->pin_reset.pin, GPIO_OWNER_GPIO);
|
||||
if (rst == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_descriptor_set_flags(rst, GPIO_FLAG_DIRECTION_OUTPUT);
|
||||
gpio_descriptor_set_level(rst, config->reset_active_high);
|
||||
delay_millis(10);
|
||||
gpio_descriptor_set_level(rst, !config->reset_active_high);
|
||||
gpio_descriptor_release(rst);
|
||||
// The reset pulse exits boot mode; give the controller time to re-init.
|
||||
delay_millis(80);
|
||||
}
|
||||
|
||||
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<Cst66xxInternal*>(malloc(sizeof(Cst66xxInternal)));
|
||||
if (internal == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
internal->last_x = 0;
|
||||
internal->last_y = 0;
|
||||
internal->has_point = false;
|
||||
internal->swap_xy = config->swap_xy;
|
||||
internal->mirror_x = config->mirror_x;
|
||||
internal->mirror_y = config->mirror_y;
|
||||
|
||||
perform_hardware_reset(config);
|
||||
|
||||
// Put the controller into normal reporting mode and verify the identity. The first read
|
||||
// after reset can miss, so retry like the vendor's cst66xx_updata_tpinfo (read 0xD0030000,
|
||||
// expect buf[2]==buf[3]==0xCA).
|
||||
bool confirmed = false;
|
||||
for (int attempt = 0; attempt < 5 && !confirmed; ++attempt) {
|
||||
set_normal_mode(parent, config->address);
|
||||
uint8_t info[50] = {};
|
||||
if (i2c_controller_write(parent, config->address, REG_INFO, sizeof(REG_INFO), I2C_TIMEOUT) == ERROR_NONE &&
|
||||
i2c_controller_read(parent, config->address, info, sizeof(info), I2C_TIMEOUT) == ERROR_NONE &&
|
||||
info[2] == 0xCA && info[3] == 0xCA) {
|
||||
confirmed = true;
|
||||
} else {
|
||||
delay_millis(10);
|
||||
}
|
||||
}
|
||||
if (confirmed) {
|
||||
LOG_I(TAG, "CST66xx initialised");
|
||||
} else {
|
||||
LOG_W(TAG, "CST66xx identity not confirmed (continuing)");
|
||||
}
|
||||
|
||||
device_set_driver_data(device, internal);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop(Device* device) {
|
||||
auto* internal = static_cast<Cst66xxInternal*>(device_get_driver_data(device));
|
||||
free(internal);
|
||||
device_set_driver_data(device, nullptr);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region PointerApi
|
||||
|
||||
static error_t cst66xx_read_data(Device* device, TickType_t /*timeout*/) {
|
||||
auto* internal = static_cast<Cst66xxInternal*>(device_get_driver_data(device));
|
||||
const auto* config = GET_CONFIG(device);
|
||||
auto* parent = device_get_parent(device);
|
||||
|
||||
// CST66xx frame (cst66xx_report): write the read-point register, then read 9 bytes.
|
||||
// buf[2]=report type (0xFF=position/key), buf[3] low nibble = finger count, high nibble =
|
||||
// key count. The first 5-byte slot (buf[4..8]) is a key slot when key count > 0, otherwise
|
||||
// the first finger; further fingers, if any, follow at buf[index+...].
|
||||
uint8_t buf[9] = {};
|
||||
error_t err = i2c_controller_write(parent, config->address, REG_READ_POINT, sizeof(REG_READ_POINT), I2C_TIMEOUT);
|
||||
if (err == ERROR_NONE) {
|
||||
err = i2c_controller_read(parent, config->address, buf, sizeof(buf), I2C_TIMEOUT);
|
||||
}
|
||||
if (err != ERROR_NONE) {
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// Acknowledge/clear the frame after every read.
|
||||
i2c_controller_write(parent, config->address, CMD_ACK, sizeof(CMD_ACK), I2C_TIMEOUT);
|
||||
|
||||
const uint8_t reportType = buf[2];
|
||||
const uint8_t fingerCount = buf[3] & 0x0F;
|
||||
const uint8_t keyCount = (buf[3] & 0xF0) >> 4;
|
||||
|
||||
// The controller also reports the three bezel touch-keys (heart/speech-bubble/paper-airplane)
|
||||
// mutually exclusively with finger coordinates in this same frame shape. Kernel drivers don't
|
||||
// have a home for UI-navigation policy (see the CST66xx driver's module README), so key frames
|
||||
// are just treated as "no touch" here.
|
||||
if ((reportType == 0xFF && keyCount > 0) || reportType != 0xFF || fingerCount < 1) {
|
||||
internal->has_point = false;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// First finger's slot follows any key slots.
|
||||
const uint8_t index = keyCount * 5;
|
||||
const uint8_t event = buf[index + 8] >> 4; // 0 = up
|
||||
if (event == 0) {
|
||||
internal->has_point = false;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
int16_t rawX = static_cast<int16_t>(buf[index + 4] | (static_cast<uint16_t>(buf[index + 7] & 0x0F) << 8));
|
||||
int16_t rawY = static_cast<int16_t>(buf[index + 5] | (static_cast<uint16_t>(buf[index + 7] & 0xF0) << 4));
|
||||
|
||||
if (internal->swap_xy) {
|
||||
std::swap(rawX, rawY);
|
||||
}
|
||||
if (internal->mirror_x) {
|
||||
rawX = static_cast<int16_t>(config->x_max - 1 - rawX);
|
||||
}
|
||||
if (internal->mirror_y) {
|
||||
rawY = static_cast<int16_t>(config->y_max - 1 - rawY);
|
||||
}
|
||||
|
||||
internal->last_x = rawX;
|
||||
internal->last_y = rawY;
|
||||
internal->has_point = true;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static bool cst66xx_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<Cst66xxInternal*>(device_get_driver_data(device));
|
||||
if (!internal->has_point || max_point_count < 1) {
|
||||
*point_count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
x[0] = static_cast<uint16_t>(internal->last_x);
|
||||
y[0] = static_cast<uint16_t>(internal->last_y);
|
||||
if (strength != nullptr) {
|
||||
strength[0] = 0;
|
||||
}
|
||||
*point_count = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static error_t cst66xx_set_swap_xy(Device* device, bool swap) {
|
||||
static_cast<Cst66xxInternal*>(device_get_driver_data(device))->swap_xy = swap;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t cst66xx_get_swap_xy(Device* device, bool* swap) {
|
||||
*swap = static_cast<Cst66xxInternal*>(device_get_driver_data(device))->swap_xy;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t cst66xx_set_mirror_x(Device* device, bool mirror) {
|
||||
static_cast<Cst66xxInternal*>(device_get_driver_data(device))->mirror_x = mirror;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t cst66xx_get_mirror_x(Device* device, bool* mirror) {
|
||||
*mirror = static_cast<Cst66xxInternal*>(device_get_driver_data(device))->mirror_x;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t cst66xx_set_mirror_y(Device* device, bool mirror) {
|
||||
static_cast<Cst66xxInternal*>(device_get_driver_data(device))->mirror_y = mirror;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t cst66xx_get_mirror_y(Device* device, bool* mirror) {
|
||||
*mirror = static_cast<Cst66xxInternal*>(device_get_driver_data(device))->mirror_y;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// enter_sleep/exit_sleep are not exposed: the ported protocol has no equivalent commands.
|
||||
|
||||
// endregion
|
||||
|
||||
static const PointerApi cst66xx_pointer_api = {
|
||||
.enter_sleep = nullptr,
|
||||
.exit_sleep = nullptr,
|
||||
.read_data = cst66xx_read_data,
|
||||
.get_touched_points = cst66xx_get_touched_points,
|
||||
.set_swap_xy = cst66xx_set_swap_xy,
|
||||
.get_swap_xy = cst66xx_get_swap_xy,
|
||||
.set_mirror_x = cst66xx_set_mirror_x,
|
||||
.get_mirror_x = cst66xx_get_mirror_x,
|
||||
.set_mirror_y = cst66xx_set_mirror_y,
|
||||
.get_mirror_y = cst66xx_get_mirror_y,
|
||||
};
|
||||
|
||||
Driver cst66xx_driver = {
|
||||
.name = "cst66xx",
|
||||
.compatible = (const char*[]) { "hynitron,cst66xx", nullptr },
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = &cst66xx_pointer_api,
|
||||
.device_type = &POINTER_TYPE,
|
||||
.owner = &cst66xx_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
@@ -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 cst66xx_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(&cst66xx_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
check(driver_remove_destruct(&cst66xx_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module cst66xx_module = {
|
||||
.name = "cst66xx",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user