diff options
Diffstat (limited to 'drivers/sensors')
-rw-r--r-- | drivers/sensors/cirque_pinnacle.c | 289 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle.h | 105 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle_gestures.c | 238 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle_gestures.h | 110 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle_i2c.c | 4 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle_regdefs.h | 405 | ||||
-rw-r--r-- | drivers/sensors/cirque_pinnacle_spi.c | 4 | ||||
-rw-r--r-- | drivers/sensors/paw3204.c | 172 | ||||
-rw-r--r-- | drivers/sensors/paw3204.h | 68 | ||||
-rw-r--r-- | drivers/sensors/pimoroni_trackball.c | 13 | ||||
-rw-r--r-- | drivers/sensors/pimoroni_trackball.h | 1 | ||||
-rw-r--r-- | drivers/sensors/pmw3360.c | 567 | ||||
-rw-r--r-- | drivers/sensors/pmw3360.h | 134 | ||||
-rw-r--r-- | drivers/sensors/pmw3360_firmware.h | 288 | ||||
-rw-r--r-- | drivers/sensors/pmw3389.c | 580 | ||||
-rw-r--r-- | drivers/sensors/pmw3389.h | 135 | ||||
-rw-r--r-- | drivers/sensors/pmw3389_firmware.h | 307 | ||||
-rw-r--r-- | drivers/sensors/pmw33xx_common.c | 219 | ||||
-rw-r--r-- | drivers/sensors/pmw33xx_common.h | 150 |
19 files changed, 2347 insertions, 1442 deletions
diff --git a/drivers/sensors/cirque_pinnacle.c b/drivers/sensors/cirque_pinnacle.c index 2db7f916fe..8bd4eb736e 100644 --- a/drivers/sensors/cirque_pinnacle.c +++ b/drivers/sensors/cirque_pinnacle.c @@ -1,52 +1,26 @@ // 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/Circular_Trackpad +// with modifications and changes for QMK +// refer to documentation: Gen2 and Gen3 (Pinnacle ASIC) at https://www.cirque.com/documentation + #include "cirque_pinnacle.h" #include "print.h" #include "debug.h" #include "wait.h" +#include "timer.h" + +#include <stdlib.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 - -// Register config values for this demo -#define SYSCONFIG_1_VALUE 0x00 -#define FEEDCONFIG_1_VALUE 0x03 // 0x03 for absolute mode 0x01 for relative mode -#define FEEDCONFIG_2_VALUE 0x1C // 0x1F for normal functionality 0x1E for intellimouse disabled -#define Z_IDLE_COUNT_VALUE 0x05 -// clang-format on +#ifndef CIRQUE_PINNACLE_ATTENUATION +# 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 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); @@ -59,6 +33,7 @@ void print_byte(uint8_t byte) { } #endif +#if CIRQUE_PINNACLE_POSITION_MODE /* Logical Scaling Functions */ // Clips raw coordinates to "reachable" window of sensor // NOTE: values outside this window can only appear as a result of noise @@ -74,6 +49,7 @@ void ClipCoordinates(pinnacle_data_t* coordinates) { coordinates->yValue = CIRQUE_PINNACLE_Y_UPPER; } } +#endif uint16_t cirque_pinnacle_get_scale(void) { return scale_data; @@ -84,6 +60,7 @@ void cirque_pinnacle_set_scale(uint16_t scale) { // Scales data to desired X & Y resolution void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution) { +#if CIRQUE_PINNACLE_POSITION_MODE uint32_t xTemp = 0; uint32_t yTemp = 0; @@ -99,49 +76,65 @@ void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResoluti // scale coordinates to (xResolution, yResolution) range coordinates->xValue = (uint16_t)(xTemp * xResolution / CIRQUE_PINNACLE_X_RANGE); coordinates->yValue = (uint16_t)(yTemp * yResolution / CIRQUE_PINNACLE_Y_RANGE); +#else + int32_t xTemp = 0, yTemp = 0; + ldiv_t temp; + static int32_t xRemainder, yRemainder; + + temp = ldiv(((int32_t)coordinates->xDelta) * (int32_t)xResolution + xRemainder, (int32_t)CIRQUE_PINNACLE_X_RANGE); + xTemp = temp.quot; + xRemainder = temp.rem; + + temp = ldiv(((int32_t)coordinates->yDelta) * (int32_t)yResolution + yRemainder, (int32_t)CIRQUE_PINNACLE_Y_RANGE); + yTemp = temp.quot; + yRemainder = temp.rem; + + coordinates->xDelta = (int16_t)xTemp; + coordinates->yDelta = (int16_t)yTemp; +#endif } // 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 - RAP_Write(0x04, temp); + feedconfig1 |= HOSTREG__FEEDCONFIG1__FEED_ENABLE; } else { - temp &= ~0x01; // Clear Feed Enable bit - RAP_Write(0x04, temp); + feedconfig1 &= ~HOSTREG__FEEDCONFIG1__FEED_ENABLE; } + 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(); } @@ -149,49 +142,89 @@ 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; +bool cirque_pinnacle_set_adc_attenuation(uint8_t adcGain) { + uint8_t adcconfig = 0x00; + + ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1); + adcGain &= EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK; + if (adcGain == (adcconfig & EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK)) { + return false; + } + adcconfig &= ~EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK; + adcconfig |= adcGain; + ERA_WriteByte(EXTREG__TRACK_ADCCONFIG, adcconfig); + ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1); - ERA_ReadBytes(0x0187, &temp, 1); - temp &= 0x3F; // clear top two bits - temp |= adcGain; - ERA_WriteByte(0x0187, temp); - ERA_ReadBytes(0x0187, &temp, 1); + return true; } // 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(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(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(); +} - ERA_ReadBytes(0x0149, &temp, 1); - ERA_WriteByte(0x0149, 0x04); - ERA_ReadBytes(0x0149, &temp, 1); +// Enable/disable cursor smoothing, smoothing is enabled by default +void cirque_pinnacle_cursor_smoothing(bool enable) { + uint8_t feedconfig3; - ERA_ReadBytes(0x0168, &temp, 1); - ERA_WriteByte(0x0168, 0x03); - ERA_ReadBytes(0x0168, &temp, 1); + 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 Functions */ +/* Pinnacle-based TM040040/TM035035/TM023023 Functions */ void cirque_pinnacle_init(void) { #if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi) spi_init(); @@ -200,39 +233,101 @@ void cirque_pinnacle_init(void) { #endif touchpad_init = true; + + // send a RESET command now, in case QMK had a soft-reset without a power cycle + RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1__RESET); + wait_ms(30); // Pinnacle needs 10-15ms to boot, so wait long enough before configuring + RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1_DEFVAL); + wait_us(50); + // Host clears SW_CC flag cirque_pinnacle_clear_flags(); - // Host configures bits of registers 0x03 and 0x05 - RAP_Write(SYSCONFIG_1, SYSCONFIG_1_VALUE); - RAP_Write(FEEDCONFIG_2, FEEDCONFIG_2_VALUE); +#if CIRQUE_PINNACLE_POSITION_MODE + RAP_Write(HOSTREG__FEEDCONFIG2, HOSTREG__FEEDCONFIG2_DEFVAL); +#else + // FeedConfig2 (Feature flags for Relative Mode Only) + uint8_t feedconfig2 = HOSTREG__FEEDCONFIG2__GLIDE_EXTEND_DISABLE | HOSTREG__FEEDCONFIG2__INTELLIMOUSE_MODE; +# if !defined(CIRQUE_PINNACLE_TAP_ENABLE) + feedconfig2 |= HOSTREG__FEEDCONFIG2__ALL_TAP_DISABLE; +# endif +# if !defined(CIRQUE_PINNACLE_SECONDARY_TAP_ENABLE) + feedconfig2 |= HOSTREG__FEEDCONFIG2__SECONDARY_TAP_DISABLE; +# elif !defined(CIRQUE_PINNACLE_TAP_ENABLE) +# error CIRQUE_PINNACLE_TAP_ENABLE must be defined for CIRQUE_PINNACLE_SECONDARY_TAP_ENABLE to work +# endif +# if !defined(CIRQUE_PINNACLE_SIDE_SCROLL_ENABLE) + feedconfig2 |= HOSTREG__FEEDCONFIG2__SCROLL_DISABLE; +# endif + RAP_Write(HOSTREG__FEEDCONFIG2, feedconfig2); +#endif - // Host enables preferred output mode (absolute) - RAP_Write(FEEDCONFIG_1, FEEDCONFIG_1_VALUE); + // FeedConfig1 (Data Output Flags) + RAP_Write(HOSTREG__FEEDCONFIG1, CIRQUE_PINNACLE_POSITION_MODE ? HOSTREG__FEEDCONFIG1__DATA_TYPE__REL0_ABS1 : HOSTREG__FEEDCONFIG1_DEFVAL); - // Host sets z-idle packet count to 5 (default is 30) - RAP_Write(Z_IDLE_COUNT, Z_IDLE_COUNT_VALUE); +#if CIRQUE_PINNACLE_POSITION_MODE + // Host sets z-idle packet count to 5 (default is 0x1E/30) + RAP_Write(HOSTREG__ZIDLE, 5); +#endif - cirque_pinnacle_set_adc_attenuation(0xFF); + bool calibrate = cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION); + +#ifdef CIRQUE_PINNACLE_CURVED_OVERLAY cirque_pinnacle_tune_edge_sensitivity(); + calibrate = true; +#endif + if (calibrate) { + // Force a calibration after setting ADC attenuation + cirque_pinnacle_calibrate(); + } + cirque_pinnacle_enable_feed(true); } -// Reads XYZ data from Pinnacle registers 0x14 through 0x17 -// Stores result in pinnacle_data_t struct with xValue, yValue, and zValue members pinnacle_data_t cirque_pinnacle_read_data(void) { - uint8_t data[6] = {0}; - pinnacle_data_t result = {0}; - RAP_ReadBytes(PACKET_BYTE_0, data, 6); + uint8_t data_ready = 0; + uint8_t data[6] = {0}; + pinnacle_data_t result = {0}; + + // Check if there is valid data available + 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; + } - cirque_pinnacle_clear_flags(); + // Read all data bytes + RAP_ReadBytes(HOSTREG__PACKETBYTE_0, data, 6); - result.buttonFlags = data[0] & 0x3F; - result.xValue = data[2] | ((data[4] & 0x0F) << 8); - result.yValue = data[3] | ((data[4] & 0xF0) << 4); - result.zValue = data[5] & 0x3F; + // Get ready for the next data sample + cirque_pinnacle_clear_flags(); - result.touchDown = (result.xValue != 0 || result.yValue != 0); +#if CIRQUE_PINNACLE_POSITION_MODE + // Decode data for absolute mode + // Register 0x13 is unused in this mode (palm detection area) + result.buttonFlags = data[0] & 0x3F; // bit0 to bit5 are switch 0-5, only hardware button presses (from input pin on the Pinnacle chip) + result.xValue = data[2] | ((data[4] & 0x0F) << 8); // merge high and low bits for X + result.yValue = data[3] | ((data[4] & 0xF0) << 4); // merge high and low bits for Y + result.zValue = data[5] & 0x3F; // Z is only lower 6 bits, upper 2 bits are reserved/unused + result.touchDown = (result.xValue != 0 || result.yValue != 0); // (0,0) is a "magic coordinate" to indicate "finger touched down" +#else + // Decode data for relative mode + // Registers 0x16 and 0x17 are unused in this mode + result.buttons = data[0] & 0x07; // Only three buttons are supported + if ((data[0] & 0x10) && data[1] != 0) { + result.xDelta = -((int16_t)256 - (int16_t)(data[1])); + } else { + result.xDelta = data[1]; + } + if ((data[0] & 0x20) && data[2] != 0) { + result.yDelta = ((int16_t)256 - (int16_t)(data[2])); + } else { + result.yDelta = -((int16_t)data[2]); + } + result.wheelCount = ((int8_t*)data)[3]; +#endif + result.valid = true; return result; } diff --git a/drivers/sensors/cirque_pinnacle.h b/drivers/sensors/cirque_pinnacle.h index c8cb360e03..320e7d936e 100644 --- a/drivers/sensors/cirque_pinnacle.h +++ b/drivers/sensors/cirque_pinnacle.h @@ -2,53 +2,63 @@ #pragma once +#include "cirque_pinnacle_regdefs.h" #include <stdint.h> #include <stdbool.h> -// Convenient way to store and access measurements -typedef struct { - uint16_t xValue; - uint16_t yValue; - uint16_t zValue; - uint8_t buttonFlags; - bool touchDown; -} pinnacle_data_t; - -void cirque_pinnacle_init(void); -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); -void cirque_pinnacle_set_scale(uint16_t scale); - #ifndef CIRQUE_PINNACLE_TIMEOUT -# define CIRQUE_PINNACLE_TIMEOUT 20 +# define CIRQUE_PINNACLE_TIMEOUT 20 // I2C timeout in milliseconds #endif -// Coordinate scaling values -#ifndef CIRQUE_PINNACLE_X_LOWER -# define CIRQUE_PINNACLE_X_LOWER 127 // min "reachable" X value -#endif -#ifndef CIRQUE_PINNACLE_X_UPPER -# define CIRQUE_PINNACLE_X_UPPER 1919 // max "reachable" X value -#endif -#ifndef CIRQUE_PINNACLE_Y_LOWER -# define CIRQUE_PINNACLE_Y_LOWER 63 // min "reachable" Y value +#define CIRQUE_PINNACLE_ABSOLUTE_MODE 1 +#define CIRQUE_PINNACLE_RELATIVE_MODE 0 +#ifndef CIRQUE_PINNACLE_POSITION_MODE +# define CIRQUE_PINNACLE_POSITION_MODE CIRQUE_PINNACLE_ABSOLUTE_MODE #endif -#ifndef CIRQUE_PINNACLE_Y_UPPER -# define CIRQUE_PINNACLE_Y_UPPER 1471 // max "reachable" Y value + +#define CIRQUE_PINNACLE_DEFAULT_SCALE 1024 +#ifndef CIRQUE_PINNACLE_DIAMETER_MM +# define CIRQUE_PINNACLE_DIAMETER_MM 40 #endif -#ifndef CIRQUE_PINNACLE_X_RANGE -# define CIRQUE_PINNACLE_X_RANGE (CIRQUE_PINNACLE_X_UPPER - CIRQUE_PINNACLE_X_LOWER) + +#if CIRQUE_PINNACLE_POSITION_MODE +// Coordinate scaling values +# ifndef CIRQUE_PINNACLE_X_LOWER +# define CIRQUE_PINNACLE_X_LOWER 127 // min "reachable" X value +# endif +# ifndef CIRQUE_PINNACLE_X_UPPER +# define CIRQUE_PINNACLE_X_UPPER 1919 // max "reachable" X value +# endif +# ifndef CIRQUE_PINNACLE_Y_LOWER +# define CIRQUE_PINNACLE_Y_LOWER 63 // min "reachable" Y value +# endif +# ifndef CIRQUE_PINNACLE_Y_UPPER +# define CIRQUE_PINNACLE_Y_UPPER 1471 // max "reachable" Y value +# endif +# ifndef CIRQUE_PINNACLE_X_RANGE +# define CIRQUE_PINNACLE_X_RANGE (CIRQUE_PINNACLE_X_UPPER - CIRQUE_PINNACLE_X_LOWER) +# endif +# ifndef CIRQUE_PINNACLE_Y_RANGE +# define CIRQUE_PINNACLE_Y_RANGE (CIRQUE_PINNACLE_Y_UPPER - CIRQUE_PINNACLE_Y_LOWER) +# endif +# if defined(POINTING_DEVICE_GESTURES_SCROLL_ENABLE) +# define CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE +# endif +#else +# define CIRQUE_PINNACLE_X_RANGE 256 +# define CIRQUE_PINNACLE_Y_RANGE 256 +# if defined(POINTING_DEVICE_GESTURES_SCROLL_ENABLE) +# define CIRQUE_PINNACLE_SIDE_SCROLL_ENABLE +# endif #endif -#ifndef CIRQUE_PINNACLE_Y_RANGE -# define CIRQUE_PINNACLE_Y_RANGE (CIRQUE_PINNACLE_Y_UPPER - CIRQUE_PINNACLE_Y_LOWER) +#if !defined(POINTING_DEVICE_TASK_THROTTLE_MS) +# define POINTING_DEVICE_TASK_THROTTLE_MS 10 // Cirque Pinnacle in normal operation produces data every 10ms. Advanced configuration for pen/stylus usage might require lower values. #endif - #if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) # 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" @@ -72,3 +82,32 @@ void cirque_pinnacle_set_scale(uint16_t scale); # endif # 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 +#if CIRQUE_PINNACLE_POSITION_MODE + uint16_t xValue; + uint16_t yValue; + uint16_t zValue; + uint8_t buttonFlags; + bool touchDown; +#else + int16_t xDelta; + int16_t yDelta; + int8_t wheelCount; + uint8_t buttons; +#endif +} 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); +void cirque_pinnacle_set_scale(uint16_t scale); diff --git a/drivers/sensors/cirque_pinnacle_gestures.c b/drivers/sensors/cirque_pinnacle_gestures.c new file mode 100644 index 0000000000..a73b745e59 --- /dev/null +++ b/drivers/sensors/cirque_pinnacle_gestures.c @@ -0,0 +1,238 @@ +/* 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" +#include "wait.h" +#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED) +# include "keyboard.h" +#endif + +#if (defined(CIRQUE_PINNACLE_TAP_ENABLE) || defined(CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE)) && CIRQUE_PINNACLE_POSITION_MODE +static cirque_pinnacle_features_t features = {.tap_enable = true, .circular_scroll_enable = true}; +#endif + +#if defined(CIRQUE_PINNACLE_TAP_ENABLE) && CIRQUE_PINNACLE_POSITION_MODE +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 +# if !CIRQUE_PINNACLE_POSITION_MODE +# error "Circular scroll is not supported in relative mode" +# endif +/* 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 = INT8_MAX, 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 +/-INT8_MAX; angles are independent of resolution. + */ + if (scale) { + /* Rotate coordinates into a consistent orientation */ + report_mouse_t rot = {.x = (int8_t)((int32_t)touchData.xValue * INT8_MAX * 2 / scale - center), .y = (int8_t)((int32_t)touchData.yValue * INT8_MAX * 2 / scale - center)}; +# if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED) + if (!is_keyboard_left()) { + rot = pointing_device_adjust_by_defines_right(rot); + } else +# endif + { + rot = pointing_device_adjust_by_defines(rot); + } + x = rot.x; + y = rot.y; + } 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 + */ + scroll.axis = x < 0; + } + } 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 +# if !CIRQUE_PINNACLE_POSITION_MODE +# error "Circular scroll is not supported in relative mode" +# endif + 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 + +#if defined(CIRQUE_PINNACLE_TAP_ENABLE) && CIRQUE_PINNACLE_POSITION_MODE + 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..d2aa206b2b --- /dev/null +++ b/drivers/sensors/cirque_pinnacle_gestures.h @@ -0,0 +1,110 @@ +/* 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; |