feat(input): Cirque Pinnacle trackpad driver

Add the initial input driver for the Cirque Pinnacle ASIC found
in Cirque GlidePoint trackpads.
This commit is contained in:
crides 2022-01-23 14:47:23 -06:00 committed by Peter Johanson
parent 985b5ef3e7
commit 98fcff0b5f
10 changed files with 530 additions and 0 deletions

View file

@ -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.

View file

@ -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"]

View file

@ -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"]

View file

@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_GPIO gpio)
add_subdirectory_ifdef(CONFIG_KSCAN kscan) add_subdirectory_ifdef(CONFIG_KSCAN kscan)
add_subdirectory_ifdef(CONFIG_SENSOR sensor) add_subdirectory_ifdef(CONFIG_SENSOR sensor)
add_subdirectory_ifdef(CONFIG_DISPLAY display) add_subdirectory_ifdef(CONFIG_DISPLAY display)
add_subdirectory_ifdef(CONFIG_INPUT input)

View file

@ -5,3 +5,4 @@ rsource "gpio/Kconfig"
rsource "kscan/Kconfig" rsource "kscan/Kconfig"
rsource "sensor/Kconfig" rsource "sensor/Kconfig"
rsource "display/Kconfig" rsource "display/Kconfig"
rsource "input/Kconfig"

View file

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

View file

@ -0,0 +1,10 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
menuconfig INPUT_PINNACLE
bool "Cirque Pinnacle trackpads"
depends on GPIO
depends on SPI || I2C
depends on INPUT
help
Enable driver for Cirque Pinnacle trackpads

View file

@ -0,0 +1,386 @@
#define DT_DRV_COMPAT cirque_pinnacle
#include <zephyr/init.h>
#include <zephyr/input/input.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/logging/log.h>
#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_SENSOR_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(PINNACLE_INST)

View file

@ -0,0 +1,95 @@
#pragma once
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/i2c.h>
#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;
};

View file

@ -4,3 +4,4 @@
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_MAX17048 max17048) add_subdirectory_ifdef(CONFIG_MAX17048 max17048)
add_subdirectory_ifdef(CONFIG_PINNACLE pinnacle)