Skip to content

Commit

Permalink
Cirque trackpad features: circular scroll, inertial cursor (#17482)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkao authored Jul 13, 2022
1 parent 904ec0c commit 5db705d
Show file tree
Hide file tree
Showing 11 changed files with 1,123 additions and 149 deletions.
4 changes: 4 additions & 0 deletions builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 21 additions & 10 deletions docs/feature_pointing_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand Down
191 changes: 90 additions & 101 deletions drivers/sensors/cirque_pinnacle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -106,90 +75,126 @@ 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();
}
}

// 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 */
Expand All @@ -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);
}

Expand All @@ -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();
Expand Down
Loading

0 comments on commit 5db705d

Please sign in to comment.