浏览代码

Cirque trackpad features: circular scroll, inertial cursor (#17482)

Daniel Kao 2 年之前
父节点
当前提交
5db705d054

+ 4 - 0
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

+ 21 - 10
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
 

+ 90 - 101
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();

+ 13 - 1
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);

+ 226 - 0
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;
+}

+ 107 - 0
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);

+ 405 - 0
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
+#define HOSTREG__CHIPID                                             HostReg__0
+
+// Chip Version Register
+#define HOSTREG__VERSION                                            HostReg__1
+
+/*--------------------------------------------------------------------------*\
+                           Status Register
+\*--------------------------------------------------------------------------*/
+// Status 1 Register -- MUST BE HOSTREG__2
+#define HOSTREG__STATUS1                                            HostReg__2
+#    define HOSTREG__STATUS1__DATA_READY                            0x04
+#    define HOSTREG__STATUS1__COMMAND_COMPLETE                      0x08
+#define HOSTREG__STATUS1_DEFVAL                                     0x00
+
+/*--------------------------------------------------------------------------*\
+                           System Config Register
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__SYSCONFIG1                                         HostReg__3
+#    define HOSTREG__SYSCONFIG1__RESET                              0x01
+#    define HOSTREG__SYSCONFIG1__STANDBY                            0x02
+#    define HOSTREG__SYSCONFIG1__AUTO_SLEEP                         0x04
+#    define HOSTREG__SYSCONFIG1__TRACK_DISABLE                      0x08
+#    define HOSTREG__SYSCONFIG1__ANYMEAS_ENABLE                     0x10
+#    define HOSTREG__SYSCONFIG1__GPIO_CTRL_ENABLE                   0x20
+#    define HOSTREG__SYSCONFIG1__WAKEUP_TOGGLE                      0x40
+#    define HOSTREG__SYSCONFIG1__FORCE_WAKEUP                       0x80
+#define HOSTREG__SYSCONFIG1_DEFVAL                                  0x00
+
+/*--------------------------------------------------------------------------*\
+                           Feed Config Registers
+\*--------------------------------------------------------------------------*/
+// Feed Config Register1
+#define HOSTREG__FEEDCONFIG1                                        HostReg__4
+#    define HOSTREG__FEEDCONFIG1__FEED_ENABLE                       0x01
+#    define HOSTREG__FEEDCONFIG1__DATA_TYPE__REL0_ABS1              0x02
+#    define HOSTREG__FEEDCONFIG1__FILTER_DISABLE                    0x04
+#    define HOSTREG__FEEDCONFIG1__X_AXIS_DISABLE                    0x08
+#    define HOSTREG__FEEDCONFIG1__Y_AXIS_DISABLE                    0x10
+#    define HOSTREG__FEEDCONFIG1__AXIS_FOR_Z__Y0_X1                 0x20
+#    define HOSTREG__FEEDCONFIG1__X_DATA_INVERT                     0x40
+#    define HOSTREG__FEEDCONFIG1__Y_DATA_INVERT                     0x80
+#define HOSTREG__FEEDCONFIG1_DEFVAL                                 0x00
+
+// Feed Config Register2
+#define HOSTREG__FEEDCONFIG2                                        HostReg__5
+#    define HOSTREG__FEEDCONFIG2__INTELLIMOUSE_MODE                 0x01
+#    define HOSTREG__FEEDCONFIG2__ALL_TAP_DISABLE                   0x02
+#    define HOSTREG__FEEDCONFIG2__SECONDARY_TAP_DISABLE             0x04
+#    define HOSTREG__FEEDCONFIG2__SCROLL_DISABLE                    0x08
+#    define HOSTREG__FEEDCONFIG2__GLIDE_EXTEND_DISABLE              0x10
+#    define HOSTREG__FEEDCONFIG2__PALM_BEFORE_Z_ENABLE              0x20
+#    define HOSTREG__FEEDCONFIG2__BUTNS_46_SCROLL_5_MIDDLE          0x40
+#    define HOSTREG__FEEDCONFIG2__SWAP_XY_RELATIVE                  0x80
+#define HOSTREG__FEEDCONFIG2_DEFVAL                                 0x00
+
+// Feed Config Register3
+#define HOSTREG__FEEDCONFIG3                                        HostReg__6
+#    define HOSTREG__FEEDCONFIG3__BTNS_456_TO_123_IN_REL            0x01
+#    define HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING      0x02
+#    define HOSTREG__FEEDCONFIG3__DISABLE_PALM_NERD_MEAS            0x04
+#    define HOSTREG__FEEDCONFIG3__DISABLE_NOISE_AVOIDANCE           0x08
+#    define HOSTREG__FEEDCONFIG3__DISABLE_WRAP_LOCKOUT              0x10
+#    define HOSTREG__FEEDCONFIG3__DISABLE_DYNAMIC_EMI_ADJUST        0x20
+#    define HOSTREG__FEEDCONFIG3__DISABLE_HW_EMI_DETECT             0x40
+#    define HOSTREG__FEEDCONFIG3__DISABLE_SW_EMI_DETECT             0x80
+#define HOSTREG__FEEDCONFIG3_DEFVAL                                 0x00
+
+/*--------------------------------------------------------------------------*\
+                           Calibration Config
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__CALCONFIG1                                         HostReg__7
+#    define HOSTREG__CALCONFIG1__CALIBRATE                          0x01
+#    define HOSTREG__CALCONFIG1__BACKGROUND_COMP_ENABLE             0x02
+#    define HOSTREG__CALCONFIG1__NERD_COMP_ENABLE                   0x04
+#    define HOSTREG__CALCONFIG1__TRACK_ERROR_COMP_ENABLE            0x08
+#    define HOSTREG__CALCONFIG1__TAP_COMP_ENABLE                    0x10
+#    define HOSTREG__CALCONFIG1__PALM_ERROR_COMP_ENABLE             0x20
+#    define HOSTREG__CALCONFIG1__CALIBRATION_MATRIX_DISABLE         0x40
+#    define HOSTREG__CALCONFIG1__FORCE_PRECALIBRATION_NOISE_CHECK   0x80
+#define HOSTREG__CALCONFIG1_DEFVAL                                  (HOSTREG__CALCONFIG1__BACKGROUND_COMP_ENABLE | HOSTREG__CALCONFIG1__NERD_COMP_ENABLE | HOSTREG__CALCONFIG1__TRACK_ERROR_COMP_ENABLE | HOSTREG__CALCONFIG1__TAP_COMP_ENABLE | HOSTREG__CALCONFIG1__PALM_ERROR_COMP_ENABLE)
+
+/*--------------------------------------------------------------------------*\
+                           PS2 Aux Control Register
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__PS2AUX_CTRL                                        HostReg__8
+#    define HOSTREG__PS2AUX_CTRL__CMD_PASSTHRU_ENABLE               0x01
+#    define HOSTREG__PS2AUX_CTRL__SP_EXTENDED_MODE                  0x02
+#    define HOSTREG__PS2AUX_CTRL__GS_DISABLE                        0x04
+#    define HOSTREG__PS2AUX_CTRL__SP_DISABLE                        0x08
+#    define HOSTREG__PS2AUX_CTRL__GS_COORDINATE_DISABLE             0x10
+#    define HOSTREG__PS2AUX_CTRL__SP_COORDINATE_DISABLE             0x20
+#    define HOSTREG__PS2AUX_CTRL__DISABLE_AA00_DETECT               0x40
+#    define HOSTREG__PS2AUX_CTRL__AUX_PRESENT                       0x80
+#define HOSTREG__PR2AUX_CTRL_DEFVAL                                 0x00
+
+/*--------------------------------------------------------------------------*\
+                           Sample Rate Value
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__SAMPLERATE                                         HostReg__9
+#    define HOSTREG__SAMPLERATE__10_SPS                             0x0A
+#    define HOSTREG__SAMPLERATE__20_SPS                             0x14
+#    define HOSTREG__SAMPLERATE__40_SPS                             0x28
+#    define HOSTREG__SAMPLERATE__60_SPS                             0x3C
+#    define HOSTREG__SAMPLERATE__80_SPS                             0x50
+#    define HOSTREG__SAMPLERATE__100_SPS                            0x64
+#    define HOSTREG__SAMPLERATE__200_SPS                            0xC8        // 200sps not supported
+                                                                                // only for ps2 compatibility
+                                                                                // rate set to 100sps
+#define HOSTREG__SAMPLERATE_DEFVAL                                  HOSTREG__SAMPLERATE__100_SPS
+
+/*--------------------------------------------------------------------------*\
+                           Z Idle Value
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__ZIDLE                                              HostReg__10
+#define HOSTREG__ZIDLE_DEFVAL                                       30 // 0x1E
+
+/*--------------------------------------------------------------------------*\
+                           Z Scaler Value
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__ZSCALER                                            HostReg__11
+#define HOSTREG__ZSCALER_DEFVAL                                     8 // 0x08
+
+/*--------------------------------------------------------------------------*\
+                           Sleep Interval Value
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__SLEEP_INTERVAL                                     HostReg__12
+#define HOSTREG__SLEEP_INTERVAL_DEFVAL                              73 // 0x49
+
+/*--------------------------------------------------------------------------*\
+                           Sleep Delay Value
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__SLEEP_DELAY                                        HostReg__13
+#define HOSTREG__SLEEP_DELAY_DEFVAL                                 39 // 0x27
+
+/*--------------------------------------------------------------------------*\
+                           Dynamic EMI Bad Channel Count Thresholds
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__DYNAMIC_EMI_ADJUST_THRESHOLD                       HostReg__14
+#define HOSTREG__DYNAMIC_EMI_ADJUST_THRESHOLD_DEFVAL                66 // 0x42
+
+/*--------------------------------------------------------------------------*\
+                           Packet Registers
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__PACKETBYTE_0                                       HostReg__18
+#define HOSTREG__PACKETBYTE_1                                       HostReg__19
+#define HOSTREG__PACKETBYTE_2                                       HostReg__20
+#define HOSTREG__PACKETBYTE_3                                       HostReg__21
+#define HOSTREG__PACKETBYTE_4                                       HostReg__22
+#define HOSTREG__PACKETBYTE_5                                       HostReg__23
+
+/*--------------------------------------------------------------------------*\
+                           Port A GPIO Control
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__PORTA_GPIO_CTRL                                    HostReg__24
+#define HOSTREG__PORTA_GPIO_CTRL_DEFVAL                             0xFF
+
+/*--------------------------------------------------------------------------*\
+                           Port A GPIO Data
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__PORTA_GPIO_DATA                                    HostReg__25
+#define HOSTREG__PORTA_GPIO_DATA_DEFVAL                             0x00
+
+/*--------------------------------------------------------------------------*\
+                           Port B GPIO Control And Data
+\*--------------------------------------------------------------------------*/
+
+#define HOSTREG__PORTB_GPIO_CTRL_DATA                               HostReg__26
+#    define HOSTREG__PORTB_GPIO_DATA__PB0                           0x01
+#    define HOSTREG__PORTB_GPIO_DATA__PB1                           0x02
+#    define HOSTREG__PORTB_GPIO_DATA__PB2                           0x04
+#    define HOSTREG__PORTB_GPIO_CTRL__PB0                           0x08
+#    define HOSTREG__PORTB_GPIO_CTRL__PB1                           0x10
+#    define HOSTREG__PORTB_GPIO_CTRL__PB2                           0x20
+#    define HOSTREG__PORTB_GPIO_RSVD_0                              0x40
+#    define HOSTREG__PORTB_GPIO_READ1_WRITE0                        0x80
+#define HOSTREG__PORTB_GPIO_CTRL_DATA_DEFVAL                        (HOSTREG__PORTB_GPIO_CTRL__PB0 | HOSTREG__PORTB_GPIO_CTRL__PB1 | HOSTREG__PORTB_GPIO_CTRL__PB2)
+
+/*--------------------------------------------------------------------------*\
+                           Extended Register Access
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__EXT_REG_AXS_VALUE                                  HostReg__27
+
+#define HOSTREG__EXT_REG_AXS_ADDR_HIGH                              HostReg__28
+#define HOSTREG__EXT_REG_AXS_ADDR_LOW                               HostReg__29
+
+#define HOSTREG__EXT_REG_AXS_CTRL                                   HostReg__30
+#    define HOSTREG__EREG_AXS__READ                                 0x01
+#    define HOSTREG__EREG_AXS__WRITE                                0x02
+#    define HOSTREG__EREG_AXS__INC_ADDR_READ                        0x04
+#    define HOSTREG__EREG_AXS__INC_ADDR_WRITE                       0x08
+#    define HOSTREG__EREG_AXS__RSVD_3                               0x10
+#    define HOSTREG__EREG_AXS__RSVD_2                               0x20
+#    define HOSTREG__EREG_AXS__RSVD_1                               0x40
+#    define HOSTREG__EREG_AXS__RSVD_0                               0x80
+
+#define HOSTREG__EXT_REG_AXS_VALUE_DEFVAL                           0x00
+#define HOSTREG__EXT_REG_AXS_ADDR_HIGH_DEFVAL                       0x00
+#define HOSTREG__EXT_REG_AXS_ADDR_LOW_DEFVAL                        0x00
+#define HOSTREG__EXT_REG_AXS_CTRL_DEFVAL                            0x00
+
+/*--------------------------------------------------------------------------*\
+                           Product ID
+\*--------------------------------------------------------------------------*/
+#define HOSTREG__PRODUCT_ID                                         HostReg__31
+
+
+
+//Some useful values
+#define I2C_ADDRESS_DEFAULT                                         0x2A
+#define FIRMWARE_ID                                                 0x07
+#define FIRMWARE_VERSION                                            0x9D
+
+//Anymeas config options
+//First setting is HostReg 5.  This sets toggle frequency (EF) and gain.
+//Gain is upper two bits (0xC0), frequency is lower 6 bits (0x3F)
+#define AnyMeas_AccumBits_ElecFreq                                  HostReg__5
+#    define ADCCNFG_ELEC_FREQ                                       0x3F  /* Bit 4, 3, 2, 1, 0 */
+#        define ADCCNFG_EF_0                                        0x02  // 500,000Hz
+#        define ADCCNFG_EF_1                                        0x03  // 444,444Hz
+#        define ADCCNFG_EF_2                                        0x04  // 400,000Hz
+#        define ADCCNFG_EF_3                                        0x05  // 363,636Hz
+#        define ADCCNFG_EF_4                                        0x06  // 333,333Hz
+#        define ADCCNFG_EF_5                                        0x07  // 307,692Hz
+#        define ADCCNFG_EF_6                                        0x09  // 267,000Hz
+#        define ADCCNFG_EF_7                                        0x0B  // 235,000Hz
+#    define ADCCNFG_ACCUMBITSSELECT                                 0xC0  /* Bit 7, 6 */
+#        define ADCCNFG_ACCBITS_17_14_0                             0x00  //This is about 2x gain
+#        define ADCCNFG_ACCBITS_17_15_1                             0x40  //This is about 1.6x gain
+#        define ADCCNFG_ACCBITS_17_2__80                            0x80  //This is about 1.3x gain
+#        define ADCCNFG_ACCBITS_17_2__C0                            0xC0  //This is lowest gain
+//Note, all frequencies above are based on default 500ns aperture.  If aperture is shorter the frequencies will be faster and if aperture is longer the frequencies will be slower.
+
+//Next is HostReg 6.  This sets the sample length.  There are four possible settings to bit length.  All other settings are not normally used and should be a 0.
+#define AnyMeas_BitLength                                           HostReg__6
+#    define ADCCTRL_BIT_LENGTH                                      0x03  /* Bit 1, 0 */
+#        define ADCCTRL_SAMPLES_32                                  0x00  //Note: this does not work.
+#        define ADCCTRL_SAMPLES_128                                 0x01
+#        define ADCCTRL_SAMPLES_256                                 0x02
+#        define ADCCTRL_SAMPLES_512                                 0x03
+#    define ADCCTRL_ENABLE                                          0x20  /* Bit 5 */
+#    define ADCCTRL_INT_FLAG                                        0x40  /* Bit 6 */
+#    define ADCCTRL_START_BUSY                                      0x80  /* Bit 7 */
+//The smaller the sample length the faster the measurement but the lower the SNR.  For high SNR requirements 512 sample length is recommended.  Alternatively, multiple 128 or 256 length measurements could be averaged.
+
+//Next is HostReg 7.  This sets the sense mux.  Pinnacle has 2 sense lines, Sense N and Sense P1.  There is also a Sense P2 but it is not bonded out, it is only internal.
+//Signal on Sense N will be inverted from signal on Sense P1.  Other than sign inversion, signal strength should be the same.
+#define AnyMeas_ADC_MuxControl                                      HostReg__7
+#    define ADCMUXCTRL_SENSEP1GATE                                  0x01  //Enables Sense P1.  Can be combined with Sense N input or exclusivly Sense P1 alone.
+#    define ADCMUXCTRL_SENSEP2GATE                                  0x02  //Not used.
+#    define ADCMUXCTRL_SENSENGATE                                   0x04  //Enables Sense N.  Can be combined with Sense P inputs or exclusivly Sense N alone.
+#    define ADCMUXCTRL_REF0GATE                                     0x08  //This enables the RefCap0.  This is a capacitor inside the chip that is roughly 0.25pF. It is also controlled with the toggle and polarity bits so those bits must be set properly as well in order to use it.
+#    define ADCMUXCTRL_REF1GATE                                     0x10  //This enables the RefCap1.  This is a capacitor inside the chip that is roughly 0.5pF. It is also controlled with the toggle and polarity bits so those bits must be set properly as well in order to use it.
+#    define ADCMUXCTRL_OSCMEASEN                                    0x80  //this is a test mode for measuring the internal oscillator.  It is for IC test only.
+
+//Next is HostReg 8.  This contains various ADC config settings that are not likely to be used.
+#define AnyMeas_ADC_Config2                                         HostReg__8
+#    define ADCCNFG2_ADC_CLK_SELECT                                 0x01  /* Bit 0 */   //If 0 use the standard 8Mhz clock.  If 1 use a divide by 2, 4Mhz clock.  Only used if extra slow toggle frequencies are required.
+#    define ADCCNFG2_EMI_FLAG                                       0x02  /* Bit 1 */   //EMI flag threshold only used with internal FW.  Not valid in anymeas mode.
+#    define ADCCNFG2_EMI_FLAG_THRESHOLD_0                           0x04  /* Bit 2 */   //EMI flag threshold only used with internal FW.  Not valid in anymeas mode.
+#    define ADCCNFG2_EMI_FLAG_THRESHOLD_1                           0x08  /* Bit 3 */   //EMI flag threshold only used with internal FW.  Not valid in anymeas mode.
+#    define ADCCNFG2_DSX2_EXTEND                                    0x10  /* Bit 4 */   //extend one signal on the receive.  Could also be helpful in situations where sensor cap is extremely high.
+#    define ADCCNFG2_ETOGGLE_DELAY                                  0x20  /* Bit 5 */   //delay a bit before toggling electrodes.  Could be helpful in situations where sensor cap is extremely high.
+
+//Next is HostReg 9.  This sets the aperture length.  Bottom 4 bits set the aperture width
+#define AnyMeas_ADC_AWidth                                          HostReg__9
+#    define ADCAWIDTH_AWIDTHMASK                                    0x0F
+#        define ADCAWIDTH_APERTURE_OPEN                             0x00  //does not work
+#        define ADCAWIDTH_APERTURE_125NS                            0x01  //does not work
+#        define ADCAWIDTH_APERTURE_250NS                            0x02
+#        define ADCAWIDTH_APERTURE_375NS                            0x03
+#        define ADCAWIDTH_APERTURE_500NS                            0x04
+#        define ADCAWIDTH_APERTURE_625NS                            0x05
+#        define ADCAWIDTH_APERTURE_750NS                            0x06
+#        define ADCAWIDTH_APERTURE_875NS                            0x07
+#        define ADCAWIDTH_APERTURE_1000NS                           0x08
+#        define ADCAWIDTH_APERTURE_1125NS                           0x09
+#        define ADCAWIDTH_APERTURE_1250NS                           0x0A
+#        define ADCAWIDTH_APERTURE_1375NS                           0x0B
+#        define ADCAWIDTH_APERTURE_1500NS                           0x0C
+#        define ADCAWIDTH_APERTURE_1625NS                           0x0D
+#        define ADCAWIDTH_APERTURE_1750NS                           0x0E
+#        define ADCAWIDTH_APERTURE_1875NS                           0x0F
+#    define ADCAWIDTH_AWIDTHPLUSHALF                                0x10
+#    define ADCAWIDTH_AOPEN                                         0x20
+#    define ADCAWIDTH_W2WAIT                                        0x40
+
+//next two registers give the high and low bytes to the 16 bit address where Pinnacle will pull the measurement data.  Normally these addresses are within the base 32 registers.
+#define AnyMeas_pADCMeasInfoStart_High_Byte                         HostReg__10
+#define AnyMeas_pADCMeasInfoStart_Low_Byte                          HostReg__11
+
+//Next is the measurement index, this sets the measurement state machine to the start and should be a 0 at start.
+#define AnyMeas_MeasIndex                                           HostReg__12
+#   define ANYMEASSTATE_RESET_START                                 0x00
+#   define ANYMEASSTATE_START_MEASUREMENT                           0x01
+#   define ANYMEASSTATE_WAIT_FOR_MEASUREMENT_AND_HOST               0x02
+
+//next is the state itself of the measurement, should always be 0.
+#define AnyMeas_State                                               HostReg__13
+
+//next is the number of measurements.  Use 0x80 to repeat the single measurement or repeat a number of measurements.
+//0x40 will turn the ADC off after measurements.  This will result in longer startup time for a subsequent measurement, but lower idle power draw.
+#define AnyMeas_Control_NumMeas                                     HostReg__14
+#    define ANYMEAS_CONTROL__NUM_MEAS_MASK                          0x3F
+#    define ANYMEAS_CONTROL__ADC_POST_MEAS_PWR                      0x40
+#    define ANYMEAS_CONTROL__REPEAT                                 0x80
+
+//These are not used
+#define AnyMeas_pADCMeasInfo_High_Byte                              HostReg__15
+#define AnyMeas_pADCMeasInfo_Low_Byte                               HostReg__16
+
+//16 bit result of measurement will be found in these two registers.
+#define AnyMeas_Result_High_Byte                                    HostReg__17
+#define AnyMeas_Result_Low_Byte                                     HostReg__18
+
+// ---------------- Extended Register Assignments ----------------------------
+/*--------------------------------------------------------------------------*\
+                           ADC Mux Control
+\*--------------------------------------------------------------------------*/
+#define EXTREG__ADCMUX_CTRL                                         0x00EB
+#    define EXTREG__ADCMUX_CTRL__SNSP_ENABLE                        0x01
+#    define EXTREG__ADCMUX_CTRL__SNSN_ENABLE                        0x04
+
+/*--------------------------------------------------------------------------*\
+                           Timer Reload Registers
+\*--------------------------------------------------------------------------*/
+#define EXTREG__PACKET_TIMER_RELOAD                                 0x019F
+#define EXTREG__TRACK_TIMER_RELOAD                                  0x019E
+// These two registers should have matching content.
+#    define EXTREG__TIMER_RELOAD__300_SPS                           0x06
+#    define EXTREG__TIMER_RELOAD__200_SPS                           0x09
+#    define EXTREG__TIMER_RELOAD__100_SPS                           0x13
+
+/*--------------------------------------------------------------------------*\
+                           Track ADC Config
+\*--------------------------------------------------------------------------*/
+#define EXTREG__TRACK_ADCCONFIG                                     0x0187
+// ADC-attenuation settings (held in BIT_7 and BIT_6)
+// 1X = most sensitive, 4X = least sensitive
+#    define EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK             0x3F
+#        define EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_1X           0x00
+#        define EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_2X           0x40
+#        define EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_3X           0x80
+#        define EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X           0xC0
+#define EXTREG__TRACK_ADCCONFIG_DEFVAL                              0x4E
+
+
+/*--------------------------------------------------------------------------*\
+                           Tune Edge Sensitivity
+\*--------------------------------------------------------------------------*/
+// These registers are not detailed in any publically available documentation
+// Names inferred from debug prints in https://github.com/cirque-corp/Cirque_Pinnacle_1CA027/blob/master/Circular_Trackpad
+#define EXTREG__XAXIS_WIDEZMIN                                      0x0149
+#define EXTREG__YAXIS_WIDEZMIN                                      0x0168
+#define EXTREG__XAXIS_WIDEZMIN_DEFVAL                               0x06
+#define EXTREG__YAXIS_WIDEZMIN_DEFVAL                               0x05
+
+// clang-format on

