From 3538335f5a1533597c592432e0e21dd05f9c2dc3 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Sun, 25 Feb 2024 18:41:46 +0000 Subject: [PATCH] input: add a pat912x driver Add an initial input driver for the PixArt PAT9125EL, just core functionalities for now, will add more configuration properties at a later stage. Signed-off-by: Fabio Baltieri --- drivers/input/CMakeLists.txt | 1 + drivers/input/Kconfig | 1 + drivers/input/Kconfig.pat912x | 10 + drivers/input/input_pat912x.c | 268 ++++++++++++++++++++++ dts/bindings/input/pixart,pat912x.yaml | 27 +++ tests/drivers/build_all/input/app.overlay | 8 + 6 files changed, 315 insertions(+) create mode 100644 drivers/input/Kconfig.pat912x create mode 100644 drivers/input/input_pat912x.c create mode 100644 dts/bindings/input/pixart,pat912x.yaml diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 22eca7b2b8..d679c9d0fd 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -18,6 +18,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_GT911 input_gt911.c) zephyr_library_sources_ifdef(CONFIG_INPUT_ITE_IT8XXX2_KBD input_ite_it8xxx2_kbd.c) zephyr_library_sources_ifdef(CONFIG_INPUT_KBD_MATRIX input_kbd_matrix.c) zephyr_library_sources_ifdef(CONFIG_INPUT_NPCX_KBD input_npcx_kbd.c) +zephyr_library_sources_ifdef(CONFIG_INPUT_PAT912X input_pat912x.c) zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c) zephyr_library_sources_ifdef(CONFIG_INPUT_XPT2046 input_xpt2046.c) # zephyr-keep-sorted-stop diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index a61fd94d7d..dd113130b3 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -20,6 +20,7 @@ source "drivers/input/Kconfig.gt911" source "drivers/input/Kconfig.it8xxx2" source "drivers/input/Kconfig.kbd_matrix" source "drivers/input/Kconfig.npcx" +source "drivers/input/Kconfig.pat912x" source "drivers/input/Kconfig.sdl" source "drivers/input/Kconfig.stmpe811" source "drivers/input/Kconfig.xpt2046" diff --git a/drivers/input/Kconfig.pat912x b/drivers/input/Kconfig.pat912x new file mode 100644 index 0000000000..ccbf3f0c77 --- /dev/null +++ b/drivers/input/Kconfig.pat912x @@ -0,0 +1,10 @@ +# Copyright 2024 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_PAT912X + bool "PAT912X miniature optical navigation chip input driver" + default y + depends on DT_HAS_PIXART_PAT912X_ENABLED + select I2C + help + PAT912X miniature optical navigation chip input driver diff --git a/drivers/input/input_pat912x.c b/drivers/input/input_pat912x.c new file mode 100644 index 0000000000..616984ddac --- /dev/null +++ b/drivers/input/input_pat912x.c @@ -0,0 +1,268 @@ +/* + * Copyright 2024 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT pixart_pat912x + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(input_pat912x, CONFIG_INPUT_LOG_LEVEL); + +#define PAT912X_PRODUCT_ID1 0x00 +#define PAT912X_PRODUCT_ID2 0x01 +#define PAT912X_MOTION_STATUS 0x02 +#define PAT912X_DELTA_X_LO 0x03 +#define PAT912X_DELTA_Y_LO 0x04 +#define PAT912X_OPERATION_MODE 0x05 +#define PAT912X_CONFIGURATION 0x06 +#define PAT912X_WRITE_PROTECT 0x09 +#define PAT912X_SLEEP1 0x0a +#define PAT912X_SLEEP2 0x0b +#define PAT912X_RES_X 0x0d +#define PAT912X_RES_Y 0x0e +#define PAT912X_DELTA_XY_HI 0x12 +#define PAT912X_SHUTTER 0x14 +#define PAT912X_FRAME_AVG 0x17 +#define PAT912X_ORIENTATION 0x19 + +#define PRODUCT_ID_PAT9125EL 0x3191 + +#define CONFIGURATION_RESET 0x97 +#define CONFIGURATION_CLEAR 0x17 +#define CONFIGURATION_PD_ENH BIT(3) +#define WRITE_PROTECT_ENABLE 0x00 +#define WRITE_PROTECT_DISABLE 0x5a +#define MOTION_STATUS_MOTION BIT(7) + +#define PAT912X_DATA_SIZE_BITS 12 + +#define RESET_DELAY_MS 2 + +struct pat912x_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec motion_gpio; + int32_t axis_x; + int32_t axis_y; +}; + +struct pat912x_data { + const struct device *dev; + struct k_work motion_work; + struct gpio_callback motion_cb; +}; + +static void pat912x_motion_work_handler(struct k_work *work) +{ + struct pat912x_data *data = CONTAINER_OF( + work, struct pat912x_data, motion_work); + const struct device *dev = data->dev; + const struct pat912x_config *cfg = dev->config; + int32_t x, y; + uint8_t val; + uint8_t xy[2]; + int ret; + + ret = i2c_reg_read_byte_dt(&cfg->i2c, PAT912X_MOTION_STATUS, &val); + if (ret < 0) { + return; + } + + if ((val & MOTION_STATUS_MOTION) == 0x00) { + return; + } + + ret = i2c_burst_read_dt(&cfg->i2c, PAT912X_DELTA_X_LO, xy, sizeof(xy)); + if (ret < 0) { + return; + } + x = xy[0]; + y = xy[1]; + + ret = i2c_reg_read_byte_dt(&cfg->i2c, PAT912X_DELTA_XY_HI, &val); + if (ret < 0) { + return; + } + y |= (val << 8) & 0xf00; + x |= (val << 4) & 0xf00; + + x = sign_extend(x, PAT912X_DATA_SIZE_BITS - 1); + y = sign_extend(y, PAT912X_DATA_SIZE_BITS - 1); + + LOG_DBG("x=%4d y=%4d", x, y); + + if (cfg->axis_x >= 0) { + bool sync = cfg->axis_y < 0; + + input_report_rel(data->dev, cfg->axis_x, x, sync, K_FOREVER); + } + + if (cfg->axis_y >= 0) { + input_report_rel(data->dev, cfg->axis_y, y, true, K_FOREVER); + } + + /* Trigger one more scan in case more data is available. */ + k_work_submit(&data->motion_work); +} + +static void pat912x_motion_handler(const struct device *gpio_dev, + struct gpio_callback *cb, + uint32_t pins) +{ + struct pat912x_data *data = CONTAINER_OF( + cb, struct pat912x_data, motion_cb); + + k_work_submit(&data->motion_work); +} + +static int pat912x_configure(const struct device *dev) +{ + const struct pat912x_config *cfg = dev->config; + uint8_t id[2]; + int ret; + + ret = i2c_burst_read_dt(&cfg->i2c, PAT912X_PRODUCT_ID1, id, sizeof(id)); + if (ret < 0) { + return ret; + } + + if (sys_get_be16(id) != PRODUCT_ID_PAT9125EL) { + LOG_ERR("Invalid product id: %04x", sys_get_be16(id)); + return -ENOTSUP; + } + + /* Software reset */ + + i2c_reg_write_byte_dt(&cfg->i2c, PAT912X_CONFIGURATION, CONFIGURATION_RESET); + /* no ret value check, the device NACKs */ + + k_sleep(K_MSEC(RESET_DELAY_MS)); + + ret = i2c_reg_write_byte_dt(&cfg->i2c, PAT912X_CONFIGURATION, CONFIGURATION_CLEAR); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int pat912x_init(const struct device *dev) +{ + const struct pat912x_config *cfg = dev->config; + struct pat912x_data *data = dev->data; + int ret; + + if (!i2c_is_ready_dt(&cfg->i2c)) { + LOG_ERR("%s is not ready", cfg->i2c.bus->name); + return -ENODEV; + } + + data->dev = dev; + + k_work_init(&data->motion_work, pat912x_motion_work_handler); + + if (!gpio_is_ready_dt(&cfg->motion_gpio)) { + LOG_ERR("%s is not ready", cfg->motion_gpio.port->name); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&cfg->motion_gpio, GPIO_INPUT); + if (ret != 0) { + LOG_ERR("Motion pin configuration failed: %d", ret); + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&cfg->motion_gpio, + GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("Motion interrupt configuration failed: %d", ret); + return ret; + } + + gpio_init_callback(&data->motion_cb, pat912x_motion_handler, + BIT(cfg->motion_gpio.pin)); + + ret = pat912x_configure(dev); + if (ret != 0) { + LOG_ERR("Device configuration failed: %d", ret); + return ret; + } + + ret = gpio_add_callback_dt(&cfg->motion_gpio, &data->motion_cb); + if (ret < 0) { + LOG_ERR("Could not set motion callback: %d", ret); + return ret; + } + + /* Trigger an initial read to clear any pending motion status.*/ + k_work_submit(&data->motion_work); + + ret = pm_device_runtime_enable(dev); + if (ret < 0) { + LOG_ERR("Failed to enable runtime power management"); + return ret; + } + + return 0; +} + +#ifdef CONFIG_PM_DEVICE +static int pat912x_pm_action(const struct device *dev, + enum pm_device_action action) +{ + const struct pat912x_config *cfg = dev->config; + uint8_t val; + int ret; + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + val = CONFIGURATION_PD_ENH; + break; + case PM_DEVICE_ACTION_RESUME: + val = 0; + break; + default: + return -ENOTSUP; + } + + ret = i2c_reg_update_byte_dt(&cfg->i2c, PAT912X_CONFIGURATION, + CONFIGURATION_PD_ENH, val); + if (ret < 0) { + return ret; + } + + return 0; +} +#endif + +#define PAT912X_INIT(n) \ + static const struct pat912x_config pat912x_cfg_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + .motion_gpio = GPIO_DT_SPEC_INST_GET(n, motion_gpios), \ + .axis_x = DT_INST_PROP_OR(n, zephyr_axis_x, -1), \ + .axis_y = DT_INST_PROP_OR(n, zephyr_axis_y, -1), \ + }; \ + \ + static struct pat912x_data pat912x_data_##n; \ + \ + PM_DEVICE_DT_INST_DEFINE(n, pat912x_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(n, pat912x_init, PM_DEVICE_DT_INST_GET(n), \ + &pat912x_data_##n, &pat912x_cfg_##n, \ + POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(PAT912X_INIT) diff --git a/dts/bindings/input/pixart,pat912x.yaml b/dts/bindings/input/pixart,pat912x.yaml new file mode 100644 index 0000000000..0a50161032 --- /dev/null +++ b/dts/bindings/input/pixart,pat912x.yaml @@ -0,0 +1,27 @@ +# Copyright 2024 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: PAT9125EL Miniature Optical Navigation Chip + +compatible: "pixart,pat912x" + +include: i2c-device.yaml + +properties: + motion-gpios: + type: phandle-array + required: true + description: + GPIO connected to the motion pin, active low. + + zephyr,axis-x: + type: int + description: | + The input code for the X axis to report for the device, typically any of + INPUT_REL_*. No report produced for the device X axis if unspecified. + + zephyr,axis-y: + type: int + description: | + The input code for the Y axis to report for the device, typically any of + INPUT_REL_*. No report produced for the device Y axis if unspecified. diff --git a/tests/drivers/build_all/input/app.overlay b/tests/drivers/build_all/input/app.overlay index be37f319a3..dc1d237539 100644 --- a/tests/drivers/build_all/input/app.overlay +++ b/tests/drivers/build_all/input/app.overlay @@ -197,6 +197,14 @@ touch-average-control = <1>; tracking-index = <0>; }; + + pat@5 { + compatible = "pixart,pat912x"; + reg = <0x5>; + motion-gpios = <&test_gpio 0 0>; + zephyr,axis-x = <0>; + zephyr,axis-y = <1>; + }; }; spi@2 {