From 04b6c96e1059c72aeb8938e6c9a7db40af33864b Mon Sep 17 00:00:00 2001 From: "Dylan (Luberry) Kozicki" Date: Mon, 7 Mar 2022 20:31:13 -0500 Subject: [PATCH] initial untested implementation of the pmw3389 driver --- app/drivers/sensor/CMakeLists.txt | 3 +- app/drivers/sensor/pmw3389/CMakeLists.txt | 9 + app/drivers/sensor/pmw3389/pmw3389.c | 290 +++++++++++++++++++ app/drivers/sensor/pmw3389/pmw3389.h | 92 ++++++ app/drivers/sensor/pmw3389/pmw3389_trigger.c | 132 +++++++++ 5 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 app/drivers/sensor/pmw3389/CMakeLists.txt create mode 100644 app/drivers/sensor/pmw3389/pmw3389.c create mode 100644 app/drivers/sensor/pmw3389/pmw3389.h create mode 100644 app/drivers/sensor/pmw3389/pmw3389_trigger.c diff --git a/app/drivers/sensor/CMakeLists.txt b/app/drivers/sensor/CMakeLists.txt index b549320f..5d061280 100644 --- a/app/drivers/sensor/CMakeLists.txt +++ b/app/drivers/sensor/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) -add_subdirectory_ifdef(CONFIG_EC11 ec11) \ No newline at end of file +add_subdirectory_ifdef(CONFIG_EC11 ec11) +add_subdirectory_ifdef(CONFIG_PMW3389 pmw3389) \ No newline at end of file diff --git a/app/drivers/sensor/pmw3389/CMakeLists.txt b/app/drivers/sensor/pmw3389/CMakeLists.txt new file mode 100644 index 00000000..0db5bd48 --- /dev/null +++ b/app/drivers/sensor/pmw3389/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_include_directories(.) + +zephyr_library() + +zephyr_library_sources(pmw3389.c) +zephyr_library_sources_ifdef(CONFIG_PMW3389_TRIGGER pmw3389_trigger.c) \ No newline at end of file diff --git a/app/drivers/sensor/pmw3389/pmw3389.c b/app/drivers/sensor/pmw3389/pmw3389.c new file mode 100644 index 00000000..32bcce63 --- /dev/null +++ b/app/drivers/sensor/pmw3389/pmw3389.c @@ -0,0 +1,290 @@ + +#define DT_DRV_COMPAT pixart_pmw3389 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmw3389.h" + +LOG_MODULE_REGISTER(PMW3389, CONFIG_SENSOR_LOG_LEVEL); + +static int pmw3389_access(const struct device *dev, + uint8_t reg, uint8_t *value) +{ + struct pmw3389_data *data = dev->data; + const struct pmw3389_config *cfg = dev->config; + const struct spi_config *spi_cfg = &cfg->bus_cfg.spi_cfg->spi_conf; + uint8_t access[1] = {reg}; + struct spi_buf bufs[] = { + { + .buf = access, + .len = 1, + }, + { + .buf = value, + .len = 1, + }, + }; + struct spi_buf_set tx = { + .buffers = bufs, + .count = 2, + }; + struct spi_buf_set *rx; + if (reg & PMW3389_WR_MASK > 0) + { + rx = &(struct spi_buf_set){ + .buffers = bufs, + .count = 2, + }; + } + return spi_transceive(data->bus, spi_cfg, &tx, rx); +} +static int pmw3389_read_reg(const struct device *dev, const uint8_t reg, uint8_t *value) +{ + return pmw3389_access(dev, reg, value); +} +static int pmw3389_write_reg(const struct device *dev, const uint8_t reg, const uint8_t value) +{ + return pmw3389_access(dev, reg & PMW3389_WR_MASK, &value); +} + +// converts twos complement data to an int16 +static int16_t pmw3389_raw_to_int16(const uint8_t src[2]) +{ + int16_t res = sys_get_be16(src[2]); + if (res > BIT_MASK(15)) + res -= BIT(16); + return res; +} + +static int pmw3389_read_raw(const struct device *dev, const uint8_t reg_high, const uint8_t reg_low, int16_t *value) +{ + uint8_t raw[2] = {0x0, 0x0}; + int err; + err = pmw3389_read_reg(dev, reg_high, &raw[0]); + if (err) + { + LOG_ERR("could not read high byte at %x", reg_high); + return err; + } + err = pmw3389_read_reg(dev, reg_low, &raw[1]); + if (err) + { + LOG_ERR("could not read low byte at %x", reg_low); + return err; + } + *value = pmw3389_raw_to_int16(raw); + return 0; +} + +static bool pmw3389_spi_check_id(const struct device *dev) +{ + int err; + uint8_t val; + err = pmw3389_read_reg(dev, &val, PMW3389_REG_PID); + if (err) + { + LOG_ERR("could not read PID"); + return false; + } + if (val != PMW3389_PID) + { + LOG_ERR("invalid PID"); + return false; + } + err = pmw3389_read_reg(dev, &val, PMW3389_REG_REV); + if (err) + { + LOG_ERR("could not read REV"); + return false; + } + if (val != PMW3389_REV) + { + LOG_ERR("invalid REV"); + return false; + } + return true; +} + +int pmw3389_spi_init(const struct device *dev) +{ + struct pmw3389_data *data = dev->data; + const struct pmw3389_config *cfg = dev->config; + const struct pmw3389_spi_cfg *spi_cfg = cfg->bus_cfg.spi_cfg; + + if (spi_cfg->cs_gpios_label != NULL) + { + + /* handle SPI CS thru GPIO if it is the case */ + data->cs_ctrl.gpio_dev = device_get_binding(spi_cfg->cs_gpios_label); + if (!data->cs_ctrl.gpio_dev) + { + LOG_ERR("Unable to get GPIO SPI CS device"); + return -ENODEV; + } + } + + return 0; +} + +static int pmw3389_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct pmw3389_data *data = dev->data; + const struct pmw3389_config *cfg = dev->config; + uint16_t dx = 0, dy = 0; + + if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_POS_DX && + chan != SENSOR_CHAN_POS_DY) + return -ENOTSUP; + if (!pmw3389_spi_check_id(dev)) + return -EINVAL; + int err; + if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POS_DX) + { + err = pmw3389_read_raw(dev, PMW3389_REG_DX_H, PMW3389_REG_DX_L, &dx); + if (err) + { + LOG_DBG("could not read x motion"); + return -EIO; + } + } + if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POS_DY) + { + err = pmw3389_read_raw(dev, PMW3389_REG_DY_H, PMW3389_REG_DY_L, &dy); + if (err) + { + LOG_DBG("could not read y motion"); + return -EIO; + } + } + data->dx = dx; + data->dy = dy; + return 0; +} + +static int pmw3389_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct pmw3389_data *data = dev->data; + const struct pmw3389_config *cfg = dev->config; + + switch (chan) + { + case SENSOR_CHAN_POS_DX: + return data->dx; + case SENSOR_CHAN_POS_DY: + return data->dy; + default: + return -ENOTSUP; + } + return 0; +} + +static const struct sensor_driver_api pmw3389_driver_api = { +#ifdef CONFIG_PMW3389_TRIGGER + .trigger_set = pmw3389_trigger_set, +#endif + // .attr_set = pmw3389_attr_set, + .sample_fetch = pmw3389_sample_fetch, + .channel_get = pmw3389_channel_get, +}; + +static int pmw3389_init_chip(const struct device *dev) +{ + struct pmw3389_data *data = dev->data; + return 0; +} + +static int pmw3389_init(const struct device *dev) +{ + const struct pmw3389_config *const config = dev->config; + struct pmw3389_data *data = dev->data; + + data->bus = device_get_binding(config->bus_name); + if (!data->bus) + { + LOG_DBG("master not found: %s", log_strdup(config->bus_name)); + return -EINVAL; + } + + config->bus_init(dev); + + if (pmw3389_init_chip(dev) < 0) + { + LOG_DBG("failed to initialize chip"); + return -EIO; + } + +#ifdef CONFIG_PMW3389_TRIGGER + if (pmw3389_init_interrupt(dev) < 0) + { + LOG_DBG("Failed to initialize interrupt!"); + return -EIO; + } +#endif + + return 0; +} + +#define PMW3389_HAS_CS(n) DT_INST_SPI_DEV_HAS_CS_GPIOS(n) + +#define PMW3389_DATA_SPI_CS(n) \ + { \ + .cs_ctrl = { \ + .gpio_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(n), \ + .gpio_dt_flags = DT_INST_SPI_DEV_CS_GPIOS_FLAGS(n), \ + }, \ + } + +#define PMW3389_DATA_SPI(n) COND_CODE_1(PMW3389_HAS_CS(n), (PMW3389_DATA_SPI_CS(n)), ({})) + +#define PMW3389_SPI_CS_PTR(n) COND_CODE_1(PMW3389_HAS_CS(n), (&(pmw3389_data_##n.cs_ctrl)), (NULL)) + +#define PMW3389_SPI_CS_LABEL(n) \ + COND_CODE_1(PMW3389_HAS_CS(n), (DT_INST_SPI_DEV_CS_GPIOS_LABEL(n)), (NULL)) + +#define PMW3389_SPI_CFG(n) \ + (&(struct pmw3389_spi_cfg){ \ + .spi_conf = \ + { \ + .frequency = DT_INST_PROP(n, spi_max_frequency), \ + .operation = \ + (SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA), \ + .slave = DT_INST_REG_ADDR(n), \ + .cs = PMW3389_SPI_CS_PTR(n), \ + }, \ + .cs_gpios_label = PMW3389_SPI_CS_LABEL(n), \ + }) + +#define PMW3389_GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx) \ + { \ + .port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \ + .pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx), \ + .dt_flags = DT_GPIO_FLAGS_BY_IDX(node_id, prop, idx), \ + } + +#define PMW3389_CONFIG_SPI(n) \ + { \ + .bus_name = DT_INST_BUS_LABEL(n), .bus_init = pmw3389_spi_init, \ + .bus_cfg = {.spi_cfg = PMW3389_SPI_CFG(n)}, \ + COND_CODE_0(DT_INST_NODE_HAS_PROP(n, resolution), (1), (DT_INST_PROP(n, resolution))) \ + COND_CODE_1(CONFIG_PMW3389_TRIGGER, \ + (, PMW3389_GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(n), motswk_gpios, 0), ), \ + ()) \ + } + +#define PMW3389_INST(n) \ + static struct pmw3389_data pmw3389_data_##n = PMW3389_DATA_SPI(n); \ + static const struct pmw3389_config pmw3389_cfg_##n = PMW3389_CONFIG_SPI(n); \ + DEVICE_DT_INST_DEFINE(n, pmw3389_init, device_pm_control_nop, &pmw3389_data_##n, &pmw3389_cfg_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &pmw3389_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PMW3389_INST) \ No newline at end of file diff --git a/app/drivers/sensor/pmw3389/pmw3389.h b/app/drivers/sensor/pmw3389/pmw3389.h new file mode 100644 index 00000000..ee000f30 --- /dev/null +++ b/app/drivers/sensor/pmw3389/pmw3389.h @@ -0,0 +1,92 @@ +#ifndef ZEPHYR_DRIVERS_SENSOR_PIXART_PMW3389_H_ +#define ZEPHYR_DRIVERS_SENSOR_PIXART_PMW3389_H_ + +#include +#include +#include +#include +#include + +#define PMW3389_WR_MASK 0x80 + +#define PMW3389_PID 0x47 +#define PMW3389_REV 0x01 + +#define PMW3389_REG_PID 0x00 +#define PMW3389_REG_REV 0x01 + +#define PMW3389_REG_DX_L 0x2A +#define PMW3389_REG_DX_H 0x2B +#define PMW3389_REG_DY_L 0x2C +#define PMW3389_REG_DY_H 0x2D + +struct pmw3389_gpio_dt_spec { + const struct device *port; + gpio_pin_t pin; + gpio_dt_flags_t dt_flags; +}; + +struct pmw3389_spi_cfg { + struct spi_config spi_conf; + const char *cs_gpios_label; +}; + +union pmw3389_bus_cfg { + struct pmw3389_spi_cfg *spi_cfg; +}; + +struct pmw3389_config { + char *bus_name; + int (*bus_init)(const struct device *dev); + const union pmw3389_bus_cfg bus_cfg; + int resolution; + struct pmw3389_gpio_dt_spec reset; +#if CONFIG_PMW3389_TRIGGER + struct pmw3389_gpio_dt_spec motswk; +#endif // CONFIG_PMW3389_TRIGGER +}; + +struct pmw3389_data; + +struct pmw3389_transfer_function { + int (*read_data)(const struct device *dev, uint16_t *value); +}; + +struct pmw3389_data { + const struct device *bus; + struct spi_cs_control cs_ctrl; + + uint16_t dx; + uint16_t dy; + + const struct pmw3389_transfer_function *hw_tf; + +#ifdef CONFIG_PMW3389_TRIGGER + + struct gpio_callback motswk_gpio_cb; + const struct device *dev; + + sensor_trigger_handler_t handler; + const struct sensor_trigger *trigger; + +#if defined(CONFIG_PMW3389_TRIGGER_OWN_THREAD) + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_PMW3389_THREAD_STACK_SIZE); + struct k_sem gpio_sem; + struct k_thread thread; +#elif defined(CONFIG_PMW3389_TRIGGER_GLOBAL_THREAD) + struct k_work work; +#endif + +#endif /* CONFIG_PMW3389_TRIGGER */ +}; + +int pmw3389_spi_init(const struct device *dev); +#ifdef CONFIG_PMW3389_TRIGGER + +int pmw3389_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); + +int pmw3389_init_interrupt(const struct device *dev); +#endif + +#endif /* ZEPHYR_DRIVERS_SENSOR_PIXART_PMW3389_H_ */ \ No newline at end of file diff --git a/app/drivers/sensor/pmw3389/pmw3389_trigger.c b/app/drivers/sensor/pmw3389/pmw3389_trigger.c new file mode 100644 index 00000000..1ccd74f4 --- /dev/null +++ b/app/drivers/sensor/pmw3389/pmw3389_trigger.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT pixart_pmw3389 + +#include +#include +#include +#include +#include + +#include "pmw3389.h" + +// extern struct pmw3389_data pmw3389_driver; + +#include +LOG_MODULE_DECLARE(PMW3389, CONFIG_SENSOR_LOG_LEVEL); + +static inline void setup_int(const struct device *dev, bool enable) +{ + struct pmw3389_data *data = dev->data; + const struct pmw3389_config *cfg = dev->config; + + if (gpio_pin_interrupt_configure(cfg->motswk.port, cfg->motswk.pin, + enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE)) + { + LOG_WRN("Unable to set MOTSWK GPIO interrupt"); + } +} + +static void pmw3389_motswk_gpio_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + struct pmw3389_data *drv_data = CONTAINER_OF(cb, struct pmw3389_data, motswk_gpio_cb); + + LOG_DBG(""); + + setup_int(drv_data->dev, false); + +#if defined(CONFIG_PMW3389_TRIGGER_OWN_THREAD) + k_sem_give(&drv_data->gpio_sem); +#elif defined(CONFIG_PMW3389_TRIGGER_GLOBAL_THREAD) + k_work_submit(&drv_data->work); +#endif +} + +static void pmw3389_thread_cb(const struct device *dev) +{ + struct pmw3389_data *drv_data = dev->data; + + LOG_DBG("%p", drv_data->handler); + drv_data->handler(dev, drv_data->trigger); + + // Enable once the wall/spam of interrupts is solved + setup_int(dev, true); +} + +#ifdef CONFIG_PMW3389_TRIGGER_OWN_THREAD +static void pmw3389_thread(int dev_ptr, int unused) +{ + const struct device *dev = INT_TO_POINTER(dev_ptr); + struct pmw3389_data *drv_data = dev->data; + + ARG_UNUSED(unused); + + while (1) + { + k_sem_take(&drv_data->gpio_sem, K_FOREVER); + pmw3389_thread_cb(dev); + } +} +#endif + +#ifdef CONFIG_PMW3389_TRIGGER_GLOBAL_THREAD +static void pmw3389_work_cb(struct k_work *work) +{ + struct pmw3389_data *drv_data = CONTAINER_OF(work, struct pmw3389_data, work); + + LOG_DBG(""); + + pmw3389_thread_cb(drv_data->dev); +} +#endif + +int pmw3389_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct pmw3389_data *drv_data = dev->data; + + setup_int(dev, false); + + k_msleep(5); + + drv_data->trigger = trig; + drv_data->handler = handler; + + setup_int(dev, true); + + return 0; +} + +int pmw3389_init_interrupt(const struct device *dev) +{ + struct pmw3389_data *drv_data = dev->data; + const struct pmw3389_config *drv_cfg = dev->config; + + drv_data->dev = dev; + /* setup gpio interrupt */ + + gpio_init_callback(&drv_data->motswk_gpio_cb, pmw3389_motswk_gpio_callback, BIT(drv_cfg->motswk_spec.pin)); + + if (gpio_add_callback(drv_cfg->motswk_spec.port, &drv_data->motswk_gpio_cb) < 0) + { + LOG_DBG("Failed to set MOTSWK callback!"); + return -EIO; + } + +#if defined(CONFIG_PMW3389_TRIGGER_OWN_THREAD) + k_sem_init(&drv_data->gpio_sem, 0, UINT_MAX); + + k_thread_create(&drv_data->thread, drv_data->thread_stack, CONFIG_PMW3389_THREAD_STACK_SIZE, + (k_thread_entry_t)pmw3389_thread, dev, 0, NULL, + K_PRIO_COOP(CONFIG_PMW3389_THREAD_PRIORITY), 0, K_NO_WAIT); +#elif defined(CONFIG_PMW3389_TRIGGER_GLOBAL_THREAD) + k_work_init(&drv_data->work, pmw3389_work_cb); +#endif + + return 0; +} \ No newline at end of file