+ 2 - 0
quantum/pointing_device.h

@@ -31,6 +31,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #    include "drivers/sensors/analog_joystick.h"
 #elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
 #    include "drivers/sensors/cirque_pinnacle.h"
+#    include "drivers/sensors/cirque_pinnacle_gestures.h"
+#    include "pointing_device_gestures.h"
 #elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
 #    include "i2c_master.h"
 #    include "drivers/sensors/pimoroni_trackball.h"

+ 64 - 37
quantum/pointing_device_drivers.c

@@ -97,27 +97,48 @@ const pointing_device_driver_t pointing_device_driver = {
 // clang-format on
 
 #elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
-#    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)
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+static bool cursor_glide_enable = true;
+
+static cursor_glide_context_t glide = {.config = {
+                                           .coef       = 102, /* Good default friction coef */
+                                           .interval   = 10,  /* 100sps */
+                                           .trigger_px = 10,  /* Default threshold in case of hover, set to 0 if you'd like */
+                                       }};
+
+void cirque_pinnacle_enable_cursor_glide(bool enable) {
+    cursor_glide_enable = enable;
+}
+
+void cirque_pinnacle_configure_cursor_glide(float trigger_px) {
+    glide.config.trigger_px = trigger_px;
+}
 #    endif
 
 report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
     pinnacle_data_t          touchData = cirque_pinnacle_read_data();
     mouse_xy_report_t        report_x = 0, report_y = 0;
     static mouse_xy_report_t x = 0, y = 0;
-    static uint16_t          mouse_timer = 0;
-    static bool              is_z_down   = false;
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+    cursor_glide_t           glide_report = {0};
+
+    if (cursor_glide_enable) {
+        glide_report = cursor_glide_check(&glide);
+    }
+#    endif
 
 #    if !CIRQUE_PINNACLE_POSITION_MODE
 #        error Cirque Pinnacle with relative mode not implemented yet.
 #    endif
 
     if (!touchData.valid) {
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+        if (cursor_glide_enable && glide_report.valid) {
+            report_x = glide_report.dx;
+            report_y = glide_report.dy;
+            goto mouse_report_update;
+        }
+#    endif
         return mouse_report;
     }
 
@@ -130,45 +151,51 @@ report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
     // Scale coordinates to arbitrary X, Y resolution
     cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale());
 
