From e4de46b3b0aa140967835ece0bebef61c22e4a51 Mon Sep 17 00:00:00 2001 From: HorrorTroll Date: Sun, 15 Mar 2026 17:28:40 +0700 Subject: [PATCH] Added PixArt PMW-3325 mouse sensor driver (#26065) * Added support PMW3325 sensor driver * Missing PMW3325 on pointing device document * Suggested changes resolved --- builddefs/common_features.mk | 4 +- docs/features/pointing_device.md | 17 ++ drivers/sensors/pmw3325.c | 179 ++++++++++++++++++++++ drivers/sensors/pmw3325.h | 44 ++++++ quantum/pointing_device/pointing_device.h | 4 + 5 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 drivers/sensors/pmw3325.c create mode 100644 drivers/sensors/pmw3325.h diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index cd4e67a4bd4..b7cb2193fa3 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -125,7 +125,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 paw3222 pmw3320 pmw3360 pmw3389 pimoroni_trackball custom +VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick azoteq_iqs5xx cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 paw3222 pmw3320 pmw3325 pmw3360 pmw3389 pimoroni_trackball 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) @@ -159,6 +159,8 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_gestures.c else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), paw3222) SPI_DRIVER_REQUIRED = yes + else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pmw3325) + SPI_DRIVER_REQUIRED = yes else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball) I2C_DRIVER_REQUIRED = yes else ifneq ($(filter $(strip $(POINTING_DEVICE_DRIVER)),pmw3360 pmw3389),) diff --git a/docs/features/pointing_device.md b/docs/features/pointing_device.md index eedbf59b95b..8319db78231 100644 --- a/docs/features/pointing_device.md +++ b/docs/features/pointing_device.md @@ -320,6 +320,23 @@ The PMW3320 sensor uses a serial type protocol for communication, and requires a The CPI range is 500-3500, in increments of 250. Defaults to 1000 CPI. +### PMW-3325 Sensor + +To use the PMW-3325 sensor, add this to your `rules.mk`: + +```make +POINTING_DEVICE_DRIVER = pmw3325 +``` + +The following pins must be defined in `config.h`: + +| Setting (`config.h`) | Description | Default | +| --------------------- | ------------------------------------------------------------------ | ---------------------------- | +| `PMW3325_CS_PIN` | (Required) The pin connected to the chip select pin of the sensor. | `POINTING_DEVICE_CS_PIN` | +| `PMW3325_SPI_DIVISOR` | (Required) The SPI clock divisor. This is dependent on your MCU. | _not defined_ | + +The CPI range is 100-5000, in increments of 100. Defaults to 2000 CPI. + ### PMW 3360 and PMW 3389 Sensor This drivers supports both the PMW 3360 and PMW 3389 sensor as well as multiple sensors of the same type _per_ controller, so 2 can be attached at the same side for split keyboards (or unsplit keyboards). diff --git a/drivers/sensors/pmw3325.c b/drivers/sensors/pmw3325.c new file mode 100644 index 00000000000..b8eb6ee88cf --- /dev/null +++ b/drivers/sensors/pmw3325.c @@ -0,0 +1,179 @@ +// Copyright 2024 Colin Lam (Ploopy Corporation) +// Copyright 2026 HorrorTroll +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "pmw3325.h" +#include "wait.h" +#include "gpio.h" +#include "spi_master.h" +#include "pointing_device_internal.h" + +#define MSB1 0x80 +#define MSB0 0x7F + +const pointing_device_driver_t pmw3325_pointing_device_driver = { + .init = pmw3325_init, + .get_report = pmw3325_get_report, + .set_cpi = pmw3325_set_cpi, + .get_cpi = pmw3325_get_cpi, +}; + +// Convert a 16-bit twos complement binary-represented number into a +// signed 16-bit integer. +static int16_t convert_twoscomp_16(uint8_t high, uint8_t low) { + uint16_t data = (high << 8) | low; + + if ((data & 0x8000) == 0x8000) { + return -32768 + (data & 0x7FFF); + } else { + return data; + } +} + +void pmw3325_write(uint8_t reg_addr, uint8_t data) { + spi_start(PMW3325_CS_PIN, false, 3, PMW3325_SPI_DIVISOR); + wait_us(1); // tNCS_SCLK + spi_write(reg_addr | MSB1); + wait_us(180); // tSRAD + spi_write(data); + wait_us(1); // tSCLK_NCS + spi_stop(); + wait_us(20); // tSRR +} + +uint8_t pmw3325_read(uint8_t reg_addr) { + spi_start(PMW3325_CS_PIN, false, 3, PMW3325_SPI_DIVISOR); + wait_us(1); // tNCS_SCLK + spi_write(reg_addr & MSB0); + wait_us(180); // tSRAD + uint8_t data = spi_read(); + wait_us(1); // tSCLK_NCS + spi_stop(); + wait_us(20); // tSRR + + return data; +} + +bool pmw3325_init(void) { + wait_ms(50); + gpio_set_pin_output(PMW3325_CS_PIN); + + // CS must be kept high at power-up stage for at least 1ms + gpio_write_pin_low(PMW3325_CS_PIN); + wait_ms(10); + gpio_write_pin_high(PMW3325_CS_PIN); + + spi_init(); + + // reboot + pmw3325_write(0x3A, 0x5A); + wait_ms(10); + + pmw3325_write(0x18, 0x39); + + if (!pmw3325_check_signature()) { + return false; + } + + // read a burst, then discard + pmw3325_read(0x02); + pmw3325_read(0x03); + pmw3325_read(0x04); + pmw3325_read(0x05); + pmw3325_read(0x06); + + // initialize + pmw3325_write(0x78, 0x80); + pmw3325_write(0x79, 0x80); + pmw3325_write(0x14, 0x80); + pmw3325_write(0x20, 0x40); + pmw3325_write(0x1A, 0x40); + pmw3325_write(0x47, 0x00); + pmw3325_write(0x48, 0x01); + pmw3325_write(0x60, 0x01); + pmw3325_write(0x69, 0x03); + pmw3325_write(0x1D, 0x90); + pmw3325_write(0x1B, 0x2E); + pmw3325_write(0x24, 0x05); + pmw3325_write(0x56, 0x00); + pmw3325_write(0x2C, 0x8A); + pmw3325_write(0x2D, 0x58); + pmw3325_write(0x40, 0x80); + pmw3325_write(0x7F, 0x01); + pmw3325_write(0x7A, 0x32); + pmw3325_write(0x6A, 0x93); + pmw3325_write(0x6B, 0x68); + pmw3325_write(0x6C, 0x71); + pmw3325_write(0x6D, 0x50); + pmw3325_write(0x7F, 0x00); + pmw3325_write(0x7F, 0x02); + pmw3325_write(0x29, 0x1C); + pmw3325_write(0x2A, 0x1A); + pmw3325_write(0x2B, 0x90); + pmw3325_write(0x40, 0x80); + pmw3325_write(0x7F, 0x00); + return true; +} + +report_pmw3325_t pmw3325_read_burst(void) { + report_pmw3325_t report = {0}; + + uint8_t motion = pmw3325_read(0x02); + if ((motion & MSB1) == MSB1) { + // Motion detected + uint8_t dx_l = pmw3325_read(0x03); + uint8_t dx_h = pmw3325_read(0x04); + uint8_t dy_l = pmw3325_read(0x05); + uint8_t dy_h = pmw3325_read(0x06); + + report.dx = convert_twoscomp_16(dx_h, dx_l); + report.dy = convert_twoscomp_16(dy_h, dy_l); + } + + return report; +} + +static const uint8_t pmw3325_cpi_lut[50] = { + 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x0F, 0x12, 0x14, 0x16, + 0x19, 0x1B, 0x1D, 0x20, 0x22, 0x24, 0x27, 0x29, 0x2B, 0x2E, + 0x30, 0x32, 0x34, 0x37, 0x39, 0x3B, 0x3E, 0x40, 0x42, 0x45, + 0x47, 0x49, 0x4C, 0x4E, 0x50, 0x53, 0x55, 0x57, 0x5A, 0x5C, + 0x5E, 0x61, 0x63, 0x65, 0x68, 0x6A, 0x6C, 0x6F, 0x71, 0x73 +}; + +void pmw3325_set_cpi(uint16_t cpi) { + uint8_t cpival = CONSTRAIN((cpi / PMW3325_CPI_STEP), (PMW3325_CPI_MIN / PMW3325_CPI_STEP), (PMW3325_CPI_MAX / PMW3325_CPI_STEP)) - 1U; + + pmw3325_write(0x1B, pmw3325_cpi_lut[cpival]); +} + +uint16_t pmw3325_get_cpi(void) { + uint8_t cpival = pmw3325_read(0x1B); + + for (uint8_t cpi = 0; cpi < 50; cpi++) { + if (pmw3325_cpi_lut[cpi] == cpival) { + return (cpi + 1) * PMW3325_CPI_STEP; + } + } + + return 0; +} + +report_mouse_t pmw3325_get_report(report_mouse_t mouse_report) { + report_pmw3325_t data = pmw3325_read_burst(); + + if (data.dx != 0 || data.dy != 0) { + pd_dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy); + mouse_report.x = CONSTRAIN_HID_XY(data.dx); + mouse_report.y = CONSTRAIN_HID_XY(data.dy); + } + + return mouse_report; +} + +bool pmw3325_check_signature(void) { + uint8_t checkval_1 = pmw3325_read(0x00); + uint8_t checkval_2 = pmw3325_read(0x3F); + + return (checkval_1 == 0x43 && checkval_2 == 0xBC); +} diff --git a/drivers/sensors/pmw3325.h b/drivers/sensors/pmw3325.h new file mode 100644 index 00000000000..17a965aa19a --- /dev/null +++ b/drivers/sensors/pmw3325.h @@ -0,0 +1,44 @@ +// Copyright 2021 Colin Lam (Ploopy Corporation) +// Copyright 2026 HorrorTroll +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "pointing_device.h" + +#ifndef PMW3325_CS_PIN +# ifdef POINTING_DEVICE_CS_PIN +# define PMW3325_CS_PIN POINTING_DEVICE_CS_PIN +# else +# error "No chip select pin defined -- missing POINTING_DEVICE_CS_PIN or PMW3325_CS_PIN define" +# endif +#endif + +#ifndef PMW3325_SPI_DIVISOR +# error "No PMW3325 SPI divisor defined -- missing PMW3325_SPI_DIVISOR" +#endif + +typedef struct { + int16_t dx; + int16_t dy; +} report_pmw3325_t; + +extern const pointing_device_driver_t pmw3325_pointing_device_driver; + +bool pmw3325_init(void); +report_pmw3325_t pmw3325_read_burst(void); +void pmw3325_set_cpi(uint16_t cpi); +uint16_t pmw3325_get_cpi(void); +report_mouse_t pmw3325_get_report(report_mouse_t mouse_report); +bool pmw3325_check_signature(void); + +#if !defined(PMW3325_CPI) +# define PMW3325_CPI 2000 +#endif + +#define PMW3325_CPI_MIN 100 +#define PMW3325_CPI_MAX 5000 +#define PMW3325_CPI_STEP 100 +#define CONSTRAIN(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))) diff --git a/quantum/pointing_device/pointing_device.h b/quantum/pointing_device/pointing_device.h index bb0e43af3c4..78c5f326dee 100644 --- a/quantum/pointing_device/pointing_device.h +++ b/quantum/pointing_device/pointing_device.h @@ -43,6 +43,10 @@ typedef struct { # include "spi_master.h" # include "drivers/sensors/paw3222.h" # define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW +#elif defined(POINTING_DEVICE_DRIVER_pmw3325) +# include "spi_master.h" +# include "drivers/sensors/pmw3325.h" +# define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW #elif defined(POINTING_DEVICE_DRIVER_adns9800) # include "spi_master.h" # include "drivers/sensors/adns9800.h"