diff --git a/app/drivers/sensor/CMakeLists.txt b/app/drivers/sensor/CMakeLists.txt index b549320f..c8931eda 100644 --- a/app/drivers/sensor/CMakeLists.txt +++ b/app/drivers/sensor/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) -add_subdirectory_ifdef(CONFIG_EC11 ec11) \ No newline at end of file +add_subdirectory_ifdef(CONFIG_EC11 ec11) +add_subdirectory_ifdef(CONFIG_PINNACLE cirque_trackpad) diff --git a/app/drivers/sensor/Kconfig b/app/drivers/sensor/Kconfig index a828f6c6..01a8c491 100644 --- a/app/drivers/sensor/Kconfig +++ b/app/drivers/sensor/Kconfig @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT rsource "battery/Kconfig" -rsource "ec11/Kconfig" \ No newline at end of file +rsource "ec11/Kconfig" +rsource "cirque_trackpad/Kconfig" diff --git a/app/drivers/sensor/cirque_trackpad/CMakeLists.txt b/app/drivers/sensor/cirque_trackpad/CMakeLists.txt new file mode 100644 index 00000000..92b6744c --- /dev/null +++ b/app/drivers/sensor/cirque_trackpad/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_include_directories(.) + +zephyr_library() + +zephyr_library_sources(cirque_trackpad.c) diff --git a/app/drivers/sensor/cirque_trackpad/Kconfig b/app/drivers/sensor/cirque_trackpad/Kconfig new file mode 100644 index 00000000..16a47924 --- /dev/null +++ b/app/drivers/sensor/cirque_trackpad/Kconfig @@ -0,0 +1,51 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig PINNACLE + bool "PINNACLE Incremental Encoder Sensor" + depends on GPIO + depends on SPI + help + Enable driver for Cirque Pinnacle trackpads + +if PINNACLE + +choice + prompt "Trigger mode" + default PINNACLE_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config PINNACLE_TRIGGER_NONE + bool "No trigger" + +config PINNACLE_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select PINNACLE_TRIGGER + +config PINNACLE_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select PINNACLE_TRIGGER + +endchoice + +config PINNACLE_TRIGGER + bool + +config PINNACLE_THREAD_PRIORITY + int "Thread priority" + depends on PINNACLE_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config PINNACLE_THREAD_STACK_SIZE + int "Thread stack size" + depends on PINNACLE_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # PINNACLE diff --git a/app/drivers/sensor/cirque_trackpad/cirque_trackpad.c b/app/drivers/sensor/cirque_trackpad/cirque_trackpad.c new file mode 100644 index 00000000..779595ce --- /dev/null +++ b/app/drivers/sensor/cirque_trackpad/cirque_trackpad.c @@ -0,0 +1,260 @@ +#define DT_DRV_COMPAT cirque_pinnacle + +#include +#include +#include +#include +#include + +#include "cirque_trackpad.h" + +LOG_MODULE_REGISTER(pinnacle, CONFIG_SENSOR_LOG_LEVEL); + +static int pinnacle_seq_read(const struct device *dev, const uint8_t start, uint8_t *buf, const uint8_t len) { + uint8_t tx_buffer[len + 3], rx_dummy[3]; + tx_buffer[0] = PINNACLE_READ | start; + memset(&tx_buffer[1], PINNACLE_AUTOINC, len + 1); + tx_buffer[len + 2] = PINNACLE_DUMMY; + + const struct spi_buf tx_buf = { + .buf = tx_buffer, + .len = len + 3, + }; + const struct spi_buf_set tx = { + .buffers = &tx_buf, + .count = 1, + }; + 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, + }; + const struct pinnacle_data *data = dev->data; + const struct pinnacle_config *config = dev->config; + return spi_transceive(data->spi, &config->spi_config, &tx, &rx); +} + +static int pinnacle_write(const struct device *dev, const uint8_t addr, const uint8_t val) { + 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[1] = { + { + .buf = rx_buffer, + .len = sizeof(rx_buffer), + }, + }; + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = 1, + }; + const struct pinnacle_data *data = dev->data; + const struct pinnacle_config *config = dev->config; + const int ret = spi_transceive(data->spi, &config->spi_config, &tx, &rx); + if (rx_buffer[1] != 0xFB) { + LOG_ERR("bad ret val"); + return -EIO; + } + if (ret < 0) { + LOG_ERR("spi ret: %d", ret); + } + return ret; +} + +static int pinnacle_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { + const struct pinnacle_data *data = dev->data; + switch (chan) { + case SENSOR_CHAN_POS_DX: val->val1 = data->dx; break; + case SENSOR_CHAN_POS_DY: val->val1 = data->dy; break; + case SENSOR_CHAN_PRESS: val->val1 = data->btn; break; + default: return -ENOTSUP; + } + return 0; +} + +static int pinnacle_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val) { + const struct pinnacle_config *config = dev->config; + if (attr == SENSOR_ATTR_PINNACLE_GE) { + const uint8_t ge_set = val->val1 ? 0 : PINNACLE_FEED_CFG2_DIS_GE; + const uint8_t taps_set = config->no_taps ? PINNACLE_FEED_CFG2_DIS_TAP : 0; + pinnacle_write(dev, PINNACLE_FEED_CFG2, ge_set | taps_set); + return 0; + } + return -ENOTSUP; +} + +static int pinnacle_sample_fetch(const struct device *dev, enum sensor_channel chan) { + uint8_t packet[3]; + int res = pinnacle_seq_read(dev, PINNACLE_2_2_PACKET0, packet, 3); + if (res < 0) { + LOG_ERR("res: %d", res); + return res; + } + struct pinnacle_data *data = dev->data; + data->btn = packet[0] & PINNACLE_PACKET0_BTN_PRIM; + data->dx = (int16_t) (int8_t) packet[1]; + data->dy = (int16_t) (int8_t) packet[2]; + return 0; +} + +#ifdef CONFIG_PINNACLE_TRIGGER +static void set_int(const struct device *dev, const bool en) { + const struct pinnacle_config *config = dev->config; + int ret = gpio_pin_interrupt_configure(config->dr_port, config->dr_pin, en ? GPIO_INT_LEVEL_ACTIVE : GPIO_INT_DISABLE); + if (ret < 0) { + LOG_ERR("can't set interrupt"); + } +} + +static int pinnacle_trigger_set(const struct device *dev, const struct sensor_trigger *trig, sensor_trigger_handler_t handler) { + struct pinnacle_data *data = dev->data; + + set_int(dev, false); + if (trig->type != SENSOR_TRIG_DATA_READY) { + return -ENOTSUP; + } + data->data_ready_trigger = trig; + data->data_ready_handler = handler; + set_int(dev, true); + return 0; +} + +static void pinnacle_int_cb(const struct device *dev) { + struct pinnacle_data *data = dev->data; + data->data_ready_handler(dev, data->data_ready_trigger); + set_int(dev, true); +} + +#ifdef CONFIG_PINNACLE_TRIGGER_OWN_THREAD +static void pinnacle_thread(void *arg) { + const struct device *dev = arg; + struct pinnacle_data *data = dev->data; + + while (1) { + k_sem_take(&data->gpio_sem, K_FOREVER); + pinnacle_int_cb(dev); + pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear SW_DR + } +} +#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) +static void pinnacle_work_cb(struct k_work *work) { + struct pinnacle_data *data = CONTAINER_OF(work, struct pinnacle_data, work); + pinnacle_int_cb(data->dev); + pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear SW_DR +} +#endif + +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); + const struct device *dev = data->dev; +#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) + k_sem_give(&data->gpio_sem); +#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) + k_work_submit(&data->work); +#endif +} +#endif + +#define SPI_BUS DT_BUS(DT_DRV_INST(0)) +#define SPI_REG DT_REG_ADDR(DT_DRV_INST(0)) + +static int pinnacle_init(const struct device *dev) { + struct pinnacle_data *data = dev->data; + const struct pinnacle_config *config = dev->config; + data->spi = DEVICE_DT_GET(SPI_BUS); + + pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear CC + pinnacle_write(dev, PINNACLE_Z_IDLE, 0); // No Z-Idle packets + if (config->sleep_en) { + pinnacle_write(dev, PINNACLE_SYS_CFG, PINNACLE_SYS_CFG_EN_SLEEP); + } + if (config->no_taps) { + pinnacle_write(dev, PINNACLE_FEED_CFG2, PINNACLE_FEED_CFG2_DIS_TAP); + } + uint8_t feed_cfg1 = PINNACLE_FEED_CFG1_EN_FEED; + if (config->invert_x) { + feed_cfg1 |= PINNACLE_FEED_CFG1_INV_X; + } + if (config->invert_y) { + feed_cfg1 |= PINNACLE_FEED_CFG1_INV_Y; + } + if (feed_cfg1) { + pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); + } + +#ifdef CONFIG_PINNACLE_TRIGGER + data->dev = dev; + gpio_pin_configure(config->dr_port, config->dr_pin, GPIO_INPUT | config->dr_flags); + gpio_init_callback(&data->gpio_cb, pinnacle_gpio_cb, BIT(config->dr_pin)); + int ret = gpio_add_callback(config->dr_port, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to set DR callback: %d", ret); + return -EIO; + } + +#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) + k_sem_init(&data->gpio_sem, 0, UINT_MAX); + + k_thread_create(&data->thread, data->thread_stack, CONFIG_PINNACLE_THREAD_STACK_SIZE, + (k_thread_entry_t) pinnacle_thread, (void *) dev, 0, NULL, + K_PRIO_COOP(CONFIG_PINNACLE_THREAD_PRIORITY), 0, K_NO_WAIT); +#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) + k_work_init(&data->work, pinnacle_work_cb); +#endif + pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); +#endif + return 0; +} + +static const struct sensor_driver_api pinnacle_driver_api = { +#if CONFIG_PINNACLE_TRIGGER + .trigger_set = pinnacle_trigger_set, +#endif + .sample_fetch = pinnacle_sample_fetch, + .channel_get = pinnacle_channel_get, + .attr_set = pinnacle_attr_set, +}; + +static struct pinnacle_data pinnacle_data; +static const struct pinnacle_config pinnacle_config = { + .spi_cs = { + .gpio_dev = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(SPI_BUS, cs_gpios, SPI_REG)), + .gpio_pin = DT_GPIO_PIN_BY_IDX(SPI_BUS, cs_gpios, SPI_REG), + .delay = 0, + .gpio_dt_flags = DT_GPIO_FLAGS_BY_IDX(SPI_BUS, cs_gpios, SPI_REG), + }, + .spi_config = { + .cs = &pinnacle_config.spi_cs, + .frequency = DT_INST_PROP(0, spi_max_frequency), + .slave = DT_INST_REG_ADDR(0), + .operation = (SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE | SPI_TRANSFER_MSB), + }, + .invert_x = DT_INST_PROP(0, invert_x), + .invert_y = DT_INST_PROP(0, invert_y), + .sleep_en = DT_INST_PROP(0, sleep), + .no_taps = DT_INST_PROP(0, no_taps), +#ifdef CONFIG_PINNACLE_TRIGGER + .dr_port = DEVICE_DT_GET(DT_GPIO_CTLR(DT_DRV_INST(0), dr_gpios)), + .dr_pin = DT_INST_GPIO_PIN(0, dr_gpios), + .dr_flags = DT_INST_GPIO_FLAGS(0, dr_gpios), +#endif +}; + +DEVICE_DT_INST_DEFINE(0, pinnacle_init, device_pm_control_nop, &pinnacle_data, &pinnacle_config, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &pinnacle_driver_api); diff --git a/app/drivers/sensor/cirque_trackpad/cirque_trackpad.h b/app/drivers/sensor/cirque_trackpad/cirque_trackpad.h new file mode 100644 index 00000000..95c01a01 --- /dev/null +++ b/app/drivers/sensor/cirque_trackpad/cirque_trackpad.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#define PINNACLE_READ 0xA0 +#define PINNACLE_WRITE 0x80 + +#define PINNACLE_AUTOINC 0xFC +#define PINNACLE_DUMMY 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_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_SWAP_XY 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_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 { + const struct device *spi; + int16_t dx, dy; + int8_t wheel; + uint8_t btn; +#ifdef CONFIG_PINNACLE_TRIGGER + const struct device *dev; + const struct sensor_trigger *data_ready_trigger; + struct gpio_callback gpio_cb; + sensor_trigger_handler_t data_ready_handler; +#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_PINNACLE_THREAD_STACK_SIZE); + struct k_sem gpio_sem; + struct k_thread thread; +#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) + struct k_work work; +#endif +#endif +}; + +struct pinnacle_config { + struct spi_cs_control spi_cs; + struct spi_config spi_config; + bool invert_x, invert_y, sleep_en, no_taps; +#ifdef CONFIG_PINNACLE_TRIGGER + const struct device *dr_port; + uint8_t dr_pin, dr_flags; +#endif +}; diff --git a/app/drivers/zephyr/dts/bindings/sensor/cirque,pinnacle.yaml b/app/drivers/zephyr/dts/bindings/sensor/cirque,pinnacle.yaml new file mode 100644 index 00000000..fa7e561d --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/sensor/cirque,pinnacle.yaml @@ -0,0 +1,27 @@ +description: | + Sensor driver for the Cirque Pinnacle trackpad ASICs + +compatible: "cirque,pinnacle" + +include: base.yaml + +on-bus: spi + +properties: + spi-max-frequency: + type: int + required: true + description: | + Maximum SPI clock speed supported by the device, in Hz. + dr-gpios: + type: phandle-array + required: true + description: Data ready pin for the trackpad + invert-x: + type: boolean + invert-y: + type: boolean + sleep: + type: boolean + no-taps: + type: boolean diff --git a/app/include/zmk/sensors.h b/app/include/zmk/sensors.h index 8c6c28b3..09fbd5f6 100644 --- a/app/include/zmk/sensors.h +++ b/app/include/zmk/sensors.h @@ -10,3 +10,10 @@ #define ZMK_KEYMAP_HAS_SENSORS DT_NODE_HAS_STATUS(ZMK_KEYMAP_SENSORS_NODE, okay) #define ZMK_KEYMAP_SENSORS_LEN DT_PROP_LEN(ZMK_KEYMAP_SENSORS_NODE, sensors) #define ZMK_KEYMAP_SENSORS_BY_IDX(idx) DT_PHANDLE_BY_IDX(ZMK_KEYMAP_SENSORS_NODE, sensors, idx) + +#include + +enum { + // Cirque Pinnacle Glide Extend + SENSOR_ATTR_PINNACLE_GE = SENSOR_ATTR_PRIV_START, +};