diff --git a/app/module/drivers/CMakeLists.txt b/app/module/drivers/CMakeLists.txt index 5281c3dcb21e..a3a15c651688 100644 --- a/app/module/drivers/CMakeLists.txt +++ b/app/module/drivers/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_GPIO gpio) add_subdirectory_ifdef(CONFIG_KSCAN kscan) add_subdirectory_ifdef(CONFIG_SENSOR sensor) add_subdirectory_ifdef(CONFIG_DISPLAY display) +add_subdirectory_ifdef(CONFIG_INPUT input) diff --git a/app/module/drivers/Kconfig b/app/module/drivers/Kconfig index c57ed3347c99..db3f49a5510f 100644 --- a/app/module/drivers/Kconfig +++ b/app/module/drivers/Kconfig @@ -5,3 +5,4 @@ rsource "gpio/Kconfig" rsource "kscan/Kconfig" rsource "sensor/Kconfig" rsource "display/Kconfig" +rsource "input/Kconfig" diff --git a/app/module/drivers/input/CMakeLists.txt b/app/module/drivers/input/CMakeLists.txt new file mode 100644 index 000000000000..20cb359823f9 --- /dev/null +++ b/app/module/drivers/input/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library_amend() + +zephyr_library_sources_ifdef(CONFIG_INPUT_PINNACLE input_pinnacle.c) diff --git a/app/module/drivers/input/Kconfig b/app/module/drivers/input/Kconfig new file mode 100644 index 000000000000..9035a2f6fce7 --- /dev/null +++ b/app/module/drivers/input/Kconfig @@ -0,0 +1,12 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig INPUT_PINNACLE + bool "Cirque Pinnacle trackpads" + default y + depends on GPIO + depends on SPI || I2C + depends on INPUT + depends on DT_HAS_CIRQUE_PINNACLE_ENABLED + help + Enable driver for Cirque Pinnacle trackpads diff --git a/app/module/drivers/input/input_pinnacle.c b/app/module/drivers/input/input_pinnacle.c new file mode 100644 index 000000000000..40040cfab2b5 --- /dev/null +++ b/app/module/drivers/input/input_pinnacle.c @@ -0,0 +1,386 @@ +#define DT_DRV_COMPAT cirque_pinnacle + +#include +#include +#include + +#include + +#include "input_pinnacle.h" + +LOG_MODULE_REGISTER(pinnacle, CONFIG_INPUT_LOG_LEVEL); + +static int pinnacle_seq_read(const struct device *dev, const uint8_t addr, uint8_t *buf, + const uint8_t len) { + const struct pinnacle_config *config = dev->config; +#if DT_INST_ON_BUS(0, spi) + uint8_t tx_buffer[len + 3], rx_dummy[3]; + tx_buffer[0] = PINNACLE_READ | addr; + memset(&tx_buffer[1], PINNACLE_AUTOINC, len + 2); + + const struct spi_buf tx_buf[2] = { + { + .buf = tx_buffer, + .len = 3, + }, + { + .buf = &tx_buffer[3], + .len = len, + }, + }; + const struct spi_buf_set tx = { + .buffers = tx_buf, + .count = 2, + }; + struct spi_buf rx_buf[2] = { + { + .buf = rx_dummy, + .len = 3, + }, + { + .buf = buf, + .len = len, + }, + }; + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = 2, + }; + int ret = spi_transceive_dt(&config->bus, &tx, &rx); + + return ret; +#elif DT_INST_ON_BUS(0, i2c) + return i2c_burst_read_dt(&config->bus, PINNACLE_READ | addr, buf, len); +#endif +} + +static int pinnacle_write(const struct device *dev, const uint8_t addr, const uint8_t val) { + const struct pinnacle_config *config = dev->config; +#if DT_INST_ON_BUS(0, spi) + uint8_t tx_buffer[2] = {PINNACLE_WRITE | addr, val}; + uint8_t rx_buffer[2]; + + const struct spi_buf tx_buf = { + .buf = tx_buffer, + .len = 2, + }; + const struct spi_buf_set tx = { + .buffers = &tx_buf, + .count = 1, + }; + + const struct spi_buf rx_buf = { + .buf = rx_buffer, + .len = 2, + }; + const struct spi_buf_set rx = { + .buffers = &rx_buf, + .count = 1, + }; + + const int ret = spi_transceive_dt(&config->bus, &tx, &rx); + + if (rx_buffer[1] != PINNACLE_FILLER) { + LOG_ERR("bad ret val %d", rx_buffer[1]); + return -EIO; + } + + if (ret < 0) { + LOG_ERR("spi ret: %d", ret); + } + return ret; +#elif DT_INST_ON_BUS(0, i2c) + return i2c_reg_write_byte_dt(&config->bus, PINNACLE_WRITE | addr, val); +#endif +} + +static void set_int(const struct device *dev, const bool en) { + const struct pinnacle_config *config = dev->config; + int ret = gpio_pin_interrupt_configure_dt(&config->dr, + en ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE); + if (ret < 0) { + LOG_ERR("can't set interrupt"); + } +} + +static int pinnacle_clear_status(const struct device *dev) { + int ret = pinnacle_write(dev, PINNACLE_STATUS1, 0); + if (ret < 0) { + LOG_ERR("Failed to clear STATUS1 register: %d", ret); + } + + return ret; +} + +#if 0 +static int pinnacle_era_read(const struct device *dev, const uint16_t addr, uint8_t *val) { + int ret; + + set_int(dev, false); + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_HIGH_BYTE, (uint8_t)(addr >> 8)); + if (ret < 0) { + LOG_ERR("Failed to write ERA high byte (%d)", ret); + return -EIO; + } + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_LOW_BYTE, (uint8_t)(addr & 0x00FF)); + if (ret < 0) { + LOG_ERR("Failed to write ERA low byte (%d)", ret); + return -EIO; + } + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_CONTROL, PINNACLE_ERA_CONTROL_READ); + if (ret < 0) { + LOG_ERR("Failed to write ERA control (%d)", ret); + return -EIO; + } + + uint8_t control_val; + do { + + ret = pinnacle_seq_read(dev, PINNACLE_REG_ERA_CONTROL, &control_val, 1); + if (ret < 0) { + LOG_ERR("Failed to read ERA control (%d)", ret); + return -EIO; + } + + } while (control_val != 0x00); + + ret = pinnacle_seq_read(dev, PINNACLE_REG_ERA_VALUE, val, 1); + + if (ret < 0) { + LOG_ERR("Failed to read ERA value (%d)", ret); + return -EIO; + } + + ret = pinnacle_clear_status(dev); + + set_int(dev, true); + + return ret; +} +#endif + +static int pinnacle_era_write(const struct device *dev, const uint16_t addr, uint8_t val) { + int ret; + + set_int(dev, false); + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_VALUE, val); + if (ret < 0) { + LOG_ERR("Failed to write ERA value (%d)", ret); + return -EIO; + } + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_HIGH_BYTE, (uint8_t)(addr >> 8)); + if (ret < 0) { + LOG_ERR("Failed to write ERA high byte (%d)", ret); + return -EIO; + } + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_LOW_BYTE, (uint8_t)(addr & 0x00FF)); + if (ret < 0) { + LOG_ERR("Failed to write ERA low byte (%d)", ret); + return -EIO; + } + + ret = pinnacle_write(dev, PINNACLE_REG_ERA_CONTROL, PINNACLE_ERA_CONTROL_WRITE); + if (ret < 0) { + LOG_ERR("Failed to write ERA control (%d)", ret); + return -EIO; + } + + uint8_t control_val; + do { + + ret = pinnacle_seq_read(dev, PINNACLE_REG_ERA_CONTROL, &control_val, 1); + if (ret < 0) { + LOG_ERR("Failed to read ERA control (%d)", ret); + return -EIO; + } + + } while (control_val != 0x00); + + ret = pinnacle_clear_status(dev); + + set_int(dev, true); + + return ret; +} + +static void pinnacle_report_data(const struct device *dev) { + const struct pinnacle_config *config = dev->config; + uint8_t packet[3]; + int ret; + ret = pinnacle_seq_read(dev, PINNACLE_STATUS1, packet, 1); + if (ret < 0) { + LOG_ERR("read status: %d", ret); + return; + } + if (!(packet[0] & PINNACLE_STATUS1_SW_DR)) { + return; + } + ret = pinnacle_seq_read(dev, PINNACLE_2_2_PACKET0, packet, 3); + if (ret < 0) { + LOG_ERR("read packet: %d", ret); + return; + } + struct pinnacle_data *data = dev->data; + uint8_t btn = packet[0] & + (PINNACLE_PACKET0_BTN_PRIM | PINNACLE_PACKET0_BTN_SEC | PINNACLE_PACKET0_BTN_AUX); + int16_t dx = (int16_t)(int8_t)packet[1]; + int16_t dy = (int16_t)(int8_t)packet[2]; + LOG_DBG("button: %d, dx: %d dy: %d", btn, dx, dy); + if (data->in_int) { + LOG_DBG("Clearing status bit"); + ret = pinnacle_clear_status(dev); + data->in_int = true; + } + + if (!config->no_taps && (btn || data->btn_cache)) { + for (int i = 0; i < 3; i++) { + uint8_t btn_val = btn & BIT(i); + if (btn_val != (data->btn_cache & BIT(i))) { + input_report_key(dev, INPUT_BTN_0 + i, btn_val ? 1 : 0, false, K_FOREVER); + } + } + } + + data->btn_cache = btn; + + input_report_rel(dev, INPUT_REL_X, dx, false, K_FOREVER); + input_report_rel(dev, INPUT_REL_Y, dy, true, K_FOREVER); + + return; +} + +static void pinnacle_work_cb(struct k_work *work) { + struct pinnacle_data *data = CONTAINER_OF(work, struct pinnacle_data, work); + pinnacle_report_data(data->dev); +} + +static void pinnacle_gpio_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins) { + struct pinnacle_data *data = CONTAINER_OF(cb, struct pinnacle_data, gpio_cb); + data->in_int = true; + k_work_submit(&data->work); +} + +static int pinnacle_adc_sensitivity_reg_value(enum pinnacle_sensitivity sensitivity) { + switch (sensitivity) { + case PINNACLE_SENSITIVITY_1X: + return PINNACLE_TRACKING_ADC_CONFIG_1X; + case PINNACLE_SENSITIVITY_2X: + return PINNACLE_TRACKING_ADC_CONFIG_2X; + case PINNACLE_SENSITIVITY_3X: + return PINNACLE_TRACKING_ADC_CONFIG_3X; + case PINNACLE_SENSITIVITY_4X: + return PINNACLE_TRACKING_ADC_CONFIG_4X; + default: + return PINNACLE_TRACKING_ADC_CONFIG_1X; + } +} + +static int pinnacle_init(const struct device *dev) { + struct pinnacle_data *data = dev->data; + const struct pinnacle_config *config = dev->config; + + LOG_WRN("pinnacle start"); + data->in_int = false; + int ret; + k_msleep(4); + ret = pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear CC + if (ret < 0) { + LOG_ERR("can't write %d", ret); + return ret; + } + k_usleep(50); + ret = pinnacle_write(dev, PINNACLE_SYS_CFG, PINNACLE_SYS_CFG_RESET); + if (ret < 0) { + LOG_ERR("can't reset %d", ret); + return ret; + } + k_msleep(20); + ret = pinnacle_write(dev, PINNACLE_Z_IDLE, 0x05); // No Z-Idle packets + if (ret < 0) { + LOG_ERR("can't write %d", ret); + return ret; + } + if (config->sleep_en) { + ret = pinnacle_write(dev, PINNACLE_SYS_CFG, PINNACLE_SYS_CFG_EN_SLEEP); + if (ret < 0) { + LOG_ERR("can't write %d", ret); + return ret; + } + } + + if (config->sensitivity > PINNACLE_SENSITIVITY_1X) { + ret = pinnacle_era_write(dev, PINNACLE_ERA_REG_TRACKING_ADC_CONFIG, + pinnacle_adc_sensitivity_reg_value(config->sensitivity)); + if (ret < 0) { + LOG_ERR("Failed to set ADC sensitivity %d", ret); + return ret; + } + } + + uint8_t feed_cfg2 = PINNACLE_FEED_CFG2_EN_IM; + if (config->no_taps) { + feed_cfg2 |= PINNACLE_FEED_CFG2_DIS_TAP; + } + if (config->rotate_90) { + feed_cfg2 |= PINNACLE_FEED_CFG2_ROTATE_90; + } + ret = pinnacle_write(dev, PINNACLE_FEED_CFG2, feed_cfg2); + if (ret < 0) { + LOG_ERR("can't write %d", ret); + return ret; + } + uint8_t feed_cfg1 = PINNACLE_FEED_CFG1_EN_FEED; + if (feed_cfg1) { + ret = pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); + } + if (ret < 0) { + LOG_ERR("can't write %d", ret); + return ret; + } + + data->dev = dev; + + pinnacle_clear_status(dev); + + gpio_pin_configure_dt(&config->dr, GPIO_INPUT); + gpio_init_callback(&data->gpio_cb, pinnacle_gpio_cb, BIT(config->dr.pin)); + ret = gpio_add_callback(config->dr.port, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to set DR callback: %d", ret); + return -EIO; + } + + k_work_init(&data->work, pinnacle_work_cb); + + pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); + + set_int(dev, true); + + return 0; +} + +#define PINNACLE_INST(n) \ + static struct pinnacle_data pinnacle_data_##n; \ + static const struct pinnacle_config pinnacle_config_##n = { \ + .bus = COND_CODE_1( \ + DT_INST_ON_BUS(0, i2c), (I2C_DT_SPEC_INST_GET(0)), \ + (SPI_DT_SPEC_INST_GET(0, \ + SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE | \ + SPI_TRANSFER_MSB | SPI_MODE_CPHA, \ + 0))), \ + .rotate_90 = DT_INST_PROP(0, rotate_90), \ + .sleep_en = DT_INST_PROP(0, sleep), \ + .no_taps = DT_INST_PROP(0, no_taps), \ + .sensitivity = DT_INST_ENUM_IDX_OR(0, sensitivity, PINNACLE_SENSITIVITY_1X), \ + .dr = GPIO_DT_SPEC_GET_OR(DT_DRV_INST(0), dr_gpios, {}), \ + }; \ + DEVICE_DT_INST_DEFINE(n, pinnacle_init, NULL, &pinnacle_data_##n, &pinnacle_config_##n, \ + POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(PINNACLE_INST) diff --git a/app/module/drivers/input/input_pinnacle.h b/app/module/drivers/input/input_pinnacle.h new file mode 100644 index 000000000000..58947fa938c6 --- /dev/null +++ b/app/module/drivers/input/input_pinnacle.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +#define PINNACLE_READ 0xA0 +#define PINNACLE_WRITE 0x80 + +#define PINNACLE_AUTOINC 0xFC +#define PINNACLE_FILLER 0xFB + +// Registers +#define PINNACLE_FW_ID 0x00 // ASIC ID. +#define PINNACLE_FW_VER 0x01 // Firmware Version Firmware revision number. +#define PINNACLE_STATUS1 0x02 // Contains status flags about the state of Pinnacle. +#define PINNACLE_STATUS1_SW_DR BIT(2) +#define PINNACLE_STATUS1_SW_CC BIT(3) +#define PINNACLE_SYS_CFG 0x03 // Contains system operation and configuration bits. +#define PINNACLE_SYS_CFG_EN_SLEEP BIT(2) +#define PINNACLE_SYS_CFG_SHUTDOWN BIT(1) +#define PINNACLE_SYS_CFG_RESET BIT(0) + +#define PINNACLE_FEED_CFG1 0x04 // Contains feed operation and configuration bits. +#define PINNACLE_FEED_CFG1_EN_FEED BIT(0) +#define PINNACLE_FEED_CFG1_ABS_MODE BIT(1) +#define PINNACLE_FEED_CFG1_DIS_FILT BIT(2) +#define PINNACLE_FEED_CFG1_DIS_X BIT(3) +#define PINNACLE_FEED_CFG1_DIS_Y BIT(4) +#define PINNACLE_FEED_CFG1_INV_X BIT(6) +#define PINNACLE_FEED_CFG1_INV_Y BIT(7) +#define PINNACLE_FEED_CFG2 0x05 // Contains feed operation and configuration bits. +#define PINNACLE_FEED_CFG2_EN_IM BIT(0) // Intellimouse +#define PINNACLE_FEED_CFG2_DIS_TAP BIT(1) // Disable all taps +#define PINNACLE_FEED_CFG2_DIS_SEC BIT(2) // Disable secondary tap +#define PINNACLE_FEED_CFG2_DIS_SCRL BIT(3) // Disable scroll +#define PINNACLE_FEED_CFG2_DIS_GE BIT(4) // Disable GlideExtend +#define PINNACLE_FEED_CFG2_ROTATE_90 BIT(7) // Swap X & Y +#define PINNACLE_CAL_CFG 0x07 // Contains calibration configuration bits. +#define PINNACLE_PS2_AUX 0x08 // Contains Data register for PS/2 Aux Control. +#define PINNACLE_SAMPLE 0x09 // Sample Rate Number of samples generated per second. +#define PINNACLE_Z_IDLE 0x0A // Number of Z=0 packets sent when Z goes from >0 to 0. +#define PINNACLE_Z_SCALER 0x0B // Contains the pen Z_On threshold. +#define PINNACLE_SLEEP_INTERVAL 0x0C // Sleep Interval +#define PINNACLE_SLEEP_TIMER 0x0D // Sleep Timer +#define PINNACLE_AG_PACKET0 0x10 // trackpad Data (Pinnacle AG) +#define PINNACLE_2_2_PACKET0 0x12 // trackpad Data +#define PINNACLE_REG_COUNT 0x18 + +#define PINNACLE_REG_ERA_VALUE 0x1B +#define PINNACLE_REG_ERA_HIGH_BYTE 0x1C +#define PINNACLE_REG_ERA_LOW_BYTE 0x1D +#define PINNACLE_REG_ERA_CONTROL 0x1E + +#define PINNACLE_ERA_CONTROL_READ 0x01 +#define PINNACLE_ERA_CONTROL_WRITE 0x02 + +#define PINNACLE_ERA_REG_TRACKING_ADC_CONFIG 0x0187 + +#define PINNACLE_TRACKING_ADC_CONFIG_1X 0x00 +#define PINNACLE_TRACKING_ADC_CONFIG_2X 0x40 +#define PINNACLE_TRACKING_ADC_CONFIG_3X 0x80 +#define PINNACLE_TRACKING_ADC_CONFIG_4X 0xC0 + +#define PINNACLE_PACKET0_BTN_PRIM BIT(0) // Primary button +#define PINNACLE_PACKET0_BTN_SEC BIT(1) // Secondary button +#define PINNACLE_PACKET0_BTN_AUX BIT(2) // Auxiliary (middle?) button +#define PINNACLE_PACKET0_X_SIGN BIT(4) // X delta sign +#define PINNACLE_PACKET0_Y_SIGN BIT(5) // Y delta sign + +struct pinnacle_data { + uint8_t btn_cache; + bool in_int; + const struct device *dev; + struct gpio_callback gpio_cb; + struct k_work work; +}; + +enum pinnacle_sensitivity { + PINNACLE_SENSITIVITY_1X, + PINNACLE_SENSITIVITY_2X, + PINNACLE_SENSITIVITY_3X, + PINNACLE_SENSITIVITY_4X, +}; + +struct pinnacle_config { +#if DT_INST_ON_BUS(0, i2c) + const struct i2c_dt_spec bus; +#elif DT_INST_ON_BUS(0, spi) + const struct spi_dt_spec bus; +#endif + bool rotate_90, sleep_en, no_taps; + enum pinnacle_sensitivity sensitivity; + const struct gpio_dt_spec dr; +}; diff --git a/app/module/dts/bindings/input/cirque,pinnacle-common.yaml b/app/module/dts/bindings/input/cirque,pinnacle-common.yaml new file mode 100644 index 000000000000..fd1d6826df7d --- /dev/null +++ b/app/module/dts/bindings/input/cirque,pinnacle-common.yaml @@ -0,0 +1,18 @@ +properties: + dr-gpios: + type: phandle-array + description: Data ready pin for the trackpad + rotate-90: + type: boolean + sleep: + type: boolean + no-taps: + type: boolean + sensitivity: + type: string + enum: + - 1x + - 2x + - 3x + - 4x + description: ADC attenuation (sensitivity) setting. diff --git a/app/module/dts/bindings/input/cirque,pinnacle-i2c.yaml b/app/module/dts/bindings/input/cirque,pinnacle-i2c.yaml new file mode 100644 index 000000000000..da2603a73b06 --- /dev/null +++ b/app/module/dts/bindings/input/cirque,pinnacle-i2c.yaml @@ -0,0 +1,6 @@ +description: | + Sensor driver for the Cirque Pinnacle trackpad ASICs, using the I2C interface + +compatible: "cirque,pinnacle" + +include: ["i2c-device.yaml", "cirque,pinnacle-common.yaml"] diff --git a/app/module/dts/bindings/input/cirque,pinnacle-spi.yaml b/app/module/dts/bindings/input/cirque,pinnacle-spi.yaml new file mode 100644 index 000000000000..0bd70ee15d48 --- /dev/null +++ b/app/module/dts/bindings/input/cirque,pinnacle-spi.yaml @@ -0,0 +1,6 @@ +description: | + Sensor driver for the Cirque Pinnacle trackpad ASICs, using the SPI interface + +compatible: "cirque,pinnacle" + +include: ["spi-device.yaml", "cirque,pinnacle-common.yaml"] diff --git a/app/module/dts/bindings/vendor-prefixes.txt b/app/module/dts/bindings/vendor-prefixes.txt new file mode 100644 index 000000000000..4466d52c1e9e --- /dev/null +++ b/app/module/dts/bindings/vendor-prefixes.txt @@ -0,0 +1 @@ +cirque Cirque Corporation