diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 86182c1ce0..2b9e29333a 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -163,6 +163,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), navigator_trackpad) I2C_DRIVER_REQUIRED = yes SRC += drivers/sensors/navigator.c + SRC += drivers/sensors/navigator_trackpad_common.c + SRC += drivers/sensors/navigator_trackpad.c + SRC += drivers/sensors/navigator_trackpad_mouse.c + # Define PTP mode for mouse mode (uses absolute coordinates internally) + OPT_DEFS += -DNAVIGATOR_TRACKPAD_PTP_MODE else ifneq ($(filter $(strip $(POINTING_DEVICE_DRIVER)),pmw3360 pmw3389),) SPI_DRIVER_REQUIRED = yes SRC += drivers/sensors/pmw33xx_common.c @@ -171,9 +176,35 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) endif PRECISION_TRACKPAD_ENABLE ?= no + +# Valid precision trackpad driver types +VALID_PRECISION_TRACKPAD_DRIVER_TYPES := navigator_trackpad custom + ifeq ($(strip $(PRECISION_TRACKPAD_ENABLE)), yes) - OPT_DEFS += -DPRECISION_TRACKPAD_ENABLE - SRC += $(QUANTUM_DIR)/precision_trackpad.c + # Validate driver type + ifeq ($(filter $(PRECISION_TRACKPAD_DRIVER),$(VALID_PRECISION_TRACKPAD_DRIVER_TYPES)),) + $(call CATASTROPHIC_ERROR,Invalid PRECISION_TRACKPAD_DRIVER,\ + PRECISION_TRACKPAD_DRIVER="$(PRECISION_TRACKPAD_DRIVER)" is not a valid PTP driver) + else + OPT_DEFS += -DPRECISION_TRACKPAD_ENABLE + SRC += $(QUANTUM_DIR)/precision_trackpad.c + + # Include driver source (unless custom) + ifneq ($(strip $(PRECISION_TRACKPAD_DRIVER)), custom) + # Add common code + SRC += drivers/sensors/navigator_trackpad_common.c + SRC += drivers/sensors/navigator_trackpad_ptp.c + SRC += drivers/sensors/navigator.c + I2C_DRIVER_REQUIRED = yes + + # Define PTP mode for precision trackpad + OPT_DEFS += -DNAVIGATOR_TRACKPAD_PTP_MODE + + # Set driver name macro (used by PRECISION_TRACKPAD_DRIVER macro) + OPT_DEFS += -DPRECISION_TRACKPAD_DRIVER_NAME=$(strip $(PRECISION_TRACKPAD_DRIVER)) + OPT_DEFS += -DPRECISION_TRACKPAD_DRIVER_$(strip $(shell echo $(PRECISION_TRACKPAD_DRIVER) | tr '[:lower:]' '[:upper:]')) + endif + endif endif QUANTUM_PAINTER_ENABLE ?= no diff --git a/drivers/sensors/navigator_trackpad.c b/drivers/sensors/navigator_trackpad.c index b068bdc621..fb089cb6da 100644 --- a/drivers/sensors/navigator_trackpad.c +++ b/drivers/sensors/navigator_trackpad.c @@ -1,888 +1,14 @@ // Copyright 2025 ZSA Technology Labs, Inc // SPDX-License-Identifier: GPL-2.0-or-later -// THIS IS A WORK IN PROGRESS, AS THE TRACKPAD IC's FIRMWARE IS STILL IN DEVELOPMENT -// DO NOT USE THIS CODE IN PRODUCTION +// Dispatcher/compatibility layer for Navigator trackpad +// Provides backward compatibility for existing code -#include -#include -#include #include "navigator_trackpad.h" -#include "i2c_master.h" -#include "quantum.h" -#include "timer.h" +#include "navigator_trackpad_common.h" - -#ifdef PROTOCOL_LUFA -# error "LUFA is not supported yet" -#endif - -#ifdef POINTING_DEVICE_ENABLE -const pointing_device_driver_t navigator_trackpad_pointing_device_driver = {.init = navigator_trackpad_device_init, .get_report = navigator_trackpad_get_report, .get_cpi = navigator_trackpad_get_cpi, .set_cpi = navigator_trackpad_set_cpi}; -#endif - -deferred_token callback_token = 0; -uint16_t current_cpi = DEFAULT_CPI_TICK; -uint8_t has_motion = 0; -extern bool set_scrolling; -bool trackpad_init; - -#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING -float macos_scroll_accumulated_h = 0; -float macos_scroll_accumulated_v = 0; -#endif - -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) -cgen6_report_t ptp_report; -trackpad_gesture_t gesture = {0}; -# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE -scroll_inertia_t scroll_inertia = {0}; -# endif -#endif - -// Helper functions to parse ptp reports -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) -uint8_t finger_count(cgen6_report_t *report) { - uint8_t fingers = 0; - if (report->fingers[0].tip) { - fingers++; - } - if (report->fingers[1].tip) { - fingers++; - } - - return fingers; -} -#endif - -i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt) { - i2c_status_t res = i2c_receive(NAVIGATOR_TRACKPAD_ADDRESS, data, cnt, NAVIGATOR_TRACKPAD_TIMEOUT); - if (res != I2C_STATUS_SUCCESS) { - return res; - } - wait_us(cnt * 15); - return res; -} - -void cirque_gen6_clear(void) { - uint8_t buf[CGEN6_MAX_PACKET_SIZE]; - for (uint8_t i = 0; i < 5; i++) { - wait_ms(1); - if (cirque_gen6_read_report(buf, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) { - break; - } - } -} - -uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read) { - uint8_t cksum = 0; - uint8_t res = CGEN6_SUCCESS; - uint8_t len[2]; - uint16_t read = 0; - - uint8_t preamble[8] = {0x01, 0x09, (uint8_t)(addr & (uint32_t)0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)}; - - // Read the length of the data + 3 bytes (first 2 bytes for the length and the last byte for the checksum) - // Create a buffer to store the data - uint8_t buf[cnt + 3]; - if (i2c_transmit_and_receive(NAVIGATOR_TRACKPAD_ADDRESS, preamble, 8, buf, cnt + 3, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) { - res |= CGEN6_I2C_FAILED; - trackpad_init = false; - } - - // Read the data length - for (uint8_t i = 0; i < 2; i++) { - cksum += len[i] = buf[i]; - read++; - } - - // Populate the data buffer - for (uint16_t i = 2; i < cnt + 2; i++) { - cksum += data[i - 2] = buf[i]; - read++; - } - - if (!fast_read) { - // Check the checksum - if (cksum != buf[read]) { - res |= CGEN6_CKSUM_FAILED; - } - - // Check the length (incremented first to account for the checksum) - if (++read != (len[0] | (len[1] << 8))) { - res |= CGEN6_LEN_MISMATCH; - } - - wait_ms(1); - } else { - wait_us(250); - } - - return res; -} - -uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt) { - uint8_t res = CGEN6_SUCCESS; - uint8_t cksum = 0, i = 0; - uint8_t preamble[8] = {0x00, 0x09, (uint8_t)(addr & 0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)}; - - uint8_t buf[cnt + 9]; - // Calculate the checksum - for (; i < 8; i++) { - cksum += buf[i] = preamble[i]; - } - - for (i = 0; i < cnt; i++) { - cksum += buf[i + 8] = data[i]; - } - - buf[cnt + 8] = cksum; - - if (i2c_transmit(NAVIGATOR_TRACKPAD_ADDRESS, buf, cnt + 9, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) { - res |= CGEN6_I2C_FAILED; - trackpad_init = false; - } - - wait_ms(1); - - return res; -} - -uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read) { - uint8_t data; - uint8_t res = cirque_gen6_read_memory(addr, &data, 1, fast_read); - if (res != CGEN6_SUCCESS) { - printf("Failed to read 8bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); - return 0; - } - return data; -} - -uint16_t cirque_gen6_read_reg_16(uint32_t addr) { - uint8_t buf[2]; - uint8_t res = cirque_gen6_read_memory(addr, buf, 2, false); - if (res != CGEN6_SUCCESS) { - printf("Failed to read 16bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); - return 0; - } - return (buf[1] << 8) | buf[0]; -} - -uint32_t cirque_gen6_read_reg_32(uint32_t addr) { - uint8_t buf[4]; - uint8_t res = cirque_gen6_read_memory(addr, buf, 4, false); - if (res != CGEN6_SUCCESS) { - printf("Failed to read 32bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); - return 0; - } - return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; -} - -uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data) { - return cirque_gen6_write_memory(addr, &data, 1); -} - -uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data) { - uint8_t buf[2] = {data & 0xFF, (data >> 8) & 0xFF}; - return cirque_gen6_write_memory(addr, buf, 2); -} - -uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data) { - uint8_t buf[4] = {data & 0xFF, (data >> 8) & 0xFF, (data >> 16) & 0xFF, (data >> 24) & 0xFF}; - return cirque_gen6_write_memory(addr, buf, 4); -} - -uint8_t cirque_gen6_set_relative_mode(void) { - uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false); - feed_config4 &= 0xF3; - return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4); -} - -uint8_t cirque_gen6_set_ptp_mode(void) { - uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false); - feed_config4 &= 0xF7; - feed_config4 |= 0x04; - return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4); -} - -uint8_t cirque_gen6_swap_xy(bool set) { - uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); - if (set) { - xy_config |= 0x04; - } else { - xy_config &= ~0x04; - } - return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); -} - -uint8_t cirque_gen6_invert_y(bool set) { - uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); - if (set) { - xy_config |= 0x02; - } else { - xy_config &= ~0x02; - } - return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); -} - -uint8_t cirque_gen6_invert_x(bool set) { - uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); - if (set) { - xy_config |= 0x01; - } else { - xy_config &= ~0x01; - } - return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); -} - -uint8_t cirque_gen6_enable_logical_scaling(bool set) { - uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); - if (set) { - xy_config &= ~0x08; - } else { - xy_config |= 0x08; - } - return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); -} - -void cirque_gen_6_read_report(void) { - uint8_t packet[CGEN6_MAX_PACKET_SIZE]; - if (cirque_gen6_read_report(packet, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) { - return; - } - - uint8_t report_id = packet[2]; - -#ifdef CONSOLE_ENABLE - static uint16_t report_id_counter = 0; - if (++report_id_counter % 100 == 0) { - printf("Cirque report ID: 0x%02x (expecting 0x01 for PTP)\n", report_id); - } -#endif - -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) - if (report_id == CGEN6_PTP_REPORT_ID) { - ptp_report.fingers[0].tip = (packet[3] & 0x02) >> 1; - ptp_report.fingers[0].x = packet[5] << 8 | packet[4]; - ptp_report.fingers[0].y = packet[7] << 8 | packet[6]; - ptp_report.fingers[1].tip = (packet[8] & 0x02) >> 1; - ptp_report.fingers[1].x = packet[10] << 8 | packet[9]; - ptp_report.fingers[1].y = packet[12] << 8 | packet[11]; - ptp_report.buttons = packet[16]; - } -#endif -#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) - if (report_id == CGEN6_MOUSE_REPORT_ID) { - ptp_report.buttons = packet[3]; - ptp_report.xDelta = packet[4]; - ptp_report.yDelta = packet[5]; - ptp_report.scrollDelta = packet[6]; - ptp_report.panDelta = packet[7]; - - has_motion = 1; - } -#endif -} - -// Check if the DR pin is asserted, if it is there is motion data to sample. -uint8_t cirque_gen6_has_motion(void) { - return cirque_gen6_read_reg(CGEN6_I2C_DR, true); -} - -uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg) { - if (!trackpad_init) { - navigator_trackpad_device_init(); - return NAVIGATOR_TRACKPAD_PROBE; - } - if (cirque_gen6_has_motion()) { - has_motion = 1; - cirque_gen_6_read_report(); - } - return NAVIGATOR_TRACKPAD_READ; -} - -void navigator_trackpad_device_init(void) { - i2c_init(); - i2c_status_t status = i2c_ping_address(NAVIGATOR_TRACKPAD_ADDRESS, NAVIGATOR_TRACKPAD_TIMEOUT); - if (status != I2C_STATUS_SUCCESS) { - trackpad_init = false; - return; - } - cirque_gen6_clear(); - wait_ms(50); - - uint8_t res = CGEN6_SUCCESS; -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) - res = cirque_gen6_set_ptp_mode(); -#elif defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) - res = cirque_gen6_set_relative_mode(); -#endif - - if (res != CGEN6_SUCCESS) { - return; - } - - // Reset to the default alignment - cirque_gen6_swap_xy(false); - cirque_gen6_invert_x(false); - cirque_gen6_invert_y(false); - cirque_gen6_swap_xy(true); - cirque_gen6_invert_x(true); - cirque_gen6_invert_y(true); - cirque_gen6_enable_logical_scaling(true); - - trackpad_init = true; - // Only register the callback for the first time - if (!callback_token) { - callback_token = defer_exec(NAVIGATOR_TRACKPAD_READ, cirque_gen6_read_callback, NULL); - } -} - -#ifdef POINTING_DEVICE_ENABLE -report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report) { #ifdef PRECISION_TRACKPAD_ENABLE - // When PTP is enabled, don't send mouse reports - PTP task handles everything - return mouse_report; + #include "navigator_trackpad_ptp.h" #else - // Mouse mode - process gestures and send mouse reports -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) - // Handle pending click release from previous cycle - if (gesture.pending_click) { - gesture.pending_click = false; - mouse_report.buttons = 0; - return mouse_report; - } - -# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE - // Process scroll inertia when active - if (scroll_inertia.active && timer_elapsed(scroll_inertia.timer) >= NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL) { - scroll_inertia.timer = timer_read(); - - // Apply friction to velocity (Q8 fixed point math) - // Friction reduces velocity towards zero - int16_t friction_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256; - int16_t friction_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256; - - // Ensure we always reduce by at least 1 if not zero - if (scroll_inertia.vx > 0 && friction_x < 1) friction_x = 1; - if (scroll_inertia.vx < 0 && friction_x > -1) friction_x = -1; - if (scroll_inertia.vy > 0 && friction_y < 1) friction_y = 1; - if (scroll_inertia.vy < 0 && friction_y > -1) friction_y = -1; - - scroll_inertia.vx -= friction_x; - scroll_inertia.vy -= friction_y; - - // Convert Q8 velocity to scroll value -# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - // macOS mode: send raw velocity deltas (descriptor tells macOS the resolution) - int16_t scroll_x = scroll_inertia.vx / 256; - int16_t scroll_y = scroll_inertia.vy / 256; -# else - // Hi-res mode: apply multiplier for Windows/Linux - int16_t scroll_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256; - int16_t scroll_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256; -# endif - - // Clamp to int8_t range - scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x); - scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y); - - // Check if velocity is too low to continue - int16_t abs_vx = scroll_inertia.vx < 0 ? -scroll_inertia.vx : scroll_inertia.vx; - int16_t abs_vy = scroll_inertia.vy < 0 ? -scroll_inertia.vy : scroll_inertia.vy; - -# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - // In macOS mode, track consecutive frames with no output to detect janky tail - if (scroll_x == 0 && scroll_y == 0) { - scroll_inertia.no_output_count++; - } else { - scroll_inertia.no_output_count = 0; - } - - // Stop if: velocity very low OR too many consecutive frames without output - bool velocity_too_low = (abs_vx < 64 && abs_vy < 64); // Same as hi-res mode - bool stalled = (scroll_inertia.no_output_count > 5); // 5 frames (~35ms) with no output - if (velocity_too_low || stalled) { - scroll_inertia.active = false; - } else -# else - if (abs_vx < 64 && abs_vy < 64) { // Threshold in Q8 (0.25 in real units) - scroll_inertia.active = false; - } else -# endif - { - // Apply scroll inversion if configured -# ifdef NAVIGATOR_SCROLL_INVERT_X - mouse_report.h = -scroll_x; -# else - mouse_report.h = scroll_x; -# endif -# ifdef NAVIGATOR_SCROLL_INVERT_Y - mouse_report.v = scroll_y; -# else - mouse_report.v = -scroll_y; -# endif - return mouse_report; - } - } -# endif -#endif // End scroll inertia block - -#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) - if (!has_motion || !trackpad_init) { - return mouse_report; - } - - mouse_report.x = ptp_report.xDelta; - mouse_report.y = ptp_report.yDelta; - mouse_report.v = ptp_report.scrollDelta; - mouse_report.h = ptp_report.panDelta; - mouse_report.buttons = ptp_report.buttons; -#elif defined(NAVIGATOR_TRACKPAD_PTP_MODE) - if (!has_motion || !trackpad_init) { - return mouse_report; - } - // Create local snapshot to avoid race condition with callback updating ptp_report - cgen6_report_t local_report = ptp_report; - - uint8_t raw_fingers = finger_count(&local_report); - bool is_touching = local_report.fingers[0].tip; - bool was_idle = (gesture.state == TP_IDLE); - uint8_t fingers = raw_fingers; - - // Handle finger down - record start position (regardless of current state) - if (is_touching && was_idle) { - gesture.touch_start_time = timer_read(); - gesture.prev_x = local_report.fingers[0].x; - gesture.prev_y = local_report.fingers[0].y; - gesture.settled_x = 0; // Will be set after settle time - gesture.settled_y = 0; - gesture.settled = false; - gesture.max_finger_count = fingers; - gesture.state = TP_MOVING; -# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE - // Stop any ongoing scroll inertia when finger touches - scroll_inertia.active = false; - scroll_inertia.smooth_vx = 0; - scroll_inertia.smooth_vy = 0; -# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - scroll_inertia.no_output_count = 0; -# endif -# endif -# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - // Reset macOS scroll accumulator when starting new gesture - macos_scroll_accumulated_h = 0; - macos_scroll_accumulated_v = 0; -# endif - } - - // Handle finger up - evaluate tap at lift time (libinput style) - if (!is_touching && !was_idle) { - uint16_t duration = timer_elapsed(gesture.touch_start_time); - - // Calculate distance from settled position (or treat as no movement if never settled) - int32_t dist_sq = 0; - if (gesture.settled) { - int16_t dx = gesture.prev_x - gesture.settled_x; - int16_t dy = gesture.prev_y - gesture.settled_y; - dist_sq = (int32_t)dx * dx + (int32_t)dy * dy; - } - - // Check tap conditions: short duration AND small movement from settled position - bool is_tap = (duration <= NAVIGATOR_TRACKPAD_TAP_TIMEOUT) && - (dist_sq <= NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD); - - // Don't trigger single-finger taps that happen shortly after a scroll ends (within 100ms) - // But allow two-finger taps (right-click) even after scrolling -# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS - if (is_tap && gesture.max_finger_count == 1 && - timer_elapsed(gesture.last_scroll_end) < 100) { - is_tap = false; // Suppress single-finger tap after scrolling - } -# endif - - if (is_tap) { -# ifdef NAVIGATOR_TRACKPAD_ENABLE_DOUBLE_TAP - if (gesture.max_finger_count >= 2) { - mouse_report.x = 0; - mouse_report.y = 0; - mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON2); - gesture.pending_click = true; - } else -# endif -# ifdef NAVIGATOR_TRACKPAD_ENABLE_TAP - if (gesture.max_finger_count == 1) { - mouse_report.x = 0; - mouse_report.y = 0; - mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1); - gesture.pending_click = true; - } -# endif - } - -# if defined(NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE) && defined(NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS) - // Start scroll inertia if we were scrolling and have enough velocity - if (gesture.state == TP_SCROLLING) { - // Use smoothed velocity (already in Q8 format) - int16_t abs_vx = scroll_inertia.smooth_vx < 0 ? -scroll_inertia.smooth_vx : scroll_inertia.smooth_vx; - int16_t abs_vy = scroll_inertia.smooth_vy < 0 ? -scroll_inertia.smooth_vy : scroll_inertia.smooth_vy; - // Trigger threshold is now in Q8 (multiply by 256) - if (abs_vx >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256) || - abs_vy >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256)) { - scroll_inertia.vx = scroll_inertia.smooth_vx; - scroll_inertia.vy = scroll_inertia.smooth_vy; - scroll_inertia.timer = timer_read(); - scroll_inertia.active = true; - } - } -# endif - - // Record if this was a scroll gesture ending -# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS - if (gesture.state == TP_SCROLLING || gesture.max_finger_count >= 2) { - gesture.last_scroll_end = timer_read(); - } -# endif - - gesture.state = TP_IDLE; - set_scrolling = false; - } - - // Handle ongoing touch - movement and scrolling - if (is_touching && !was_idle) { - // Track max fingers during gesture - if (fingers > gesture.max_finger_count) { - gesture.max_finger_count = fingers; - } - -# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS - // Determine mode based on finger count - // Once scrolling starts, keep scrolling until all fingers lift (no mid-gesture transitions) - if (fingers >= 2 && gesture.state != TP_SCROLLING) { - gesture.state = TP_SCROLLING; - // Reset position tracking - use finger[0] initially - gesture.prev_x = local_report.fingers[0].x; - gesture.prev_y = local_report.fingers[0].y; - } - // Note: We don't transition from SCROLLING back to MOVING mid-gesture anymore - // Once scroll starts, it continues until all fingers lift (gesture ends) -# endif - - uint16_t duration = timer_elapsed(gesture.touch_start_time); - - // Record settled position once settle time elapses - if (!gesture.settled && duration >= NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME) { - gesture.settled = true; - gesture.settled_x = local_report.fingers[0].x; - gesture.settled_y = local_report.fingers[0].y; - } - - // Check if we should suppress movement (might still be a tap) - int32_t dist_sq = 0; - if (gesture.settled) { - int16_t dx = local_report.fingers[0].x - gesture.settled_x; - int16_t dy = local_report.fingers[0].y - gesture.settled_y; - dist_sq = (int32_t)dx * dx + (int32_t)dy * dy; - } - - // Only report movement if: timeout exceeded OR moved beyond tap threshold (after settling) - bool should_move = (duration > NAVIGATOR_TRACKPAD_TAP_TIMEOUT) || - (gesture.settled && dist_sq > NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD); - - if (should_move) { - int16_t delta_x = local_report.fingers[0].x - gesture.prev_x; - int16_t delta_y = local_report.fingers[0].y - gesture.prev_y; - - // Clamp deltas to prevent jumps from bad data - if (delta_x > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = NAVIGATOR_TRACKPAD_MAX_DELTA; - if (delta_x < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = -NAVIGATOR_TRACKPAD_MAX_DELTA; - if (delta_y > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = NAVIGATOR_TRACKPAD_MAX_DELTA; - if (delta_y < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = -NAVIGATOR_TRACKPAD_MAX_DELTA; - - if (delta_x != 0 || delta_y != 0) { -# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS - if (gesture.state == TP_SCROLLING) { -# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - // macOS mode: send raw deltas, macOS handles scaling via HIDScrollResolution - // Apple trackpads report raw sensor deltas and let macOS apply acceleration - int16_t scroll_x = delta_x; - int16_t scroll_y = delta_y; -# else - // Hi-res mode: apply multiplier for Windows/Linux - // These OSes divide by the Resolution Multiplier (120) - int16_t scroll_x = delta_x * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER; - int16_t scroll_y = delta_y * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER; -# endif - -# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE - // Track velocity for inertia using exponential smoothing (Q8 fixed point) - // Alpha = 0.3 (77/256) - balances responsiveness with smoothness - // When direction changes, reset smoothing to avoid fighting old momentum - int16_t new_vx = delta_x * 256; - int16_t new_vy = delta_y * 256; - - // Detect direction change (signs differ and both non-zero) - bool dir_change_x = (scroll_inertia.smooth_vx > 0 && new_vx < 0) || - (scroll_inertia.smooth_vx < 0 && new_vx > 0); - bool dir_change_y = (scroll_inertia.smooth_vy > 0 && new_vy < 0) || - (scroll_inertia.smooth_vy < 0 && new_vy > 0); - - if (dir_change_x) { - // Direction changed - blend toward zero first, then pick up new direction - scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 128 + new_vx * 128) / 256; - } else { - // Same direction - normal EMA smoothing - scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 179 + new_vx * 77) / 256; - } - - if (dir_change_y) { - scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 128 + new_vy * 128) / 256; - } else { - scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 179 + new_vy * 77) / 256; - } -# endif - - // Clamp to int8_t range for the report - scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x); - scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y); - - // Apply scroll inversion if configured -# ifdef NAVIGATOR_SCROLL_INVERT_X - mouse_report.h = -scroll_x; -# else - mouse_report.h = scroll_x; -# endif -# ifdef NAVIGATOR_SCROLL_INVERT_Y - mouse_report.v = scroll_y; -# else - mouse_report.v = -scroll_y; -# endif - mouse_report.x = 0; - mouse_report.y = 0; - } else -# endif - { - // One-finger movement: mouse cursor - mouse_report.x = (delta_x < 0) ? -powf(-delta_x, 1.2) : powf(delta_x, 1.2); - mouse_report.y = (delta_y < 0) ? -powf(-delta_y, 1.2) : powf(delta_y, 1.2); - } - } - } - - // Update prev position for next frame - gesture.prev_x = local_report.fingers[0].x; - gesture.prev_y = local_report.fingers[0].y; - } -#endif - - has_motion = 0; - return mouse_report; -#endif // PRECISION_TRACKPAD_ENABLE -} -#endif // POINTING_DEVICE_ENABLE - -void set_cirque_cpi(void) { - // traverse the sequence by compairing the cpi_x value with the current cpi_x value - // set the cpi to the next value in the sequence - switch (current_cpi) { - case CPI_1: { - current_cpi = CPI_2; - break; - } - case CPI_2: { - current_cpi = CPI_3; - break; - } - case CPI_3: { - current_cpi = CPI_4; - break; - } - case CPI_4: { - current_cpi = CPI_5; - break; - } - case CPI_5: { - current_cpi = CPI_6; - break; - } - case CPI_6: { - current_cpi = CPI_7; - break; - } - case CPI_7: { - current_cpi = CPI_1; - break; - } - default: { - current_cpi = CPI_4; - break; - } - } -} - -uint16_t navigator_trackpad_get_cpi(void) { - return current_cpi; -} - -void restore_cpi(uint8_t cpi) { - current_cpi = cpi; - set_cirque_cpi(); -} - -void navigator_trackpad_set_cpi(uint16_t cpi) { - if (cpi == 0) { // Decrease one tick - if (current_cpi > 1) { - current_cpi--; - } - } else { - if (current_cpi < CPI_TICKS) { - current_cpi++; - } - } - set_cirque_cpi(); -}; - -#ifdef PRECISION_TRACKPAD_ENABLE -// External declaration for PTP report sending -extern void send_trackpad(report_trackpad_t *report); - -// PTP task function - converts trackpad touches to PTP reports -void navigator_trackpad_task(void) { -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) - if (!trackpad_init) { - return; - } - - // Always check for data, even if has_motion isn't set - // Windows needs continuous updates for gestures - if (!has_motion && !cirque_gen6_has_motion()) { - return; - } - - // Read fresh data if available - if (cirque_gen6_has_motion()) { - cirque_gen_6_read_report(); - } - - // Create local snapshot to avoid race condition with callback - cgen6_report_t local_report = ptp_report; - - uint8_t raw_fingers = finger_count(&local_report); - -#ifdef CONSOLE_ENABLE - // Track min/max coordinates to find actual sensor range - static uint16_t max_x = 0, max_y = 0; - static uint16_t min_x = 0xFFFF, min_y = 0xFFFF; - static uint16_t debug_counter = 0; - - if (local_report.fingers[0].tip) { - if (local_report.fingers[0].x > max_x) max_x = local_report.fingers[0].x; - if (local_report.fingers[0].y > max_y) max_y = local_report.fingers[0].y; - if (local_report.fingers[0].x < min_x && local_report.fingers[0].x > 0) min_x = local_report.fingers[0].x; - if (local_report.fingers[0].y < min_y && local_report.fingers[0].y > 0) min_y = local_report.fingers[0].y; - } - if (local_report.fingers[1].tip) { - if (local_report.fingers[1].x > max_x) max_x = local_report.fingers[1].x; - if (local_report.fingers[1].y > max_y) max_y = local_report.fingers[1].y; - if (local_report.fingers[1].x < min_x && local_report.fingers[1].x > 0) min_x = local_report.fingers[1].x; - if (local_report.fingers[1].y < min_y && local_report.fingers[1].y > 0) min_y = local_report.fingers[1].y; - } - - if (++debug_counter % 100 == 0) { // Print every 100 reports - printf("PTP: fingers=%d, range: x=%d-%d, y=%d-%d, current: f0(%d,%d) f1(%d,%d)\n", - raw_fingers, min_x, max_x, min_y, max_y, - local_report.fingers[0].x, local_report.fingers[0].y, - local_report.fingers[1].x, local_report.fingers[1].y); - } -#endif - - // Initialize PTP report - report_trackpad_t ptp = {0}; - ptp.report_id = REPORT_ID_TRACKPAD; - - // Map Cirque coordinates (0-4095 in PTP mode) to PTP logical range (0-4095) - // Physical dimensions: 4 inches (0x190) x 2.75 inches (0x113) - // The Cirque sensor already provides 12-bit resolution - - if (raw_fingers >= 1 && local_report.fingers[0].tip) { - ptp.contacts[0].confidence = 1; // Assume intentional touch - ptp.contacts[0].tip = 1; - ptp.contacts[0].contact_id = 0; - ptp.contacts[0].x = local_report.fingers[0].x; // Already 12-bit - ptp.contacts[0].y = local_report.fingers[0].y; - } - - if (raw_fingers >= 2 && local_report.fingers[1].tip) { - ptp.contacts[1].confidence = 1; - ptp.contacts[1].tip = 1; - ptp.contacts[1].contact_id = 1; - ptp.contacts[1].x = local_report.fingers[1].x; - ptp.contacts[1].y = local_report.fingers[1].y; - } - - // Set contact count - ptp.contact_count = raw_fingers; - -#ifdef CONSOLE_ENABLE - if (raw_fingers > 2) { - printf("ERROR: raw_fingers=%d (should be 0-2!)\n", raw_fingers); - } -#endif - - // Set scan time (in 100μs units) - // Use timer_read() which returns milliseconds, convert to 100μs units - ptp.scan_time = (timer_read() * 10) & 0xFFFF; - - // Set button states (physical buttons from Cirque) - ptp.button1 = (local_report.buttons & 0x01) ? 1 : 0; - ptp.button2 = (local_report.buttons & 0x02) ? 1 : 0; - ptp.button3 = (local_report.buttons & 0x04) ? 1 : 0; - - // Send the PTP report -#ifdef CONSOLE_ENABLE - // Log raw bytes being sent - static uint16_t byte_log_counter = 0; - if (++byte_log_counter % 50 == 0 && raw_fingers > 0) { - uint8_t *bytes = (uint8_t *)&ptp; - printf("Sending bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14]); - } -#endif - send_trackpad(&ptp); - -#ifdef CONSOLE_ENABLE - // Detailed PTP report logging - static uint16_t report_counter = 0; - if (++report_counter % 50 == 0 || raw_fingers > 0) { // Log every 50 reports or when touching - printf("PTP Report: count=%d, scan=%d, btn=%d%d%d\n", - ptp.contact_count, ptp.scan_time, ptp.button1, ptp.button2, ptp.button3); - if (ptp.contacts[0].tip) { - printf(" C0: id=%d, conf=%d, tip=%d, x=%d, y=%d\n", - ptp.contacts[0].contact_id, ptp.contacts[0].confidence, - ptp.contacts[0].tip, ptp.contacts[0].x, ptp.contacts[0].y); - } - if (ptp.contacts[1].tip) { - printf(" C1: id=%d, conf=%d, tip=%d, x=%d, y=%d\n", - ptp.contacts[1].contact_id, ptp.contacts[1].confidence, - ptp.contacts[1].tip, ptp.contacts[1].x, ptp.contacts[1].y); - } - } -#endif - - // Also send a report with no contacts when fingers lift - // This is critical for Windows to detect gesture end - static uint8_t prev_contact_count = 0; - if (raw_fingers == 0 && prev_contact_count > 0) { - // Send empty report to signal fingers lifted - report_trackpad_t empty_ptp = {0}; - empty_ptp.report_id = REPORT_ID_TRACKPAD; - empty_ptp.scan_time = (timer_read() * 10) & 0xFFFF; - send_trackpad(&empty_ptp); - } - prev_contact_count = raw_fingers; - - has_motion = 0; -#endif -} + #include "navigator_trackpad_mouse.h" #endif diff --git a/drivers/sensors/navigator_trackpad.h b/drivers/sensors/navigator_trackpad.h index 94fac09d61..784f558edb 100644 --- a/drivers/sensors/navigator_trackpad.h +++ b/drivers/sensors/navigator_trackpad.h @@ -2,211 +2,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include -#include "report.h" -#ifdef POINTING_DEVICE_ENABLE -# include "pointing_device.h" -#endif +// Dispatcher/compatibility layer for Navigator trackpad +// Includes the appropriate implementation based on build configuration -#define NAVIGATOR_TRACKPAD_READ 7 -#define NAVIGATOR_TRACKPAD_PROBE 1000 - -#ifndef NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD -# define NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD 100 // Max movement (squared) before tap becomes a drag -#endif - -#ifndef NAVIGATOR_TRACKPAD_TAP_TIMEOUT -# define NAVIGATOR_TRACKPAD_TAP_TIMEOUT 200 // Max duration (ms) for a tap -#endif - -#ifndef NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME -# define NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME 30 // Ignore movement during initial contact (ms) -#endif - -#ifndef NAVIGATOR_TRACKPAD_MAX_DELTA -# define NAVIGATOR_TRACKPAD_MAX_DELTA 250 // Max allowed delta per frame to prevent jumps -#endif - -#ifndef NAVIGATOR_TRACKPAD_ADDRESS -# define NAVIGATOR_TRACKPAD_ADDRESS 0x58 -#endif - -#ifndef NAVIGATOR_TRACKPAD_TIMEOUT -# define NAVIGATOR_TRACKPAD_TIMEOUT 100 -#endif - -#define NAVIGATOR_TRACKPAD_PTP_MODE -#if !defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) && !defined(NAVIGATOR_TRACKPAD_PTP_MODE) -# define NAVIGATOR_TRACKPAD_PTP_MODE -#endif - -#define CGEN6_MAX_PACKET_SIZE 17 -#define CGEN6_PTP_REPORT_ID 0x01 -#define CGEN6_MOUSE_REPORT_ID 0x06 -#define CGEN6_ABSOLUTE_REPORT_ID 0x09 - -// C3 error codes when reading memory -#define CGEN6_SUCCESS 0x00 -#define CGEN6_CKSUM_FAILED 0x01 -#define CGEN6_LEN_MISMATCH 0x02 -#define CGEN6_I2C_FAILED 0x03 - -// C3 register addresses -#define CGEN6_REG_BASE 0x20000800 -#define CGEN6_HARDWARE_ID CGEN6_REG_BASE + 0x08 -#define CGEN6_FIRMWARE_ID CGEN6_REG_BASE + 0x09 -#define CGEN6_FIRMWARE_REV CGEN6_REG_BASE + 0x10 -#define CGEN6_VENDOR_ID CGEN6_REG_BASE + 0x0A -#define CGEN6_PRODUCT_ID CGEN6_REG_BASE + 0x0C -#define CGEN6_VERSION_ID CGEN6_REG_BASE + 0x0E -#define CGEN6_FEED_CONFIG4 0x200E000B -#define CGEN6_FEED_CONFIG3 0x200E000A -#define CGEN6_SYS_CONFIG1 0x20000008 -#define CGEN6_XY_CONFIG 0x20080018 -#define CGEN6_SFR_BASE 0x40000008 -#define CGEN6_GPIO_BASE 0x00052000 -#define CGEN6_I2C_DR 0x61010000 - -#define CPI_TICKS 7 -#define DEFAULT_CPI_TICK 4 -#define CPI_1 200 -#define CPI_2 400 -#define CPI_3 800 -#define CPI_4 1024 -#define CPI_5 1400 -#define CPI_6 1800 -#define CPI_7 2048 - -#ifndef NAVIGATOR_TRACKPAD_SCROLL_DIVIDER -# define NAVIGATOR_TRACKPAD_SCROLL_DIVIDER 10 -#endif - -#ifndef NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER -# define NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER 3 -#endif - -// Two-finger scrolling (define to enable) -// #define NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS - -// macOS scrolling mode (define to enable) -// macOS doesn't respect the HID Resolution Multiplier descriptor. -// When enabled, sends raw scroll deltas without the multiplier (like Apple trackpads). -// macOS applies its own HIDScrollResolution (400 DPI) and acceleration curves. -// When disabled (default), applies multiplier for Windows/Linux hi-res scrolling. -// #define NAVIGATOR_TRACKPAD_MACOS_SCROLLING - -// Scroll inversion configuration -// Define these to invert scroll direction on respective axes -// #define NAVIGATOR_SCROLL_INVERT_X -// #define NAVIGATOR_SCROLL_INVERT_Y - -// Scroll inertia configuration -/* -To enable, add to your config.h: -#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE - -Configurable values (all optional): -- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION - Higher = stops faster (default: 50, range 1-255) -- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL - Time between glide reports in ms (default: 15) -- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER - Minimum velocity to trigger glide (default: 3) -*/ -#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE -#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION -#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION 5 // Higher = stops faster (1-255) -#endif -#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL -#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL 7 // Glide report interval in ms -#endif -#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER -#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER 1 // Min velocity to trigger glide -#endif - -typedef struct { - int16_t vx; // Current X velocity (Q8 fixed point) - int16_t vy; // Current Y velocity (Q8 fixed point) - int16_t smooth_vx; // Smoothed X velocity (Q8 fixed point) - int16_t smooth_vy; // Smoothed Y velocity (Q8 fixed point) - uint16_t timer; // Timer for interval tracking - bool active; // Is glide currently active -#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING - uint8_t no_output_count; // Counter for consecutive frames with no output -#endif -} scroll_inertia_t; -#endif - -#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) -# ifndef MOUSE_EXTENDED_REPORT -# define MOUSE_EXTENDED_REPORT -# endif -typedef struct { - uint8_t tip; - uint16_t x; - uint16_t y; -} cgen6_finger_t; - -typedef struct { - cgen6_finger_t fingers[2]; - uint8_t buttons; -} cgen6_report_t; - -// Trackpad gesture state machine -typedef enum { - TP_IDLE, // No fingers touching - TP_MOVING, // One finger movement = mouse cursor - TP_SCROLLING, // Two finger movement = scroll -} trackpad_state_t; - -typedef struct { - trackpad_state_t state; - uint16_t touch_start_time; // When finger first touched - uint16_t settled_x; // Position after settle time (for tap threshold) - uint16_t settled_y; - uint16_t prev_x; // Previous position (for delta calculation) - uint16_t prev_y; - uint8_t max_finger_count; // Max fingers seen during this gesture - bool settled; // Has the settle time elapsed? - bool pending_click; // Need to send a click release next cycle - uint16_t last_scroll_end; // Time when last scroll gesture ended -} trackpad_gesture_t; -#endif - -#if defined(NAVIGATOR_TRACKPAD_ABSOLUTE_MODE) -typedef struct { - uint16_t x; - uint16_t y; - uint8_t palm; - uint8_t z; -} finger_data_t; - -typedef struct { - finger_data_t fingers[3]; // Cirque support 5 fingers, we only need 3 for our application - uint8_t contact_flags; - uint8_t buttons; -} cgen6_report_t; -#endif - -#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) -typedef struct { - uint8_t buttons; - int8_t xDelta; - int8_t yDelta; - int8_t scrollDelta; - int8_t panDelta; -} cgen6_report_t; -#endif - -#ifdef POINTING_DEVICE_ENABLE -const pointing_device_driver_t navigator_trackpad_pointing_device_driver; -report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report); -#endif - -void navigator_trackpad_device_init(void); -uint16_t navigator_trackpad_get_cpi(void); -void navigator_trackpad_set_cpi(uint16_t cpi); -void restore_cpi(uint8_t cpi); +#include "navigator_trackpad_common.h" #ifdef PRECISION_TRACKPAD_ENABLE -void navigator_trackpad_task(void); + #include "navigator_trackpad_ptp.h" +#else + #include "navigator_trackpad_mouse.h" #endif diff --git a/drivers/sensors/navigator_trackpad_common.c b/drivers/sensors/navigator_trackpad_common.c new file mode 100644 index 0000000000..3a063828c6 --- /dev/null +++ b/drivers/sensors/navigator_trackpad_common.c @@ -0,0 +1,373 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +// Common hardware layer for Navigator trackpad +// Shared by both mouse and PTP implementations + +#include +#include +#include "navigator_trackpad_common.h" +#include "i2c_master.h" +#include "quantum.h" +#include "timer.h" + +// Shared globals +deferred_token callback_token = 0; +uint16_t current_cpi = DEFAULT_CPI_TICK; +uint8_t has_motion = 0; +bool trackpad_init = false; +cgen6_report_t ptp_report; + +// I2C communication functions +i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt) { + i2c_status_t res = i2c_receive(NAVIGATOR_TRACKPAD_ADDRESS, data, cnt, NAVIGATOR_TRACKPAD_TIMEOUT); + if (res != I2C_STATUS_SUCCESS) { + return res; + } + wait_us(cnt * 15); + return res; +} + +void cirque_gen6_clear(void) { + uint8_t buf[CGEN6_MAX_PACKET_SIZE]; + for (uint8_t i = 0; i < 5; i++) { + wait_ms(1); + if (cirque_gen6_read_report(buf, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) { + break; + } + } +} + +uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read) { + uint8_t cksum = 0; + uint8_t res = CGEN6_SUCCESS; + uint8_t len[2]; + uint16_t read = 0; + + uint8_t preamble[8] = {0x01, 0x09, (uint8_t)(addr & (uint32_t)0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)}; + + // Read the length of the data + 3 bytes (first 2 bytes for the length and the last byte for the checksum) + // Create a buffer to store the data + uint8_t buf[cnt + 3]; + if (i2c_transmit_and_receive(NAVIGATOR_TRACKPAD_ADDRESS, preamble, 8, buf, cnt + 3, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) { + res |= CGEN6_I2C_FAILED; + trackpad_init = false; + } + + // Read the data length + for (uint8_t i = 0; i < 2; i++) { + cksum += len[i] = buf[i]; + read++; + } + + // Populate the data buffer + for (uint16_t i = 2; i < cnt + 2; i++) { + cksum += data[i - 2] = buf[i]; + read++; + } + + if (!fast_read) { + // Check the checksum + if (cksum != buf[read]) { + res |= CGEN6_CKSUM_FAILED; + } + + // Check the length (incremented first to account for the checksum) + if (++read != (len[0] | (len[1] << 8))) { + res |= CGEN6_LEN_MISMATCH; + } + + wait_ms(1); + } else { + wait_us(250); + } + + return res; +} + +uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt) { + uint8_t res = CGEN6_SUCCESS; + uint8_t cksum = 0, i = 0; + uint8_t preamble[8] = {0x00, 0x09, (uint8_t)(addr & 0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)}; + + uint8_t buf[cnt + 9]; + // Calculate the checksum + for (; i < 8; i++) { + cksum += buf[i] = preamble[i]; + } + + for (i = 0; i < cnt; i++) { + cksum += buf[i + 8] = data[i]; + } + + buf[cnt + 8] = cksum; + + if (i2c_transmit(NAVIGATOR_TRACKPAD_ADDRESS, buf, cnt + 9, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) { + res |= CGEN6_I2C_FAILED; + trackpad_init = false; + } + + wait_ms(1); + + return res; +} + +// Register access functions +uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read) { + uint8_t data; + uint8_t res = cirque_gen6_read_memory(addr, &data, 1, fast_read); + if (res != CGEN6_SUCCESS) { + printf("Failed to read 8bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); + return 0; + } + return data; +} + +uint16_t cirque_gen6_read_reg_16(uint32_t addr) { + uint8_t buf[2]; + uint8_t res = cirque_gen6_read_memory(addr, buf, 2, false); + if (res != CGEN6_SUCCESS) { + printf("Failed to read 16bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); + return 0; + } + return (buf[1] << 8) | buf[0]; +} + +uint32_t cirque_gen6_read_reg_32(uint32_t addr) { + uint8_t buf[4]; + uint8_t res = cirque_gen6_read_memory(addr, buf, 4, false); + if (res != CGEN6_SUCCESS) { + printf("Failed to read 32bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res); + return 0; + } + return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; +} + +uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data) { + return cirque_gen6_write_memory(addr, &data, 1); +} + +uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data) { + uint8_t buf[2] = {data & 0xFF, (data >> 8) & 0xFF}; + return cirque_gen6_write_memory(addr, buf, 2); +} + +uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data) { + uint8_t buf[4] = {data & 0xFF, (data >> 8) & 0xFF, (data >> 16) & 0xFF, (data >> 24) & 0xFF}; + return cirque_gen6_write_memory(addr, buf, 4); +} + +// Configuration functions +uint8_t cirque_gen6_set_relative_mode(void) { + uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false); + feed_config4 &= 0xF3; + return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4); +} + +uint8_t cirque_gen6_set_ptp_mode(void) { + uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false); + feed_config4 &= 0xF7; + feed_config4 |= 0x04; + return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4); +} + +uint8_t cirque_gen6_swap_xy(bool set) { + uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); + if (set) { + xy_config |= 0x04; + } else { + xy_config &= ~0x04; + } + return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); +} + +uint8_t cirque_gen6_invert_y(bool set) { + uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); + if (set) { + xy_config |= 0x02; + } else { + xy_config &= ~0x02; + } + return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); +} + +uint8_t cirque_gen6_invert_x(bool set) { + uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); + if (set) { + xy_config |= 0x01; + } else { + xy_config &= ~0x01; + } + return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); +} + +uint8_t cirque_gen6_enable_logical_scaling(bool set) { + uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false); + if (set) { + xy_config &= ~0x08; + } else { + xy_config |= 0x08; + } + return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config); +} + +// Motion detection +uint8_t cirque_gen6_has_motion(void) { + return cirque_gen6_read_reg(CGEN6_I2C_DR, true); +} + +// Report reading - fills ptp_report global +void cirque_gen_6_read_report(void) { + uint8_t packet[CGEN6_MAX_PACKET_SIZE]; + if (cirque_gen6_read_report(packet, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) { + return; + } + + uint8_t report_id = packet[2]; + + // PTP mode report + if (report_id == CGEN6_PTP_REPORT_ID) { + ptp_report.fingers[0].tip = (packet[3] & 0x02) >> 1; + ptp_report.fingers[0].x = packet[5] << 8 | packet[4]; + ptp_report.fingers[0].y = packet[7] << 8 | packet[6]; + ptp_report.fingers[1].tip = (packet[8] & 0x02) >> 1; + ptp_report.fingers[1].x = packet[10] << 8 | packet[9]; + ptp_report.fingers[1].y = packet[12] << 8 | packet[11]; + ptp_report.scan_time = packet[14] << 8 | packet[13]; + ptp_report.buttons = packet[16]; + } + // Mouse/relative mode report + else if (report_id == CGEN6_MOUSE_REPORT_ID) { + ptp_report.buttons = packet[3]; + ptp_report.xDelta = packet[4]; + ptp_report.yDelta = packet[5]; + ptp_report.scrollDelta = packet[6]; + ptp_report.panDelta = packet[7]; + has_motion = 1; + } +} + +// Deferred callback for polling +uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg) { + if (!trackpad_init) { + navigator_trackpad_device_init(); + return NAVIGATOR_TRACKPAD_PROBE; + } + if (cirque_gen6_has_motion()) { + has_motion = 1; + cirque_gen_6_read_report(); + } + return NAVIGATOR_TRACKPAD_READ; +} + +// Device initialization +void navigator_trackpad_device_init(void) { + i2c_init(); + i2c_status_t status = i2c_ping_address(NAVIGATOR_TRACKPAD_ADDRESS, NAVIGATOR_TRACKPAD_TIMEOUT); + if (status != I2C_STATUS_SUCCESS) { + trackpad_init = false; + return; + } + cirque_gen6_clear(); + wait_ms(50); + + uint8_t res = CGEN6_SUCCESS; +#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) + res = cirque_gen6_set_ptp_mode(); +#elif defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) + res = cirque_gen6_set_relative_mode(); +#endif + + if (res != CGEN6_SUCCESS) { + return; + } + + // Reset to the default alignment + cirque_gen6_swap_xy(false); + cirque_gen6_invert_x(false); + cirque_gen6_invert_y(false); + cirque_gen6_swap_xy(true); + cirque_gen6_invert_x(true); + cirque_gen6_invert_y(true); + cirque_gen6_enable_logical_scaling(true); + + trackpad_init = true; + // Only register the callback for the first time + if (!callback_token) { + callback_token = defer_exec(NAVIGATOR_TRACKPAD_READ, cirque_gen6_read_callback, NULL); + } +} + +// CPI management +void set_cirque_cpi(void) { + // traverse the sequence by comparing the cpi_x value with the current cpi_x value + // set the cpi to the next value in the sequence + switch (current_cpi) { + case CPI_1: { + current_cpi = CPI_2; + break; + } + case CPI_2: { + current_cpi = CPI_3; + break; + } + case CPI_3: { + current_cpi = CPI_4; + break; + } + case CPI_4: { + current_cpi = CPI_5; + break; + } + case CPI_5: { + current_cpi = CPI_6; + break; + } + case CPI_6: { + current_cpi = CPI_7; + break; + } + case CPI_7: { + current_cpi = CPI_1; + break; + } + default: { + current_cpi = CPI_4; + break; + } + } +} + +uint16_t navigator_trackpad_get_cpi(void) { + return current_cpi; +} + +void restore_cpi(uint8_t cpi) { + current_cpi = cpi; + set_cirque_cpi(); +} + +void navigator_trackpad_set_cpi(uint16_t cpi) { + if (cpi == 0) { // Decrease one tick + if (current_cpi > 1) { + current_cpi--; + } + } else { + if (current_cpi < CPI_TICKS) { + current_cpi++; + } + } + set_cirque_cpi(); +} + +// Helper function to count active fingers +uint8_t cirque_gen6_finger_count(cgen6_report_t *report) { + uint8_t fingers = 0; + if (report->fingers[0].tip) { + fingers++; + } + if (report->fingers[1].tip) { + fingers++; + } + return fingers; +} diff --git a/drivers/sensors/navigator_trackpad_common.h b/drivers/sensors/navigator_trackpad_common.h new file mode 100644 index 0000000000..bc63f1f7ae --- /dev/null +++ b/drivers/sensors/navigator_trackpad_common.h @@ -0,0 +1,139 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include +#include "i2c_master.h" +#include "deferred_exec.h" + +// I2C configuration +#define NAVIGATOR_TRACKPAD_READ 7 +#define NAVIGATOR_TRACKPAD_PROBE 1000 + +#ifndef NAVIGATOR_TRACKPAD_ADDRESS +# define NAVIGATOR_TRACKPAD_ADDRESS 0x58 +#endif + +#ifndef NAVIGATOR_TRACKPAD_TIMEOUT +# define NAVIGATOR_TRACKPAD_TIMEOUT 100 +#endif + +// Packet and report IDs +#define CGEN6_MAX_PACKET_SIZE 17 +#define CGEN6_PTP_REPORT_ID 0x01 +#define CGEN6_MOUSE_REPORT_ID 0x06 +#define CGEN6_ABSOLUTE_REPORT_ID 0x09 + +// C3 error codes when reading memory +#define CGEN6_SUCCESS 0x00 +#define CGEN6_CKSUM_FAILED 0x01 +#define CGEN6_LEN_MISMATCH 0x02 +#define CGEN6_I2C_FAILED 0x03 + +// C3 register addresses +#define CGEN6_REG_BASE 0x20000800 +#define CGEN6_HARDWARE_ID CGEN6_REG_BASE + 0x08 +#define CGEN6_FIRMWARE_ID CGEN6_REG_BASE + 0x09 +#define CGEN6_FIRMWARE_REV CGEN6_REG_BASE + 0x10 +#define CGEN6_VENDOR_ID CGEN6_REG_BASE + 0x0A +#define CGEN6_PRODUCT_ID CGEN6_REG_BASE + 0x0C +#define CGEN6_VERSION_ID CGEN6_REG_BASE + 0x0E +#define CGEN6_FEED_CONFIG4 0x200E000B +#define CGEN6_FEED_CONFIG3 0x200E000A +#define CGEN6_SYS_CONFIG1 0x20000008 +#define CGEN6_XY_CONFIG 0x20080018 +#define CGEN6_SFR_BASE 0x40000008 +#define CGEN6_GPIO_BASE 0x00052000 +#define CGEN6_I2C_DR 0x61010000 + +// CPI configuration +#define CPI_TICKS 7 +#define DEFAULT_CPI_TICK 4 +#define CPI_1 200 +#define CPI_2 400 +#define CPI_3 800 +#define CPI_4 1024 +#define CPI_5 1400 +#define CPI_6 1800 +#define CPI_7 2048 + +// Physical trackpad dimensions (in 0.01 inch units for HID descriptor) +// Navigator trackpad is circular, 40mm diameter (1.575 inches actual) +// We report larger physical dimensions to maintain good cursor speed +// The OS uses this ratio to calculate movement sensitivity +#ifndef TRACKPAD_PHYSICAL_WIDTH +# define TRACKPAD_PHYSICAL_WIDTH 157 // 1.57 inches (40mm actual size) +#endif +#ifndef TRACKPAD_PHYSICAL_HEIGHT +# define TRACKPAD_PHYSICAL_HEIGHT 157 // 1.57 inches (40mm actual size) +#endif + +// Logical coordinate range from Cirque Gen6 sensor in PTP mode +// Actual usable range is approximately 1-897, rounded to 900 +#define TRACKPAD_LOGICAL_MAX 900 + +// Common finger structure (used by both mouse and PTP modes) +typedef struct { + uint8_t tip; + uint16_t x; + uint16_t y; +} cgen6_finger_t; + +// Common report structure (used by both modes) +typedef struct { + cgen6_finger_t fingers[2]; + uint8_t buttons; + uint16_t scan_time; // Sensor timestamp (100μs units) - used by PTP mode + int8_t xDelta; // Used by mouse mode + int8_t yDelta; // Used by mouse mode + int8_t scrollDelta; // Used by mouse mode + int8_t panDelta; // Used by mouse mode +} cgen6_report_t; + +// Low-level I2C functions +i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt); +void cirque_gen6_clear(void); +uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read); +uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt); + +// Register access functions +uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read); +uint16_t cirque_gen6_read_reg_16(uint32_t addr); +uint32_t cirque_gen6_read_reg_32(uint32_t addr); +uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data); +uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data); +uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data); + +// Configuration functions +uint8_t cirque_gen6_set_relative_mode(void); +uint8_t cirque_gen6_set_ptp_mode(void); +uint8_t cirque_gen6_swap_xy(bool set); +uint8_t cirque_gen6_invert_y(bool set); +uint8_t cirque_gen6_invert_x(bool set); +uint8_t cirque_gen6_enable_logical_scaling(bool set); + +// Motion detection and report reading +uint8_t cirque_gen6_has_motion(void); +void cirque_gen_6_read_report(void); + +// Deferred callback +uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg); + +// Device initialization +void navigator_trackpad_device_init(void); + +// CPI management +uint16_t navigator_trackpad_get_cpi(void); +void navigator_trackpad_set_cpi(uint16_t cpi); +void restore_cpi(uint8_t cpi); + +// Shared globals +extern deferred_token callback_token; +extern uint16_t current_cpi; +extern uint8_t has_motion; +extern bool trackpad_init; +extern cgen6_report_t ptp_report; // Shared between modes + +// Helper functions +uint8_t cirque_gen6_finger_count(cgen6_report_t *report); diff --git a/drivers/sensors/navigator_trackpad_mouse.c b/drivers/sensors/navigator_trackpad_mouse.c new file mode 100644 index 0000000000..0b59ffebe0 --- /dev/null +++ b/drivers/sensors/navigator_trackpad_mouse.c @@ -0,0 +1,374 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +// Mouse mode implementation for Navigator trackpad +// Handles gesture detection, tap-to-click, two-finger scrolling, and scroll inertia + +#include +#include "navigator_trackpad_mouse.h" +#include "navigator_trackpad_common.h" +#include "navigator.h" // For scroll utilities +#include "quantum.h" +#include "timer.h" + +// Pointing device driver definition +const pointing_device_driver_t navigator_trackpad_pointing_device_driver = { + .init = navigator_trackpad_device_init, + .get_report = navigator_trackpad_get_report, + .get_cpi = navigator_trackpad_get_cpi, + .set_cpi = navigator_trackpad_set_cpi +}; + +// Mode-specific globals +trackpad_gesture_t gesture = {0}; +extern bool set_scrolling; // Declared in navigator.c + +#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE +scroll_inertia_t scroll_inertia = {0}; +#endif + +#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING +float macos_scroll_accumulated_h = 0; +float macos_scroll_accumulated_v = 0; +#endif + +report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report) { +#ifdef PRECISION_TRACKPAD_ENABLE + // When PTP is enabled, don't send mouse reports - PTP task handles everything + return mouse_report; +#else + // Mouse mode - process gestures and send mouse reports +#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) + // Handle pending click release from previous cycle + if (gesture.pending_click) { + gesture.pending_click = false; + mouse_report.buttons = 0; + return mouse_report; + } + +# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE + // Process scroll inertia when active + if (scroll_inertia.active && timer_elapsed(scroll_inertia.timer) >= NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL) { + scroll_inertia.timer = timer_read(); + + // Apply friction to velocity (Q8 fixed point math) + // Friction reduces velocity towards zero + int16_t friction_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256; + int16_t friction_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256; + + // Ensure we always reduce by at least 1 if not zero + if (scroll_inertia.vx > 0 && friction_x < 1) friction_x = 1; + if (scroll_inertia.vx < 0 && friction_x > -1) friction_x = -1; + if (scroll_inertia.vy > 0 && friction_y < 1) friction_y = 1; + if (scroll_inertia.vy < 0 && friction_y > -1) friction_y = -1; + + scroll_inertia.vx -= friction_x; + scroll_inertia.vy -= friction_y; + + // Convert Q8 velocity to scroll value +# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + // macOS mode: send raw velocity deltas (descriptor tells macOS the resolution) + int16_t scroll_x = scroll_inertia.vx / 256; + int16_t scroll_y = scroll_inertia.vy / 256; +# else + // Hi-res mode: apply multiplier for Windows/Linux + int16_t scroll_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256; + int16_t scroll_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256; +# endif + + // Clamp to int8_t range + scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x); + scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y); + + // Check if velocity is too low to continue + int16_t abs_vx = scroll_inertia.vx < 0 ? -scroll_inertia.vx : scroll_inertia.vx; + int16_t abs_vy = scroll_inertia.vy < 0 ? -scroll_inertia.vy : scroll_inertia.vy; + +# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + // In macOS mode, track consecutive frames with no output to detect janky tail + if (scroll_x == 0 && scroll_y == 0) { + scroll_inertia.no_output_count++; + } else { + scroll_inertia.no_output_count = 0; + } + + // Stop if: velocity very low OR too many consecutive frames without output + bool velocity_too_low = (abs_vx < 64 && abs_vy < 64); // Same as hi-res mode + bool stalled = (scroll_inertia.no_output_count > 5); // 5 frames (~35ms) with no output + if (velocity_too_low || stalled) { + scroll_inertia.active = false; + } else +# else + if (abs_vx < 64 && abs_vy < 64) { // Threshold in Q8 (0.25 in real units) + scroll_inertia.active = false; + } else +# endif + { + // Apply scroll inversion if configured +# ifdef NAVIGATOR_SCROLL_INVERT_X + mouse_report.h = -scroll_x; +# else + mouse_report.h = scroll_x; +# endif +# ifdef NAVIGATOR_SCROLL_INVERT_Y + mouse_report.v = scroll_y; +# else + mouse_report.v = -scroll_y; +# endif + return mouse_report; + } + } +# endif +#endif // End scroll inertia block + +#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) + if (!has_motion || !trackpad_init) { + return mouse_report; + } + + mouse_report.x = ptp_report.xDelta; + mouse_report.y = ptp_report.yDelta; + mouse_report.v = ptp_report.scrollDelta; + mouse_report.h = ptp_report.panDelta; + mouse_report.buttons = ptp_report.buttons; +#elif defined(NAVIGATOR_TRACKPAD_PTP_MODE) + if (!has_motion || !trackpad_init) { + return mouse_report; + } + // Create local snapshot to avoid race condition with callback updating ptp_report + cgen6_report_t local_report = ptp_report; + + uint8_t raw_fingers = cirque_gen6_finger_count(&local_report); + bool is_touching = local_report.fingers[0].tip; + bool was_idle = (gesture.state == TP_IDLE); + uint8_t fingers = raw_fingers; + + // Handle finger down - record start position (regardless of current state) + if (is_touching && was_idle) { + gesture.touch_start_time = timer_read(); + gesture.prev_x = local_report.fingers[0].x; + gesture.prev_y = local_report.fingers[0].y; + gesture.settled_x = 0; // Will be set after settle time + gesture.settled_y = 0; + gesture.settled = false; + gesture.max_finger_count = fingers; + gesture.state = TP_MOVING; +# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE + // Stop any ongoing scroll inertia when finger touches + scroll_inertia.active = false; + scroll_inertia.smooth_vx = 0; + scroll_inertia.smooth_vy = 0; +# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + scroll_inertia.no_output_count = 0; +# endif +# endif +# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + // Reset macOS scroll accumulator when starting new gesture + macos_scroll_accumulated_h = 0; + macos_scroll_accumulated_v = 0; +# endif + } + + // Handle finger up - evaluate tap at lift time (libinput style) + if (!is_touching && !was_idle) { + uint16_t duration = timer_elapsed(gesture.touch_start_time); + + // Calculate distance from settled position (or treat as no movement if never settled) + int32_t dist_sq = 0; + if (gesture.settled) { + int16_t dx = gesture.prev_x - gesture.settled_x; + int16_t dy = gesture.prev_y - gesture.settled_y; + dist_sq = (int32_t)dx * dx + (int32_t)dy * dy; + } + + // Check tap conditions: short duration AND small movement from settled position + bool is_tap = (duration <= NAVIGATOR_TRACKPAD_TAP_TIMEOUT) && + (dist_sq <= NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD); + + // Don't trigger single-finger taps that happen shortly after a scroll ends (within 100ms) + // But allow two-finger taps (right-click) even after scrolling +# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS + if (is_tap && gesture.max_finger_count == 1 && + timer_elapsed(gesture.last_scroll_end) < 100) { + is_tap = false; // Suppress single-finger tap after scrolling + } +# endif + + if (is_tap) { +# ifdef NAVIGATOR_TRACKPAD_ENABLE_DOUBLE_TAP + if (gesture.max_finger_count >= 2) { + mouse_report.x = 0; + mouse_report.y = 0; + mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON2); + gesture.pending_click = true; + } else +# endif +# ifdef NAVIGATOR_TRACKPAD_ENABLE_TAP + if (gesture.max_finger_count == 1) { + mouse_report.x = 0; + mouse_report.y = 0; + mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1); + gesture.pending_click = true; + } +# endif + } + +# if defined(NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE) && defined(NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS) + // Start scroll inertia if we were scrolling and have enough velocity + if (gesture.state == TP_SCROLLING) { + // Use smoothed velocity (already in Q8 format) + int16_t abs_vx = scroll_inertia.smooth_vx < 0 ? -scroll_inertia.smooth_vx : scroll_inertia.smooth_vx; + int16_t abs_vy = scroll_inertia.smooth_vy < 0 ? -scroll_inertia.smooth_vy : scroll_inertia.smooth_vy; + // Trigger threshold is now in Q8 (multiply by 256) + if (abs_vx >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256) || + abs_vy >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256)) { + scroll_inertia.vx = scroll_inertia.smooth_vx; + scroll_inertia.vy = scroll_inertia.smooth_vy; + scroll_inertia.timer = timer_read(); + scroll_inertia.active = true; + } + } +# endif + + // Record if this was a scroll gesture ending +# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS + if (gesture.state == TP_SCROLLING || gesture.max_finger_count >= 2) { + gesture.last_scroll_end = timer_read(); + } +# endif + + gesture.state = TP_IDLE; + set_scrolling = false; + } + + // Handle ongoing touch - movement and scrolling + if (is_touching && !was_idle) { + // Track max fingers during gesture + if (fingers > gesture.max_finger_count) { + gesture.max_finger_count = fingers; + } + +# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS + // Determine mode based on finger count + // Once scrolling starts, keep scrolling until all fingers lift (no mid-gesture transitions) + if (fingers >= 2 && gesture.state != TP_SCROLLING) { + gesture.state = TP_SCROLLING; + // Reset position tracking - use finger[0] initially + gesture.prev_x = local_report.fingers[0].x; + gesture.prev_y = local_report.fingers[0].y; + } + // Note: We don't transition from SCROLLING back to MOVING mid-gesture anymore + // Once scroll starts, it continues until all fingers lift (gesture ends) +# endif + + uint16_t duration = timer_elapsed(gesture.touch_start_time); + + // Record settled position once settle time elapses + if (!gesture.settled && duration >= NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME) { + gesture.settled = true; + gesture.settled_x = local_report.fingers[0].x; + gesture.settled_y = local_report.fingers[0].y; + } + + // Check if we should suppress movement (might still be a tap) + int32_t dist_sq = 0; + if (gesture.settled) { + int16_t dx = local_report.fingers[0].x - gesture.settled_x; + int16_t dy = local_report.fingers[0].y - gesture.settled_y; + dist_sq = (int32_t)dx * dx + (int32_t)dy * dy; + } + + // Only report movement if: timeout exceeded OR moved beyond tap threshold (after settling) + bool should_move = (duration > NAVIGATOR_TRACKPAD_TAP_TIMEOUT) || + (gesture.settled && dist_sq > NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD); + + if (should_move) { + int16_t delta_x = local_report.fingers[0].x - gesture.prev_x; + int16_t delta_y = local_report.fingers[0].y - gesture.prev_y; + + // Clamp deltas to prevent jumps from bad data + if (delta_x > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = NAVIGATOR_TRACKPAD_MAX_DELTA; + if (delta_x < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = -NAVIGATOR_TRACKPAD_MAX_DELTA; + if (delta_y > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = NAVIGATOR_TRACKPAD_MAX_DELTA; + if (delta_y < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = -NAVIGATOR_TRACKPAD_MAX_DELTA; + + if (delta_x != 0 || delta_y != 0) { +# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS + if (gesture.state == TP_SCROLLING) { +# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + // macOS mode: send raw deltas, macOS handles scaling via HIDScrollResolution + // Apple trackpads report raw sensor deltas and let macOS apply acceleration + int16_t scroll_x = delta_x; + int16_t scroll_y = delta_y; +# else + // Hi-res mode: apply multiplier for Windows/Linux + // These OSes divide by the Resolution Multiplier (120) + int16_t scroll_x = delta_x * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER; + int16_t scroll_y = delta_y * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER; +# endif + +# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE + // Track velocity for inertia using exponential smoothing (Q8 fixed point) + // Alpha = 0.3 (77/256) - balances responsiveness with smoothness + // When direction changes, reset smoothing to avoid fighting old momentum + int16_t new_vx = delta_x * 256; + int16_t new_vy = delta_y * 256; + + // Detect direction change (signs differ and both non-zero) + bool dir_change_x = (scroll_inertia.smooth_vx > 0 && new_vx < 0) || + (scroll_inertia.smooth_vx < 0 && new_vx > 0); + bool dir_change_y = (scroll_inertia.smooth_vy > 0 && new_vy < 0) || + (scroll_inertia.smooth_vy < 0 && new_vy > 0); + + if (dir_change_x) { + // Direction changed - blend toward zero first, then pick up new direction + scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 128 + new_vx * 128) / 256; + } else { + // Same direction - normal EMA smoothing + scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 179 + new_vx * 77) / 256; + } + + if (dir_change_y) { + scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 128 + new_vy * 128) / 256; + } else { + scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 179 + new_vy * 77) / 256; + } +# endif + + // Clamp to int8_t range for the report + scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x); + scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y); + + // Apply scroll inversion if configured +# ifdef NAVIGATOR_SCROLL_INVERT_X + mouse_report.h = -scroll_x; +# else + mouse_report.h = scroll_x; +# endif +# ifdef NAVIGATOR_SCROLL_INVERT_Y + mouse_report.v = scroll_y; +# else + mouse_report.v = -scroll_y; +# endif + mouse_report.x = 0; + mouse_report.y = 0; + } else +# endif + { + // One-finger movement: mouse cursor + mouse_report.x = (delta_x < 0) ? -powf(-delta_x, 1.2) : powf(delta_x, 1.2); + mouse_report.y = (delta_y < 0) ? -powf(-delta_y, 1.2) : powf(delta_y, 1.2); + } + } + } + + // Update prev position for next frame + gesture.prev_x = local_report.fingers[0].x; + gesture.prev_y = local_report.fingers[0].y; + } +#endif + + has_motion = 0; + return mouse_report; +#endif // PRECISION_TRACKPAD_ENABLE +} diff --git a/drivers/sensors/navigator_trackpad_mouse.h b/drivers/sensors/navigator_trackpad_mouse.h new file mode 100644 index 0000000000..60e5f3ca17 --- /dev/null +++ b/drivers/sensors/navigator_trackpad_mouse.h @@ -0,0 +1,109 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "navigator_trackpad_common.h" +#include "pointing_device.h" +#include "report.h" + +// Tap detection configuration +#ifndef NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD +# define NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD 100 // Max movement (squared) before tap becomes a drag +#endif + +#ifndef NAVIGATOR_TRACKPAD_TAP_TIMEOUT +# define NAVIGATOR_TRACKPAD_TAP_TIMEOUT 200 // Max duration (ms) for a tap +#endif + +#ifndef NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME +# define NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME 30 // Ignore movement during initial contact (ms) +#endif + +#ifndef NAVIGATOR_TRACKPAD_MAX_DELTA +# define NAVIGATOR_TRACKPAD_MAX_DELTA 250 // Max allowed delta per frame to prevent jumps +#endif + +// Scroll configuration +#ifndef NAVIGATOR_TRACKPAD_SCROLL_DIVIDER +# define NAVIGATOR_TRACKPAD_SCROLL_DIVIDER 10 +#endif + +#ifndef NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER +# define NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER 3 +#endif + +// Two-finger scrolling (define to enable) +// #define NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS + +// macOS scrolling mode (define to enable) +// macOS doesn't respect the HID Resolution Multiplier descriptor. +// When enabled, sends raw scroll deltas without the multiplier (like Apple trackpads). +// macOS applies its own HIDScrollResolution (400 DPI) and acceleration curves. +// When disabled (default), applies multiplier for Windows/Linux hi-res scrolling. +// #define NAVIGATOR_TRACKPAD_MACOS_SCROLLING + +// Scroll inversion configuration +// Define these to invert scroll direction on respective axes +// #define NAVIGATOR_SCROLL_INVERT_X +// #define NAVIGATOR_SCROLL_INVERT_Y + +// Scroll inertia configuration +/* +To enable, add to your config.h: +#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE + +Configurable values (all optional): +- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION - Higher = stops faster (default: 50, range 1-255) +- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL - Time between glide reports in ms (default: 15) +- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER - Minimum velocity to trigger glide (default: 3) +*/ +#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE +#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION +#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION 5 // Higher = stops faster (1-255) +#endif +#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL +#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL 7 // Glide report interval in ms +#endif +#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER +#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER 1 // Min velocity to trigger glide +#endif + +typedef struct { + int16_t vx; // Current X velocity (Q8 fixed point) + int16_t vy; // Current Y velocity (Q8 fixed point) + int16_t smooth_vx; // Smoothed X velocity (Q8 fixed point) + int16_t smooth_vy; // Smoothed Y velocity (Q8 fixed point) + uint16_t timer; // Timer for interval tracking + bool active; // Is glide currently active +#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING + uint8_t no_output_count; // Counter for consecutive frames with no output +#endif +} scroll_inertia_t; +#endif + +// Trackpad gesture state machine +typedef enum { + TP_IDLE, // No fingers touching + TP_MOVING, // One finger movement = mouse cursor + TP_SCROLLING, // Two finger movement = scroll +} trackpad_state_t; + +typedef struct { + trackpad_state_t state; + uint16_t touch_start_time; // When finger first touched + uint16_t settled_x; // Position after settle time (for tap threshold) + uint16_t settled_y; + uint16_t prev_x; // Previous position (for delta calculation) + uint16_t prev_y; + uint8_t max_finger_count; // Max fingers seen during this gesture + bool settled; // Has the settle time elapsed? + bool pending_click; // Need to send a click release next cycle + uint16_t last_scroll_end; // Time when last scroll gesture ended +} trackpad_gesture_t; + +// Mouse mode functions +report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report); + +// Pointing device driver struct +extern const pointing_device_driver_t navigator_trackpad_pointing_device_driver; diff --git a/drivers/sensors/navigator_trackpad_ptp.c b/drivers/sensors/navigator_trackpad_ptp.c new file mode 100644 index 0000000000..c15ec7c52b --- /dev/null +++ b/drivers/sensors/navigator_trackpad_ptp.c @@ -0,0 +1,166 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +// PTP (Precision Touchpad) mode implementation for Navigator trackpad +// Converts Cirque Gen 6 sensor data to Windows Precision Touchpad HID reports + +#include "navigator_trackpad_ptp.h" +#include "navigator_trackpad_common.h" +#include "precision_trackpad_drivers.h" +#include "quantum.h" +#include "report.h" + +#ifdef PRECISION_TRACKPAD_ENABLE + +// External declaration for PTP report sending +extern void send_trackpad(report_trackpad_t *report); + +#if defined(NAVIGATOR_TRACKPAD_PTP_MODE) + +// PTP task function - converts trackpad touches to PTP reports +static bool navigator_trackpad_ptp_task(void) { + if (!has_motion || !trackpad_init) { + return false; + } + + // Create local snapshot to avoid race condition with callback + cgen6_report_t local_report = ptp_report; + + uint8_t raw_fingers = cirque_gen6_finger_count(&local_report); + + // Initialize PTP report + report_trackpad_t ptp = {0}; + ptp.report_id = REPORT_ID_TRACKPAD; + + // Sensitivity scaling: Track previous RAW sensor position and accumulated scaled position + // Apply sensitivity only to the delta from raw sensor, then accumulate + static uint16_t prev_raw_x[2] = {0, 0}; + static uint16_t prev_raw_y[2] = {0, 0}; + static uint16_t accum_x[2] = {0, 0}; + static uint16_t accum_y[2] = {0, 0}; + static bool was_touching[2] = {false, false}; + + if (local_report.fingers[0].tip) { + uint16_t raw_x = local_report.fingers[0].x; + uint16_t raw_y = local_report.fingers[0].y; + + if (was_touching[0]) { + // Calculate delta from RAW sensor data + int16_t delta_x = (int16_t)(raw_x - prev_raw_x[0]); + int16_t delta_y = (int16_t)(raw_y - prev_raw_y[0]); + + // Apply sensitivity to the delta + delta_x = (int16_t)((float)delta_x * NAVIGATOR_TRACKPAD_SENSITIVITY); + delta_y = (int16_t)((float)delta_y * NAVIGATOR_TRACKPAD_SENSITIVITY); + + // Add scaled delta to accumulated position + int32_t new_x = (int32_t)accum_x[0] + delta_x; + int32_t new_y = (int32_t)accum_y[0] + delta_y; + + // Clamp to valid range + if (new_x < 0) new_x = 0; + if (new_x > 4095) new_x = 4095; + if (new_y < 0) new_y = 0; + if (new_y > 4095) new_y = 4095; + + accum_x[0] = (uint16_t)new_x; + accum_y[0] = (uint16_t)new_y; + } else { + // First touch - initialize accumulated position to raw position + accum_x[0] = raw_x; + accum_y[0] = raw_y; + } + + ptp.contacts[0].confidence = 1; + ptp.contacts[0].tip = 1; + ptp.contacts[0].contact_id = 0; + ptp.contacts[0].x = accum_x[0]; + ptp.contacts[0].y = accum_y[0]; + + prev_raw_x[0] = raw_x; + prev_raw_y[0] = raw_y; + was_touching[0] = true; + } else { + was_touching[0] = false; + } + + if (local_report.fingers[1].tip) { + uint16_t raw_x = local_report.fingers[1].x; + uint16_t raw_y = local_report.fingers[1].y; + + if (was_touching[1]) { + // Calculate delta from RAW sensor data + int16_t delta_x = (int16_t)(raw_x - prev_raw_x[1]); + int16_t delta_y = (int16_t)(raw_y - prev_raw_y[1]); + + // Apply sensitivity to the delta + delta_x = (int16_t)((float)delta_x * NAVIGATOR_TRACKPAD_SENSITIVITY); + delta_y = (int16_t)((float)delta_y * NAVIGATOR_TRACKPAD_SENSITIVITY); + + // Add scaled delta to accumulated position + int32_t new_x = (int32_t)accum_x[1] + delta_x; + int32_t new_y = (int32_t)accum_y[1] + delta_y; + + // Clamp to valid range + if (new_x < 0) new_x = 0; + if (new_x > 4095) new_x = 4095; + if (new_y < 0) new_y = 0; + if (new_y > 4095) new_y = 4095; + + accum_x[1] = (uint16_t)new_x; + accum_y[1] = (uint16_t)new_y; + } else { + // First touch - initialize accumulated position to raw position + accum_x[1] = raw_x; + accum_y[1] = raw_y; + } + + ptp.contacts[1].confidence = 1; + ptp.contacts[1].tip = 1; + ptp.contacts[1].contact_id = 1; + ptp.contacts[1].x = accum_x[1]; + ptp.contacts[1].y = accum_y[1]; + + prev_raw_x[1] = raw_x; + prev_raw_y[1] = raw_y; + was_touching[1] = true; + } else { + was_touching[1] = false; + } + + // Set scan time, contact count, and buttons (after contacts per Microsoft spec) + // Use the sensor's scan_time for accurate gesture velocity calculation + ptp.scan_time = local_report.scan_time; + ptp.contact_count = raw_fingers; + ptp.button1 = (local_report.buttons & 0x01) ? 1 : 0; + ptp.button2 = (local_report.buttons & 0x02) ? 1 : 0; + ptp.button3 = (local_report.buttons & 0x04) ? 1 : 0; + + if (raw_fingers > 0) { + send_trackpad(&ptp); + } + + has_motion = 0; + return true; +} +#else +// Stub for when PTP mode is not enabled +static bool navigator_trackpad_ptp_task(void) { + return false; +} +#endif + +// Internal init function +static void navigator_trackpad_ptp_init(void) { + navigator_trackpad_device_init(); // Common init +} + +// Driver registration +const precision_trackpad_driver_t navigator_trackpad_precision_trackpad_driver = { + .init = navigator_trackpad_ptp_init, + .task = navigator_trackpad_ptp_task, + .set_cpi = navigator_trackpad_set_cpi, + .get_cpi = navigator_trackpad_get_cpi +}; + +#endif // PRECISION_TRACKPAD_ENABLE diff --git a/drivers/sensors/navigator_trackpad_ptp.h b/drivers/sensors/navigator_trackpad_ptp.h new file mode 100644 index 0000000000..532dee997a --- /dev/null +++ b/drivers/sensors/navigator_trackpad_ptp.h @@ -0,0 +1,28 @@ +// Copyright 2025 ZSA Technology Labs, Inc +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "navigator_trackpad_common.h" +#include "precision_trackpad_drivers.h" +#include "report.h" + +// Trackpad sensitivity multiplier (1.0 = native, >1.0 = more sensitive) +// Higher values make cursor/gestures move faster +// Add to your config.h to customize: +// #define NAVIGATOR_TRACKPAD_SENSITIVITY 1.5f +#ifndef NAVIGATOR_TRACKPAD_SENSITIVITY +# define NAVIGATOR_TRACKPAD_SENSITIVITY 1.0f +#endif + +#ifdef PRECISION_TRACKPAD_ENABLE +// Driver registration +extern const precision_trackpad_driver_t navigator_trackpad_precision_trackpad_driver; +#ifndef NAVIGATOR_TRACKPAD_PTP_MODE +#error "NAVIGATOR_TRACKPAD_PTP_MODE must be defined when using the precision trackpad driver" +#endif +#endif + +#ifndef NAVIGATOR_TRACKPAD_SENSITIVITY +# define NAVIGATOR_TRACKPAD_SENSITIVITY 1.3f +#endif diff --git a/keyboards/zsa/common/trackpad.c b/keyboards/zsa/common/trackpad.c deleted file mode 100644 index d80100852e..0000000000 --- a/keyboards/zsa/common/trackpad.c +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 ZSA Technology Labs -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "precision_trackpad.h" -#include "drivers/sensors/navigator_trackpad.h" - -void precision_trackpad_init_kb(void) { - // Initialize the navigator trackpad sensor - navigator_trackpad_device_init(); -} - -bool precision_trackpad_task_kb(void) { - // Call the PTP task to process sensor data and send reports - navigator_trackpad_task(); - return true; // Activity occurred -} diff --git a/keyboards/zsa/common/trackpad.h b/keyboards/zsa/common/trackpad.h deleted file mode 100644 index 65fe640182..0000000000 --- a/keyboards/zsa/common/trackpad.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 ZSA Technology Labs -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "quantum.h" - -#ifdef PRECISION_TRACKPAD_ENABLE - -// Initialize the trackpad -void trackpad_init(void); - -// Update trackpad state and send reports -void trackpad_task(void); - -#endif // PRECISION_TRACKPAD_ENABLE diff --git a/ptp_monitor.py b/ptp_monitor.py deleted file mode 100755 index a4095d670e..0000000000 --- a/ptp_monitor.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3 -""" -Linux HID Monitor for Windows Precision Trackpad Reports -Reads raw HID reports from /dev/hidrawX and parses them - -Usage: sudo python3 ptp_monitor.py /dev/hidrawX -""" -import struct -import sys -import time - -def parse_ptp_report(data): - """Parse our PTP report format""" - if len(data) < 15: - return None - - report_id = data[0] - - # Debug: print first few bytes - if data[0] == 0x01: - print(f"Input Report (0x01): {' '.join(f'{b:02x}' for b in data[:15])}") - - if report_id == 0x01: # REPORT_ID_TRACKPAD - # Contact 0 (bytes 1-5) - c0_flags = data[1] - c0_confidence = (c0_flags >> 0) & 1 - c0_tip = (c0_flags >> 1) & 1 - c0_contact_id = (c0_flags >> 2) & 3 - c0_x = struct.unpack('> 0) & 1 - c1_tip = (c1_flags >> 1) & 1 - c1_contact_id = (c1_flags >> 2) & 3 - c1_x = struct.unpack('> 0) & 1 - btn2 = (buttons >> 1) & 1 - btn3 = (buttons >> 2) & 1 - - return { - 'type': 'input', - 'contact_count': contact_count, - 'scan_time': scan_time, - 'c0': { - 'tip': c0_tip, - 'confidence': c0_confidence, - 'id': c0_contact_id, - 'x': c0_x, - 'y': c0_y - }, - 'c1': { - 'tip': c1_tip, - 'confidence': c1_confidence, - 'id': c1_contact_id, - 'x': c1_x, - 'y': c1_y - }, - 'buttons': {'b1': btn1, 'b2': btn2, 'b3': btn3} - } - - elif report_id in [0x02, 0x03, 0x04, 0x05]: # Feature reports - return { - 'type': 'feature', - 'report_id': report_id, - 'data': data[:15] - } - - return None - -def format_report(report, show_all=False): - """Format report for display""" - if report['type'] == 'input': - lines = [] - lines.append(f"Count: {report['contact_count']}, " - f"Scan: {report['scan_time']:5d}, " - f"Btns: {report['buttons']['b1']}{report['buttons']['b2']}{report['buttons']['b3']}") - - # Only show contacts that are active (or show all if requested) - if report['c0']['tip'] or show_all: - lines.append(f" C0: id={report['c0']['id']}, " - f"conf={report['c0']['confidence']}, " - f"tip={report['c0']['tip']}, " - f"x={report['c0']['x']:4d}, " - f"y={report['c0']['y']:4d}") - - if report['c1']['tip'] or show_all: - lines.append(f" C1: id={report['c1']['id']}, " - f"conf={report['c1']['confidence']}, " - f"tip={report['c1']['tip']}, " - f"x={report['c1']['x']:4d}, " - f"y={report['c1']['y']:4d}") - - return '\n'.join(lines) - - elif report['type'] == 'feature': - hex_str = ' '.join(f'{b:02x}' for b in report['data']) - return f"Feature Report ID: 0x{report['report_id']:02x}, Data: {hex_str}" - -def main(): - if len(sys.argv) < 2: - print("Usage: sudo python3 ptp_monitor.py /dev/hidrawX [--all]") - print("\nOptions:") - print(" --all Show all contacts, even when not touching") - print("\nExample:") - print(" sudo python3 ptp_monitor.py /dev/hidraw4") - sys.exit(1) - - device_path = sys.argv[1] - show_all = '--all' in sys.argv - - print(f"Opening {device_path}...") - print("Touch the trackpad to see reports (Ctrl+C to exit)") - print("=" * 70) - - try: - with open(device_path, 'rb') as f: - report_count = 0 - last_report_time = time.time() - - while True: - data = f.read(64) - if not data: - time.sleep(0.001) - continue - - report = parse_ptp_report(data) - if report: - report_count += 1 - current_time = time.time() - delta_ms = (current_time - last_report_time) * 1000 - last_report_time = current_time - - # Only print if there's activity or we're in verbose mode - if report['type'] == 'input': - if show_all or report['contact_count'] > 0 or report_count % 100 == 0: - print(f"\n[{report_count:5d}] Δ{delta_ms:5.1f}ms") - print(format_report(report, show_all)) - else: - # Always show feature reports - print(f"\n[{report_count:5d}] Δ{delta_ms:5.1f}ms") - print(format_report(report, show_all)) - - except KeyboardInterrupt: - print("\n" + "=" * 70) - print(f"Captured {report_count} reports") - except FileNotFoundError: - print(f"Error: Device {device_path} not found") - print("\nTry: ls -la /sys/class/hidraw/") - sys.exit(1) - except PermissionError: - print(f"Error: Permission denied. Run with sudo:") - print(f" sudo python3 {sys.argv[0]} {device_path}") - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/quantum/precision_trackpad.c b/quantum/precision_trackpad.c index e6942632e2..253e263413 100644 --- a/quantum/precision_trackpad.c +++ b/quantum/precision_trackpad.c @@ -4,24 +4,39 @@ #include "precision_trackpad.h" #include "timer.h" -// Weak functions that can be overridden by keyboard-specific implementations -__attribute__((weak)) void precision_trackpad_init_kb(void) {} -__attribute__((weak)) bool precision_trackpad_task_kb(void) { - return false; -} +// Include the driver implementation +#if defined(PRECISION_TRACKPAD_DRIVER_NAVIGATOR_TRACKPAD) +# include "drivers/sensors/navigator_trackpad_ptp.h" +#endif + +// Driver selection (resolved at compile time by build system) +const precision_trackpad_driver_t *precision_trackpad_driver = + &PRECISION_TRACKPAD_DRIVER(PRECISION_TRACKPAD_DRIVER_NAME); void precision_trackpad_init(void) { - precision_trackpad_init_kb(); + if (precision_trackpad_driver && precision_trackpad_driver->init) { + precision_trackpad_driver->init(); + } } bool precision_trackpad_task(void) { - static uint32_t last_update = 0; - - // Throttle updates to 125Hz (8ms) to avoid overwhelming USB - if (timer_elapsed32(last_update) < 8) { - return false; + // No throttling - let the driver control timing + // PTP mode needs responsive updates for gesture recognition + if (precision_trackpad_driver && precision_trackpad_driver->task) { + return precision_trackpad_driver->task(); } - - last_update = timer_read32(); - return precision_trackpad_task_kb(); + return false; +} + +void precision_trackpad_set_cpi(uint16_t cpi) { + if (precision_trackpad_driver && precision_trackpad_driver->set_cpi) { + precision_trackpad_driver->set_cpi(cpi); + } +} + +uint16_t precision_trackpad_get_cpi(void) { + if (precision_trackpad_driver && precision_trackpad_driver->get_cpi) { + return precision_trackpad_driver->get_cpi(); + } + return 0; } diff --git a/quantum/precision_trackpad.h b/quantum/precision_trackpad.h index 1d9093f8b4..64fc1f2b99 100644 --- a/quantum/precision_trackpad.h +++ b/quantum/precision_trackpad.h @@ -5,6 +5,7 @@ #include #include +#include "precision_trackpad_drivers.h" /** * @brief Initialize the precision trackpad @@ -23,3 +24,20 @@ void precision_trackpad_init(void); * @return true if trackpad activity occurred, false otherwise */ bool precision_trackpad_task(void); + +/** + * @brief Set the trackpad CPI (counts per inch) + * + * @param cpi The desired CPI value + */ +void precision_trackpad_set_cpi(uint16_t cpi); + +/** + * @brief Get the current trackpad CPI + * + * @return Current CPI value + */ +uint16_t precision_trackpad_get_cpi(void); + +// Driver pointer (set at compile time) +extern const precision_trackpad_driver_t *precision_trackpad_driver; diff --git a/quantum/precision_trackpad_drivers.h b/quantum/precision_trackpad_drivers.h new file mode 100644 index 0000000000..63ed6019d8 --- /dev/null +++ b/quantum/precision_trackpad_drivers.h @@ -0,0 +1,20 @@ +// Copyright 2025 ZSA Technology Labs, Inc <@zsa> +// Copyright 2025 Florian Didron +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +// Precision trackpad driver interface +typedef struct { + void (*init)(void); + bool (*task)(void); + void (*set_cpi)(uint16_t); + uint16_t (*get_cpi)(void); +} precision_trackpad_driver_t; + +// Driver registration macros (mirror pointing_device pattern) +#define PRECISION_TRACKPAD_DRIVER_CONCAT(name) name##_precision_trackpad_driver +#define PRECISION_TRACKPAD_DRIVER(name) PRECISION_TRACKPAD_DRIVER_CONCAT(name) diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h index 5e46a863fd..8df6ad9137 100644 --- a/tmk_core/protocol/report.h +++ b/tmk_core/protocol/report.h @@ -40,14 +40,16 @@ enum hid_report_ids { REPORT_ID_JOYSTICK, REPORT_ID_DIGITIZER, - // PTP trackpad uses its own report ID sequence starting at 0x01 - // to match Windows PTP HID specification - REPORT_ID_TRACKPAD = 0x01, - REPORT_ID_TRACKPAD_CONFIG = 0x0A, - REPORT_ID_TRACKPAD_FEATURE = 0x0B, - REPORT_ID_TRACKPAD_MAX_COUNT = 0x0C, - REPORT_ID_TRACKPAD_PTPHQA = 0x0D, - REPORT_ID_COUNT = REPORT_ID_TRACKPAD_PTPHQA + // PTP trackpad uses separate report IDs (per Microsoft PTP specification) + // These don't auto-increment from above, they're on a separate interface + REPORT_ID_TRACKPAD = 0x01, // Input report (multi-touch data) + REPORT_ID_TRACKPAD_MAX_COUNT = 0x02, // Feature: Contact Count Maximum + REPORT_ID_TRACKPAD_CONFIG = 0x03, // Feature: Input Mode configuration + REPORT_ID_TRACKPAD_FEATURE = 0x04, // Feature: Surface/Button switches + REPORT_ID_TRACKPAD_PTPHQA = 0x05, // Feature: Certification blob + + // REPORT_ID_COUNT must be the highest ID from the auto-incrementing sequence + REPORT_ID_COUNT = REPORT_ID_DIGITIZER // Highest report ID value }; #define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT) @@ -260,6 +262,7 @@ typedef struct { } PACKED report_trackpad_contact_t; // Main trackpad input report +// Field order per Microsoft PTP spec: [ReportID][Contacts][ScanTime][Count][Buttons] typedef struct { uint8_t report_id; // Report ID for trackpad report_trackpad_contact_t contacts[2]; // Up to 2 simultaneous contacts diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index 4c38846037..a78cf0afbb 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -385,8 +385,11 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = { #endif #ifdef PRECISION_TRACKPAD_ENABLE +// Include trackpad dimensions +#include "drivers/sensors/navigator_trackpad_common.h" + // Windows Precision Touchpad (PTP) HID Descriptor -// Based on Microsoft PTP specification +// Follows Microsoft PTP specification exactly const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { // Touch Pad Input TLC HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers @@ -394,7 +397,7 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { HID_RI_COLLECTION(8, 0x01), // Application HID_RI_REPORT_ID(8, REPORT_ID_TRACKPAD), - // First finger + // First finger (contact 0) HID_RI_USAGE(8, 0x22), // Finger HID_RI_COLLECTION(8, 0x02), // Logical HID_RI_LOGICAL_MINIMUM(8, 0x00), @@ -416,21 +419,22 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { // X/Y position HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop HID_RI_LOGICAL_MINIMUM(8, 0x00), - HID_RI_LOGICAL_MAXIMUM(16, 0x0384), // 900 (actual Cirque range: 1-897) + HID_RI_LOGICAL_MAXIMUM(16, TRACKPAD_LOGICAL_MAX), // 4095 (12-bit resolution) HID_RI_REPORT_SIZE(8, 0x10), - HID_RI_UNIT_EXPONENT(8, 0x0E), // -2 + HID_RI_UNIT_EXPONENT(8, 0x0E), // -2 (hundredths) HID_RI_UNIT(8, 0x13), // Inch, English Linear HID_RI_PHYSICAL_MINIMUM(8, 0x00), - HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm) + HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_WIDTH), // Physical width in 0.01 inch HID_RI_REPORT_COUNT(8, 0x01), HID_RI_USAGE(8, 0x30), // X HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm) + HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_HEIGHT), // Physical height in 0.01 inch HID_RI_USAGE(8, 0x31), // Y HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_RI_END_COLLECTION(0), // Second finger (identical structure) + HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers (switch back!) HID_RI_USAGE(8, 0x22), // Finger HID_RI_COLLECTION(8, 0x02), // Logical HID_RI_LOGICAL_MINIMUM(8, 0x00), @@ -451,22 +455,21 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop HID_RI_LOGICAL_MINIMUM(8, 0x00), - HID_RI_LOGICAL_MAXIMUM(16, 0x0384), // 900 (actual Cirque range: 1-897) + HID_RI_LOGICAL_MAXIMUM(16, TRACKPAD_LOGICAL_MAX), // 4095 (12-bit resolution) HID_RI_REPORT_SIZE(8, 0x10), HID_RI_UNIT_EXPONENT(8, 0x0E), HID_RI_UNIT(8, 0x13), HID_RI_PHYSICAL_MINIMUM(8, 0x00), - HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm) + HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_WIDTH), // Physical width in 0.01 inch HID_RI_REPORT_COUNT(8, 0x01), HID_RI_USAGE(8, 0x30), // X HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm) + HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_HEIGHT), // Physical height in 0.01 inch HID_RI_USAGE(8, 0x31), // Y HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_RI_END_COLLECTION(0), - // Scan Time - HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers + // Scan Time (comes AFTER contacts per Microsoft spec) HID_RI_UNIT_EXPONENT(8, 0x0C), // -4 HID_RI_UNIT(16, 0x1001), // Seconds HID_RI_LOGICAL_MINIMUM(8, 0x00), @@ -475,10 +478,11 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { HID_RI_PHYSICAL_MAXIMUM(32, 0x0000FFFF), HID_RI_REPORT_SIZE(8, 0x10), HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers HID_RI_USAGE(8, 0x56), // Scan Time HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - // Contact Count + // Contact Count (8 bits, not 4!) HID_RI_USAGE(8, 0x54), // Contact Count HID_RI_LOGICAL_MAXIMUM(8, 0x7F), HID_RI_REPORT_SIZE(8, 0x08),