diff options
Diffstat (limited to 'drivers/sensors/pmw33xx_common.c')
-rw-r--r-- | drivers/sensors/pmw33xx_common.c | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/drivers/sensors/pmw33xx_common.c b/drivers/sensors/pmw33xx_common.c new file mode 100644 index 0000000000..538c40c462 --- /dev/null +++ b/drivers/sensors/pmw33xx_common.c @@ -0,0 +1,218 @@ +// Copyright 2022 Stefan Kerkmann (KarlK90) +// Copyright 2022 Ulrich Spörlein (@uqs) +// Copyright 2021 Alabastard (@Alabastard-64) +// Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> +// Copyright 2019 Sunjun Kim +// Copyright 2020 Ploopy Corporation +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "debug.h" +#include "pmw33xx_common.h" +#include "print.h" +#include "string.h" +#include "wait.h" +#include "spi_master.h" +#include "progmem.h" + +extern const uint8_t pmw33xx_firmware_data[PMW33XX_FIRMWARE_LENGTH] PROGMEM; +extern const uint8_t pmw33xx_firmware_signature[3] PROGMEM; + +static const pin_t cs_pins[] = PMW33XX_CS_PINS; +static bool in_burst[sizeof(cs_pins) / sizeof(pin_t)] = {0}; + +const size_t pmw33xx_number_of_sensors = sizeof(cs_pins) / sizeof(pin_t); + +bool __attribute__((cold)) pmw33xx_upload_firmware(uint8_t sensor); +bool __attribute__((cold)) pmw33xx_check_signature(uint8_t sensor); + +void pmw33xx_set_cpi_all_sensors(uint16_t cpi) { + for (uint8_t sensor = 0; sensor < pmw33xx_number_of_sensors; sensor++) { + pmw33xx_set_cpi(sensor, cpi); + } +} + +bool pmw33xx_spi_start(uint8_t sensor) { + if (!spi_start(cs_pins[sensor], false, 3, PMW33XX_SPI_DIVISOR)) { + spi_stop(); + return false; + } + // tNCS-SCLK, 10ns + wait_us(1); + return true; +} + +bool pmw33xx_write(uint8_t sensor, uint8_t reg_addr, uint8_t data) { + if (!pmw33xx_spi_start(sensor)) { + return false; + } + + if (reg_addr != REG_Motion_Burst) { + in_burst[sensor] = false; + } + + // send address of the register, with MSBit = 1 to indicate it's a write + uint8_t command[2] = {reg_addr | 0x80, data}; + if (spi_transmit(command, sizeof(command)) != SPI_STATUS_SUCCESS) { + return false; + } + + // tSCLK-NCS for write operation is 35us + wait_us(35); + spi_stop(); + + // tSWW/tSWR (=18us) minus tSCLK-NCS. Could be shortened, but it looks like + // a safe lower bound + wait_us(145); + return true; +} + +uint8_t pmw33xx_read(uint8_t sensor, uint8_t reg_addr) { + if (!pmw33xx_spi_start(sensor)) { + return 0; + } + + // send adress of the register, with MSBit = 0 to indicate it's a read + spi_write(reg_addr & 0x7f); + // tSRAD (=160us) + wait_us(160); + uint8_t data = spi_read(); + + // tSCLK-NCS, 120ns + wait_us(1); + spi_stop(); + + // tSRW/tSRR (=20us) mins tSCLK-NCS + wait_us(19); + return data; +} + +bool pmw33xx_check_signature(uint8_t sensor) { + uint8_t signature_dump[3] = { + pmw33xx_read(sensor, REG_Product_ID), + pmw33xx_read(sensor, REG_Inverse_Product_ID), + pmw33xx_read(sensor, REG_SROM_ID), + }; + + return memcmp(pmw33xx_firmware_signature, signature_dump, sizeof(signature_dump)) == 0; +} + +bool pmw33xx_upload_firmware(uint8_t sensor) { + // Datasheet claims we need to disable REST mode first, but during startup + // it's already disabled and we're not turning it on ... + // pmw33xx_write(REG_Config2, 0x00); // disable REST mode + if (!pmw33xx_write(sensor, REG_SROM_Enable, 0x1d)) { + return false; + } + wait_ms(10); + pmw33xx_write(sensor, REG_SROM_Enable, 0x18); + + if (!pmw33xx_spi_start(sensor)) { + return false; + } + + spi_write(REG_SROM_Load_Burst | 0x80); + wait_us(15); + + for (size_t i = 0; i < PMW33XX_FIRMWARE_LENGTH; i++) { + spi_write(pgm_read_byte(pmw33xx_firmware_data + i)); + wait_us(15); + } + wait_us(200); + + pmw33xx_read(sensor, REG_SROM_ID); + pmw33xx_write(sensor, REG_Config2, 0x00); + + return true; +} + +bool pmw33xx_init(uint8_t sensor) { + if (sensor >= pmw33xx_number_of_sensors) { + return false; + } + spi_init(); + + // power up, need to first drive NCS high then low. the datasheet does not + // say for how long, 40us works well in practice. + if (!pmw33xx_spi_start(sensor)) { + return false; + } + wait_us(40); + spi_stop(); + wait_us(40); + + if (!pmw33xx_write(sensor, REG_Power_Up_Reset, 0x5a)) { + return false; + } + wait_ms(50); + + // read registers and discard + pmw33xx_read(sensor, REG_Motion); + pmw33xx_read(sensor, REG_Delta_X_L); + pmw33xx_read(sensor, REG_Delta_X_H); + pmw33xx_read(sensor, REG_Delta_Y_L); + pmw33xx_read(sensor, REG_Delta_Y_H); + + if (!pmw33xx_upload_firmware(sensor)) { + dprintf("PMW33XX (%d): firmware upload failed!\n", sensor); + return false; + } + + spi_stop(); + + wait_ms(10); + pmw33xx_set_cpi(sensor, PMW33XX_CPI); + + wait_ms(1); + + pmw33xx_write(sensor, REG_Config2, 0x00); + pmw33xx_write(sensor, REG_Angle_Tune, CONSTRAIN(ROTATIONAL_TRANSFORM_ANGLE, -127, 127)); + pmw33xx_write(sensor, REG_Lift_Config, PMW33XX_LIFTOFF_DISTANCE); + + if (!pmw33xx_check_signature(sensor)) { + dprintf("PMW33XX (%d): firmware signature verification failed!\n", sensor); + return false; + } + + return true; +} + +pmw33xx_report_t pmw33xx_read_burst(uint8_t sensor) { + pmw33xx_report_t report = {0}; + + if (sensor >= pmw33xx_number_of_sensors) { + return report; + } + + if (!in_burst[sensor]) { + dprintf("PMW33XX (%d): burst\n", sensor); + if (!pmw33xx_write(sensor, REG_Motion_Burst, 0x00)) { + return report; + } + in_burst[sensor] = true; + } + + if (!pmw33xx_spi_start(sensor)) { + return report; + } + + spi_write(REG_Motion_Burst); + wait_us(35); // waits for tSRAD_MOTBR + + spi_receive((uint8_t*)&report, sizeof(report)); + + // panic recovery, sometimes burst mode works weird. + if (report.motion.w & 0b111) { + in_burst[sensor] = false; + } + + spi_stop(); + + if (debug_config.mouse) { + dprintf("PMW33XX (%d): motion: 0x%x dx: %i dy: %i\n", sensor, report.motion.w, report.delta_x, report.delta_y); + } + + report.delta_x *= -1; + report.delta_y *= -1; + + return report; +} |