-    if (x && y && touchData.xValue && touchData.yValue) {
-        report_x = (mouse_xy_report_t)(touchData.xValue - x);
-        report_y = (mouse_xy_report_t)(touchData.yValue - y);
-    }
-    x              = touchData.xValue;
-    y              = touchData.yValue;
-    mouse_report.x = report_x;
-    mouse_report.y = report_y;
-
-    if (touchData.touchDown != is_z_down) {
-        is_z_down = touchData.touchDown;
-        if (!touchData.zValue) {
-            if (timer_elapsed(mouse_timer) < CIRQUE_PINNACLE_TAPPING_TERM && mouse_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();
+    if (!cirque_pinnacle_gestures(&mouse_report, touchData)) {
+        if (x && y && touchData.xValue && touchData.yValue) {
+            report_x = (mouse_xy_report_t)(touchData.xValue - x);
+            report_y = (mouse_xy_report_t)(touchData.yValue - y);
+        }
+        x = touchData.xValue;
+        y = touchData.yValue;
+
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+        if (cursor_glide_enable) {
+            if (touchData.touchDown) {
+                cursor_glide_update(&glide, report_x, report_y, touchData.zValue);
+            } else if (!glide_report.valid) {
+                glide_report = cursor_glide_start(&glide);
+                if (glide_report.valid) {
+                    report_x = glide_report.dx;
+                    report_y = glide_report.dy;
+                }
             }
         }
-        mouse_timer = timer_read();
-    }
-    if (timer_elapsed(mouse_timer) > (CIRQUE_PINNACLE_TOUCH_DEBOUNCE)) {
-        mouse_timer = 0;
+#    endif
     }
 
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+mouse_report_update:
+#    endif
+    mouse_report.x = report_x;
+    mouse_report.y = report_y;
+
     return mouse_report;
 }
 
+uint16_t cirque_pinnacle_get_cpi(void) {
+    return CIRQUE_PINNACLE_PX_TO_INCH(cirque_pinnacle_get_scale());
+}
+void cirque_pinnacle_set_cpi(uint16_t cpi) {
+    cirque_pinnacle_set_scale(CIRQUE_PINNACLE_INCH_TO_PX(cpi));
+}
+
 // clang-format off
 const pointing_device_driver_t pointing_device_driver = {
     .init       = cirque_pinnacle_init,
     .get_report = cirque_pinnacle_get_report,
-    .set_cpi    = cirque_pinnacle_set_scale,
-    .get_cpi    = cirque_pinnacle_get_scale
+    .set_cpi    = cirque_pinnacle_set_cpi,
+    .get_cpi    = cirque_pinnacle_get_cpi
 };
 // clang-format on
 

+ 133 - 0
quantum/pointing_device_gestures.c

@@ -0,0 +1,133 @@
+/* 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 <string.h>
+#include "pointing_device_gestures.h"
+#include "timer.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+#    ifdef POINTING_DEVICE_MOTION_PIN
+#        error POINTING_DEVICE_MOTION_PIN not supported when using inertial cursor. Need repeated calls to get_report() to generate glide events.
+#    endif
+
+static void cursor_glide_stop(cursor_glide_context_t* glide) {
+    memset(&glide->status, 0, sizeof(glide->status));
+}
+
+static cursor_glide_t cursor_glide(cursor_glide_context_t* glide) {
+    cursor_glide_status_t* status = &glide->status;
+    cursor_glide_t         report;
+    int32_t                p;
+    int32_t                x, y;
+
+    if (status->v0 == 0) {
+        report.dx    = 0;
+        report.dy    = 0;
+        report.valid = false;
+        cursor_glide_stop(glide);
+        goto exit;
+    }
+
+    status->counter++;
+    /* Calculate current 1D position */
+    p = status->v0 * status->counter - (int32_t)glide->config.coef * status->counter * status->counter / 2;
+    /*
+     * Translate to x & y axes
+     * Done this way instead of applying friction to each axis separately, so we don't end up with the shorter axis stuck at 0 towards the end of diagonal movements.
+     */
+    x            = (int32_t)(p * status->dx0 / status->v0);
+    y            = (int32_t)(p * status->dy0 / status->v0);
+    report.dx    = (mouse_xy_report_t)(x - status->x);
+    report.dy    = (mouse_xy_report_t)(y - status->y);
+    report.valid = true;
+    if (report.dx <= 1 && report.dx >= -1 && report.dy <= 1 && report.dy >= -1) {
+        /* Stop gliding once speed is low enough */
+        cursor_glide_stop(glide);
+        goto exit;
+    }
+    status->x     = x;
+    status->y     = y;
+    status->timer = timer_read();
+
+exit:
+    return report;
+}
+
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide) {
+    cursor_glide_t         invalid_report = {0, 0, false};
+    cursor_glide_status_t* status         = &glide->status;
+
+    if (status->z || (status->dx0 == 0 && status->dy0 == 0) || timer_elapsed(status->timer) < glide->config.interval) {
+        return invalid_report;
+    } else {
+        return cursor_glide(glide);
+    }
+}
+
+static inline uint16_t sqrt32(uint32_t x) {
+    uint32_t l, m, h;
+
+    if (x == 0) {
+        return 0;
+    } else if (x > (UINT16_MAX >> 2)) {
+        /* Safe upper bound to avoid integer overflow with m * m */
+        h = UINT16_MAX;
+    } else {
+        /* Upper bound based on closest log2 */
+        h = (1 << (((__builtin_clzl(1) - __builtin_clzl(x) + 1) + 1) >> 1));
+    }
+    /* Lower bound based on closest log2 */
+    l = (1 << ((__builtin_clzl(1) - __builtin_clzl(x)) >> 1));
+
+    /* Binary search to find integer square root */
+    while (l != h - 1) {
+        m = (l + h) / 2;
+        if (m * m <= x) {
+            l = m;
+        } else {
+            h = m;
+        }
+    }
+    return l;
+}
+
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide) {
+    cursor_glide_t         invalid_report = {0, 0, false};
+    cursor_glide_status_t* status         = &glide->status;
+
+    status->timer   = timer_read();
+    status->counter = 0;
+    status->v0      = (status->dx0 == 0 && status->dy0 == 0) ? 0.0 : sqrt32(((int32_t)status->dx0 * 256 * status->dx0 * 256) + ((int32_t)status->dy0 * 256 * status->dy0 * 256)); // skip trigonometry if not needed, calculate distance in Q8
+    status->x       = 0;
+    status->y       = 0;
+    status->z       = 0;
+
+    if (status->v0 < ((uint32_t)glide->config.trigger_px * 256)) { /* Q8 comparison */
+        /* Not enough velocity to be worth gliding, abort */
+        cursor_glide_stop(glide);
+        return invalid_report;
+    }
+
+    return cursor_glide(glide);
+}
+
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z) {
+    cursor_glide_status_t* status = &glide->status;
+
+    status->dx0 = dx;
+    status->dy0 = dy;
+    status->z   = z;
+}
+#endif

