Browse Source

add: Cirque Pinnacle trackpad driver

xmkb
crides 3 years ago committed by Julia Luna
parent
commit
58f54161cc
Signed by: xenua
GPG Key ID: 6A0C04FA9A7D7582
  1. 3
      app/drivers/sensor/CMakeLists.txt
  2. 3
      app/drivers/sensor/Kconfig
  3. 8
      app/drivers/sensor/cirque_trackpad/CMakeLists.txt
  4. 51
      app/drivers/sensor/cirque_trackpad/Kconfig
  5. 260
      app/drivers/sensor/cirque_trackpad/cirque_trackpad.c
  6. 80
      app/drivers/sensor/cirque_trackpad/cirque_trackpad.h
  7. 27
      app/drivers/zephyr/dts/bindings/sensor/cirque,pinnacle.yaml
  8. 7
      app/include/zmk/sensors.h

3
app/drivers/sensor/CMakeLists.txt

@ -2,4 +2,5 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery)
add_subdirectory_ifdef(CONFIG_EC11 ec11) add_subdirectory_ifdef(CONFIG_EC11 ec11)
add_subdirectory_ifdef(CONFIG_PINNACLE cirque_trackpad)

3
app/drivers/sensor/Kconfig

@ -2,4 +2,5 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
rsource "battery/Kconfig" rsource "battery/Kconfig"
rsource "ec11/Kconfig" rsource "ec11/Kconfig"
rsource "cirque_trackpad/Kconfig"

8
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)

51
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

260
app/drivers/sensor/cirque_trackpad/cirque_trackpad.c

@ -0,0 +1,260 @@
#define DT_DRV_COMPAT cirque_pinnacle
#include <drivers/spi.h>
#include <init.h>
#include <drivers/sensor.h>
#include <zmk/sensors.h>
#include <logging/log.h>
#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);

80
app/drivers/sensor/cirque_trackpad/cirque_trackpad.h

@ -0,0 +1,80 @@
#pragma once
#include <device.h>
#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
};

27
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

7
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_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_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) #define ZMK_KEYMAP_SENSORS_BY_IDX(idx) DT_PHANDLE_BY_IDX(ZMK_KEYMAP_SENSORS_NODE, sensors, idx)
#include <drivers/sensor.h>
enum {
// Cirque Pinnacle Glide Extend
SENSOR_ATTR_PINNACLE_GE = SENSOR_ATTR_PRIV_START,
};

Loading…
Cancel
Save