mirror of
https://github.com/zsa/qmk_firmware.git
synced 2026-02-26 14:08:34 +00:00
feat(trackball) creates a trackball module
This commit is contained in:
@@ -123,7 +123,7 @@ ifeq ($(strip $(MOUSEKEY_ENABLE)), yes)
|
||||
MOUSE_ENABLE := yes
|
||||
endif
|
||||
|
||||
VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick azoteq_iqs5xx cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3320 pmw3360 pmw3389 pimoroni_trackball navigator_trackball navigator_trackpad custom
|
||||
VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick azoteq_iqs5xx cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3320 pmw3360 pmw3389 pimoroni_trackball navigator_trackpad custom
|
||||
ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
|
||||
ifeq ($(filter $(POINTING_DEVICE_DRIVER),$(VALID_POINTING_DEVICE_DRIVER_TYPES)),)
|
||||
$(call CATASTROPHIC_ERROR,Invalid POINTING_DEVICE_DRIVER,POINTING_DEVICE_DRIVER="$(POINTING_DEVICE_DRIVER)" is not a valid pointing device type)
|
||||
@@ -157,9 +157,6 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
|
||||
SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_gestures.c
|
||||
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball)
|
||||
I2C_DRIVER_REQUIRED = yes
|
||||
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), navigator_trackball)
|
||||
I2C_DRIVER_REQUIRED = yes
|
||||
SRC += drivers/sensors/navigator.c
|
||||
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), navigator_trackpad)
|
||||
I2C_DRIVER_REQUIRED = yes
|
||||
SRC += drivers/sensors/navigator.c
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// This is the QMK driver for the Navigator Trackball. It is comprised of two ICs:
|
||||
// 1. The sci18is606 is a i2c to spi bridge that converts the i2c protocol to the spi protocol. It allows the trackball to
|
||||
// be plugged using the TRRS jack used by ZSA keyboards or any other split keyboard.
|
||||
// 2. The paw3805ek is a high-speed motion detection sensor. It is used to detect the motion of the trackball.
|
||||
|
||||
#include "i2c_master.h"
|
||||
#include "navigator_trackball.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "quantum.h"
|
||||
|
||||
const pointing_device_driver_t navigator_trackball_pointing_device_driver = {
|
||||
.init = navigator_trackball_device_init,
|
||||
.get_report = navigator_trackball_get_report,
|
||||
.get_cpi = navigator_trackball_get_cpi,
|
||||
.set_cpi = navigator_trackball_set_cpi
|
||||
};
|
||||
|
||||
uint8_t current_cpi = NAVIGATOR_TRACKBALL_CPI;
|
||||
|
||||
uint8_t has_motion = 0;
|
||||
|
||||
uint8_t trackball_init = 0;
|
||||
|
||||
deferred_token callback_token = 0;
|
||||
|
||||
// The sequence of commands to configure and boot the paw3805ek sensor.
|
||||
paw3805ek_reg_seq_t paw3805ek_configure_seq[] = {
|
||||
{0x06, 0x80}, // Software reset
|
||||
{0x00, 0x00}, // Request the sensor ID
|
||||
{0x09 | WRITE_REG_BIT, 0x5A}, // Disable the write protection
|
||||
#ifdef MOUSE_EXTENDED_REPORT
|
||||
{0x19 | WRITE_REG_BIT, 0x30}, // Set the sensor orientation, set motion data length to 16 bits
|
||||
#else
|
||||
{0x19 | WRITE_REG_BIT, 0x34}, // Set the sensor orientation, set motion data length to 8 bits
|
||||
#endif
|
||||
//{0x26 | WRITE_REG_BIT, 0x10}, // Enable burst mode
|
||||
{0x09 | WRITE_REG_BIT, 0x00}, // Enable the write protection
|
||||
};
|
||||
|
||||
// A wrapper function for i2c_transmit that adds the address of the bridge chip to the data.
|
||||
i2c_status_t sci18is606_write(uint8_t *data, uint8_t length) {
|
||||
return i2c_transmit(NAVIGATOR_TRACKBALL_ADDRESS, data, length, NAVIGATOR_TRACKBALL_TIMEOUT);
|
||||
}
|
||||
|
||||
// A wrapper function for i2c_receive that adds the address of the bridge chip to the data.
|
||||
i2c_status_t sci18is606_read(uint8_t *data, uint8_t length) {
|
||||
return i2c_receive(NAVIGATOR_TRACKBALL_ADDRESS, data, length, NAVIGATOR_TRACKBALL_TIMEOUT);
|
||||
}
|
||||
|
||||
// A wrapper function that allows to write and optionally read from the bridge chip.
|
||||
i2c_status_t sci18is606_spi_tx(uint8_t *data, uint8_t length, bool read) {
|
||||
i2c_status_t status = sci18is606_write(data, length);
|
||||
wait_us(length * 15);
|
||||
// Read the SPI response if the command expects it
|
||||
if (read) {
|
||||
status = sci18is606_read(data, length);
|
||||
}
|
||||
if (status != I2C_STATUS_SUCCESS) {
|
||||
trackball_init = 0;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Configure the bridge chip to enable SPI mode.
|
||||
i2c_status_t sci18is606_configure(void) {
|
||||
uint8_t spi_conf[2] = {SCI18IS606_CONF_SPI, SCI18IS606_CONF};
|
||||
i2c_status_t status = sci18is606_write(spi_conf, 2);
|
||||
wait_ms(10);
|
||||
if (status != I2C_STATUS_SUCCESS) {
|
||||
trackball_init = 0;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool paw3805ek_set_cpi(void) {
|
||||
|
||||
paw3805ek_reg_seq_t cpi_reg_seq[] = {
|
||||
{0x09 | WRITE_REG_BIT, 0x5A}, // Disable write protection
|
||||
{0x0D | WRITE_REG_BIT, current_cpi},
|
||||
{0x0E | WRITE_REG_BIT, current_cpi},
|
||||
{0x09 | WRITE_REG_BIT, 0x00}, // Enable the write protection
|
||||
};
|
||||
|
||||
// Run the spi sequence to configure the cpi.
|
||||
for (uint8_t i = 0; i < sizeof(cpi_reg_seq) / sizeof(paw3805ek_reg_seq_t); i++) {
|
||||
uint8_t buf[3];
|
||||
buf[0] = NCS_PIN;
|
||||
buf[1] = cpi_reg_seq[i].reg;
|
||||
buf[2] = cpi_reg_seq[i].data;
|
||||
if (sci18is606_spi_tx(buf, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Run the paw3805ek configuration sequence.
|
||||
bool paw3805ek_configure(void) {
|
||||
for (uint8_t i = 0; i < sizeof(paw3805ek_configure_seq) / sizeof(paw3805ek_reg_seq_t); i++) {
|
||||
uint8_t buf[3];
|
||||
buf[0] = NCS_PIN;
|
||||
buf[1] = paw3805ek_configure_seq[i].reg;
|
||||
buf[2] = paw3805ek_configure_seq[i].data;
|
||||
if (sci18is606_spi_tx(buf, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
// Wait for the sensor to restart after the software reset cmd
|
||||
wait_ms(1);
|
||||
|
||||
// Check the sensor ID to validate the spi link after the reset
|
||||
if (i == 1 && buf[1] != PAW3805EK_ID) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assert the CS pin to read the motion register.
|
||||
bool paw3805ek_has_motion(void) {
|
||||
uint8_t motion[3] = {0x01, 0x02, 0x00};
|
||||
if (sci18is606_spi_tx(motion, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
return motion[1] & 0x80;
|
||||
}
|
||||
|
||||
// Read the motion data from the paw3805ek sensor.
|
||||
void paw3804ek_read_motion(report_mouse_t *mouse_report) {
|
||||
#ifdef MOUSE_EXTENDED_REPORT
|
||||
uint8_t delta_x_l[2] = {0x01, 0x03};
|
||||
if (sci18is606_spi_tx(delta_x_l, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t delta_y_l[2] = {0x01, 0x04};
|
||||
if (sci18is606_spi_tx(delta_y_l, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t delta_x_h[2] = {0x01, 0x11};
|
||||
if (sci18is606_spi_tx(delta_x_h, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t delta_y_h[2] = {0x01, 0x12};
|
||||
if (sci18is606_spi_tx(delta_y_h, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_report->x = (int16_t)((delta_x_h[1] << 8) | delta_x_l[1]);
|
||||
mouse_report->y = (int16_t)((delta_y_h[1] << 8) | delta_y_l[1]);
|
||||
#else
|
||||
uint8_t delta_x[2] = {0x01, 0x03};
|
||||
if (sci18is606_spi_tx(delta_x, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t delta_y[2] = {0x01, 0x04};
|
||||
if (sci18is606_spi_tx(delta_y, 3, true) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_report->x = delta_x[1];
|
||||
mouse_report->y = delta_y[1];
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deffered execution callback that periodically checks for motion.
|
||||
uint32_t sci18is606_read_callback(uint32_t trigger_time, void *cb_arg) {
|
||||
if (!trackball_init) {
|
||||
navigator_trackball_device_init();
|
||||
return NAVIGATOR_TRACKBALL_PROBE;
|
||||
}
|
||||
if (paw3805ek_has_motion()) {
|
||||
has_motion = 1;
|
||||
}
|
||||
return NAVIGATOR_TRACKBALL_READ;
|
||||
}
|
||||
|
||||
void navigator_trackball_device_init(void) {
|
||||
i2c_init();
|
||||
if (sci18is606_configure() == I2C_STATUS_SUCCESS) {
|
||||
paw3805ek_configure();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
trackball_init = 1;
|
||||
restore_cpi(current_cpi);
|
||||
if (!callback_token) {
|
||||
// Register the callback to read the trackball motion
|
||||
callback_token = defer_exec(NAVIGATOR_TRACKBALL_READ, sci18is606_read_callback, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
report_mouse_t navigator_trackball_get_report(report_mouse_t mouse_report) {
|
||||
if (!trackball_init) {
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
if (has_motion) {
|
||||
has_motion = 0;
|
||||
paw3804ek_read_motion(&mouse_report);
|
||||
}
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
uint16_t navigator_trackball_get_cpi(void) {
|
||||
return current_cpi;
|
||||
}
|
||||
|
||||
void restore_cpi(uint8_t cpi) {
|
||||
current_cpi = cpi;
|
||||
paw3805ek_set_cpi();
|
||||
}
|
||||
|
||||
void navigator_trackball_set_cpi(uint16_t cpi) {
|
||||
if (cpi == 0) { // Decrease one tick
|
||||
if (current_cpi > NAVIGATOR_TRACKBALL_CPI_TICK) {
|
||||
current_cpi -= NAVIGATOR_TRACKBALL_CPI_TICK;
|
||||
paw3805ek_set_cpi();
|
||||
}
|
||||
} else {
|
||||
if (current_cpi <= NAVIGATOR_TRACKBALL_CPI_MAX - NAVIGATOR_TRACKBALL_CPI_TICK) {
|
||||
current_cpi += NAVIGATOR_TRACKBALL_CPI_TICK;
|
||||
paw3805ek_set_cpi();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "pointing_device.h"
|
||||
|
||||
#ifndef NAVIGATOR_TRACKBALL_ADDRESS
|
||||
# define NAVIGATOR_TRACKBALL_ADDRESS 0x50
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKBALL_CPI
|
||||
# define NAVIGATOR_TRACKBALL_CPI 40
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKBALL_CPI_TICK
|
||||
# define NAVIGATOR_TRACKBALL_CPI_TICK 5
|
||||
#endif
|
||||
|
||||
#define NAVIGATOR_TRACKBALL_CPI_MAX 125
|
||||
|
||||
#ifndef NAVIGATOR_TRACKBALL_TIMEOUT
|
||||
# define NAVIGATOR_TRACKBALL_TIMEOUT 100
|
||||
#endif
|
||||
|
||||
#define NAVIGATOR_TRACKBALL_READ 7
|
||||
#define NAVIGATOR_TRACKBALL_PROBE 1000
|
||||
|
||||
#define NCS_PIN 0x01
|
||||
#define PAW3805EK_ID 0x31
|
||||
|
||||
#define SCI18IS606_CONF 0xDC //00001110b // MSB first, Mode 3, 155kHz
|
||||
|
||||
#define SCI18IS606_RW_SPI 0x00
|
||||
#define SCI18IS606_CONF_SPI 0xF0
|
||||
#define SCI18IS606_CLR_INT 0xF1
|
||||
#define SCI18IS606_GET_ID 0xFE
|
||||
|
||||
#define WRITE_REG_BIT 0x80
|
||||
|
||||
typedef struct {
|
||||
uint8_t reg;
|
||||
uint8_t data;
|
||||
} paw3805ek_reg_seq_t;
|
||||
|
||||
const pointing_device_driver_t navigator_trackball_pointing_device_driver;
|
||||
|
||||
void navigator_trackball_device_init(void);
|
||||
report_mouse_t navigator_trackball_get_report(report_mouse_t mouse_report);
|
||||
uint16_t navigator_trackball_get_cpi(void);
|
||||
void navigator_trackball_set_cpi(uint16_t cpi);
|
||||
void restore_cpi(uint8_t cpi);
|
||||
Submodule modules/zsa updated: 96d0cb94f9...6c50ecf97a
@@ -74,10 +74,6 @@ typedef struct {
|
||||
# include "spi_master.h"
|
||||
# include "drivers/sensors/pmw33xx_common.h"
|
||||
# define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW
|
||||
#elif defined(POINTING_DEVICE_DRIVER_navigator_trackball)
|
||||
# include "i2c_master.h"
|
||||
# include "drivers/sensors/navigator_trackball.h"
|
||||
# include "drivers/sensors/navigator.h"
|
||||
#elif defined(POINTING_DEVICE_DRIVER_navigator_trackpad)
|
||||
# include "i2c_master.h"
|
||||
# include "drivers/sensors/navigator_trackpad.h"
|
||||
|
||||
Reference in New Issue
Block a user