+ 58 - 0
quantum/pointing_device_gestures.h

@@ -0,0 +1,58 @@
+/* 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 <stdint.h>
+#include "report.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+typedef struct {
+    mouse_xy_report_t dx;
+    mouse_xy_report_t dy;
+    bool              valid;
+} cursor_glide_t;
+
+typedef struct {
+    uint16_t trigger_px; /* Pixels of movement needed to trigger cursor glide */
+    uint16_t coef;       /* Coefficient of friction */
+    uint16_t interval;   /* Glide report interval, in milliseconds */
+} cursor_glide_config_t;
+
+typedef struct {
+    int32_t           v0;
+    int32_t           x;
+    int32_t           y;
+    uint16_t          z;
+    uint16_t          timer;
+    uint16_t          counter;
+    mouse_xy_report_t dx0;
+    mouse_xy_report_t dy0;
+} cursor_glide_status_t;
+
+typedef struct {
+    cursor_glide_config_t config;
+    cursor_glide_status_t status;
+} cursor_glide_context_t;
+
+/* Check glide report conditions, calculates glide coordinates */
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide);
+
+/* Start glide reporting, gives first set of glide coordinates */
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide);
+
+/* Update glide engine on the latest cursor movement, cursor glide is based on the final movement */
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z);
+#endif