MustafaMulla29/wireless-mouse-pcb
A tscircuit package created by MustafaMulla29.
- Version
- 1.0.16
- License
- unset
- Stars
- 0
firmware/src/pmw3610.c
/* SPDX-License-Identifier: Apache-2.0 */
#include "pmw3610.h"
#include <errno.h>
#include <stdbool.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#define PMW_PIN_RESET 10
#define PMW_PIN_SCLK 12
#define PMW_PIN_SDIO 14
#define PMW_PIN_NCS 16
#define PMW_PIN_MOTION 17
#define PMW_REG_PRODUCT_ID 0x00
#define PMW_REG_MOTION 0x02
#define PMW_REG_PERFORMANCE 0x11
#define PMW_REG_BURST_READ 0x12
#define PMW_REG_RUN_DOWNSHIFT 0x1B
#define PMW_REG_REST1_RATE 0x1C
#define PMW_REG_REST1_DOWNSHIFT 0x1D
#define PMW_REG_OBSERVATION1 0x2D
#define PMW_REG_SPI_CLOCK 0x41
#define PMW_REG_PAGE 0x7F
#define PMW_REG_RES_STEP 0x85
#define PMW_PRODUCT_ID 0x3E
#define PMW_SPI_CLOCK_ON 0xBA
#define PMW_SPI_CLOCK_OFF 0xB5
static const struct device *const gpio0 = DEVICE_DT_GET(DT_NODELABEL(gpio0));
static inline void spi_delay(void)
{
/* 1 us comfortably exceeds every 120-250 ns bit-level minimum. */
k_busy_wait(1);
}
static void sdio_output(void)
{
(void)gpio_pin_configure(gpio0, PMW_PIN_SDIO, GPIO_OUTPUT_HIGH);
}
static void sdio_input(void)
{
(void)gpio_pin_configure(gpio0, PMW_PIN_SDIO, GPIO_INPUT);
}
static void shift_out(uint8_t value)
{
sdio_output();
for (int bit = 7; bit >= 0; bit--) {
(void)gpio_pin_set(gpio0, PMW_PIN_SDIO, (value >> bit) & 1U);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 1);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
spi_delay();
}
}
/* The PMW3610 holds SCLK high for tSRAD after the final address bit, then
* presents D7 on the following falling edge. This cannot use shift_out()
* because that helper immediately emits the final falling edge.
*/
static void shift_out_read_address(uint8_t reg)
{
sdio_output();
for (int bit = 7; bit >= 1; bit--) {
(void)gpio_pin_set(gpio0, PMW_PIN_SDIO, (reg >> bit) & 1U);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 1);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
spi_delay();
}
(void)gpio_pin_set(gpio0, PMW_PIN_SDIO, reg & 1U);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 1);
spi_delay();
sdio_input();
/* PMW3610 tSRAD is 4 us from the final address rising edge. */
k_busy_wait(4);
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
spi_delay();
}
static uint8_t shift_in(bool more_bytes)
{
uint8_t value = 0;
for (int bit = 7; bit >= 0; bit--) {
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 1);
spi_delay();
value |= (uint8_t)(gpio_pin_get(gpio0, PMW_PIN_SDIO) > 0) << bit;
if (bit > 0 || more_bytes) {
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
spi_delay();
}
}
return value;
}
static void write_raw(uint8_t reg, uint8_t value)
{
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 0);
spi_delay();
shift_out(reg | BIT(7));
shift_out(value);
/* PMW3610 tSCLK-NCS(write) is 10 us. */
k_busy_wait(10);
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 1);
/* PMW3610 tSWW is 30 us. */
k_busy_wait(30);
}
static uint8_t read_reg(uint8_t reg)
{
uint8_t value;
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 0);
spi_delay();
shift_out_read_address(reg & 0x7F);
value = shift_in(false);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 1);
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
k_busy_wait(1);
return value;
}
static void write_sequence(const uint8_t (*pairs)[2], size_t count)
{
write_raw(PMW_REG_SPI_CLOCK, PMW_SPI_CLOCK_ON);
k_busy_wait(300);
for (size_t i = 0; i < count; i++) {
write_raw(pairs[i][0], pairs[i][1]);
}
write_raw(PMW_REG_SPI_CLOCK, PMW_SPI_CLOCK_OFF);
}
static void read_burst4(uint8_t data[4])
{
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 0);
spi_delay();
shift_out_read_address(PMW_REG_BURST_READ);
for (size_t i = 0; i < 4; i++) {
data[i] = shift_in(i + 1 < 4);
}
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 1);
(void)gpio_pin_set(gpio0, PMW_PIN_SCLK, 0);
/* PMW3610 tBEXIT is 250 ns. */
k_busy_wait(1);
}
static int16_t sign_extend_12(uint16_t value)
{
if ((value & BIT(11)) != 0U) {
value |= 0xF000;
}
return (int16_t)value;
}
int pmw3610_set_cpi(uint16_t cpi)
{
uint8_t step;
uint8_t orientation = 0;
if (cpi < 200 || cpi > 3200 || (cpi % 200) != 0) {
return -EINVAL;
}
step = (uint8_t)(cpi / 200);
if (IS_ENABLED(CONFIG_PMW3610_SWAP_XY)) {
orientation |= BIT(7);
}
if (IS_ENABLED(CONFIG_PMW3610_INVERT_X)) {
orientation |= BIT(6);
}
if (IS_ENABLED(CONFIG_PMW3610_INVERT_Y)) {
orientation |= BIT(5);
}
const uint8_t sequence[][2] = {
{PMW_REG_PAGE, 0xFF},
{PMW_REG_RES_STEP, (uint8_t)(orientation | step)},
{PMW_REG_PAGE, 0x00},
};
write_sequence(sequence, ARRAY_SIZE(sequence));
return 0;
}
int pmw3610_init(void)
{
uint8_t observation;
const uint8_t clear_observation[][2] = {
{PMW_REG_OBSERVATION1, 0x00},
};
const uint8_t required_settings[][2] = {
{PMW_REG_PERFORMANCE, 0x0D},
{PMW_REG_RUN_DOWNSHIFT, 0x04},
{PMW_REG_REST1_RATE, 0x04},
{PMW_REG_REST1_DOWNSHIFT, 0x0F},
};
if (!device_is_ready(gpio0)) {
return -ENODEV;
}
(void)gpio_pin_configure(gpio0, PMW_PIN_NCS, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure(gpio0, PMW_PIN_SCLK, GPIO_OUTPUT_LOW);
(void)gpio_pin_configure(gpio0, PMW_PIN_SDIO, GPIO_OUTPUT_HIGH);
(void)gpio_pin_configure(gpio0, PMW_PIN_MOTION, GPIO_INPUT | GPIO_PULL_UP);
(void)gpio_pin_configure(gpio0, PMW_PIN_RESET, GPIO_OUTPUT_LOW);
k_msleep(1);
(void)gpio_pin_set(gpio0, PMW_PIN_RESET, 1);
k_msleep(5);
/* Reset the serial port and wait more than one 150 us frame. */
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 0);
spi_delay();
(void)gpio_pin_set(gpio0, PMW_PIN_NCS, 1);
k_busy_wait(200);
if (read_reg(PMW_REG_PRODUCT_ID) != PMW_PRODUCT_ID) {
return -ENODEV;
}
write_sequence(clear_observation, ARRAY_SIZE(clear_observation));
k_msleep(10);
observation = read_reg(PMW_REG_OBSERVATION1);
if ((observation & 0x0F) != 0x0F) {
return -EIO;
}
/* Required dummy reads after power-up. */
(void)read_reg(0x02);
(void)read_reg(0x03);
(void)read_reg(0x04);
(void)read_reg(0x05);
write_sequence(required_settings, ARRAY_SIZE(required_settings));
return pmw3610_set_cpi(1200);
}
int pmw3610_read_motion(struct pmw3610_motion *motion)
{
uint8_t data[4];
uint16_t x;
uint16_t y;
if (motion == NULL) {
return -EINVAL;
}
motion->valid = false;
motion->overflow = false;
motion->x = 0;
motion->y = 0;
if (gpio_pin_get(gpio0, PMW_PIN_MOTION) > 0) {
return 0;
}
read_burst4(data);
/* PixArt requires a Motion-register write after burst reads to clear
* residual motion before the next sample.
*/
write_raw(PMW_REG_MOTION, 0x00);
motion->valid = (data[0] & BIT(7)) != 0U;
motion->overflow = (data[0] & BIT(4)) != 0U;
x = (uint16_t)data[1] | ((uint16_t)(data[3] & 0xF0) << 4);
y = (uint16_t)data[2] | ((uint16_t)(data[3] & 0x0F) << 8);
motion->x = sign_extend_12(x);
motion->y = sign_extend_12(y);
return 0;
}