-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add: Cirque Pinnacle trackpad driver
- Loading branch information
Showing
8 changed files
with
418 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
#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 }; | ||
|
||
const struct spi_buf tx_buf = { | ||
.buf = tx_buffer, | ||
.len = 2, | ||
}; | ||
const struct spi_buf_set tx = { | ||
.buffers = &tx_buf, | ||
.count = 1, | ||
}; | ||
const struct pinnacle_data *data = dev->data; | ||
const struct pinnacle_config *config = dev->config; | ||
return spi_write(data->spi, &config->spi_config, &tx); | ||
} | ||
|
||
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 = ((packet[0] & PINNACLE_PACKET0_X_SIGN) ? 0xFF00 : 0) | packet[1]; | ||
data->dy = ((packet[0] & PINNACLE_PACKET0_Y_SIGN) ? 0xFF00 : 0) | 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); | ||
} | ||
} | ||
#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); | ||
} | ||
#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); | ||
struct device *dev = data->dev; | ||
pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear SW_DR | ||
set_int(dev, false); | ||
#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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}; |
Oops, something went wrong.