From 828a0c04a18b9e1feee8a4b47d20d9bd11ace1ca Mon Sep 17 00:00:00 2001 From: Ilia Kharin Date: Sun, 25 Feb 2024 21:13:06 +0100 Subject: [PATCH] drivers: input: pinnacle: add driver for trackpad The initial version of an input driver for Cirque Pinnacle ASIC supports: * Setting sensitivity * Choosing between relative and absolute modes * Relative mode * Primary tap * Swapping X and Y * Absolute mode * Setting number of idle packets * Clipping coordinates outside of active range * Scaling coordinates * Inverting X and Y coordinates Signed-off-by: Ilia Kharin --- drivers/input/CMakeLists.txt | 1 + drivers/input/Kconfig | 1 + drivers/input/Kconfig.pinnacle | 14 + drivers/input/input_pinnacle.c | 909 ++++++++++++++++++ .../input/cirque,pinnacle-common.yaml | 113 +++ dts/bindings/input/cirque,pinnacle-i2c.yaml | 8 + dts/bindings/input/cirque,pinnacle-spi.yaml | 8 + dts/bindings/vendor-prefixes.txt | 1 + tests/drivers/build_all/input/app.overlay | 24 +- 9 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 drivers/input/Kconfig.pinnacle create mode 100644 drivers/input/input_pinnacle.c create mode 100644 dts/bindings/input/cirque,pinnacle-common.yaml create mode 100644 dts/bindings/input/cirque,pinnacle-i2c.yaml create mode 100644 dts/bindings/input/cirque,pinnacle-spi.yaml diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 67311587fd..3a5c901c44 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_ITE_IT8XXX2_KBD input_ite_it8xxx2_kbd. zephyr_library_sources_ifdef(CONFIG_INPUT_KBD_MATRIX input_kbd_matrix.c) zephyr_library_sources_ifdef(CONFIG_INPUT_NPCX_KBD input_npcx_kbd.c) zephyr_library_sources_ifdef(CONFIG_INPUT_PAT912X input_pat912x.c) +zephyr_library_sources_ifdef(CONFIG_INPUT_PINNACLE input_pinnacle.c) zephyr_library_sources_ifdef(CONFIG_INPUT_PMW3610 input_pmw3610.c) zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c) zephyr_library_sources_ifdef(CONFIG_INPUT_XEC_KBD input_xec_kbd.c) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 4562fda5a3..72c6625849 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -22,6 +22,7 @@ source "drivers/input/Kconfig.it8xxx2" source "drivers/input/Kconfig.kbd_matrix" source "drivers/input/Kconfig.npcx" source "drivers/input/Kconfig.pat912x" +source "drivers/input/Kconfig.pinnacle" source "drivers/input/Kconfig.pmw3610" source "drivers/input/Kconfig.sdl" source "drivers/input/Kconfig.stmpe811" diff --git a/drivers/input/Kconfig.pinnacle b/drivers/input/Kconfig.pinnacle new file mode 100644 index 0000000000..05b9bbb0ad --- /dev/null +++ b/drivers/input/Kconfig.pinnacle @@ -0,0 +1,14 @@ +# Cirque Pinnacle 1CA027 touch controller based device configuration options +# +# Copyright (c) 2024 Ilia Kharin +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_PINNACLE + bool "Cirque Pinnacle 1CA027 Touch Controller Based Device" + default y + depends on DT_HAS_CIRQUE_PINNACLE_ENABLED + select GPIO + select I2C if $(dt_compat_on_bus,$(DT_COMPAT_CIRQUE_PINNACLE),i2c) + select SPI if $(dt_compat_on_bus,$(DT_COMPAT_CIRQUE_PINNACLE),spi) + help + Enable driver for Cirque Pinnacle 1CA027 tracked input device. diff --git a/drivers/input/input_pinnacle.c b/drivers/input/input_pinnacle.c new file mode 100644 index 0000000000..bcf60be1d7 --- /dev/null +++ b/drivers/input/input_pinnacle.c @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2024 Ilia Kharin + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT cirque_pinnacle + +#include +#include +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) +#include +#endif +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) +#include +#endif +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(pinnacle, CONFIG_INPUT_LOG_LEVEL); + +/* + * Register Access Protocol Standard Registers. + * Standard registers have 5-bit addresses, BIT[4:0], that range from + * 0x00 to 0x1F. For reading, a register address has to be combined with + * 0xA0 for reading and 0x80 for writing bits, BIT[7:5]. + */ +#define PINNACLE_REG_FIRMWARE_ID 0x00 /* R */ +#define PINNACLE_REG_FIRMWARE_VERSION 0x01 /* R */ +#define PINNACLE_REG_STATUS1 0x02 /* R/W */ +#define PINNACLE_REG_SYS_CONFIG1 0x03 /* R/W */ +#define PINNACLE_REG_FEED_CONFIG1 0x04 /* R/W */ +#define PINNACLE_REG_FEED_CONFIG2 0x05 /* R/W */ +#define PINNACLE_REG_FEED_CONFIG3 0x06 /* R/W */ +#define PINNACLE_REG_CAL_CONFIG1 0x07 /* R/W */ +#define PINNACLE_REG_PS2_AUX_CONTROL 0x08 /* R/W */ +#define PINNACLE_REG_SAMPLE_RATE 0x09 /* R/W */ +#define PINNACLE_REG_Z_IDLE 0x0A /* R/W */ +#define PINNACLE_REG_Z_SCALER 0x0B /* R/W */ +#define PINNACLE_REG_SLEEP_INTERVAL 0x0C /* R/W */ +#define PINNACLE_REG_SLEEP_TIMER 0x0D /* R/W */ +#define PINNACLE_REG_EMI_THRESHOLD 0x0E /* R/W */ +#define PINNACLE_REG_PACKET_BYTE0 0x12 /* R */ +#define PINNACLE_REG_PACKET_BYTE1 0x13 /* R */ +#define PINNACLE_REG_PACKET_BYTE2 0x14 /* R */ +#define PINNACLE_REG_PACKET_BYTE3 0x15 /* R */ +#define PINNACLE_REG_PACKET_BYTE4 0x16 /* R */ +#define PINNACLE_REG_PACKET_BYTE5 0x17 /* R */ +#define PINNACLE_REG_GPIO_A_CTRL 0x18 /* R/W */ +#define PINNACLE_REG_GPIO_A_DATA 0x19 /* R/W */ +#define PINNACLE_REG_GPIO_B_CTRL_DATA 0x1A /* R/W */ +/* Value of the extended register */ +#define PINNACLE_REG_ERA_VALUE 0x1B /* R/W */ +/* High byte BIT[15:8] of the 16 bit extended register */ +#define PINNACLE_REG_ERA_ADDR_HIGH 0x1C /* R/W */ +/* Low byte BIT[7:0] of the 16 bit extended register */ +#define PINNACLE_REG_ERA_ADDR_LOW 0x1D /* R/W */ +#define PINNACLE_REG_ERA_CTRL 0x1E /* R/W */ +#define PINNACLE_REG_PRODUCT_ID 0x1F /* R */ + +/* Extended Register Access */ +#define PINNACLE_ERA_REG_CONFIG 0x0187 /* R/W */ + +/* Firmware ASIC ID value */ +#define PINNACLE_FIRMWARE_ID 0x07 + +/* Status1 definition */ +#define PINNACLE_STATUS1_SW_DR BIT(2) +#define PINNACLE_STATUS1_SW_CC BIT(3) + +/* SysConfig1 definition */ +#define PINNACLE_SYS_CONFIG1_RESET BIT(0) +#define PINNACLE_SYS_CONFIG1_SHUTDOWN BIT(1) +#define PINNACLE_SYS_CONFIG1_LOW_POWER_MODE BIT(2) + +/* FeedConfig1 definition */ +#define PINNACLE_FEED_CONFIG1_FEED_ENABLE BIT(0) +#define PINNACLE_FEED_CONFIG1_DATA_MODE_ABSOLUTE BIT(1) +#define PINNACLE_FEED_CONFIG1_FILTER_DISABLE BIT(2) +#define PINNACLE_FEED_CONFIG1_X_DISABLE BIT(3) +#define PINNACLE_FEED_CONFIG1_Y_DISABLE BIT(4) +#define PINNACLE_FEED_CONFIG1_X_INVERT BIT(6) +#define PINNACLE_FEED_CONFIG1_Y_INVERT BIT(7) +/* X max to 0 */ +#define PINNACLE_FEED_CONFIG1_X_DATA_INVERT BIT(6) +/* Y max to 0 */ +#define PINNACLE_FEED_CONFIG1_Y_DATA_INVERT BIT(7) + +/* FeedConfig2 definition */ +#define PINNACLE_FEED_CONFIG2_INTELLIMOUSE_ENABLE BIT(0) +#define PINNACLE_FEED_CONFIG2_ALL_TAPS_DISABLE BIT(1) +#define PINNACLE_FEED_CONFIG2_SECONDARY_TAP_DISABLE BIT(2) +#define PINNACLE_FEED_CONFIG2_SCROLL_DISABLE BIT(3) +#define PINNACLE_FEED_CONFIG2_GLIDE_EXTEND_DISABLE BIT(4) +/* 90 degrees rotation */ +#define PINNACLE_FEED_CONFIG2_SWAP_X_AND_Y BIT(7) + +/* Relative position status in PacketByte0 */ +#define PINNACLE_PACKET_BYTE0_BTN_PRIMARY BIT(0) +#define PINNACLE_PACKET_BYTE0_BTN_SECONDRY BIT(1) + +/* Extended Register Access Control */ +#define PINNACLE_ERA_CTRL_READ BIT(0) +#define PINNACLE_ERA_CTRL_WRITE BIT(1) +#define PINNACLE_ERA_CTRL_READ_AUTO_INC BIT(2) +#define PINNACLE_ERA_CTRL_WRITE_AUTO_INC BIT(3) +/* Asserting both BIT(1) and BIT(0) means WRITE/Verify */ +#define PINNACLE_ERA_CTRL_WRITE_VERIFY (BIT(1) | BIT(0)) +#define PINNACLE_ERA_CTRL_COMPLETE 0x00 + +/* Extended Register Access Config */ +#define PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X1 0x00 +#define PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X2 0x40 +#define PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X3 0x80 +#define PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X4 0xC0 + +/* + * Delay and retry count for waiting completion of calibration with 200 ms of + * timeout. + */ +#define PINNACLE_CALIBRATION_AWAIT_DELAY_POLL_US 50000 +#define PINNACLE_CALIBRATION_AWAIT_RETRY_COUNT 4 + +/* + * Delay and retry count for waiting completion of ERA command with 50 ms of + * timeout. + */ +#define PINNACLE_ERA_AWAIT_DELAY_POLL_US 10000 +#define PINNACLE_ERA_AWAIT_RETRY_COUNT 5 + +/* Special definitions */ +#define PINNACLE_SPI_FB 0xFB /* Filler byte */ +#define PINNACLE_SPI_FC 0xFC /* Auto-increment byte */ + +/* Read and write masks */ +#define PINNACLE_READ_MSK 0xA0 +#define PINNACLE_WRITE_MSK 0x80 + +/* Read and write register addresses */ +#define PINNACLE_READ_REG(addr) (PINNACLE_READ_MSK | addr) +#define PINNACLE_WRITE_REG(addr) (PINNACLE_WRITE_MSK | addr) + +struct pinnacle_bus { + union { +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) + struct i2c_dt_spec i2c; +#endif +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) + struct spi_dt_spec spi; +#endif + }; + bool (*is_ready)(const struct pinnacle_bus *bus); + int (*write)(const struct pinnacle_bus *bus, uint8_t address, uint8_t value); + int (*seq_write)(const struct pinnacle_bus *bus, uint8_t *address, uint8_t *value, + uint8_t count); + int (*read)(const struct pinnacle_bus *bus, uint8_t address, uint8_t *value); + int (*seq_read)(const struct pinnacle_bus *bus, uint8_t address, uint8_t *data, + uint8_t count); +}; + +enum pinnacle_sensitivity { + PINNACLE_SENSITIVITY_X1, + PINNACLE_SENSITIVITY_X2, + PINNACLE_SENSITIVITY_X3, + PINNACLE_SENSITIVITY_X4, +}; + +struct pinnacle_config { + const struct pinnacle_bus bus; + struct gpio_dt_spec dr_gpio; + + enum pinnacle_sensitivity sensitivity; + bool relative_mode; + uint8_t idle_packets_count; + + bool clipping_enabled; + bool scaling_enabled; + bool invert_x; + bool invert_y; + bool primary_tap_enabled; + bool swap_xy; + + uint16_t active_range_x_min; + uint16_t active_range_x_max; + uint16_t active_range_y_min; + uint16_t active_range_y_max; + + uint16_t resolution_x; + uint16_t resolution_y; +}; + +union pinnacle_sample { + struct { + uint16_t abs_x; + uint16_t abs_y; + uint8_t abs_z; + }; + struct { + int16_t rel_x; + int16_t rel_y; + bool btn_primary; + }; +}; + +struct pinnacle_data { + union pinnacle_sample sample; + const struct device *dev; + struct gpio_callback dr_cb_data; + struct k_work work; +}; + +static inline bool pinnacle_bus_is_ready(const struct device *dev) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.is_ready(&config->bus); +} + +static inline int pinnacle_write(const struct device *dev, uint8_t address, uint8_t value) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.write(&config->bus, address, value); +} +static inline int pinnacle_seq_write(const struct device *dev, uint8_t *address, uint8_t *value, + uint8_t count) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.seq_write(&config->bus, address, value, count); +} +static inline int pinnacle_read(const struct device *dev, uint8_t address, uint8_t *value) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.read(&config->bus, address, value); +} + +static inline int pinnacle_seq_read(const struct device *dev, uint8_t address, uint8_t *data, + uint8_t count) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.seq_read(&config->bus, address, data, count); +} + +static inline int pinnacle_clear_cmd_complete(const struct device *dev) +{ + const struct pinnacle_config *config = dev->config; + + return config->bus.write(&config->bus, PINNACLE_REG_STATUS1, 0x00); +} + +static int pinnacle_era_wait_for_completion(const struct device *dev) +{ + int rc; + uint8_t value; + + rc = WAIT_FOR(!pinnacle_read(dev, PINNACLE_REG_ERA_CTRL, &value) && + value == PINNACLE_ERA_CTRL_COMPLETE, + PINNACLE_ERA_AWAIT_RETRY_COUNT * PINNACLE_ERA_AWAIT_DELAY_POLL_US, + k_sleep(K_USEC(PINNACLE_ERA_AWAIT_DELAY_POLL_US))); + if (rc < 0) { + return -EIO; + } + + return 0; +} + +static int pinnacle_era_write(const struct device *dev, uint16_t address, uint8_t value) +{ + uint8_t address_buf[] = { + PINNACLE_REG_ERA_VALUE, + PINNACLE_REG_ERA_ADDR_HIGH, + PINNACLE_REG_ERA_ADDR_LOW, + PINNACLE_REG_ERA_CTRL, + }; + uint8_t value_buf[] = { + value, + address >> 8, + address & 0xFF, + PINNACLE_ERA_CTRL_WRITE, + }; + int rc; + + rc = pinnacle_seq_write(dev, address_buf, value_buf, sizeof(address_buf)); + if (rc) { + return rc; + } + + return pinnacle_era_wait_for_completion(dev); +} + +static int pinnacle_era_read(const struct device *dev, uint16_t address, uint8_t *value) +{ + uint8_t address_buf[] = { + PINNACLE_REG_ERA_ADDR_HIGH, + PINNACLE_REG_ERA_ADDR_LOW, + PINNACLE_REG_ERA_CTRL, + }; + uint8_t value_buf[] = { + address >> 8, + address & 0xFF, + PINNACLE_ERA_CTRL_READ, + }; + int rc; + + rc = pinnacle_seq_write(dev, address_buf, value_buf, sizeof(address_buf)); + if (rc) { + return rc; + } + + rc = pinnacle_era_wait_for_completion(dev); + if (rc) { + return rc; + } + + return pinnacle_read(dev, PINNACLE_REG_ERA_VALUE, value); +} + +static int pinnacle_set_sensitivity(const struct device *dev) +{ + const struct pinnacle_config *config = dev->config; + + uint8_t value; + int rc; + + rc = pinnacle_era_read(dev, PINNACLE_ERA_REG_CONFIG, &value); + if (rc) { + return rc; + } + + /* Clear BIT(7) and BIT(6) */ + value &= 0x3F; + + switch (config->sensitivity) { + case PINNACLE_SENSITIVITY_X1: + value |= PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X1; + break; + case PINNACLE_SENSITIVITY_X2: + value |= PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X2; + break; + case PINNACLE_SENSITIVITY_X3: + value |= PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X3; + break; + case PINNACLE_SENSITIVITY_X4: + value |= PINNACLE_ERA_CONFIG_ADC_ATTENUATION_X4; + break; + } + + rc = pinnacle_era_write(dev, PINNACLE_ERA_REG_CONFIG, value); + if (rc) { + return rc; + } + + /* Clear SW_CC after setting sensitivity */ + rc = pinnacle_clear_cmd_complete(dev); + if (rc) { + return rc; + } + + return 0; +} + +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) +static bool pinnacle_is_ready_i2c(const struct pinnacle_bus *bus) +{ + if (!i2c_is_ready_dt(&bus->i2c)) { + LOG_ERR("I2C bus %s is not ready", bus->i2c.bus->name); + return false; + } + + return true; +} + +static int pinnacle_write_i2c(const struct pinnacle_bus *bus, uint8_t address, uint8_t value) +{ + uint8_t buf[] = {PINNACLE_WRITE_REG(address), value}; + + return i2c_write_dt(&bus->i2c, buf, 2); +} + +static int pinnacle_seq_write_i2c(const struct pinnacle_bus *bus, uint8_t *address, uint8_t *value, + uint8_t count) +{ + uint8_t buf[count * 2]; + + for (uint8_t i = 0; i < count; ++i) { + buf[i * 2] = PINNACLE_WRITE_REG(address[i]); + buf[i * 2 + 1] = value[i]; + } + + return i2c_write_dt(&bus->i2c, buf, count * 2); +} + +static int pinnacle_read_i2c(const struct pinnacle_bus *bus, uint8_t address, uint8_t *value) +{ + uint8_t reg = PINNACLE_READ_REG(address); + + return i2c_write_read_dt(&bus->i2c, ®, 1, value, 1); +} + +static int pinnacle_seq_read_i2c(const struct pinnacle_bus *bus, uint8_t address, uint8_t *buf, + uint8_t count) +{ + uint8_t reg = PINNACLE_READ_REG(address); + + return i2c_burst_read_dt(&bus->i2c, reg, buf, count); +} +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */ + +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) +static bool pinnacle_is_ready_spi(const struct pinnacle_bus *bus) +{ + if (!spi_is_ready_dt(&bus->spi)) { + LOG_ERR("SPI bus %s is not ready", bus->spi.bus->name); + return false; + } + + return true; +} + +static int pinnacle_write_spi(const struct pinnacle_bus *bus, uint8_t address, uint8_t value) +{ + uint8_t tx_data[] = { + PINNACLE_WRITE_REG(address), + value, + }; + const struct spi_buf tx_buf[] = {{ + .buf = tx_data, + .len = sizeof(tx_data), + }}; + const struct spi_buf_set tx_set = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf), + }; + + return spi_write_dt(&bus->spi, &tx_set); +} + +static int pinnacle_seq_write_spi(const struct pinnacle_bus *bus, uint8_t *address, uint8_t *value, + uint8_t count) +{ + uint8_t tx_data[count * 2]; + const struct spi_buf tx_buf[] = {{ + .buf = tx_data, + .len = sizeof(tx_data), + }}; + const struct spi_buf_set tx_set = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf), + }; + + for (uint8_t i = 0; i < count; ++i) { + tx_data[i * 2] = PINNACLE_WRITE_REG(address[i]); + tx_data[i * 2 + 1] = value[i]; + } + + return spi_write_dt(&bus->spi, &tx_set); +} + +static int pinnacle_read_spi(const struct pinnacle_bus *bus, uint8_t address, uint8_t *value) +{ + uint8_t tx_data[] = { + PINNACLE_READ_REG(address), + PINNACLE_SPI_FB, + PINNACLE_SPI_FB, + PINNACLE_SPI_FB, + }; + const struct spi_buf tx_buf[] = {{ + .buf = tx_data, + .len = sizeof(tx_data), + }}; + const struct spi_buf_set tx_set = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf), + }; + + const struct spi_buf rx_buf[] = { + { + .buf = NULL, + .len = 3, + }, + { + .buf = value, + .len = 1, + }, + }; + const struct spi_buf_set rx_set = { + .buffers = rx_buf, + .count = ARRAY_SIZE(rx_buf), + }; + + int rc; + + rc = spi_transceive_dt(&bus->spi, &tx_set, &rx_set); + if (rc) { + LOG_ERR("Failed to read from SPI %s", bus->spi.bus->name); + return rc; + } + + return 0; +} + +static int pinnacle_seq_read_spi(const struct pinnacle_bus *bus, uint8_t address, uint8_t *buf, + uint8_t count) +{ + + uint8_t size = count + 3; + uint8_t tx_data[size]; + + tx_data[0] = PINNACLE_READ_REG(address); + tx_data[1] = PINNACLE_SPI_FC; + tx_data[2] = PINNACLE_SPI_FC; + + uint8_t i = 3; + + for (; i < (count + 2); ++i) { + tx_data[i] = PINNACLE_SPI_FC; + } + + tx_data[i++] = PINNACLE_SPI_FB; + + const struct spi_buf tx_buf[] = {{ + .buf = tx_data, + .len = size, + }}; + const struct spi_buf_set tx_set = { + .buffers = tx_buf, + .count = 1, + }; + + const struct spi_buf rx_buf[] = { + { + .buf = NULL, + .len = 3, + }, + { + .buf = buf, + .len = count, + }, + }; + const struct spi_buf_set rx_set = { + .buffers = rx_buf, + .count = ARRAY_SIZE(rx_buf), + }; + + int rc; + + rc = spi_transceive_dt(&bus->spi, &tx_set, &rx_set); + if (rc) { + LOG_ERR("Failed to read from SPI %s", bus->spi.bus->name); + return rc; + } + + return 0; +} +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */ + +static void pinnacle_decode_sample(const struct device *dev, uint8_t *rx, + union pinnacle_sample *sample) +{ + const struct pinnacle_config *config = dev->config; + + if (config->relative_mode) { + if (config->primary_tap_enabled) { + sample->btn_primary = (rx[0] & BIT(0)) == BIT(0); + } + sample->rel_x = ((rx[0] & BIT(4)) == BIT(4)) ? -(256 - rx[1]) : rx[1]; + sample->rel_y = ((rx[0] & BIT(5)) == BIT(5)) ? -(256 - rx[2]) : rx[2]; + } else { + sample->abs_x = ((rx[2] & 0x0F) << 8) | rx[0]; + sample->abs_y = ((rx[2] & 0xF0) << 4) | rx[1]; + sample->abs_z = rx[3] & 0x3F; + } +} + +static bool pinnacle_is_idle_sample(const union pinnacle_sample *sample) +{ + return (sample->abs_x == 0 && sample->abs_y == 0 && sample->abs_z == 0); +} + +static void pinnacle_clip_sample(const struct device *dev, union pinnacle_sample *sample) +{ + const struct pinnacle_config *config = dev->config; + + if (sample->abs_x < config->active_range_x_min) { + sample->abs_x = config->active_range_x_min; + } + if (sample->abs_x > config->active_range_x_max) { + sample->abs_x = config->active_range_x_max; + } + if (sample->abs_y < config->active_range_y_min) { + sample->abs_y = config->active_range_y_min; + } + if (sample->abs_y > config->active_range_y_max) { + sample->abs_y = config->active_range_y_max; + } +} + +static void pinnacle_scale_sample(const struct device *dev, union pinnacle_sample *sample) +{ + const struct pinnacle_config *config = dev->config; + + uint16_t range_x = config->active_range_x_max - config->active_range_x_min; + uint16_t range_y = config->active_range_y_max - config->active_range_y_min; + + sample->abs_x = (uint16_t)((uint32_t)(sample->abs_x - config->active_range_x_min) * + config->resolution_x / range_x); + sample->abs_y = (uint16_t)((uint32_t)(sample->abs_y - config->active_range_y_min) * + config->resolution_y / range_y); +} + +static int pinnacle_sample_fetch(const struct device *dev, union pinnacle_sample *sample) +{ + const struct pinnacle_config *config = dev->config; + + uint8_t rx[4]; + int rc; + + if (config->relative_mode) { + rc = pinnacle_seq_read(dev, PINNACLE_REG_PACKET_BYTE0, rx, 3); + } else { + rc = pinnacle_seq_read(dev, PINNACLE_REG_PACKET_BYTE2, rx, 4); + } + + if (rc) { + LOG_ERR("Failed to read data from SPI device"); + return rc; + } + + pinnacle_decode_sample(dev, rx, sample); + + rc = pinnacle_write(dev, PINNACLE_REG_STATUS1, 0x00); + if (rc) { + LOG_ERR("Failed to clear SW_CC and SW_DR"); + return rc; + } + + return 0; +} + +static int pinnacle_handle_interrupt(const struct device *dev) +{ + const struct pinnacle_config *config = dev->config; + struct pinnacle_data *drv_data = dev->data; + union pinnacle_sample *sample = &drv_data->sample; + + int rc; + + rc = pinnacle_sample_fetch(dev, sample); + if (rc) { + LOG_ERR("Failed to read data packets"); + return rc; + } + + if (config->relative_mode) { + input_report_rel(dev, INPUT_REL_X, sample->rel_x, false, K_FOREVER); + input_report_rel(dev, INPUT_REL_Y, sample->rel_y, !config->primary_tap_enabled, + K_FOREVER); + if (config->primary_tap_enabled) { + input_report_key(dev, INPUT_BTN_TOUCH, sample->btn_primary, true, + K_FOREVER); + } + } else { + if (config->clipping_enabled && !pinnacle_is_idle_sample(sample)) { + pinnacle_clip_sample(dev, sample); + if (config->scaling_enabled) { + pinnacle_scale_sample(dev, sample); + } + } + + input_report_abs(dev, INPUT_ABS_X, sample->abs_x, false, K_FOREVER); + input_report_abs(dev, INPUT_ABS_Y, sample->abs_y, false, K_FOREVER); + input_report_abs(dev, INPUT_ABS_Z, sample->abs_z, true, K_FOREVER); + } + + return 0; +} + +static void pinnacle_data_ready_gpio_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + struct pinnacle_data *drv_data = CONTAINER_OF(cb, struct pinnacle_data, dr_cb_data); + + k_work_submit(&drv_data->work); +} + +static void pinnacle_work_cb(struct k_work *work) +{ + struct pinnacle_data *drv_data = CONTAINER_OF(work, struct pinnacle_data, work); + + pinnacle_handle_interrupt(drv_data->dev); +} + +int pinnacle_init_interrupt(const struct device *dev) +{ + struct pinnacle_data *drv_data = dev->data; + const struct pinnacle_config *config = dev->config; + const struct gpio_dt_spec *gpio = &config->dr_gpio; + + int rc; + + drv_data->dev = dev; + drv_data->work.handler = pinnacle_work_cb; + + /* Configure GPIO pin for HW_DR signal */ + rc = gpio_is_ready_dt(gpio); + if (!rc) { + LOG_ERR("GPIO device %s/%d is not ready", gpio->port->name, gpio->pin); + return -ENODEV; + } + + rc = gpio_pin_configure_dt(gpio, GPIO_INPUT); + if (rc) { + LOG_ERR("Failed to configure %s/%d as input", gpio->port->name, gpio->pin); + return rc; + } + + rc = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_EDGE_TO_ACTIVE); + if (rc) { + LOG_ERR("Failed to configured interrupt for %s/%d", gpio->port->name, gpio->pin); + return rc; + } + + gpio_init_callback(&drv_data->dr_cb_data, pinnacle_data_ready_gpio_callback, + BIT(gpio->pin)); + + rc = gpio_add_callback(gpio->port, &drv_data->dr_cb_data); + if (rc) { + LOG_ERR("Failed to configured interrupt for %s/%d", gpio->port->name, gpio->pin); + return rc; + } + + return 0; +} + +static int pinnacle_init(const struct device *dev) +{ + const struct pinnacle_config *config = dev->config; + + int rc; + uint8_t value; + + if (!pinnacle_bus_is_ready(dev)) { + return -ENODEV; + } + + rc = pinnacle_read(dev, PINNACLE_REG_FIRMWARE_ID, &value); + if (rc) { + LOG_ERR("Failed to read FirmwareId"); + return rc; + } + + if (value != PINNACLE_FIRMWARE_ID) { + LOG_ERR("Incorrect Firmware ASIC ID %x", value); + return -ENODEV; + } + + /* Wait until the calibration is completed (SW_CC is asserted) */ + rc = WAIT_FOR(!pinnacle_read(dev, PINNACLE_REG_STATUS1, &value) && + (value & PINNACLE_STATUS1_SW_CC) == PINNACLE_STATUS1_SW_CC, + PINNACLE_CALIBRATION_AWAIT_RETRY_COUNT * + PINNACLE_CALIBRATION_AWAIT_DELAY_POLL_US, + k_sleep(K_USEC(PINNACLE_CALIBRATION_AWAIT_DELAY_POLL_US))); + if (rc < 0) { + LOG_ERR("Failed to wait for calibration complition"); + return -EIO; + } + + /* Clear SW_CC after Power on Reset */ + rc = pinnacle_clear_cmd_complete(dev); + if (rc) { + LOG_ERR("Failed to clear SW_CC in Status1"); + return -EIO; + } + + /* Set trackpad sensitivity */ + rc = pinnacle_set_sensitivity(dev); + if (rc) { + LOG_ERR("Failed to set sensitivity"); + return -EIO; + } + + rc = pinnacle_write(dev, PINNACLE_REG_SYS_CONFIG1, 0x00); + if (rc) { + LOG_ERR("Failed to write SysConfig1"); + return rc; + } + + /* Relative mode features */ + if (config->relative_mode) { + value = (PINNACLE_FEED_CONFIG2_GLIDE_EXTEND_DISABLE | + PINNACLE_FEED_CONFIG2_SCROLL_DISABLE | + PINNACLE_FEED_CONFIG2_SECONDARY_TAP_DISABLE); + if (config->swap_xy) { + value |= PINNACLE_FEED_CONFIG2_SWAP_X_AND_Y; + } + if (!config->primary_tap_enabled) { + value |= PINNACLE_FEED_CONFIG2_ALL_TAPS_DISABLE; + } + } else { + value = (PINNACLE_FEED_CONFIG2_GLIDE_EXTEND_DISABLE | + PINNACLE_FEED_CONFIG2_SCROLL_DISABLE | + PINNACLE_FEED_CONFIG2_SECONDARY_TAP_DISABLE | + PINNACLE_FEED_CONFIG2_ALL_TAPS_DISABLE); + } + rc = pinnacle_write(dev, PINNACLE_REG_FEED_CONFIG2, value); + if (rc) { + LOG_ERR("Failed to write FeedConfig2"); + return rc; + } + + /* Data output flags */ + value = PINNACLE_FEED_CONFIG1_FEED_ENABLE; + if (!config->relative_mode) { + value |= PINNACLE_FEED_CONFIG1_DATA_MODE_ABSOLUTE; + if (config->invert_x) { + value |= PINNACLE_FEED_CONFIG1_X_INVERT; + } + if (config->invert_y) { + value |= PINNACLE_FEED_CONFIG1_Y_INVERT; + } + } + rc = pinnacle_write(dev, PINNACLE_REG_FEED_CONFIG1, value); + if (rc) { + LOG_ERR("Failed to enable Feed in FeedConfig1"); + return rc; + } + + /* Configure count of Z-Idle packets */ + rc = pinnacle_write(dev, PINNACLE_REG_Z_IDLE, config->idle_packets_count); + if (rc) { + LOG_ERR("Failed to set count of Z-idle packets"); + return rc; + } + + rc = pinnacle_init_interrupt(dev); + if (rc) { + LOG_ERR("Failed to initialize interrupts"); + return rc; + } + + return 0; +} + +#define PINNACLE_CONFIG_BUS_I2C(inst) \ + .bus = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .is_ready = pinnacle_is_ready_i2c, \ + .write = pinnacle_write_i2c, \ + .seq_write = pinnacle_seq_write_i2c, \ + .read = pinnacle_read_i2c, \ + .seq_read = pinnacle_seq_read_i2c, \ + } + +#define PINNACLE_SPI_OP (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_MODE_CPHA | SPI_WORD_SET(8)) +#define PINNACLE_CONFIG_BUS_SPI(inst) \ + .bus = { \ + .spi = SPI_DT_SPEC_INST_GET(inst, PINNACLE_SPI_OP, 0U), \ + .is_ready = pinnacle_is_ready_spi, \ + .write = pinnacle_write_spi, \ + .seq_write = pinnacle_seq_write_spi, \ + .read = pinnacle_read_spi, \ + .seq_read = pinnacle_seq_read_spi, \ + } + +#define PINNACLE_DEFINE(inst) \ + static const struct pinnacle_config pinnacle_config_##inst = { \ + COND_CODE_1(DT_INST_ON_BUS(inst, i2c), (PINNACLE_CONFIG_BUS_I2C(inst),), ()) \ + COND_CODE_1(DT_INST_ON_BUS(inst, spi), (PINNACLE_CONFIG_BUS_SPI(inst),), ()) \ + .dr_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, data_ready_gpios, {}), \ + .relative_mode = DT_INST_ENUM_IDX(inst, data_mode), \ + .sensitivity = DT_INST_ENUM_IDX(inst, sensitivity), \ + .idle_packets_count = DT_INST_PROP(inst, idle_packets_count), \ + .clipping_enabled = DT_INST_PROP(inst, clipping_enable), \ + .active_range_x_min = DT_INST_PROP(inst, active_range_x_min), \ + .active_range_x_max = DT_INST_PROP(inst, active_range_x_max), \ + .active_range_y_min = DT_INST_PROP(inst, active_range_y_min), \ + .active_range_y_max = DT_INST_PROP(inst, active_range_y_max), \ + .scaling_enabled = DT_INST_PROP(inst, scaling_enable), \ + .resolution_x = DT_INST_PROP(inst, scaling_x_resolution), \ + .resolution_y = DT_INST_PROP(inst, scaling_y_resolution), \ + .invert_x = DT_INST_PROP(inst, invert_x), \ + .invert_y = DT_INST_PROP(inst, invert_y), \ + .primary_tap_enabled = DT_INST_PROP(inst, primary_tap_enable), \ + .swap_xy = DT_INST_PROP(inst, swap_xy), \ + }; \ + static struct pinnacle_data pinnacle_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, pinnacle_init, NULL, &pinnacle_data_##inst, \ + &pinnacle_config_##inst, POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \ + NULL); \ + BUILD_ASSERT(DT_INST_PROP(inst, active_range_x_min) < \ + DT_INST_PROP(inst, active_range_x_max), \ + "active-range-x-min must be less than active-range-x-max"); \ + BUILD_ASSERT(DT_INST_PROP(inst, active_range_y_min) < \ + DT_INST_PROP(inst, active_range_y_max), \ + "active_range-y-min must be less than active_range-y-max"); \ + BUILD_ASSERT(DT_INST_PROP(inst, scaling_x_resolution) > 0, \ + "scaling-x-resolution must be positive"); \ + BUILD_ASSERT(DT_INST_PROP(inst, scaling_y_resolution) > 0, \ + "scaling-y-resolution must be positive"); \ + BUILD_ASSERT(IN_RANGE(DT_INST_PROP(inst, idle_packets_count), 0, UINT8_MAX), \ + "idle-packets-count must be in range [0:255]"); + +DT_INST_FOREACH_STATUS_OKAY(PINNACLE_DEFINE) diff --git a/dts/bindings/input/cirque,pinnacle-common.yaml b/dts/bindings/input/cirque,pinnacle-common.yaml new file mode 100644 index 0000000000..6ea3a96a7b --- /dev/null +++ b/dts/bindings/input/cirque,pinnacle-common.yaml @@ -0,0 +1,113 @@ +# Copyright (c) 2024 Ilia Kharin +# SPDX-License-Identifier: Apache-2.0 + +description: Cirque Pinnacle 1CA027 ASIC trackpad + +properties: + data-ready-gpios: + type: phandle-array + description: | + Data Ready (DR) GPIO pin. The DR pin is connected to Pinnacle HW_DR + which is active high when SW_DR or SW_CC are asserted. If connected + directly, the MCU pin should be configured as active low. + + sensitivity: + type: string + default: "4x" + description: | + ADC attenuation, 1x is the most sensitive and 4x is the least sensitive. + enum: + - "1x" + - "2x" + - "3x" + - "4x" + + data-mode: + type: string + default: "relative" + description: | + Data output mode in which position is reported. In the relative mode + each position is reported as relative to the last position. In the + absolute mode absolute coordinates are reported. + enum: + - "absolute" + - "relative" + + idle-packets-count: + type: int + default: 0 + description: | + The number of empty packets where both X and Y are set to 0. They are + started to be sent when a finger presence is detected missing (no touch + detected) every 10 ms. They are stopped to be sent when a finger presence + it detected. An application can count these packets in order to detect + taps. When set to 0, no empty packets are sent. Supported values from 0 + to 255. + + clipping-enable: + type: boolean + description: | + In the absolute mode enable clipping of reported coordinates which are + outside of the active range. + + active-range-x-min: + type: int + default: 128 + description: | + The minimum X value which can be reported. + + active-range-x-max: + type: int + default: 1920 + description: | + The maximum X value which can be reported. + + active-range-y-min: + type: int + default: 64 + description: | + The minimum Y value which can be reported. + + active-range-y-max: + type: int + default: 1472 + description: | + The maximum Y value which can be reported. + + scaling-enable: + type: boolean + description: | + In the absolute mode enable scaling of coordinates according to + the specified resolution for X and Y axises. The scaling is applied only + when the clipping is enabled. + + scaling-x-resolution: + type: int + default: 1024 + description: Resolution for the X axis. + + scaling-y-resolution: + type: int + default: 1024 + description: Resolution for the Y axis. + + invert-x: + type: boolean + description: | + In the absolute mode invert X coordinate. + + invert-y: + type: boolean + description: | + In the absolute mode invert Y coordinate. + + primary-tap-enable: + type: boolean + description: | + In the relative mode enable the primary tap. + + swap-xy: + type: boolean + description: | + In the relative mode swap X and Y coordinates. This is equivalent for + rotating coordinates by 90 degrees. diff --git a/dts/bindings/input/cirque,pinnacle-i2c.yaml b/dts/bindings/input/cirque,pinnacle-i2c.yaml new file mode 100644 index 0000000000..3878028ea6 --- /dev/null +++ b/dts/bindings/input/cirque,pinnacle-i2c.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Ilia Kharin +# SPDX-License-Identifier: Apache-2.0 + +description: Cirque Pinnacle 1CA027 ASIC trackpad connected through I2C + +compatible: "cirque,pinnacle" + +include: ["i2c-device.yaml", "cirque,pinnacle-common.yaml"] diff --git a/dts/bindings/input/cirque,pinnacle-spi.yaml b/dts/bindings/input/cirque,pinnacle-spi.yaml new file mode 100644 index 0000000000..8a35c62a7a --- /dev/null +++ b/dts/bindings/input/cirque,pinnacle-spi.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Ilia Kharin +# SPDX-License-Identifier: Apache-2.0 + +description: Cirque Pinnacle 1CA027 ASIC trackpad connected through SPI + +compatible: "cirque,pinnacle" + +include: ["spi-device.yaml", "cirque,pinnacle-common.yaml"] diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index adb976f085..fe3d98c90f 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -134,6 +134,7 @@ chunghwa Chunghwa Picture Tubes Ltd. chuwi Chuwi Innovation Ltd. ciaa Computadora Industrial Abierta Argentina circuitdojo Circuit Dojo +cirque Cirque Corporation cirrus Cirrus Logic, Inc. cisco Cisco Systems, Inc. cloudengines Cloud Engines, Inc. diff --git a/tests/drivers/build_all/input/app.overlay b/tests/drivers/build_all/input/app.overlay index b34d781bf5..a5c3c0aff5 100644 --- a/tests/drivers/build_all/input/app.overlay +++ b/tests/drivers/build_all/input/app.overlay @@ -218,6 +218,14 @@ int-gpios = <&test_gpio 0 0>; }; + pinnacle@2a { + compatible = "cirque,pinnacle"; + reg = <0x2a>; + data-ready-gpios = <&test_gpio 0 0>; + data-mode = "relative"; + primary-tap-enable; + swap-xy; + }; }; spi@2 { @@ -230,7 +238,8 @@ /* one entry for every devices */ cs-gpios = <&test_gpio 0 0>, - <&test_gpio 0 0>; + <&test_gpio 1 0>, + <&test_gpio 2 0>; xpt2046@0 { compatible = "xptek,xpt2046"; @@ -258,6 +267,19 @@ force-awake; smart-mode; }; + + pinnacle@2 { + compatible = "cirque,pinnacle"; + reg = <0x2>; + spi-max-frequency = <0>; + data-ready-gpios = <&test_gpio 0 0>; + data-mode = "absolute"; + idle-packets-count = <20>; + clipping-enable; + scaling-enable; + invert-x; + invert-y; + }; }; }; };