Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cirque trackpad features: circular scroll, inertial cursor #17482

Merged
merged 27 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
26bbb93
cirque_pinnacle: Add timeout for potential loops
dkao Jun 26, 2022
20f677f
cirque_pinnacle: recalibrate during initialization
dkao Jun 26, 2022
421c2f9
cirque_pinnacle: Function to control smoothing
dkao Jun 26, 2022
0f2d232
cirque_pinnacle: Convert CPI according to diameter
dkao Jun 26, 2022
fbee3bb
cirque pointing driver: Make tap optional
dkao Jun 26, 2022
6e4a63a
cirque pointing driver: Circular scroll feature
dkao Jun 26, 2022
0c35461
cirque pointing driver: Inertial cursor (glide)
dkao Jun 26, 2022
f873816
cirque pointing driver: formatting
dkao Jun 26, 2022
7428dc1
cirque pointing driver: Adjust configuration functions
dkao Jun 26, 2022
43b2f99
cirque_pinnacle: Reword comment on calibration
dkao Jun 26, 2022
4d46e59
cirque_pinnacle: Import register definition header
dkao Jun 29, 2022
f85df63
cirque_pinnacle: Compile option for curved overlay
dkao Jun 29, 2022
8344eaa
cirque_pinnacle: Adjust calibration sequence
dkao Jun 29, 2022
97d1644
cirque pointing driver: Move fancy processing code
dkao Jun 30, 2022
de833e8
cirque pointing driver: Integer math for inch<->pixel conversion
dkao Jun 30, 2022
969e612
pointing_device_gestures: Adjust cursor glide types
dkao Jul 3, 2022
dbe15af
pointing_device_gestures: Q8 fixed point cursor glide
dkao Jul 3, 2022
516677a
cirque_pinnacle_gestures: Integer math circular scroll
dkao Jul 4, 2022
7158ed6
pointing device gestures: Update comments
dkao Jul 4, 2022
f08d5e7
pointing device gestures: div0 checks
dkao Jul 4, 2022
3ff32b0
pointing device gestures: Extract configurations to explicit struct
dkao Jul 4, 2022
60da778
circular scroll: Add left-handed mode
dkao Jul 5, 2022
7bf6c68
pointing device gestures: Conditional compilation
dkao Jul 9, 2022
064d9e4
cirque pointing driver: Add a default triggir_px for inertial cursor
dkao Jul 9, 2022
e48a3d1
pointing device gestures: Style
dkao Jul 9, 2022
18aa3a1
circular scroll: Use stdlib abs()
dkao Jul 9, 2022
8bd6c92
cirque_pinnacle: Minor change to read-modify-write variable names
dkao Jul 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/feature_pointing_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ This supports the Cirque Pinnacle 1CA027 Touch Controller, which is used in the
|`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_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` |

Expand Down
183 changes: 86 additions & 97 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
RAP_ReadBytes(HOSTREG__FEEDCONFIG1, &temp, 1);

if (feedEnable) {
temp |= 0x01; // Set Feed Enable bit
temp |= HOSTREG__FEEDCONFIG1__FEED_ENABLE;
} else {
temp &= ~0x01; // Clear Feed Enable bit
temp &= ~HOSTREG__FEEDCONFIG1__FEED_ENABLE;
}
RAP_Write(FEEDCONFIG_1, temp);
RAP_Write(HOSTREG__FEEDCONFIG1, temp);
}

/* 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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least on I2C we could use && touchpad_init==true directly, as this will be set to false if there is any I2C error (or timeout). Not sure if SPI considers CIRQUE_PINNACLE_TIMEOUT at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPI doesn't have ack or check for any timeout. It will return whatever state MISO happens to be floating in, if the device is not connected. That could be either 0xFF or 0x00 (or something inbetween if there's noise on the line?).
Looking at AVR's spi_master.c for example: spi_start() always returns true, except if chip select pin is invalid but that's an error before ever interacting with the device. So touch_init check can't really apply for SPI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually fixed a dead lock for me that happened with the code before. I started a small refactoring of the RAP_Read/Write functions. If you find it useful feel free to include it in this pr cirque_timeout.patch.txt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, if a single I2C transfer fails the whole driver will turn into an invalid state - which requires a reboot of the keyboard. I can think of mechanism on the pointing device driver level that will try to re-init a driver in such a case after a certain timeout. But this is not in the scope of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think handling touch_init and I2C transaction failures adds too much to this PR, let's handle that in a separate one.


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;

ERA_ReadBytes(0x0187, &temp, 1);
temp &= 0x3F; // clear top two bits
ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &temp, 1);
temp &= EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK;
temp |= adcGain;
ERA_WriteByte(0x0187, temp);
ERA_ReadBytes(0x0187, &temp, 1);
ERA_WriteByte(EXTREG__TRACK_ADCCONFIG, temp);
ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &temp, 1);
}

// Changes thresholds to improve detection of fingers
// Not needed for flat overlay?
void cirque_pinnacle_tune_edge_sensitivity(void) {
uint8_t temp = 0x00;

ERA_ReadBytes(0x0149, &temp, 1);
ERA_WriteByte(0x0149, 0x04);
ERA_ReadBytes(0x0149, &temp, 1);
ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &temp, 1);
ERA_WriteByte(EXTREG__XAXIS_WIDEZMIN, 0x04); // magic number from Cirque sample code
ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &temp, 1);

ERA_ReadBytes(0x0168, &temp, 1);
ERA_WriteByte(0x0168, 0x03);
ERA_ReadBytes(0x0168, &temp, 1);
ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &temp, 1);
ERA_WriteByte(EXTREG__YAXIS_WIDEZMIN, 0x03); // magic number from Cirque sample code
ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &temp, 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
10 changes: 9 additions & 1 deletion drivers/sensors/cirque_pinnacle.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#pragma once

#include "cirque_pinnacle_regdefs.h"
#include <stdint.h>
#include <stdbool.h>

Expand All @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -84,6 +90,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);
Expand Down
Loading