summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Kao <daniel.m.kao@gmail.com>2022-07-12 21:17:40 -0700
committerGitHub <noreply@github.com>2022-07-12 21:17:40 -0700
commit5db705d054e54a901f8968e3380e13c791991ab3 (patch)
tree078bf54330b45672d15532c38b010510da50ab74
parent904ec0ce7855e6dc62d2db2cd851f67fcc0f323f (diff)
Cirque trackpad features: circular scroll, inertial cursor (#17482)
-rw-r--r--builddefs/common_features.mk4
-rw-r--r--docs/feature_pointing_device.md31
-rw-r--r--drivers/sensors/cirque_pinnacle.c191
-rw-r--r--drivers/sensors/cirque_pinnacle.h14
-rw-r--r--drivers/sensors/cirque_pinnacle_gestures.c226
-rw-r--r--drivers/sensors/cirque_pinnacle_gestures.h107
-rw-r--r--drivers/sensors/cirque_pinnacle_regdefs.h405
-rw-r--r--quantum/pointing_device.h2
-rw-r--r--quantum/pointing_device_drivers.c101
-rw-r--r--quantum/pointing_device_gestures.c133
-rw-r--r--quantum/pointing_device_gestures.h58
11 files changed, 1123 insertions, 149 deletions
diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 5cc4508307..01dbdc3330 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -149,10 +149,14 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_i2c)
OPT_DEFS += -DSTM32_I2C -DHAL_USE_I2C=TRUE
SRC += drivers/sensors/cirque_pinnacle.c
+ SRC += drivers/sensors/cirque_pinnacle_gestures.c
+ SRC += $(QUANTUM_DIR)/pointing_device_gestures.c
QUANTUM_LIB_SRC += i2c_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_spi)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_SPI=TRUE
SRC += drivers/sensors/cirque_pinnacle.c
+ SRC += drivers/sensors/cirque_pinnacle_gestures.c
+ SRC += $(QUANTUM_DIR)/pointing_device_gestures.c
QUANTUM_LIB_SRC += spi_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_I2C=TRUE
diff --git a/docs/feature_pointing_device.md b/docs/feature_pointing_device.md
index 264362ea77..70129dfc9a 100644
--- a/docs/feature_pointing_device.md
+++ b/docs/feature_pointing_device.md
@@ -89,15 +89,15 @@ POINTING_DEVICE_DRIVER = cirque_pinnacle_spi
This supports the Cirque Pinnacle 1CA027 Touch Controller, which is used in the TM040040, TM035035 and the TM023023 trackpads. These are I2C or SPI compatible, and both configurations are supported.
-| Setting | Description | Default |
-|-------------------------------- |-----------------------------------------------------------------------|--------------------- |
-|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` |
-|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` |
-|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` |
-|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` |
-|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` |
-|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
-|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
+| Setting | Description | Default |
+|-------------------------------- |------------------------------------------------------------|--------------------|
+|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` |
+|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` |
+|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` |
+|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` |
+|`CIRQUE_PINNACLE_DIAMETER_MM` | (Optional) Diameter of the trackpad sensor in millimeters. | `40` |
+|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` |
+|`CIRQUE_PINNACLE_CURVED_OVERLAY` | (Optional) Applies settings tuned for curved overlay. | _not defined_ |
**`CIRQUE_PINNACLE_ATTENUATION`** is a measure of how much data is suppressed in regards to sensitivity. The higher the attenuation, the less sensitive the touchpad will be.
@@ -120,10 +120,21 @@ Default attenuation is set to 4X, although if you are using a thicker overlay (s
|`CIRQUE_PINNACLE_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ |
|`CIRQUE_PINNACLE_SPI_CS_PIN` | (Required) Sets the Cable Select pin connected to the sensor. | _not defined_ |
-Default Scaling/CPI is 1024.
+Default Scaling is 1024. Actual CPI depends on trackpad diameter.
Also see the `POINTING_DEVICE_TASK_THROTTLE_MS`, which defaults to 10ms when using Cirque Pinnacle, which matches the internal update rate of the position registers (in standard configuration). Advanced configuration for pen/stylus usage might require lower values.
+#### Cirque Trackpad gestures
+
+| Gesture Setting | Description | Default |
+|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|
+|`POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE` | (Optional) Enable inertial cursor. Cursor continues moving after a flick gesture and slows down by kinetic friction | _not defined_ |
+|`CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE` | (Optional) Enable circular scroll. Touch originating in outer ring can trigger scroll by moving along the perimeter. Near side triggers vertical scroll and far side triggers horizontal scroll. | _not defined_ |
+|`CIRQUE_PINNACLE_TAP_ENABLE` | (Optional) Enable tap to click. This currently only works on the master side. | _not defined_ |
+|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
+|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
+
+**`POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE`** is not specific to Cirque trackpad; any pointing device with a lift/contact status can integrate this gesture into its driver. e.g. PMW3360 can use Lift_Stat from Motion register. Note that `POINTING_DEVICE_MOTION_PIN` cannot be used with this feature; continuous polling of `pointing_device_get_report()` is needed to generate glide reports.
### Pimoroni Trackball
diff --git a/drivers/sensors/cirque_pinnacle.c b/drivers/sensors/cirque_pinnacle.c
index 1d1e4ccfc6..c50d5a2525 100644
--- a/drivers/sensors/cirque_pinnacle.c
+++ b/drivers/sensors/cirque_pinnacle.c
@@ -9,47 +9,16 @@
#include "wait.h"
#include "timer.h"
-// Registers for RAP
-// clang-format off
-#define FIRMWARE_ID 0x00
-#define FIRMWARE_VERSION_C 0x01
-#define STATUS_1 0x02
-#define SYSCONFIG_1 0x03
-#define FEEDCONFIG_1 0x04
-#define FEEDCONFIG_2 0x05
-#define CALIBRATION_CONFIG_1 0x07
-#define PS2_AU_CONTROL 0x08
-#define SAMPLE_RATE 0x09
-#define Z_IDLE_COUNT 0x0A
-#define Z_SCALER 0x0B
-#define SLEEP_INTERVAL 0x0C
-#define SLEEP_TIMER 0x0D
-#define PACKET_BYTE_0 0x12
-#define PACKET_BYTE_1 0x13
-#define PACKET_BYTE_2 0x14
-#define PACKET_BYTE_3 0x15
-#define PACKET_BYTE_4 0x16
-#define PACKET_BYTE_5 0x17
-
-#define ERA_VALUE 0x1B
-#define ERA_HIGH_BYTE 0x1C
-#define ERA_LOW_BYTE 0x1D
-#define ERA_CONTROL 0x1E
-
-// ADC-attenuation settings (held in BIT_7 and BIT_6)
-// 1X = most sensitive, 4X = least sensitive
-#define ADC_ATTENUATE_1X 0x00
-#define ADC_ATTENUATE_2X 0x40
-#define ADC_ATTENUATE_3X 0x80
-#define ADC_ATTENUATE_4X 0xC0
-
#ifndef CIRQUE_PINNACLE_ATTENUATION
-# define CIRQUE_PINNACLE_ATTENUATION ADC_ATTENUATE_4X
+# ifdef CIRQUE_PINNACLE_CURVED_OVERLAY
+# define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_2X
+# else
+# define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X
+# endif
#endif
-// clang-format on
bool touchpad_init;
-uint16_t scale_data = 1024;
+uint16_t scale_data = CIRQUE_PINNACLE_DEFAULT_SCALE;
void cirque_pinnacle_clear_flags(void);
void cirque_pinnacle_enable_feed(bool feedEnable);
@@ -106,43 +75,45 @@ void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResoluti
// Clears Status1 register flags (SW_CC and SW_DR)
void cirque_pinnacle_clear_flags() {
- RAP_Write(STATUS_1, 0x00);
+ RAP_Write(HOSTREG__STATUS1, HOSTREG__STATUS1_DEFVAL & ~(HOSTREG__STATUS1__COMMAND_COMPLETE | HOSTREG__STATUS1__DATA_READY));
wait_us(50);
}
// Enables/Disables the feed
void cirque_pinnacle_enable_feed(bool feedEnable) {
- uint8_t temp;
- RAP_ReadBytes(FEEDCONFIG_1, &temp, 1); // Store contents of FeedConfig1 register
+ uint8_t feedconfig1;
+ RAP_ReadBytes(HOSTREG__FEEDCONFIG1, &feedconfig1, 1);
if (feedEnable) {
- temp |= 0x01; // Set Feed Enable bit
+ feedconfig1 |= HOSTREG__FEEDCONFIG1__FEED_ENABLE;
} else {
- temp &= ~0x01; // Clear Feed Enable bit
+ feedconfig1 &= ~HOSTREG__FEEDCONFIG1__FEED_ENABLE;
}
- RAP_Write(FEEDCONFIG_1, temp);
+ RAP_Write(HOSTREG__FEEDCONFIG1, feedconfig1);
}
/* ERA (Extended Register Access) Functions */
// Reads <count> bytes from an extended register at <address> (16-bit address),
// stores values in <*data>
void ERA_ReadBytes(uint16_t address, uint8_t* data, uint16_t count) {
- uint8_t ERAControlValue = 0xFF;
+ uint8_t ERAControlValue = 0xFF;
+ uint16_t timeout_timer;
cirque_pinnacle_enable_feed(false); // Disable feed
- RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Send upper byte of ERA address
- RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address
+ RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Send upper byte of ERA address
+ RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address
for (uint16_t i = 0; i < count; i++) {
- RAP_Write(ERA_CONTROL, 0x05); // Signal ERA-read (auto-increment) to Pinnacle
+ RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__INC_ADDR_READ | HOSTREG__EREG_AXS__READ); // Signal ERA-read (auto-increment) to Pinnacle
// Wait for status register 0x1E to clear
+ timeout_timer = timer_read();
do {
- RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
- } while (ERAControlValue != 0x00);
+ RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1);
+ } while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT));
- RAP_ReadBytes(ERA_VALUE, data + i, 1);
+ RAP_ReadBytes(HOSTREG__EXT_REG_AXS_VALUE, data + i, 1);
cirque_pinnacle_clear_flags();
}
@@ -150,46 +121,80 @@ void ERA_ReadBytes(uint16_t address, uint8_t* data, uint16_t count) {
// Writes a byte, <data>, to an extended register at <address> (16-bit address)
void ERA_WriteByte(uint16_t address, uint8_t data) {
- uint8_t ERAControlValue = 0xFF;
+ uint8_t ERAControlValue = 0xFF;
+ uint16_t timeout_timer;
cirque_pinnacle_enable_feed(false); // Disable feed
- RAP_Write(ERA_VALUE, data); // Send data byte to be written
+ RAP_Write(HOSTREG__EXT_REG_AXS_VALUE, data); // Send data byte to be written
- RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Upper byte of ERA address
- RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address
+ RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Upper byte of ERA address
+ RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address
- RAP_Write(ERA_CONTROL, 0x02); // Signal an ERA-write to Pinnacle
+ RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__WRITE); // Signal an ERA-write to Pinnacle
// Wait for status register 0x1E to clear
+ timeout_timer = timer_read();
do {
- RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
- } while (ERAControlValue != 0x00);
+ RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1);
+ } while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT));
cirque_pinnacle_clear_flags();
}
void cirque_pinnacle_set_adc_attenuation(uint8_t adcGain) {
- uint8_t temp = 0x00;
+ uint8_t adcconfig = 0x00;
- ERA_ReadBytes(0x0187, &temp, 1);
- temp &= 0x3F; // clear top two bits
- temp |= adcGain;
- ERA_WriteByte(0x0187, temp);
- ERA_ReadBytes(0x0187, &temp, 1);
+ ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1);
+ adcconfig &= EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK;
+ adcconfig |= adcGain;
+ ERA_WriteByte(EXTREG__TRACK_ADCCONFIG, adcconfig);
+ ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1);
}
// Changes thresholds to improve detection of fingers
+// Not needed for flat overlay?
void cirque_pinnacle_tune_edge_sensitivity(void) {
- uint8_t temp = 0x00;
+ uint8_t widezmin = 0x00;
- ERA_ReadBytes(0x0149, &temp, 1);
- ERA_WriteByte(0x0149, 0x04);
- ERA_ReadBytes(0x0149, &temp, 1);
+ ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1);
+ ERA_WriteByte(EXTREG__XAXIS_WIDEZMIN, 0x04); // magic number from Cirque sample code
+ ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1);
- ERA_ReadBytes(0x0168, &temp, 1);
- ERA_WriteByte(0x0168, 0x03);
- ERA_ReadBytes(0x0168, &temp, 1);
+ ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1);
+ ERA_WriteByte(EXTREG__YAXIS_WIDEZMIN, 0x03); // magic number from Cirque sample code
+ ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1);
+}
+
+// Perform calibration
+void cirque_pinnacle_calibrate(void) {
+ uint8_t calconfig;
+ uint16_t timeout_timer;
+
+ RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1);
+ calconfig |= HOSTREG__CALCONFIG1__CALIBRATE;
+ RAP_Write(HOSTREG__CALCONFIG1, calconfig);
+
+ // Calibration takes ~100ms according to GT-AN-090624, doubling the timeout just to be safe
+ timeout_timer = timer_read();
+ do {
+ RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1);
+ } while ((calconfig & HOSTREG__CALCONFIG1__CALIBRATE) && (timer_elapsed(timeout_timer) <= 200));
+
+ cirque_pinnacle_clear_flags();
+}
+
+// Enable/disable cursor smoothing, smoothing is enabled by default
+void cirque_pinnacle_cursor_smoothing(bool enable) {
+ uint8_t feedconfig3;
+
+ RAP_ReadBytes(HOSTREG__FEEDCONFIG3, &feedconfig3, 1);
+ if (enable) {
+ feedconfig3 &= ~HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING;
+ } else {
+ feedconfig3 |= HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING;
+ }
+ RAP_Write(HOSTREG__FEEDCONFIG3, feedconfig3);
}
/* Pinnacle-based TM040040/TM035035/TM023023 Functions */
@@ -205,44 +210,28 @@ void cirque_pinnacle_init(void) {
// Host clears SW_CC flag
cirque_pinnacle_clear_flags();
- // SysConfig1 (Low Power Mode)
- // Bit 0: Reset, 1=Reset
- // Bit 1: Shutdown, 1=Shutdown, 0=Active
- // Bit 2: Sleep Enable, 1=low power mode, 0=normal mode
// send a RESET command now, in case QMK had a soft-reset without a power cycle
- RAP_Write(SYSCONFIG_1, 0x01);
+ RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1__RESET);
wait_ms(30); // Pinnacle needs 10-15ms to boot, so wait long enough before configuring
- RAP_Write(SYSCONFIG_1, 0x00);
+ RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1_DEFVAL);
wait_us(50);
// FeedConfig2 (Feature flags for Relative Mode Only)
- // Bit 0: IntelliMouse Enable, 1=enable, 0=disable
- // Bit 1: All Taps Disable, 1=disable, 0=enable
- // Bit 2: Secondary Tap Disable, 1=disable, 0=enable
- // Bit 3: Scroll Disable, 1=disable, 0=enable
- // Bit 4: GlideExtend® Disable, 1=disable, 0=enable
- // Bit 5: reserved
- // Bit 6: reserved
- // Bit 7: Swap X & Y, 1=90° rotation, 0=0° rotation
- RAP_Write(FEEDCONFIG_2, 0x00);
+ RAP_Write(HOSTREG__FEEDCONFIG2, HOSTREG__FEEDCONFIG2_DEFVAL);
// FeedConfig1 (Data Output Flags)
- // Bit 0: Feed enable, 1=feed, 0=no feed
- // Bit 1: Data mode, 1=absolute, 0=relative
- // Bit 2: Filter disable, 1=no filter, 0=filter
- // Bit 3: X disable, 1=no X data, 0=X data
- // Bit 4: Y disable, 1=no Y data, 0=Y data
- // Bit 5: reserved
- // Bit 6: X data Invert, 1=X max to 0, 0=0 to Y max
- // Bit 7: Y data Invert, 1=Y max to 0, 0=0 to Y max
- RAP_Write(FEEDCONFIG_1, CIRQUE_PINNACLE_POSITION_MODE << 1);
-
- // Host sets z-idle packet count to 5 (default is 0x1F/30)
- RAP_Write(Z_IDLE_COUNT, 5);
+ RAP_Write(HOSTREG__FEEDCONFIG1, CIRQUE_PINNACLE_POSITION_MODE ? HOSTREG__FEEDCONFIG1__DATA_TYPE__REL0_ABS1 : HOSTREG__FEEDCONFIG1_DEFVAL);
- cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION);
+ // Host sets z-idle packet count to 5 (default is 0x1E/30)
+ RAP_Write(HOSTREG__ZIDLE, 5);
+ cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION);
+#ifdef CIRQUE_PINNACLE_CURVED_OVERLAY
cirque_pinnacle_tune_edge_sensitivity();
+#endif
+ // Force a calibration after setting ADC attenuation
+ cirque_pinnacle_calibrate();
+
cirque_pinnacle_enable_feed(true);
}
@@ -252,15 +241,15 @@ pinnacle_data_t cirque_pinnacle_read_data(void) {
pinnacle_data_t result = {0};
// Check if there is valid data available
- RAP_ReadBytes(STATUS_1, &data_ready, 1); // bit2 is Software Data Ready, bit3 is Command Complete, bit0 and bit1 are reserved/unused
- if ((data_ready & 0x04) == 0) {
+ RAP_ReadBytes(HOSTREG__STATUS1, &data_ready, 1);
+ if ((data_ready & HOSTREG__STATUS1__DATA_READY) == 0) {
// no data available yet
result.valid = false; // be explicit
return result;
}
// Read all data bytes
- RAP_ReadBytes(PACKET_BYTE_0, data, 6);
+ RAP_ReadBytes(HOSTREG__PACKETBYTE_0, data, 6);
// Get ready for the next data sample
cirque_pinnacle_clear_flags();
diff --git a/drivers/sensors/cirque_pinnacle.h b/drivers/sensors/cirque_pinnacle.h
index d65bdb41b6..1c9bf06fd3 100644
--- a/drivers/sensors/cirque_pinnacle.h
+++ b/drivers/sensors/cirque_pinnacle.h
@@ -2,6 +2,7 @@
#pragma once
+#include "cirque_pinnacle_regdefs.h"
#include <stdint.h>
#include <stdbool.h>
@@ -15,6 +16,11 @@
# define CIRQUE_PINNACLE_POSITION_MODE CIRQUE_PINNACLE_ABSOLUTE_MODE
#endif
+#define CIRQUE_PINNACLE_DEFAULT_SCALE 1024
+#ifndef CIRQUE_PINNACLE_DIAMETER_MM
+# define CIRQUE_PINNACLE_DIAMETER_MM 40
+#endif
+
// Coordinate scaling values
#ifndef CIRQUE_PINNACLE_X_LOWER
# define CIRQUE_PINNACLE_X_LOWER 127 // min "reachable" X value
@@ -41,7 +47,7 @@
# include "i2c_master.h"
// Cirque's 7-bit I2C Slave Address
# ifndef CIRQUE_PINNACLE_ADDR
-# define CIRQUE_PINNACLE_ADDR 0x2A
+# define CIRQUE_PINNACLE_ADDR I2C_ADDRESS_DEFAULT
# endif
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
# include "spi_master.h"
@@ -66,6 +72,10 @@
# endif
#endif
+#define DIVIDE_UNSIGNED_ROUND(numerator, denominator) (((numerator) + ((denominator) / 2)) / (denominator))
+#define CIRQUE_PINNACLE_INCH_TO_PX(inch) (DIVIDE_UNSIGNED_ROUND((inch) * (uint32_t)CIRQUE_PINNACLE_DIAMETER_MM * 10, 254))
+#define CIRQUE_PINNACLE_PX_TO_INCH(px) (DIVIDE_UNSIGNED_ROUND((px) * (uint32_t)254, CIRQUE_PINNACLE_DIAMETER_MM * 10))
+
// Convenient way to store and access measurements
typedef struct {
bool valid; // true if valid data was read, false if no data was ready
@@ -84,6 +94,8 @@ typedef struct {
} pinnacle_data_t;
void cirque_pinnacle_init(void);
+void cirque_pinnacle_calibrate(void);
+void cirque_pinnacle_cursor_smoothing(bool enable);
pinnacle_data_t cirque_pinnacle_read_data(void);
void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution);
uint16_t cirque_pinnacle_get_scale(void);
diff --git a/drivers/sensors/cirque_pinnacle_gestures.c b/drivers/sensors/cirque_pinnacle_gestures.c
new file mode 100644
index 0000000000..f6f9177e08
--- /dev/null
+++ b/drivers/sensors/cirque_pinnacle_gestures.c
@@ -0,0 +1,226 @@
+/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdlib.h>
+#include <lib/lib8tion/lib8tion.h>
+#include "cirque_pinnacle_gestures.h"
+#include "pointing_device.h"
+#include "timer.h"
+
+#if defined(CIRQUE_PINNACLE_TAP_ENABLE) || defined(CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE)
+static cirque_pinnacle_features_t features = {.tap_enable = true, .circular_scroll_enable = true};
+#endif
+
+#ifdef CIRQUE_PINNACLE_TAP_ENABLE
+static trackpad_tap_context_t tap;
+
+static report_mouse_t trackpad_tap(report_mouse_t mouse_report, pinnacle_data_t touchData) {
+ if (touchData.touchDown != tap.touchDown) {
+ tap.touchDown = touchData.touchDown;
+ if (!touchData.zValue) {
+ if (timer_elapsed(tap.timer) < CIRQUE_PINNACLE_TAPPING_TERM && tap.timer != 0) {
+ mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
+ pointing_device_set_report(mouse_report);
+ pointing_device_send();
+# if TAP_CODE_DELAY > 0
+ wait_ms(TAP_CODE_DELAY);
+# endif
+ mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
+ pointing_device_set_report(mouse_report);
+ pointing_device_send();
+ }
+ }
+ tap.timer = timer_read();
+ }
+ if (timer_elapsed(tap.timer) > (CIRQUE_PINNACLE_TOUCH_DEBOUNCE)) {
+ tap.timer = 0;
+ }
+
+ return mouse_report;
+}
+
+void cirque_pinnacle_enable_tap(bool enable) {
+ features.tap_enable = enable;
+}
+#endif
+
+#ifdef CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE
+/* To set a trackpad exclusively as scroll wheel: outer_ring_pct = 100, trigger_px = 0, trigger_ang = 0 */
+static circular_scroll_context_t scroll = {.config = {.outer_ring_pct = 33,
+ .trigger_px = 16,
+ .trigger_ang = 9102, /* 50 degrees */
+ .wheel_clicks = 18}};
+
+static inline uint16_t atan2_16(int32_t dy, int32_t dx) {
+ if (dy == 0) {
+ if (dx >= 0) {
+ return 0;
+ } else {
+ return 32768;
+ }
+ }
+
+ int32_t abs_y = dy > 0 ? dy : -dy;
+ int16_t a;
+
+ if (dx >= 0) {
+ a = 8192 - (8192 * (dx - abs_y) / (dx + abs_y));
+ } else {
+ a = 24576 - (8192 * (dx + abs_y) / (abs_y - dx));
+ }
+
+ if (dy < 0) {
+ return -a; // negate if in quad III or IV
+ }
+ return a;
+}
+
+static circular_scroll_t circular_scroll(pinnacle_data_t touchData) {
+ circular_scroll_t report = {0, 0, false};
+ int8_t x, y, wheel_clicks;
+ uint8_t center = 256 / 2, mag;
+ int16_t ang, dot, det, opposite_side, adjacent_side;
+ uint16_t scale = cirque_pinnacle_get_scale();
+
+ if (touchData.zValue) {
+ /*
+ * Place origin at center of trackpad, treat coordinates as vectors.
+ * Scale to fixed int8_t size; angles are independent of resolution.
+ */
+ if (scale) {
+ x = (int8_t)((int32_t)touchData.xValue * 256 / scale - center);
+ y = (int8_t)((int32_t)touchData.yValue * 256 / scale - center);
+ } else {
+ x = 0;
+ y = 0;
+ }
+
+ /* Check if first touch */
+ if (!scroll.z) {
+ report.suppress_touch = false;
+ /* Check if touch falls within outer ring */
+ mag = sqrt16(x * x + y * y);
+ if (mag * 100 / center >= 100 - scroll.config.outer_ring_pct) {
+ scroll.state = SCROLL_DETECTING;
+ scroll.x = x;
+ scroll.y = y;
+ scroll.mag = mag;
+ /*
+ * Decide scroll axis:
+ * Vertical if started from righ half
+ * Horizontal if started from left half
+ * Flipped for left-handed
+ */
+# if defined(POINTING_DEVICE_ROTATION_90)
+ scroll.axis = y < 0;
+# elif defined(POINTING_DEVICE_ROTATION_180)
+ scroll.axis = x > 0;
+# elif defined(POINTING_DEVICE_ROTATION_270)
+ scroll.axis = y > 0;
+# else
+ scroll.axis = x < 0;
+# endif
+ }
+ } else if (scroll.state == SCROLL_DETECTING) {
+ report.suppress_touch = true;
+ /* Already detecting scroll, check movement from touchdown location */
+ mag = sqrt16((x - scroll.x) * (x - scroll.x) + (y - scroll.y) * (y - scroll.y));
+ if (mag >= scroll.config.trigger_px) {
+ /*
+ * Find angle of movement.
+ * 0 degrees here means movement towards center of circle
+ */
+ dot = scroll.x * x + scroll.y * y;
+ det = scroll.x * y - scroll.y * x;
+ opposite_side = abs(det); /* Based on scalar rejection */
+ adjacent_side = abs(scroll.mag * scroll.mag - abs(dot)); /* Based on scalar projection */
+ ang = (int16_t)atan2_16(opposite_side, adjacent_side);
+ if (ang < scroll.config.trigger_ang) {
+ /* Not a scroll, release coordinates */
+ report.suppress_touch = false;
+ scroll.state = NOT_SCROLL;
+ } else {
+ /* Scroll detected */
+ scroll.state = SCROLL_VALID;
+ }
+ }
+ }
+ if (scroll.state == SCROLL_VALID) {
+ report.suppress_touch = true;
+ dot = scroll.x * x + scroll.y * y;
+ det = scroll.x * y - scroll.y * x;
+ ang = (int16_t)atan2_16(det, dot);
+ wheel_clicks = ((int32_t)ang * scroll.config.wheel_clicks) / 65536;
+ if (wheel_clicks >= 1 || wheel_clicks <= -1) {
+ if (scroll.config.left_handed) {
+ if (scroll.axis == 0) {
+ report.h = -wheel_clicks;
+ } else {
+ report.v = wheel_clicks;
+ }
+ } else {
+ if (scroll.axis == 0) {
+ report.v = -wheel_clicks;
+ } else {
+ report.h = wheel_clicks;
+ }
+ }
+ scroll.x = x;
+ scroll.y = y;
+ }
+ }
+ }
+
+ scroll.z = touchData.zValue;
+ if (!scroll.z) scroll.state = SCROLL_UNINITIALIZED;
+
+ return report;
+}
+
+void cirque_pinnacle_enable_circular_scroll(bool enable) {
+ features.circular_scroll_enable = enable;
+}
+
+void cirque_pinnacle_configure_circular_scroll(uint8_t outer_ring_pct, uint8_t trigger_px, uint16_t trigger_ang, uint8_t wheel_clicks, bool left_handed) {
+ scroll.config.outer_ring_pct = outer_ring_pct;
+ scroll.config.trigger_px = trigger_px;
+ scroll.config.trigger_ang = trigger_ang;
+ scroll.config.wheel_clicks = wheel_clicks;
+ scroll.config.left_handed = left_handed;
+}
+#endif
+
+bool cirque_pinnacle_gestures(report_mouse_t* mouse_report, pinnacle_data_t touchData) {
+ bool suppress_mouse_update = false;
+
+#ifdef CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE
+ circular_scroll_t scroll_report;
+ if (features.circular_scroll_enable) {
+ scroll_report = circular_scroll(touchData);
+ mouse_report->v = scroll_report.v;
+ mouse_report->h = scroll_report.h;
+ suppress_mouse_update = scroll_report.suppress_touch;
+ }
+#endif
+
+#ifdef CIRQUE_PINNACLE_TAP_ENABLE
+ if (features.tap_enable) {
+ *mouse_report = trackpad_tap(*mouse_report, touchData);
+ }
+#endif
+
+ return suppress_mouse_update;
+}
diff --git a/drivers/sensors/cirque_pinnacle_gestures.h b/drivers/sensors/cirque_pinnacle_gestures.h
new file mode 100644
index 0000000000..f39782b467
--- /dev/null
+++ b/drivers/sensors/cirque_pinnacle_gestures.h
@@ -0,0 +1,107 @@
+/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "cirque_pinnacle.h"
+#include "report.h"
+
+typedef struct {
+ bool tap_enable;
+ bool circular_scroll_enable;
+} cirque_pinnacle_features_t;
+
+#ifdef CIRQUE_PINNACLE_TAP_ENABLE
+# ifndef CIRQUE_PINNACLE_TAPPING_TERM
+# include "action.h"
+# include "action_tapping.h"
+# define CIRQUE_PINNACLE_TAPPING_TERM GET_TAPPING_TERM(KC_BTN1, &(keyrecord_t){})
+# endif
+# ifndef CIRQUE_PINNACLE_TOUCH_DEBOUNCE
+# define CIRQUE_PINNACLE_TOUCH_DEBOUNCE (CIRQUE_PINNACLE_TAPPING_TERM * 8)
+# endif
+
+typedef struct {
+ uint16_t timer;
+ bool touchDown;
+} trackpad_tap_context_t;
+
+/* Enable/disable tap gesture */
+void cirque_pinnacle_enable_tap(bool enable);
+#endif
+
+#ifdef CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE
+typedef enum {
+ SCROLL_UNINITIALIZED,
+ SCROLL_DETECTING,
+ SCROLL_VALID,
+ NOT_SCROLL,
+} circular_scroll_status_t;
+
+typedef struct {
+ int8_t v;
+ int8_t h;
+ bool suppress_touch;
+} circular_scroll_t;
+
+typedef struct {
+ uint8_t outer_ring_pct; /* Width of outer ring, given as a percentage of the radius */
+ uint8_t trigger_px; /* Amount of movement before triggering scroll validation, in pixels 0~127 */
+ uint16_t trigger_ang; /* Angle required to validate scroll, in radians where pi = 32768 */
+ uint8_t wheel_clicks; /* How many clicks to report in a circle */
+ bool left_handed; /* Whether scrolling should be flipped for left handed use */
+} circular_scroll_config_t;
+
+typedef struct {
+ circular_scroll_config_t config;
+ circular_scroll_status_t state;
+ uint8_t mag;
+ int8_t x;
+ int8_t y;
+ uint16_t z;
+ bool axis;
+} circular_scroll_context_t;
+
+/* Enable/disable circular scroll gesture */
+void cirque_pinnacle_enable_circular_scroll(bool enable);
+
+/*
+ * Configure circular scroll gesture.
+ * Trackpad can be configured to act exclusively as a scroll wheel with outer_ring_pct = 0, trigger_px = 0, trigger_ang = 0.
+ * @param outer_ring_pct Width of outer ring from which to begin scroll validation, given as a percentage of the radius.
+ * @param trigger_px Amount of movement before triggering scroll validation. Expressed in pixels, trackpad coordinates are scaled to radius of 128 pixels for circular scroll.
+ * @param triger_ang Angle required to validate scroll, angle smaller than this will invalidate scroll. In radians where pi = 32768, 0 means movement towards center of trackpad, 16384 means movement perpendicular to center.
+ * @param wheel_clicks Number of scroll wheel clicks to report in a full rotation.
+ * @param left_handed Whether scrolling should be flipped for left-handed use.
+ */
+void cirque_pinnacle_configure_circular_scroll(uint8_t outer_ring_pct, uint8_t trigger_px, uint16_t trigger_ang, uint8_t wheel_clicks, bool left_handed);
+#endif
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+/* Implementation in pointing_device_drivers.c */
+
+/* Enable/disable inertial cursor */
+void cirque_pinnacle_enable_cursor_glide(bool enable);
+
+/*
+ * Configure inertial cursor.
+ * @param trigger_px Movement required to trigger cursor glide, set this to non-zero if you have some amount of hover.
+ */
+void cirque_pinnacle_configure_cursor_glide(float trigger_px);
+#endif
+
+/* Process available gestures */
+bool cirque_pinnacle_gestures(report_mouse_t* mouse_report, pinnacle_data_t touchData);
diff --git a/drivers/sensors/cirque_pinnacle_regdefs.h b/drivers/sensors/cirque_pinnacle_regdefs.h
new file mode 100644
index 0000000000..993da1e757
--- /dev/null
+++ b/drivers/sensors/cirque_pinnacle_regdefs.h
@@ -0,0 +1,405 @@
+// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
+// based on https://github.com/cirque-corp/Cirque_Pinnacle_1CA027/tree/master/Additional_Examples
+// with modifications and changes for QMK
+// refer to documentation: Gen2 and Gen3 (Pinnacle ASIC) at https://www.cirque.com/gen2gen3-asic-details
+
+#pragma once
+
+// clang-format off
+
+#define HostReg__0 (0x00)
+#define HostReg__1 (0x01)
+#define HostReg__2 (0x02)
+#define HostReg__3 (0x03)
+#define HostReg__4 (0x04)
+#define HostReg__5 (0x05)
+#define HostReg__6 (0x06)
+#define HostReg__7 (0x07)
+#define HostReg__8 (0x08)
+#define HostReg__9 (0x09)
+#define HostReg__10 (0x0A)
+#define HostReg__11 (0x0B)
+#define HostReg__12 (0x0C)
+#define HostReg__13 (0x0D)
+#define HostReg__14 (0x0E)
+#define HostReg__15 (0x0F)
+#define HostReg__16 (0x10)
+#define HostReg__17 (0x11)
+#define HostReg__18 (0x12)
+#define HostReg__19 (0x13)
+#define HostReg__20 (0x14)
+#define HostReg__21 (0x15)
+#define HostReg__22 (0x16)
+#define HostReg__23 (0x17)
+#define HostReg__24 (0x18)
+#define HostReg__25 (0x19)
+#define HostReg__26 (0x1A)
+#define HostReg__27 (0x1B)
+#define HostReg__28 (0x1C)
+#define HostReg__29 (0x1D)
+#define HostReg__30 (0x1E)
+#define HostReg__31 (0x1F)
+
+// ---------------- Register Assignments -------------------------------------
+
+/*--------------------------------------------------------------------------*\
+ Chip ID / Version
+\*--------------------------------------------------------------------------*/
+// Chip ID Register