// SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TAG "CST66xx" #define GET_CONFIG(device) (static_cast((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(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(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(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(buf[index + 4] | (static_cast(buf[index + 7] & 0x0F) << 8)); int16_t rawY = static_cast(buf[index + 5] | (static_cast(buf[index + 7] & 0xF0) << 4)); if (internal->swap_xy) { std::swap(rawX, rawY); } if (internal->mirror_x) { rawX = static_cast(config->x_max - 1 - rawX); } if (internal->mirror_y) { rawY = static_cast(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(device_get_driver_data(device)); if (!internal->has_point || max_point_count < 1) { *point_count = 0; return false; } x[0] = static_cast(internal->last_x); y[0] = static_cast(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(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(device_get_driver_data(device))->swap_xy; return ERROR_NONE; } static error_t cst66xx_set_mirror_x(Device* device, bool mirror) { static_cast(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(device_get_driver_data(device))->mirror_x; return ERROR_NONE; } static error_t cst66xx_set_mirror_y(Device* device, bool mirror) { static_cast(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(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 };