From 688be7274bfaafd0b77ae1d92aef9984ecd5e11f Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Mon, 28 Jul 2025 22:28:05 -0700 Subject: [PATCH 01/11] First version working driver, choppy movement at slow speeds. Signed-off-by: Mariano Uvalle --- .../shields/iqs5xx_dev/Kconfig.defconfig | 2 + app/boards/shields/iqs5xx_dev/Kconfig.shield | 0 app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf | 20 ++ .../shields/iqs5xx_dev/iqs5xx_dev.keymap | 18 + .../shields/iqs5xx_dev/iqs5xx_dev.overlay | 44 +++ .../bindings/input/azoteq,iqs5xx-common.yaml | 20 ++ app/dts/bindings/input/azoteq,iqs5xx-i2c.yaml | 6 + app/dts/bindings/vendor-prefixes.txt | 3 +- app/module/drivers/input/CMakeLists.txt | 2 + app/module/drivers/input/Kconfig | 30 ++ app/module/drivers/input/iqs5xx.c | 324 ++++++++++++++++++ 11 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 app/boards/shields/iqs5xx_dev/Kconfig.defconfig create mode 100644 app/boards/shields/iqs5xx_dev/Kconfig.shield create mode 100644 app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf create mode 100644 app/boards/shields/iqs5xx_dev/iqs5xx_dev.keymap create mode 100644 app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay create mode 100644 app/dts/bindings/input/azoteq,iqs5xx-common.yaml create mode 100644 app/dts/bindings/input/azoteq,iqs5xx-i2c.yaml create mode 100644 app/module/drivers/input/iqs5xx.c diff --git a/app/boards/shields/iqs5xx_dev/Kconfig.defconfig b/app/boards/shields/iqs5xx_dev/Kconfig.defconfig new file mode 100644 index 00000000000..4dbdaf3eb4e --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/Kconfig.defconfig @@ -0,0 +1,2 @@ +config ZMK_KEYBOARD_NAME + default "IQS5xx Dev Kit" \ No newline at end of file diff --git a/app/boards/shields/iqs5xx_dev/Kconfig.shield b/app/boards/shields/iqs5xx_dev/Kconfig.shield new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf new file mode 100644 index 00000000000..166b2affb80 --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf @@ -0,0 +1,20 @@ +CONFIG_LOG=y +CONFIG_ZMK_USB_LOGGING=n +CONFIG_ZMK_RTT_LOGGING=y +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_BLE_PASSKEY_ENTRY=n + +CONFIG_ZMK_SETTINGS_RESET_ON_START=n +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE=50 +CONFIG_ZMK_BLE_THREAD_STACK_SIZE=4096 + +CONFIG_I2C=y +# CONFIG_SPI=y +CONFIG_GPIO=y +CONFIG_PINCTRL=y +CONFIG_ZMK_MOUSE=y +CONFIG_INPUT=y +CONFIG_INPUT_AZOTEQ_IQS5XX=y +# CONFIG_INPUT_PINNACLE=y +# CONFIG_INPUT_PINNACLE_REPORT_INTERVAL_MIN=12 \ No newline at end of file diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.keymap b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.keymap new file mode 100644 index 00000000000..93fef9d50b9 --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.keymap @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < &kp SPACE >; + }; + }; +}; \ No newline at end of file diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay new file mode 100644 index 00000000000..a4c30f92720 --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay @@ -0,0 +1,44 @@ +#include + +/ { + chosen { + zmk,kscan = &kscan_direct; + zmk,matrix-transform = &default_transform; + }; + kscan_direct: kscan_direct { + compatible = "zmk,kscan-gpio-direct"; + wakeup-source; + + input-gpios + = <&gpio1 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <1>; + rows = <1>; + map = < RC(0,0) >; + }; + + tps43_input { + compatible = "zmk,input-listener"; + device = <&tps43>; + }; +}; + +/* +* SDA: P0.26 +* SCL: P0.27 +*/ +&arduino_i2c { + status = "okay"; + tps43: iqs5xx@74 { + status = "okay"; + compatible = "azoteq,iqs5xx"; + reg = <0x74>; + + reset-gpios = <&arduino_header 14 GPIO_ACTIVE_LOW>; /* P1.10 */ + rdy-gpios = <&arduino_header 15 GPIO_ACTIVE_HIGH>; /* P1.11 */ + }; +}; \ No newline at end of file diff --git a/app/dts/bindings/input/azoteq,iqs5xx-common.yaml b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml new file mode 100644 index 00000000000..85cb9a0f9a3 --- /dev/null +++ b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml @@ -0,0 +1,20 @@ +properties: + rdy-gpios: + type: phandle-array + description: "Data Ready pin for the Azoteq IQS5xx trackpad" + + reset-gpios: + type: phandle-array + description: "Reset pin for the Azoteq IQS5xx trackpad" + + # x-invert: + # type: boolean + # description: "If true, invert reported X-axis data (e.g. to handle board rotations)" + + # y-invert: + # type: boolean + # description: "If true, invert reported Y-axis data (e.g. to handle board rotations)" + + # no-taps: + # type: boolean + # description: "Disable tap detection/processing if the firmware or driver supports taps." \ No newline at end of file diff --git a/app/dts/bindings/input/azoteq,iqs5xx-i2c.yaml b/app/dts/bindings/input/azoteq,iqs5xx-i2c.yaml new file mode 100644 index 00000000000..e45f7f88f6f --- /dev/null +++ b/app/dts/bindings/input/azoteq,iqs5xx-i2c.yaml @@ -0,0 +1,6 @@ +description: | + Sensor driver for the Azoteq IQS5XX trackpad, using the I2C interface + +compatible: "azoteq,iqs5xx" + +include: ["i2c-device.yaml", "azoteq,iqs5xx-common.yaml"] \ No newline at end of file diff --git a/app/dts/bindings/vendor-prefixes.txt b/app/dts/bindings/vendor-prefixes.txt index 889ba5842d5..b368ffc44d0 100644 --- a/app/dts/bindings/vendor-prefixes.txt +++ b/app/dts/bindings/vendor-prefixes.txt @@ -1,2 +1,3 @@ zmk ZMK Project -moergo MoErgo \ No newline at end of file +moergo MoErgo +azoteq Azoteq (Pty) Ltd. \ No newline at end of file diff --git a/app/module/drivers/input/CMakeLists.txt b/app/module/drivers/input/CMakeLists.txt index f8f5b2319ad..61d160b0442 100644 --- a/app/module/drivers/input/CMakeLists.txt +++ b/app/module/drivers/input/CMakeLists.txt @@ -3,4 +3,6 @@ zephyr_library_amend() +zephyr_library_sources_ifdef(CONFIG_INPUT_AZOTEQ_IQS5XX iqs5xx.c) + zephyr_library_sources_ifdef(CONFIG_ZMK_INPUT_MOCK input_mock.c) diff --git a/app/module/drivers/input/Kconfig b/app/module/drivers/input/Kconfig index 248805d14db..8700febed5a 100644 --- a/app/module/drivers/input/Kconfig +++ b/app/module/drivers/input/Kconfig @@ -1,6 +1,36 @@ if INPUT +menuconfig INPUT_AZOTEQ_IQS5XX + bool "Azoteq IQS5xx trackpads" + default y + depends on GPIO + depends on I2C + depends on INPUT + depends on DT_HAS_AZOTEQ_IQS5XX_ENABLED + help + Enable driver for Azoteq IQS5xx trackpads. + +if INPUT_AZOTEQ_IQS5XX + +config INPUT_AZOTEQ_IQS5XX_INIT_PRIORITY + int "Azoteq IQS5xx initialization priority" + default INPUT_INIT_PRIORITY + help + Driver initialization priority for the IQS5xx driver. + Lower values initialize earlier. + +# if ZMK_MOUSE +# config ZMK_INPUT_AZOTEQ_IQS5XX_IDLE_SLEEPER +# bool "IQS5xx Sleep linked to ZMK idle state" +# default n +# help +# If enabled, the driver will suspend/resume the IQS5xx based on +# ZMK's activity state changes (e.g. powering down when idle). +# endif # ZMK_MOUSE + +endif # INPUT_AZOTEQ_IQS5XX + config ZMK_INPUT_MOCK bool "Input Mock" default y diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c new file mode 100644 index 00000000000..cebbe12a0ec --- /dev/null +++ b/app/module/drivers/input/iqs5xx.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2024 ZMK Contributors + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT azoteq_iqs5xx + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); + +/* Register addresses from datasheet */ +#define IQS5XX_GESTURE_EVENTS_0 0x000D +#define IQS5XX_GESTURE_EVENTS_1 0x000E +#define IQS5XX_NUM_FINGERS 0x0011 +#define IQS5XX_REL_X 0x0012 /* 2 bytes */ +#define IQS5XX_REL_Y 0x0014 /* 2 bytes */ +#define IQS5XX_ABS_X 0x0016 /* 2 bytes */ +#define IQS5XX_ABS_Y 0x0018 /* 2 bytes */ +#define IQS5XX_TOUCH_STRENGTH 0x001A /* 2 bytes */ +#define IQS5XX_TOUCH_AREA 0x001C + +#define IQS5XX_END_COMM_WINDOW 0xEEEE + +#define IQS5XX_SYSTEM_CONTROL_0 0x0431 +/* System Control 0 bits */ +#define IQS5XX_ACK_RESET BIT(7) +#define IQS5XX_AUTO_ATI BIT(5) +#define IQS5XX_ALP_RESEED BIT(4) +#define IQS5XX_RESEED BIT(3) + +#define IQS5XX_SYSTEM_CONFIG_0 0x058E +/* System Config 0 bits */ +#define IQS5XX_MANUAL_CONTROL BIT(7) +#define IQS5XX_SETUP_COMPLETE BIT(6) +#define IQS5XX_WDT BIT(5) +#define IQS5XX_SW_INPUT_EVENT BIT(4) +#define IQS5XX_ALP_REATI BIT(3) +#define IQS5XX_REATI BIT(2) +#define IQS5XX_SW_INPUT_SELECT BIT(1) +#define IQS5XX_SW_INPUT BIT(0) + +#define IQS5XX_SYSTEM_CONFIG_1 0x058F +/* System Config 1 bits */ +#define IQS5XX_EVENT_MODE BIT(0) +#define IQS5XX_GESTURE_EVENT BIT(1) +#define IQS5XX_TP_EVENT BIT(2) +#define IQS5XX_REATI_EVENT BIT(3) +#define IQS5XX_ALP_PROX_EVENT BIT(4) +#define IQS5XX_SNAP_EVENT BIT(5) +#define IQS5XX_TOUCH_EVENT BIT(6) +#define IQS5XX_PROX_EVENT BIT(7) + +// Filter settings register. +#define IQS5XX_FILTER_SETTINGS 0x0632 +// Filter settings bits. +#define IQS5XX_IIR_FILTER BIT(0) +#define IQS5XX_MAV_FILTER BIT(1) +#define IQS5XX_IIR_SELECT BIT(2) +#define IQS5XX_ALP_COUNT_FILTER BIT(3) + +#define IQS5XX_SYSTEM_INFO_0 0x000F +/* System Info 0 bits */ +#define IQS5XX_SHOW_RESET BIT(7) +#define IQS5XX_ALP_REATI_OCCURRED BIT(6) +#define IQS5XX_ALP_ATI_ERROR BIT(5) +#define IQS5XX_REATI_OCCURRED BIT(4) +#define IQS5XX_ATI_ERROR BIT(3) + +#define IQS5XX_SYSTEM_INFO_1 0x0010 +/* System Info 1 bits */ +#define IQS5XX_SWITCH_STATE BIT(5) +#define IQS5XX_SNAP_TOGGLE BIT(4) +#define IQS5XX_RR_MISSED BIT(3) +#define IQS5XX_TOO_MANY_FINGERS BIT(2) +#define IQS5XX_PALM_DETECT BIT(1) +#define IQS5XX_TP_MOVEMENT BIT(0) + +struct iqs5xx_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec rdy_gpio; + struct gpio_dt_spec reset_gpio; +}; + +struct iqs5xx_data { + const struct device *dev; + struct gpio_callback rdy_cb; + struct k_work work; + bool initialized; +}; + +static int iqs5xx_read_reg16(const struct device *dev, uint16_t reg, uint16_t *val) { + const struct iqs5xx_config *config = dev->config; + uint8_t buf[2]; + uint8_t reg_buf[2] = {reg >> 8, reg & 0xFF}; + int ret; + + ret = i2c_write_read_dt(&config->i2c, reg_buf, sizeof(reg_buf), buf, sizeof(buf)); + if (ret < 0) { + return ret; + } + + *val = (buf[0] << 8) | buf[1]; + return 0; +} + +static int iqs5xx_read_reg8(const struct device *dev, uint16_t reg, uint8_t *val) { + const struct iqs5xx_config *config = dev->config; + uint8_t reg_buf[2] = {reg >> 8, reg & 0xFF}; + + return i2c_write_read_dt(&config->i2c, reg_buf, sizeof(reg_buf), val, 1); +} + +static int iqs5xx_write_reg8(const struct device *dev, uint16_t reg, uint8_t val) { + const struct iqs5xx_config *config = dev->config; + uint8_t buf[3] = {reg >> 8, reg & 0xFF, val}; + + return i2c_write_dt(&config->i2c, buf, sizeof(buf)); +} + +static int iqs5xx_end_comm_window(const struct device *dev) { + const struct iqs5xx_config *config = dev->config; + uint8_t buf[3] = {IQS5XX_END_COMM_WINDOW >> 8, IQS5XX_END_COMM_WINDOW & 0xFF, 0x00}; + + return i2c_write_dt(&config->i2c, buf, sizeof(buf)); +} + +static void iqs5xx_work_handler(struct k_work *work) { + struct iqs5xx_data *data = CONTAINER_OF(work, struct iqs5xx_data, work); + const struct device *dev = data->dev; + uint8_t sys_info_0, sys_info_1, num_fingers; + int16_t rel_x, rel_y; + int ret; + + /* Read system info registers */ + ret = iqs5xx_read_reg8(dev, IQS5XX_SYSTEM_INFO_0, &sys_info_0); + if (ret < 0) { + LOG_ERR("Failed to read system info 0: %d", ret); + goto end_comm; + } + + ret = iqs5xx_read_reg8(dev, IQS5XX_SYSTEM_INFO_1, &sys_info_1); + if (ret < 0) { + LOG_ERR("Failed to read system info 1: %d", ret); + goto end_comm; + } + + /* Handle reset indication */ + if (sys_info_0 & IQS5XX_SHOW_RESET) { + LOG_INF("Device reset detected"); + /* Acknowledge reset */ + iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONTROL_0, IQS5XX_ACK_RESET); + goto end_comm; + } + + /* Check for trackpad movement */ + if (sys_info_1 & IQS5XX_TP_MOVEMENT) { + ret = iqs5xx_read_reg8(dev, IQS5XX_NUM_FINGERS, &num_fingers); + if (ret < 0) { + LOG_ERR("Failed to read number of fingers: %d", ret); + goto end_comm; + } + + /* Read relative movement data */ + ret = iqs5xx_read_reg16(dev, IQS5XX_REL_X, (uint16_t *)&rel_x); + if (ret < 0) { + LOG_ERR("Failed to read relative X: %d", ret); + goto end_comm; + } + + ret = iqs5xx_read_reg16(dev, IQS5XX_REL_Y, (uint16_t *)&rel_y); + if (ret < 0) { + LOG_ERR("Failed to read relative Y: %d", ret); + goto end_comm; + } + + /* Report movement if there's actually movement */ + if (rel_x != 0 || rel_y != 0) { + LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); + + /* Send pointer movement event */ + input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); + input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); + } + } + +end_comm: + /* End communication window */ + iqs5xx_end_comm_window(dev); +} + +static void iqs5xx_rdy_handler(const struct device *port, struct gpio_callback *cb, + gpio_port_pins_t pins) { + struct iqs5xx_data *data = CONTAINER_OF(cb, struct iqs5xx_data, rdy_cb); + + k_work_submit(&data->work); +} + +static int iqs5xx_setup_device(const struct device *dev) { + int ret; + + /* Enable event mode and trackpad events */ + ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_1, IQS5XX_EVENT_MODE | IQS5XX_TP_EVENT); + if (ret < 0) { + LOG_ERR("Failed to configure event mode: %d", ret); + return ret; + } + + // Set filter settings with: + // - IIR filter enabled + // - MAV filter enabled + // - IIR select disabled (dynamic IIR) + // - ALP count filter enabled + ret = iqs5xx_write_reg8(dev, IQS5XX_FILTER_SETTINGS, + IQS5XX_IIR_FILTER | IQS5XX_MAV_FILTER | IQS5XX_ALP_COUNT_FILTER); + + /* Configure system settings */ + ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_0, IQS5XX_SETUP_COMPLETE | IQS5XX_WDT); + if (ret < 0) { + LOG_ERR("Failed to configure system: %d", ret); + return ret; + } + + /* End communication window */ + ret = iqs5xx_end_comm_window(dev); + if (ret < 0) { + LOG_ERR("Failed to end comm window: %d", ret); + return ret; + } + + return 0; +} + +static int iqs5xx_init(const struct device *dev) { + const struct iqs5xx_config *config = dev->config; + struct iqs5xx_data *data = dev->data; + int ret; + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("I2C device not ready"); + return -ENODEV; + } + + data->dev = dev; + k_work_init(&data->work, iqs5xx_work_handler); + + /* Configure reset GPIO if available */ + if (config->reset_gpio.port) { + if (!gpio_is_ready_dt(&config->reset_gpio)) { + LOG_ERR("Reset GPIO not ready"); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + LOG_ERR("Failed to configure reset GPIO: %d", ret); + return ret; + } + + /* Reset the device */ + gpio_pin_set_dt(&config->reset_gpio, 1); + k_msleep(1); + gpio_pin_set_dt(&config->reset_gpio, 0); + k_msleep(10); + } + + /* Configure RDY GPIO */ + if (!gpio_is_ready_dt(&config->rdy_gpio)) { + LOG_ERR("RDY GPIO not ready"); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->rdy_gpio, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to configure RDY GPIO: %d", ret); + return ret; + } + + gpio_init_callback(&data->rdy_cb, iqs5xx_rdy_handler, BIT(config->rdy_gpio.pin)); + ret = gpio_add_callback(config->rdy_gpio.port, &data->rdy_cb); + if (ret < 0) { + LOG_ERR("Failed to add RDY callback: %d", ret); + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&config->rdy_gpio, GPIO_INT_EDGE_RISING); + if (ret < 0) { + LOG_ERR("Failed to configure RDY interrupt: %d", ret); + return ret; + } + + /* Wait for device to be ready */ + k_msleep(100); + + /* Setup device configuration */ + ret = iqs5xx_setup_device(dev); + if (ret < 0) { + LOG_ERR("Failed to setup device: %d", ret); + return ret; + } + + data->initialized = true; + LOG_INF("IQS5xx trackpad initialized"); + + return 0; +} + +// Replace CONFIG_INPUT_INIT_PRIORITY with the azoteq specific value. +#define IQS5XX_INIT(n) \ + static struct iqs5xx_data iqs5xx_data_##n; \ + static const struct iqs5xx_config iqs5xx_config_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + .rdy_gpio = GPIO_DT_SPEC_INST_GET(n, rdy_gpios), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \ + }; \ + DEVICE_DT_INST_DEFINE(n, iqs5xx_init, NULL, &iqs5xx_data_##n, &iqs5xx_config_##n, POST_KERNEL, \ + CONFIG_INPUT_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(IQS5XX_INIT) \ No newline at end of file From 83eaaab77cac9f93ebb641cc14a1f98a492cdaa7 Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Wed, 30 Jul 2025 02:41:11 +0000 Subject: [PATCH 02/11] Handle left and right click as one and two finger taps respectively. --- app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf | 13 +- app/module/drivers/input/iqs5xx.c | 144 +++++++++++++++++- 2 files changed, 145 insertions(+), 12 deletions(-) diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf index 166b2affb80..256b50f3153 100644 --- a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf @@ -4,17 +4,20 @@ CONFIG_ZMK_RTT_LOGGING=y CONFIG_ZMK_LOG_LEVEL_DBG=y CONFIG_ZMK_BLE_PASSKEY_ENTRY=n -CONFIG_ZMK_SETTINGS_RESET_ON_START=n +CONFIG_ZMK_BLE=y +CONFIG_ZMK_USB=y +CONFIG_ZMK_SETTINGS_RESET_ON_START=y CONFIG_MAIN_STACK_SIZE=8192 CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE=50 CONFIG_ZMK_BLE_THREAD_STACK_SIZE=4096 +# Might not be needed. +CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y + CONFIG_I2C=y -# CONFIG_SPI=y CONFIG_GPIO=y CONFIG_PINCTRL=y CONFIG_ZMK_MOUSE=y +CONFIG_ZMK_POINTING=y CONFIG_INPUT=y -CONFIG_INPUT_AZOTEQ_IQS5XX=y -# CONFIG_INPUT_PINNACLE=y -# CONFIG_INPUT_PINNACLE_REPORT_INTERVAL_MIN=12 \ No newline at end of file +CONFIG_INPUT_AZOTEQ_IQS5XX=y \ No newline at end of file diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index cebbe12a0ec..6098273f9bd 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -5,6 +5,7 @@ #define DT_DRV_COMPAT azoteq_iqs5xx +#include #include #include #include @@ -15,8 +16,6 @@ LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); /* Register addresses from datasheet */ -#define IQS5XX_GESTURE_EVENTS_0 0x000D -#define IQS5XX_GESTURE_EVENTS_1 0x000E #define IQS5XX_NUM_FINGERS 0x0011 #define IQS5XX_REL_X 0x0012 /* 2 bytes */ #define IQS5XX_REL_Y 0x0014 /* 2 bytes */ @@ -25,6 +24,9 @@ LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); #define IQS5XX_TOUCH_STRENGTH 0x001A /* 2 bytes */ #define IQS5XX_TOUCH_AREA 0x001C +#define IQS5XX_BOTTOM_BETA 0x0637 +#define IQS5XX_STATIONARY_THRESH 0x0672 + #define IQS5XX_END_COMM_WINDOW 0xEEEE #define IQS5XX_SYSTEM_CONTROL_0 0x0431 @@ -81,6 +83,29 @@ LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); #define IQS5XX_PALM_DETECT BIT(1) #define IQS5XX_TP_MOVEMENT BIT(0) +// These 2 registers have the same bit map. +// The first one configures the gestures, +// the second one reports gesture events at runtime. +#define IQS5XX_SINGLE_FINGER_GESTURES_CONF 0x06B7 +#define IQS5XX_GESTURE_EVENTS_0 0x000D +// Single finger gesture identifiers. +#define IQS5XX_SINGLE_TAP BIT(0) +#define IQS5XX_PRESS_AND_HOLD BIT(1) +#define IQS5XX_SWIPE_LEFT BIT(2) +#define IQS5XX_SWIPE_RIGHT BIT(3) +#define IQS5XX_SWIPE_UP BIT(4) +#define IQS5XX_SWIPE_DOWN BIT(5) + +// These 2 registers have the same bit map. +// The first one configures the gestures, +// the second one reports gesture events at runtime. +#define IQS5XX_MULTI_FINGER_GESTURES_CONF 0x06B8 +#define IQS5XX_GESTURE_EVENTS_1 0x000E +// Multi finger gesture identifiers. +#define IQS5XX_TWO_FINGER_TAP BIT(0) +#define IQS5XX_SCROLL BIT(1) +#define IQS5XX_ZOOM BIT(2) + struct iqs5xx_config { struct i2c_dt_spec i2c; struct gpio_dt_spec rdy_gpio; @@ -91,7 +116,11 @@ struct iqs5xx_data { const struct device *dev; struct gpio_callback rdy_cb; struct k_work work; + struct k_work_delayable button_release_work; + // TODO: Pack flags into a bitfield to save space. bool initialized; + // Flag to indicate if the button was pressed in a previous cycle. + uint8_t buttons_pressed; }; static int iqs5xx_read_reg16(const struct device *dev, uint16_t reg, uint16_t *val) { @@ -130,10 +159,27 @@ static int iqs5xx_end_comm_window(const struct device *dev) { return i2c_write_dt(&config->i2c, buf, sizeof(buf)); } +static void iqs5xx_button_release_work_handler(struct k_work *work) { + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct iqs5xx_data *data = CONTAINER_OF(dwork, struct iqs5xx_data, button_release_work); + + // TODO: This loop should only deactivate one button. + // Log a warning when that is not the case. + for (int i = 0; i < 3; i++) { + LOG_INF("Releasing synthetic button"); + if (data->buttons_pressed & BIT(i)) { + input_report_key(data->dev, INPUT_BTN_0 + i, 0, true, K_FOREVER); + // Turn off the bit. + // NOTE: This is a potential race. + data->buttons_pressed ^= BIT(i); + } + } +} + static void iqs5xx_work_handler(struct k_work *work) { struct iqs5xx_data *data = CONTAINER_OF(work, struct iqs5xx_data, work); const struct device *dev = data->dev; - uint8_t sys_info_0, sys_info_1, num_fingers; + uint8_t sys_info_0, sys_info_1, gesture_events_0, gesture_events_1, num_fingers; int16_t rel_x, rel_y; int ret; @@ -150,6 +196,18 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } + ret = iqs5xx_read_reg8(dev, IQS5XX_GESTURE_EVENTS_0, &gesture_events_0); + if (ret < 0) { + LOG_ERR("Failed to read gesture events: %d", ret); + goto end_comm; + } + + ret = iqs5xx_read_reg8(dev, IQS5XX_GESTURE_EVENTS_1, &gesture_events_1); + if (ret < 0) { + LOG_ERR("Failed to read gesture events 1: %d", ret); + goto end_comm; + } + /* Handle reset indication */ if (sys_info_0 & IQS5XX_SHOW_RESET) { LOG_INF("Device reset detected"); @@ -158,7 +216,10 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } - /* Check for trackpad movement */ + // Handle movement and gestures. + // + // Each one of these branches needs to make send the last report it makes as + // sync to ensure that the input subsystem process things in order. if (sys_info_1 & IQS5XX_TP_MOVEMENT) { ret = iqs5xx_read_reg8(dev, IQS5XX_NUM_FINGERS, &num_fingers); if (ret < 0) { @@ -184,9 +245,29 @@ static void iqs5xx_work_handler(struct k_work *work) { LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); /* Send pointer movement event */ - input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); - input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); + ret = input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); + ret = input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); } + } else if (gesture_events_0 & IQS5XX_SINGLE_TAP) { + // Cancel any pending release. + k_work_cancel_delayable(&data->button_release_work); + + // Press the button immediately. + input_report_key(dev, INPUT_BTN_0, 1, true, K_FOREVER); + data->buttons_pressed |= BIT(0); + + // Schedule release after 100ms. + k_work_schedule(&data->button_release_work, K_MSEC(100)); + } else if (gesture_events_1 & IQS5XX_TWO_FINGER_TAP) { + // Cancel any pending release. + k_work_cancel_delayable(&data->button_release_work); + + // Press the button immediately. + input_report_key(dev, INPUT_BTN_1, 1, true, K_FOREVER); + data->buttons_pressed |= BIT(1); + + // Schedule release after 100ms. + k_work_schedule(&data->button_release_work, K_MSEC(100)); } end_comm: @@ -205,12 +286,42 @@ static int iqs5xx_setup_device(const struct device *dev) { int ret; /* Enable event mode and trackpad events */ - ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_1, IQS5XX_EVENT_MODE | IQS5XX_TP_EVENT); + ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_1, + IQS5XX_EVENT_MODE | IQS5XX_TP_EVENT | IQS5XX_GESTURE_EVENT); if (ret < 0) { LOG_ERR("Failed to configure event mode: %d", ret); return ret; } + ret = iqs5xx_write_reg8(dev, IQS5XX_BOTTOM_BETA, 10); + if (ret < 0) { + LOG_ERR("Failed to set bottom beta: %d", ret); + return ret; + } + + /* Read the current value of bottom beta and log it */ + uint8_t bottom_beta; + ret = iqs5xx_read_reg8(dev, IQS5XX_BOTTOM_BETA, &bottom_beta); + if (ret < 0) { + LOG_ERR("Failed to read bottom beta: %d", ret); + return ret; + } + LOG_INF("Current bottom beta: %d", bottom_beta); + + ret = iqs5xx_write_reg8(dev, IQS5XX_STATIONARY_THRESH, 5); + if (ret < 0) { + LOG_ERR("Failed to set bottom stationary threshold: %d", ret); + return ret; + } + + uint8_t stat_threshold; + ret = iqs5xx_read_reg8(dev, IQS5XX_STATIONARY_THRESH, &stat_threshold); + if (ret < 0) { + LOG_ERR("Failed to read bottom stat_threshold: %d", ret); + return ret; + } + LOG_INF("Current stat thresh: %d", stat_threshold); + // Set filter settings with: // - IIR filter enabled // - MAV filter enabled @@ -218,6 +329,24 @@ static int iqs5xx_setup_device(const struct device *dev) { // - ALP count filter enabled ret = iqs5xx_write_reg8(dev, IQS5XX_FILTER_SETTINGS, IQS5XX_IIR_FILTER | IQS5XX_MAV_FILTER | IQS5XX_ALP_COUNT_FILTER); + if (ret < 0) { + LOG_ERR("Failed to configure filter settings: %d", ret); + return ret; + } + + // Configure single finger gestures: + ret = iqs5xx_write_reg8(dev, IQS5XX_SINGLE_FINGER_GESTURES_CONF, IQS5XX_SINGLE_TAP); + if (ret < 0) { + LOG_ERR("Failed to configure single finger gestures: %d", ret); + return ret; + } + + // Configure multi finger gestures: + ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, IQS5XX_TWO_FINGER_TAP); + if (ret < 0) { + LOG_ERR("Failed to configure multi finger gestures: %d", ret); + return ret; + } /* Configure system settings */ ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_0, IQS5XX_SETUP_COMPLETE | IQS5XX_WDT); @@ -248,6 +377,7 @@ static int iqs5xx_init(const struct device *dev) { data->dev = dev; k_work_init(&data->work, iqs5xx_work_handler); + k_work_init_delayable(&data->button_release_work, iqs5xx_button_release_work_handler); /* Configure reset GPIO if available */ if (config->reset_gpio.port) { From f18dcc64b0fca9d7760af85c299c61b0ffdd1550 Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Wed, 30 Jul 2025 07:38:09 +0000 Subject: [PATCH 03/11] Implement scroll with a driver side accumulated divisor. --- .../shields/iqs5xx_dev/iqs5xx_dev.overlay | 2 +- app/module/drivers/input/iqs5xx.c | 100 ++++++++++++------ 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay index a4c30f92720..1b4ef22c8df 100644 --- a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay @@ -21,7 +21,7 @@ map = < RC(0,0) >; }; - tps43_input { + tps43_input: tps43_input { compatible = "zmk,input-listener"; device = <&tps43>; }; diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index 6098273f9bd..fc303d31519 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -5,6 +5,7 @@ #define DT_DRV_COMPAT azoteq_iqs5xx +#include #include #include #include @@ -121,6 +122,9 @@ struct iqs5xx_data { bool initialized; // Flag to indicate if the button was pressed in a previous cycle. uint8_t buttons_pressed; + // Scroll accumulators. + int16_t scroll_x_acc; + int16_t scroll_y_acc; }; static int iqs5xx_read_reg16(const struct device *dev, uint16_t reg, uint16_t *val) { @@ -180,7 +184,6 @@ static void iqs5xx_work_handler(struct k_work *work) { struct iqs5xx_data *data = CONTAINER_OF(work, struct iqs5xx_data, work); const struct device *dev = data->dev; uint8_t sys_info_0, sys_info_1, gesture_events_0, gesture_events_1, num_fingers; - int16_t rel_x, rel_y; int ret; /* Read system info registers */ @@ -216,18 +219,26 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } - // Handle movement and gestures. - // - // Each one of these branches needs to make send the last report it makes as - // sync to ensure that the input subsystem process things in order. - if (sys_info_1 & IQS5XX_TP_MOVEMENT) { - ret = iqs5xx_read_reg8(dev, IQS5XX_NUM_FINGERS, &num_fingers); - if (ret < 0) { - LOG_ERR("Failed to read number of fingers: %d", ret); - goto end_comm; - } + bool tp_movement = (sys_info_1 & IQS5XX_TP_MOVEMENT) != 0; + bool scroll = (gesture_events_1 & IQS5XX_SCROLL) != 0; + if (!scroll) { + // Clear accumulators if we're not actively scrolling. + data->scroll_x_acc = 0; + data->scroll_y_acc = 0; + } + + uint16_t button_code; + bool button_pressed = false; + if (gesture_events_0 & IQS5XX_SINGLE_TAP) { + button_pressed = true; + button_code = INPUT_BTN_0; + } else if (gesture_events_1 & IQS5XX_TWO_FINGER_TAP) { + button_pressed = true; + button_code = INPUT_BTN_1; + } - /* Read relative movement data */ + int16_t rel_x, rel_y; + if (tp_movement || scroll) { ret = iqs5xx_read_reg16(dev, IQS5XX_REL_X, (uint16_t *)&rel_x); if (ret < 0) { LOG_ERR("Failed to read relative X: %d", ret); @@ -239,35 +250,57 @@ static void iqs5xx_work_handler(struct k_work *work) { LOG_ERR("Failed to read relative Y: %d", ret); goto end_comm; } + } - /* Report movement if there's actually movement */ - if (rel_x != 0 || rel_y != 0) { - LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); - - /* Send pointer movement event */ - ret = input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); - ret = input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); - } - } else if (gesture_events_0 & IQS5XX_SINGLE_TAP) { + // Handle movement and gestures. + // + // Each one of these branches needs to make send the last report it makes as + // sync to ensure that the input subsystem process things in order. + if (button_pressed) { // Cancel any pending release. k_work_cancel_delayable(&data->button_release_work); // Press the button immediately. - input_report_key(dev, INPUT_BTN_0, 1, true, K_FOREVER); - data->buttons_pressed |= BIT(0); + input_report_key(dev, button_code, 1, true, K_FOREVER); + data->buttons_pressed |= BIT(button_code - INPUT_BTN_0); // Schedule release after 100ms. k_work_schedule(&data->button_release_work, K_MSEC(100)); - } else if (gesture_events_1 & IQS5XX_TWO_FINGER_TAP) { - // Cancel any pending release. - k_work_cancel_delayable(&data->button_release_work); + } else if (scroll) { + int16_t scroll_div = 32; + if (rel_x != 0) { + input_report_rel(dev, INPUT_REL_HWHEEL, rel_x, true, K_FOREVER); + goto end_comm; + } + if (rel_y != 0) { + // Invert scroll direcion. + rel_y *= -1; + // input_report_rel(dev, INPUT_REL_WHEEL, rel_y, true, K_FOREVER); + data->scroll_y_acc += rel_y; + if (abs(data->scroll_y_acc) >= scroll_div) { + input_report_rel(dev, INPUT_REL_WHEEL, data->scroll_y_acc / scroll_div, true, + K_FOREVER); + data->scroll_y_acc %= scroll_div; + } + LOG_INF("New scroll y accumulator: %d", data->scroll_y_acc); - // Press the button immediately. - input_report_key(dev, INPUT_BTN_1, 1, true, K_FOREVER); - data->buttons_pressed |= BIT(1); + goto end_comm; + } + } else if (tp_movement) { + ret = iqs5xx_read_reg8(dev, IQS5XX_NUM_FINGERS, &num_fingers); + if (ret < 0) { + LOG_ERR("Failed to read number of fingers: %d", ret); + goto end_comm; + } - // Schedule release after 100ms. - k_work_schedule(&data->button_release_work, K_MSEC(100)); + /* Report movement if there's actually movement */ + if (rel_x != 0 || rel_y != 0) { + LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); + + /* Send pointer movement event */ + input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); + input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); + } } end_comm: @@ -293,7 +326,7 @@ static int iqs5xx_setup_device(const struct device *dev) { return ret; } - ret = iqs5xx_write_reg8(dev, IQS5XX_BOTTOM_BETA, 10); + ret = iqs5xx_write_reg8(dev, IQS5XX_BOTTOM_BETA, 5); if (ret < 0) { LOG_ERR("Failed to set bottom beta: %d", ret); return ret; @@ -342,7 +375,8 @@ static int iqs5xx_setup_device(const struct device *dev) { } // Configure multi finger gestures: - ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, IQS5XX_TWO_FINGER_TAP); + ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, + IQS5XX_TWO_FINGER_TAP | IQS5XX_SCROLL); if (ret < 0) { LOG_ERR("Failed to configure multi finger gestures: %d", ret); return ret; From e1b2d27710e7d046a7dec67b6a1554b9bcadf8e1 Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Sun, 3 Aug 2025 17:55:51 -0700 Subject: [PATCH 04/11] Turn off logging to evaluate real perf. Signed-off-by: Mariano Uvalle --- app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf index 256b50f3153..06606ce3246 100644 --- a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf @@ -1,7 +1,7 @@ -CONFIG_LOG=y +CONFIG_LOG=n CONFIG_ZMK_USB_LOGGING=n -CONFIG_ZMK_RTT_LOGGING=y -CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_RTT_LOGGING=n +# CONFIG_ZMK_LOG_LEVEL_DBG=y CONFIG_ZMK_BLE_PASSKEY_ENTRY=n CONFIG_ZMK_BLE=y From bee11c26814a1e9aaeb4a427ff96155776a592ba Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Sun, 3 Aug 2025 17:56:39 -0700 Subject: [PATCH 05/11] Use a better approach to turn off bit when releasing button. Signed-off-by: Mariano Uvalle --- app/module/drivers/input/iqs5xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index fc303d31519..845b59e2b95 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -175,7 +175,7 @@ static void iqs5xx_button_release_work_handler(struct k_work *work) { input_report_key(data->dev, INPUT_BTN_0 + i, 0, true, K_FOREVER); // Turn off the bit. // NOTE: This is a potential race. - data->buttons_pressed ^= BIT(i); + data->buttons_pressed &= ~BIT(i); } } } From c2256c7f266cff02a4dcade22d50ea149ca4695e Mon Sep 17 00:00:00 2001 From: Mariano Uvalle Date: Sun, 3 Aug 2025 17:57:08 -0700 Subject: [PATCH 06/11] Implement tap and hold. Signed-off-by: Mariano Uvalle --- app/module/drivers/input/iqs5xx.c | 53 +++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index 845b59e2b95..36f07328c27 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -97,6 +97,21 @@ LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); #define IQS5XX_SWIPE_UP BIT(4) #define IQS5XX_SWIPE_DOWN BIT(5) +// Time in ms, 2 registers wide. +// Hold time + tap time is used as +// a threshold for the press and +// hold gesture. +#define IQS5XX_HOLD_TIME 0x06BD +// TODO: Make hold time configurable with KConfig. + +// Mouse button helpers. +#define LEFT_BUTTON_BIT BIT(0) +#define RIGHT_BUTTON_BIT BIT(1) +#define MIDDLE_BUTTON_BIT BIT(2) +#define LEFT_BUTTON_CODE INPUT_BTN_0 +#define RIGHT_BUTTON_CODE INPUT_BTN_0 + 1 +#define MIDDLE_BUTTON_CODE INPUT_BTN_0 + 2 + // These 2 registers have the same bit map. // The first one configures the gestures, // the second one reports gesture events at runtime. @@ -122,6 +137,7 @@ struct iqs5xx_data { bool initialized; // Flag to indicate if the button was pressed in a previous cycle. uint8_t buttons_pressed; + bool active_hold; // Scroll accumulators. int16_t scroll_x_acc; int16_t scroll_y_acc; @@ -142,6 +158,13 @@ static int iqs5xx_read_reg16(const struct device *dev, uint16_t reg, uint16_t *v return 0; } +static int iqs5xx_write_reg16(const struct device *dev, uint16_t reg, uint16_t val) { + const struct iqs5xx_config *config = dev->config; + uint8_t buf[4] = {reg >> 8, reg & 0xFF, val >> 8, val & 0xFF}; + + return i2c_write_dt(&config->i2c, buf, sizeof(buf)); +} + static int iqs5xx_read_reg8(const struct device *dev, uint16_t reg, uint8_t *val) { const struct iqs5xx_config *config = dev->config; uint8_t reg_buf[2] = {reg >> 8, reg & 0xFF}; @@ -237,6 +260,9 @@ static void iqs5xx_work_handler(struct k_work *work) { button_code = INPUT_BTN_1; } + bool hold_became_active = (gesture_events_0 & IQS5XX_PRESS_AND_HOLD) && !data->active_hold; + bool hold_released = !(gesture_events_0 & IQS5XX_PRESS_AND_HOLD) && data->active_hold; + int16_t rel_x, rel_y; if (tp_movement || scroll) { ret = iqs5xx_read_reg16(dev, IQS5XX_REL_X, (uint16_t *)&rel_x); @@ -256,7 +282,15 @@ static void iqs5xx_work_handler(struct k_work *work) { // // Each one of these branches needs to make send the last report it makes as // sync to ensure that the input subsystem process things in order. - if (button_pressed) { + if (hold_became_active) { + LOG_INF("Hold became active"); + input_report_key(dev, LEFT_BUTTON_CODE, 1, true, K_FOREVER); + data->active_hold = true; + } else if (hold_released) { + LOG_INF("Hold became inactive"); + input_report_key(dev, LEFT_BUTTON_CODE, 0, true, K_FOREVER); + data->active_hold = false; + } else if (button_pressed) { // Cancel any pending release. k_work_cancel_delayable(&data->button_release_work); @@ -368,12 +402,27 @@ static int iqs5xx_setup_device(const struct device *dev) { } // Configure single finger gestures: - ret = iqs5xx_write_reg8(dev, IQS5XX_SINGLE_FINGER_GESTURES_CONF, IQS5XX_SINGLE_TAP); + ret = iqs5xx_write_reg8(dev, IQS5XX_SINGLE_FINGER_GESTURES_CONF, + IQS5XX_SINGLE_TAP | IQS5XX_PRESS_AND_HOLD); if (ret < 0) { LOG_ERR("Failed to configure single finger gestures: %d", ret); return ret; } + // Configure the hold time for the press and hold gesture: + ret = iqs5xx_write_reg16(dev, IQS5XX_HOLD_TIME, 250); + if (ret < 0) { + LOG_ERR("Failed to configure the hold time: %d", ret); + return ret; + } + uint16_t hold_time; + ret = iqs5xx_read_reg16(dev, IQS5XX_HOLD_TIME, &hold_time); + if (ret < 0) { + LOG_ERR("Failed to read back the hold time: %d", ret); + return ret; + } + LOG_INF("Configured hold time: %d", hold_time); + // Configure multi finger gestures: ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, IQS5XX_TWO_FINGER_TAP | IQS5XX_SCROLL); From bbd0e910369453e6e2fb55f8a6cfa8402054e3e0 Mon Sep 17 00:00:00 2001 From: jmug Date: Sat, 9 Aug 2025 07:09:41 +0000 Subject: [PATCH 07/11] Polish up the implementation. Expose configuration through dts bindings. --- .../shields/iqs5xx_dev/iqs5xx_dev.overlay | 11 ++ .../bindings/input/azoteq,iqs5xx-common.yaml | 53 +++++- app/module/drivers/input/iqs5xx.c | 180 ++++-------------- app/module/drivers/input/iqs5xx.h | 149 +++++++++++++++ 4 files changed, 245 insertions(+), 148 deletions(-) create mode 100644 app/module/drivers/input/iqs5xx.h diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay index 1b4ef22c8df..ee2d0d190ef 100644 --- a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay @@ -38,6 +38,17 @@ compatible = "azoteq,iqs5xx"; reg = <0x74>; + one-finger-tap; + press-and-hold; + press-and-hold-time = <250>; + two-finger-tap; + scroll; + + bottom-beta = <5>; + stationary-threshold = <5>; + + switch-xy; + reset-gpios = <&arduino_header 14 GPIO_ACTIVE_LOW>; /* P1.10 */ rdy-gpios = <&arduino_header 15 GPIO_ACTIVE_HIGH>; /* P1.11 */ }; diff --git a/app/dts/bindings/input/azoteq,iqs5xx-common.yaml b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml index 85cb9a0f9a3..602cff0ddf0 100644 --- a/app/dts/bindings/input/azoteq,iqs5xx-common.yaml +++ b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml @@ -7,14 +7,49 @@ properties: type: phandle-array description: "Reset pin for the Azoteq IQS5xx trackpad" - # x-invert: - # type: boolean - # description: "If true, invert reported X-axis data (e.g. to handle board rotations)" + one-finger-tap: + type: boolean + description: "Register single finger tap gestures and report them as a left click" - # y-invert: - # type: boolean - # description: "If true, invert reported Y-axis data (e.g. to handle board rotations)" + press-and-hold: + type: boolean + description: "Register press and hold gestures and report them as a left click until released" + + press-and-hold-time: + type: int + description: "Time in ms (in addition to the tap time) it takes to register a press and hold gesture" + default: 250 + + two-finger-tap: + type: boolean + description: "Register two finger tap gestures and report them as a right click" + + scroll: + type: boolean + description: "Register and report scroll gestures, both vertical and horizontal scroll is supported" - # no-taps: - # type: boolean - # description: "Disable tap detection/processing if the firmware or driver supports taps." \ No newline at end of file + switch-xy: + type: boolean + description: "Flip x and y axes" + + flip-x: + type: boolean + description: "Invert the direction of the x axis" + + flip-y: + type: boolean + description: "Invert the direction of the y axis" + + bottom-beta: + type: int + description: | + Amount of filtering applied at slow speeds. + 0: Most filtering, smoother, but laggier. + 255: Least filtering, least smooth, more responsive. + default: 5 + + stationary-threshold: + type: int + description: | + How far a finger must move (in pixels) to not be considered stationary. + default: 5 \ No newline at end of file diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index 36f07328c27..2bd7372fac9 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -14,134 +14,9 @@ #include #include -LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); +#include "iqs5xx.h" -/* Register addresses from datasheet */ -#define IQS5XX_NUM_FINGERS 0x0011 -#define IQS5XX_REL_X 0x0012 /* 2 bytes */ -#define IQS5XX_REL_Y 0x0014 /* 2 bytes */ -#define IQS5XX_ABS_X 0x0016 /* 2 bytes */ -#define IQS5XX_ABS_Y 0x0018 /* 2 bytes */ -#define IQS5XX_TOUCH_STRENGTH 0x001A /* 2 bytes */ -#define IQS5XX_TOUCH_AREA 0x001C - -#define IQS5XX_BOTTOM_BETA 0x0637 -#define IQS5XX_STATIONARY_THRESH 0x0672 - -#define IQS5XX_END_COMM_WINDOW 0xEEEE - -#define IQS5XX_SYSTEM_CONTROL_0 0x0431 -/* System Control 0 bits */ -#define IQS5XX_ACK_RESET BIT(7) -#define IQS5XX_AUTO_ATI BIT(5) -#define IQS5XX_ALP_RESEED BIT(4) -#define IQS5XX_RESEED BIT(3) - -#define IQS5XX_SYSTEM_CONFIG_0 0x058E -/* System Config 0 bits */ -#define IQS5XX_MANUAL_CONTROL BIT(7) -#define IQS5XX_SETUP_COMPLETE BIT(6) -#define IQS5XX_WDT BIT(5) -#define IQS5XX_SW_INPUT_EVENT BIT(4) -#define IQS5XX_ALP_REATI BIT(3) -#define IQS5XX_REATI BIT(2) -#define IQS5XX_SW_INPUT_SELECT BIT(1) -#define IQS5XX_SW_INPUT BIT(0) - -#define IQS5XX_SYSTEM_CONFIG_1 0x058F -/* System Config 1 bits */ -#define IQS5XX_EVENT_MODE BIT(0) -#define IQS5XX_GESTURE_EVENT BIT(1) -#define IQS5XX_TP_EVENT BIT(2) -#define IQS5XX_REATI_EVENT BIT(3) -#define IQS5XX_ALP_PROX_EVENT BIT(4) -#define IQS5XX_SNAP_EVENT BIT(5) -#define IQS5XX_TOUCH_EVENT BIT(6) -#define IQS5XX_PROX_EVENT BIT(7) - -// Filter settings register. -#define IQS5XX_FILTER_SETTINGS 0x0632 -// Filter settings bits. -#define IQS5XX_IIR_FILTER BIT(0) -#define IQS5XX_MAV_FILTER BIT(1) -#define IQS5XX_IIR_SELECT BIT(2) -#define IQS5XX_ALP_COUNT_FILTER BIT(3) - -#define IQS5XX_SYSTEM_INFO_0 0x000F -/* System Info 0 bits */ -#define IQS5XX_SHOW_RESET BIT(7) -#define IQS5XX_ALP_REATI_OCCURRED BIT(6) -#define IQS5XX_ALP_ATI_ERROR BIT(5) -#define IQS5XX_REATI_OCCURRED BIT(4) -#define IQS5XX_ATI_ERROR BIT(3) - -#define IQS5XX_SYSTEM_INFO_1 0x0010 -/* System Info 1 bits */ -#define IQS5XX_SWITCH_STATE BIT(5) -#define IQS5XX_SNAP_TOGGLE BIT(4) -#define IQS5XX_RR_MISSED BIT(3) -#define IQS5XX_TOO_MANY_FINGERS BIT(2) -#define IQS5XX_PALM_DETECT BIT(1) -#define IQS5XX_TP_MOVEMENT BIT(0) - -// These 2 registers have the same bit map. -// The first one configures the gestures, -// the second one reports gesture events at runtime. -#define IQS5XX_SINGLE_FINGER_GESTURES_CONF 0x06B7 -#define IQS5XX_GESTURE_EVENTS_0 0x000D -// Single finger gesture identifiers. -#define IQS5XX_SINGLE_TAP BIT(0) -#define IQS5XX_PRESS_AND_HOLD BIT(1) -#define IQS5XX_SWIPE_LEFT BIT(2) -#define IQS5XX_SWIPE_RIGHT BIT(3) -#define IQS5XX_SWIPE_UP BIT(4) -#define IQS5XX_SWIPE_DOWN BIT(5) - -// Time in ms, 2 registers wide. -// Hold time + tap time is used as -// a threshold for the press and -// hold gesture. -#define IQS5XX_HOLD_TIME 0x06BD -// TODO: Make hold time configurable with KConfig. - -// Mouse button helpers. -#define LEFT_BUTTON_BIT BIT(0) -#define RIGHT_BUTTON_BIT BIT(1) -#define MIDDLE_BUTTON_BIT BIT(2) -#define LEFT_BUTTON_CODE INPUT_BTN_0 -#define RIGHT_BUTTON_CODE INPUT_BTN_0 + 1 -#define MIDDLE_BUTTON_CODE INPUT_BTN_0 + 2 - -// These 2 registers have the same bit map. -// The first one configures the gestures, -// the second one reports gesture events at runtime. -#define IQS5XX_MULTI_FINGER_GESTURES_CONF 0x06B8 -#define IQS5XX_GESTURE_EVENTS_1 0x000E -// Multi finger gesture identifiers. -#define IQS5XX_TWO_FINGER_TAP BIT(0) -#define IQS5XX_SCROLL BIT(1) -#define IQS5XX_ZOOM BIT(2) - -struct iqs5xx_config { - struct i2c_dt_spec i2c; - struct gpio_dt_spec rdy_gpio; - struct gpio_dt_spec reset_gpio; -}; - -struct iqs5xx_data { - const struct device *dev; - struct gpio_callback rdy_cb; - struct k_work work; - struct k_work_delayable button_release_work; - // TODO: Pack flags into a bitfield to save space. - bool initialized; - // Flag to indicate if the button was pressed in a previous cycle. - uint8_t buttons_pressed; - bool active_hold; - // Scroll accumulators. - int16_t scroll_x_acc; - int16_t scroll_y_acc; -}; +LOG_MODULE_REGISTER(iqs5xx, CONFIG_INPUT_LOG_LEVEL); static int iqs5xx_read_reg16(const struct device *dev, uint16_t reg, uint16_t *val) { const struct iqs5xx_config *config = dev->config; @@ -350,6 +225,7 @@ static void iqs5xx_rdy_handler(const struct device *port, struct gpio_callback * } static int iqs5xx_setup_device(const struct device *dev) { + const struct iqs5xx_config *config = dev->config; int ret; /* Enable event mode and trackpad events */ @@ -360,7 +236,7 @@ static int iqs5xx_setup_device(const struct device *dev) { return ret; } - ret = iqs5xx_write_reg8(dev, IQS5XX_BOTTOM_BETA, 5); + ret = iqs5xx_write_reg8(dev, IQS5XX_BOTTOM_BETA, config->bottom_beta); if (ret < 0) { LOG_ERR("Failed to set bottom beta: %d", ret); return ret; @@ -375,7 +251,7 @@ static int iqs5xx_setup_device(const struct device *dev) { } LOG_INF("Current bottom beta: %d", bottom_beta); - ret = iqs5xx_write_reg8(dev, IQS5XX_STATIONARY_THRESH, 5); + ret = iqs5xx_write_reg8(dev, IQS5XX_STATIONARY_THRESH, config->stationary_threshold); if (ret < 0) { LOG_ERR("Failed to set bottom stationary threshold: %d", ret); return ret; @@ -389,6 +265,7 @@ static int iqs5xx_setup_device(const struct device *dev) { } LOG_INF("Current stat thresh: %d", stat_threshold); + // TODO: Expose these through dts bindings. // Set filter settings with: // - IIR filter enabled // - MAV filter enabled @@ -401,16 +278,18 @@ static int iqs5xx_setup_device(const struct device *dev) { return ret; } - // Configure single finger gestures: - ret = iqs5xx_write_reg8(dev, IQS5XX_SINGLE_FINGER_GESTURES_CONF, - IQS5XX_SINGLE_TAP | IQS5XX_PRESS_AND_HOLD); + uint8_t single_finger_gestures = 0; + single_finger_gestures |= config->one_finger_tap ? IQS5XX_SINGLE_TAP : 0; + single_finger_gestures |= config->press_and_hold ? IQS5XX_PRESS_AND_HOLD : 0; + // Configure single finger gestures. + ret = iqs5xx_write_reg8(dev, IQS5XX_SINGLE_FINGER_GESTURES_CONF, single_finger_gestures); if (ret < 0) { LOG_ERR("Failed to configure single finger gestures: %d", ret); return ret; } - // Configure the hold time for the press and hold gesture: - ret = iqs5xx_write_reg16(dev, IQS5XX_HOLD_TIME, 250); + // Configure the hold time for the press and hold gesture. + ret = iqs5xx_write_reg16(dev, IQS5XX_HOLD_TIME, config->press_and_hold_time); if (ret < 0) { LOG_ERR("Failed to configure the hold time: %d", ret); return ret; @@ -423,22 +302,35 @@ static int iqs5xx_setup_device(const struct device *dev) { } LOG_INF("Configured hold time: %d", hold_time); - // Configure multi finger gestures: - ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, - IQS5XX_TWO_FINGER_TAP | IQS5XX_SCROLL); + uint8_t two_finger_gestures = 0; + two_finger_gestures |= config->two_finger_tap ? IQS5XX_TWO_FINGER_TAP : 0; + two_finger_gestures |= config->scroll ? IQS5XX_SCROLL : 0; + // Configure multi finger gestures. + ret = iqs5xx_write_reg8(dev, IQS5XX_MULTI_FINGER_GESTURES_CONF, two_finger_gestures); if (ret < 0) { LOG_ERR("Failed to configure multi finger gestures: %d", ret); return ret; } - /* Configure system settings */ + // Configure axes. + uint8_t xy_config = 0; + xy_config |= config->flip_x ? IQS5XX_FLIP_X : 0; + xy_config |= config->flip_y ? IQS5XX_FLIP_Y : 0; + xy_config |= config->switch_xy ? IQS5XX_SWITCH_XY_AXIS : 0; + ret = iqs5xx_write_reg8(dev, IQS5XX_XY_CONFIG_0, xy_config); + if (ret < 0) { + LOG_ERR("Failed to configure axes: %d", ret); + return ret; + } + + // Configure system settings. ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_0, IQS5XX_SETUP_COMPLETE | IQS5XX_WDT); if (ret < 0) { LOG_ERR("Failed to configure system: %d", ret); return ret; } - /* End communication window */ + // End communication window. ret = iqs5xx_end_comm_window(dev); if (ret < 0) { LOG_ERR("Failed to end comm window: %d", ret); @@ -530,6 +422,16 @@ static int iqs5xx_init(const struct device *dev) { .i2c = I2C_DT_SPEC_INST_GET(n), \ .rdy_gpio = GPIO_DT_SPEC_INST_GET(n, rdy_gpios), \ .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \ + .one_finger_tap = DT_INST_PROP(n, one_finger_tap), \ + .press_and_hold = DT_INST_PROP(n, press_and_hold), \ + .two_finger_tap = DT_INST_PROP(n, two_finger_tap), \ + .scroll = DT_INST_PROP(n, scroll), \ + .press_and_hold_time = DT_INST_PROP_OR(n, press_and_hold_time, 250), \ + .switch_xy = DT_INST_PROP(n, switch_xy), \ + .flip_x = DT_INST_PROP(n, flip_x), \ + .flip_y = DT_INST_PROP(n, flip_y), \ + .bottom_beta = DT_INST_PROP_OR(n, bottom_beta, 5), \ + .stationary_threshold = DT_INST_PROP_OR(n, stationary_threshold, 5), \ }; \ DEVICE_DT_INST_DEFINE(n, iqs5xx_init, NULL, &iqs5xx_data_##n, &iqs5xx_config_##n, POST_KERNEL, \ CONFIG_INPUT_INIT_PRIORITY, NULL); diff --git a/app/module/drivers/input/iqs5xx.h b/app/module/drivers/input/iqs5xx.h new file mode 100644 index 00000000000..0b8f42b491d --- /dev/null +++ b/app/module/drivers/input/iqs5xx.h @@ -0,0 +1,149 @@ +#include + +#define IQS5XX_NUM_FINGERS 0x0011 +#define IQS5XX_REL_X 0x0012 /* 2 bytes */ +#define IQS5XX_REL_Y 0x0014 /* 2 bytes */ +#define IQS5XX_ABS_X 0x0016 /* 2 bytes */ +#define IQS5XX_ABS_Y 0x0018 /* 2 bytes */ +#define IQS5XX_TOUCH_STRENGTH 0x001A /* 2 bytes */ +#define IQS5XX_TOUCH_AREA 0x001C + +#define IQS5XX_BOTTOM_BETA 0x0637 +#define IQS5XX_STATIONARY_THRESH 0x0672 + +#define IQS5XX_END_COMM_WINDOW 0xEEEE + +#define IQS5XX_SYSTEM_CONTROL_0 0x0431 +/* System Control 0 bits */ +#define IQS5XX_ACK_RESET BIT(7) +#define IQS5XX_AUTO_ATI BIT(5) +#define IQS5XX_ALP_RESEED BIT(4) +#define IQS5XX_RESEED BIT(3) + +#define IQS5XX_SYSTEM_CONFIG_0 0x058E +/* System Config 0 bits */ +#define IQS5XX_MANUAL_CONTROL BIT(7) +#define IQS5XX_SETUP_COMPLETE BIT(6) +#define IQS5XX_WDT BIT(5) +#define IQS5XX_SW_INPUT_EVENT BIT(4) +#define IQS5XX_ALP_REATI BIT(3) +#define IQS5XX_REATI BIT(2) +#define IQS5XX_SW_INPUT_SELECT BIT(1) +#define IQS5XX_SW_INPUT BIT(0) + +#define IQS5XX_SYSTEM_CONFIG_1 0x058F +/* System Config 1 bits */ +#define IQS5XX_EVENT_MODE BIT(0) +#define IQS5XX_GESTURE_EVENT BIT(1) +#define IQS5XX_TP_EVENT BIT(2) +#define IQS5XX_REATI_EVENT BIT(3) +#define IQS5XX_ALP_PROX_EVENT BIT(4) +#define IQS5XX_SNAP_EVENT BIT(5) +#define IQS5XX_TOUCH_EVENT BIT(6) +#define IQS5XX_PROX_EVENT BIT(7) + +// Filter settings register. +#define IQS5XX_FILTER_SETTINGS 0x0632 +// Filter settings bits. +#define IQS5XX_IIR_FILTER BIT(0) +#define IQS5XX_MAV_FILTER BIT(1) +#define IQS5XX_IIR_SELECT BIT(2) +#define IQS5XX_ALP_COUNT_FILTER BIT(3) + +#define IQS5XX_SYSTEM_INFO_0 0x000F +/* System Info 0 bits */ +#define IQS5XX_SHOW_RESET BIT(7) +#define IQS5XX_ALP_REATI_OCCURRED BIT(6) +#define IQS5XX_ALP_ATI_ERROR BIT(5) +#define IQS5XX_REATI_OCCURRED BIT(4) +#define IQS5XX_ATI_ERROR BIT(3) + +#define IQS5XX_SYSTEM_INFO_1 0x0010 +/* System Info 1 bits */ +#define IQS5XX_SWITCH_STATE BIT(5) +#define IQS5XX_SNAP_TOGGLE BIT(4) +#define IQS5XX_RR_MISSED BIT(3) +#define IQS5XX_TOO_MANY_FINGERS BIT(2) +#define IQS5XX_PALM_DETECT BIT(1) +#define IQS5XX_TP_MOVEMENT BIT(0) + +// These 2 registers have the same bit map. +// The first one configures the gestures, +// the second one reports gesture events at runtime. +#define IQS5XX_SINGLE_FINGER_GESTURES_CONF 0x06B7 +#define IQS5XX_GESTURE_EVENTS_0 0x000D +// Single finger gesture identifiers. +#define IQS5XX_SINGLE_TAP BIT(0) +#define IQS5XX_PRESS_AND_HOLD BIT(1) +#define IQS5XX_SWIPE_LEFT BIT(2) +#define IQS5XX_SWIPE_RIGHT BIT(3) +#define IQS5XX_SWIPE_UP BIT(4) +#define IQS5XX_SWIPE_DOWN BIT(5) + +// Time in ms, 2 registers wide. +// Hold time + tap time is used as +// a threshold for the press and +// hold gesture. +#define IQS5XX_HOLD_TIME 0x06BD +// TODO: Make hold time configurable with KConfig. + +// Mouse button helpers. +#define LEFT_BUTTON_BIT BIT(0) +#define RIGHT_BUTTON_BIT BIT(1) +#define MIDDLE_BUTTON_BIT BIT(2) +#define LEFT_BUTTON_CODE INPUT_BTN_0 +#define RIGHT_BUTTON_CODE INPUT_BTN_0 + 1 +#define MIDDLE_BUTTON_CODE INPUT_BTN_0 + 2 + +// These 2 registers have the same bit map. +// The first one configures the gestures, +// the second one reports gesture events at runtime. +#define IQS5XX_MULTI_FINGER_GESTURES_CONF 0x06B8 +#define IQS5XX_GESTURE_EVENTS_1 0x000E +// Multi finger gesture identifiers. +#define IQS5XX_TWO_FINGER_TAP BIT(0) +#define IQS5XX_SCROLL BIT(1) +#define IQS5XX_ZOOM BIT(2) + +// Axes configuration. +#define IQS5XX_XY_CONFIG_0 0x0669 +#define IQS5XX_FLIP_X BIT(0) +#define IQS5XX_FLIP_Y BIT(1) +#define IQS5XX_SWITCH_XY_AXIS BIT(2) + +struct iqs5xx_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec rdy_gpio; + struct gpio_dt_spec reset_gpio; + + // Gesture configuration. + bool one_finger_tap; + bool press_and_hold; + bool two_finger_tap; + bool scroll; + uint16_t press_and_hold_time; + + // Axes configuration. + bool switch_xy; + bool flip_x; + bool flip_y; + + // Movement configuration. + uint8_t bottom_beta; + uint8_t stationary_threshold; +}; + +struct iqs5xx_data { + const struct device *dev; + struct gpio_callback rdy_cb; + struct k_work work; + struct k_work_delayable button_release_work; + // TODO: Pack flags into a bitfield to save space. + bool initialized; + // Flag to indicate if the button was pressed in a previous cycle. + uint8_t buttons_pressed; + bool active_hold; + // Scroll accumulators. + int16_t scroll_x_acc; + int16_t scroll_y_acc; +}; \ No newline at end of file From a7f044666d743e0b6ee2cb79c580049271bdd1cc Mon Sep 17 00:00:00 2001 From: jmug Date: Sat, 9 Aug 2025 00:18:37 -0700 Subject: [PATCH 08/11] Comment typo. Signed-off-by: jmug --- app/module/drivers/input/iqs5xx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index 2bd7372fac9..2cf8943e219 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -155,8 +155,8 @@ static void iqs5xx_work_handler(struct k_work *work) { // Handle movement and gestures. // - // Each one of these branches needs to make send the last report it makes as - // sync to ensure that the input subsystem process things in order. + // Each one of these branches needs to send the last report it makes as + // sync to ensure that the input subsystem processes things in order. if (hold_became_active) { LOG_INF("Hold became active"); input_report_key(dev, LEFT_BUTTON_CODE, 1, true, K_FOREVER); @@ -436,4 +436,4 @@ static int iqs5xx_init(const struct device *dev) { DEVICE_DT_INST_DEFINE(n, iqs5xx_init, NULL, &iqs5xx_data_##n, &iqs5xx_config_##n, POST_KERNEL, \ CONFIG_INPUT_INIT_PRIORITY, NULL); -DT_INST_FOREACH_STATUS_OKAY(IQS5XX_INIT) \ No newline at end of file +DT_INST_FOREACH_STATUS_OKAY(IQS5XX_INIT) From 97fb97bad9aa2a1b8fcf04857d71c970e5a602e4 Mon Sep 17 00:00:00 2001 From: jmug Date: Sat, 9 Aug 2025 01:38:27 -0700 Subject: [PATCH 09/11] Convert comment format. Signed-off-by: jmug --- app/module/drivers/input/iqs5xx.c | 48 ++++++++----------------------- app/module/drivers/input/iqs5xx.h | 20 ++++++------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index 2cf8943e219..fd83587a595 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -84,7 +84,7 @@ static void iqs5xx_work_handler(struct k_work *work) { uint8_t sys_info_0, sys_info_1, gesture_events_0, gesture_events_1, num_fingers; int ret; - /* Read system info registers */ + // Read system info registers. ret = iqs5xx_read_reg8(dev, IQS5XX_SYSTEM_INFO_0, &sys_info_0); if (ret < 0) { LOG_ERR("Failed to read system info 0: %d", ret); @@ -109,10 +109,10 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } - /* Handle reset indication */ + // Handle reset indication. if (sys_info_0 & IQS5XX_SHOW_RESET) { LOG_INF("Device reset detected"); - /* Acknowledge reset */ + // Acknowledge reset. iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONTROL_0, IQS5XX_ACK_RESET); goto end_comm; } @@ -202,18 +202,18 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } - /* Report movement if there's actually movement */ + // Report movement if there's actually movement. if (rel_x != 0 || rel_y != 0) { LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); - /* Send pointer movement event */ + // Send pointer movement event. input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); } } end_comm: - /* End communication window */ + // End communication window. iqs5xx_end_comm_window(dev); } @@ -228,7 +228,7 @@ static int iqs5xx_setup_device(const struct device *dev) { const struct iqs5xx_config *config = dev->config; int ret; - /* Enable event mode and trackpad events */ + // Enable event mode and trackpad events. ret = iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONFIG_1, IQS5XX_EVENT_MODE | IQS5XX_TP_EVENT | IQS5XX_GESTURE_EVENT); if (ret < 0) { @@ -242,29 +242,12 @@ static int iqs5xx_setup_device(const struct device *dev) { return ret; } - /* Read the current value of bottom beta and log it */ - uint8_t bottom_beta; - ret = iqs5xx_read_reg8(dev, IQS5XX_BOTTOM_BETA, &bottom_beta); - if (ret < 0) { - LOG_ERR("Failed to read bottom beta: %d", ret); - return ret; - } - LOG_INF("Current bottom beta: %d", bottom_beta); - ret = iqs5xx_write_reg8(dev, IQS5XX_STATIONARY_THRESH, config->stationary_threshold); if (ret < 0) { LOG_ERR("Failed to set bottom stationary threshold: %d", ret); return ret; } - uint8_t stat_threshold; - ret = iqs5xx_read_reg8(dev, IQS5XX_STATIONARY_THRESH, &stat_threshold); - if (ret < 0) { - LOG_ERR("Failed to read bottom stat_threshold: %d", ret); - return ret; - } - LOG_INF("Current stat thresh: %d", stat_threshold); - // TODO: Expose these through dts bindings. // Set filter settings with: // - IIR filter enabled @@ -294,13 +277,6 @@ static int iqs5xx_setup_device(const struct device *dev) { LOG_ERR("Failed to configure the hold time: %d", ret); return ret; } - uint16_t hold_time; - ret = iqs5xx_read_reg16(dev, IQS5XX_HOLD_TIME, &hold_time); - if (ret < 0) { - LOG_ERR("Failed to read back the hold time: %d", ret); - return ret; - } - LOG_INF("Configured hold time: %d", hold_time); uint8_t two_finger_gestures = 0; two_finger_gestures |= config->two_finger_tap ? IQS5XX_TWO_FINGER_TAP : 0; @@ -354,7 +330,7 @@ static int iqs5xx_init(const struct device *dev) { k_work_init(&data->work, iqs5xx_work_handler); k_work_init_delayable(&data->button_release_work, iqs5xx_button_release_work_handler); - /* Configure reset GPIO if available */ + // Configure reset GPIO if available. if (config->reset_gpio.port) { if (!gpio_is_ready_dt(&config->reset_gpio)) { LOG_ERR("Reset GPIO not ready"); @@ -367,14 +343,14 @@ static int iqs5xx_init(const struct device *dev) { return ret; } - /* Reset the device */ + // Reset the device. gpio_pin_set_dt(&config->reset_gpio, 1); k_msleep(1); gpio_pin_set_dt(&config->reset_gpio, 0); k_msleep(10); } - /* Configure RDY GPIO */ + // Configure RDY GPIO. if (!gpio_is_ready_dt(&config->rdy_gpio)) { LOG_ERR("RDY GPIO not ready"); return -ENODEV; @@ -399,10 +375,10 @@ static int iqs5xx_init(const struct device *dev) { return ret; } - /* Wait for device to be ready */ + // Wait for device to be ready. k_msleep(100); - /* Setup device configuration */ + // Setup device configuration. ret = iqs5xx_setup_device(dev); if (ret < 0) { LOG_ERR("Failed to setup device: %d", ret); diff --git a/app/module/drivers/input/iqs5xx.h b/app/module/drivers/input/iqs5xx.h index 0b8f42b491d..0ed943ae231 100644 --- a/app/module/drivers/input/iqs5xx.h +++ b/app/module/drivers/input/iqs5xx.h @@ -1,11 +1,11 @@ #include #define IQS5XX_NUM_FINGERS 0x0011 -#define IQS5XX_REL_X 0x0012 /* 2 bytes */ -#define IQS5XX_REL_Y 0x0014 /* 2 bytes */ -#define IQS5XX_ABS_X 0x0016 /* 2 bytes */ -#define IQS5XX_ABS_Y 0x0018 /* 2 bytes */ -#define IQS5XX_TOUCH_STRENGTH 0x001A /* 2 bytes */ +#define IQS5XX_REL_X 0x0012 // 2 bytes. +#define IQS5XX_REL_Y 0x0014 // 2 bytes. +#define IQS5XX_ABS_X 0x0016 // 2 bytes. +#define IQS5XX_ABS_Y 0x0018 // 2 bytes. +#define IQS5XX_TOUCH_STRENGTH 0x001A // 2 bytes. #define IQS5XX_TOUCH_AREA 0x001C #define IQS5XX_BOTTOM_BETA 0x0637 @@ -14,14 +14,14 @@ #define IQS5XX_END_COMM_WINDOW 0xEEEE #define IQS5XX_SYSTEM_CONTROL_0 0x0431 -/* System Control 0 bits */ +// System Control 0 bits. #define IQS5XX_ACK_RESET BIT(7) #define IQS5XX_AUTO_ATI BIT(5) #define IQS5XX_ALP_RESEED BIT(4) #define IQS5XX_RESEED BIT(3) #define IQS5XX_SYSTEM_CONFIG_0 0x058E -/* System Config 0 bits */ +// System Config 0 bits. #define IQS5XX_MANUAL_CONTROL BIT(7) #define IQS5XX_SETUP_COMPLETE BIT(6) #define IQS5XX_WDT BIT(5) @@ -32,7 +32,7 @@ #define IQS5XX_SW_INPUT BIT(0) #define IQS5XX_SYSTEM_CONFIG_1 0x058F -/* System Config 1 bits */ +// System Config 1 bits. #define IQS5XX_EVENT_MODE BIT(0) #define IQS5XX_GESTURE_EVENT BIT(1) #define IQS5XX_TP_EVENT BIT(2) @@ -51,7 +51,7 @@ #define IQS5XX_ALP_COUNT_FILTER BIT(3) #define IQS5XX_SYSTEM_INFO_0 0x000F -/* System Info 0 bits */ +// System Info 0 bits. #define IQS5XX_SHOW_RESET BIT(7) #define IQS5XX_ALP_REATI_OCCURRED BIT(6) #define IQS5XX_ALP_ATI_ERROR BIT(5) @@ -59,7 +59,7 @@ #define IQS5XX_ATI_ERROR BIT(3) #define IQS5XX_SYSTEM_INFO_1 0x0010 -/* System Info 1 bits */ +// System Info 1 bits. #define IQS5XX_SWITCH_STATE BIT(5) #define IQS5XX_SNAP_TOGGLE BIT(4) #define IQS5XX_RR_MISSED BIT(3) From 96601691e7901a0d7488636d5ca96ed258e508d0 Mon Sep 17 00:00:00 2001 From: jmug Date: Sat, 9 Aug 2025 17:46:53 -0700 Subject: [PATCH 10/11] Add natural scroll dts bindings. Signed-off-by: jmug --- app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay | 3 +++ app/dts/bindings/input/azoteq,iqs5xx-common.yaml | 8 ++++++++ app/module/drivers/input/iqs5xx.h | 10 +++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay index ee2d0d190ef..bbc22f29516 100644 --- a/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay @@ -42,7 +42,10 @@ press-and-hold; press-and-hold-time = <250>; two-finger-tap; + scroll; + natural-scroll-y; + natural-scroll-x; bottom-beta = <5>; stationary-threshold = <5>; diff --git a/app/dts/bindings/input/azoteq,iqs5xx-common.yaml b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml index 602cff0ddf0..c09427dceaf 100644 --- a/app/dts/bindings/input/azoteq,iqs5xx-common.yaml +++ b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml @@ -28,6 +28,14 @@ properties: type: boolean description: "Register and report scroll gestures, both vertical and horizontal scroll is supported" + natural-scroll-x: + type: boolean + description: "Whether content should track finger movement when scrolling on the x axis." + + natural-scroll-y: + type: boolean + description: "Whether content should track finger movement when scrolling on the y axis." + switch-xy: type: boolean description: "Flip x and y axes" diff --git a/app/module/drivers/input/iqs5xx.h b/app/module/drivers/input/iqs5xx.h index 0ed943ae231..a37a883edad 100644 --- a/app/module/drivers/input/iqs5xx.h +++ b/app/module/drivers/input/iqs5xx.h @@ -120,15 +120,19 @@ struct iqs5xx_config { bool one_finger_tap; bool press_and_hold; bool two_finger_tap; - bool scroll; uint16_t press_and_hold_time; + // Scrolling configuration. + bool scroll; + bool natural_scroll_x; + bool natural_scroll_y; + // Axes configuration. bool switch_xy; bool flip_x; bool flip_y; - // Movement configuration. + // Sensitivity. configuration. uint8_t bottom_beta; uint8_t stationary_threshold; }; @@ -146,4 +150,4 @@ struct iqs5xx_data { // Scroll accumulators. int16_t scroll_x_acc; int16_t scroll_y_acc; -}; \ No newline at end of file +}; From c9846abaa03939e9b8746835572a96e4b7b9dad6 Mon Sep 17 00:00:00 2001 From: jmug Date: Sat, 9 Aug 2025 17:48:13 -0700 Subject: [PATCH 11/11] Consume and implement natural scrolling config. Signed-off-by: jmug --- app/module/drivers/input/iqs5xx.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/module/drivers/input/iqs5xx.c b/app/module/drivers/input/iqs5xx.c index fd83587a595..edeae5c875c 100644 --- a/app/module/drivers/input/iqs5xx.c +++ b/app/module/drivers/input/iqs5xx.c @@ -81,6 +81,7 @@ static void iqs5xx_button_release_work_handler(struct k_work *work) { static void iqs5xx_work_handler(struct k_work *work) { struct iqs5xx_data *data = CONTAINER_OF(work, struct iqs5xx_data, work); const struct device *dev = data->dev; + const struct iqs5xx_config *config = dev->config; uint8_t sys_info_0, sys_info_1, gesture_events_0, gesture_events_1, num_fingers; int ret; @@ -176,22 +177,34 @@ static void iqs5xx_work_handler(struct k_work *work) { // Schedule release after 100ms. k_work_schedule(&data->button_release_work, K_MSEC(100)); } else if (scroll) { + // TODO: Expose this divisor. int16_t scroll_div = 32; + + // Only one scrolling direction is valid at a time. + // End the communication right after reporting the movement. if (rel_x != 0) { - input_report_rel(dev, INPUT_REL_HWHEEL, rel_x, true, K_FOREVER); + // By default the x axis is already "natural". + if (!config->natural_scroll_x) { + rel_x *= -1; + } + data->scroll_x_acc += rel_x; + if (abs(data->scroll_x_acc) >= scroll_div) { + input_report_rel(dev, INPUT_REL_HWHEEL, data->scroll_x_acc / scroll_div, true, + K_FOREVER); + data->scroll_x_acc %= scroll_div; + } goto end_comm; } if (rel_y != 0) { - // Invert scroll direcion. - rel_y *= -1; - // input_report_rel(dev, INPUT_REL_WHEEL, rel_y, true, K_FOREVER); + if (config->natural_scroll_y) { + rel_y *= -1; + } data->scroll_y_acc += rel_y; if (abs(data->scroll_y_acc) >= scroll_div) { input_report_rel(dev, INPUT_REL_WHEEL, data->scroll_y_acc / scroll_div, true, K_FOREVER); data->scroll_y_acc %= scroll_div; } - LOG_INF("New scroll y accumulator: %d", data->scroll_y_acc); goto end_comm; } @@ -202,11 +215,7 @@ static void iqs5xx_work_handler(struct k_work *work) { goto end_comm; } - // Report movement if there's actually movement. if (rel_x != 0 || rel_y != 0) { - LOG_DBG("Movement: fingers=%d, rel_x=%d, rel_y=%d", num_fingers, rel_x, rel_y); - - // Send pointer movement event. input_report_rel(dev, INPUT_REL_X, rel_x, false, K_FOREVER); input_report_rel(dev, INPUT_REL_Y, rel_y, true, K_FOREVER); } @@ -309,7 +318,7 @@ static int iqs5xx_setup_device(const struct device *dev) { // End communication window. ret = iqs5xx_end_comm_window(dev); if (ret < 0) { - LOG_ERR("Failed to end comm window: %d", ret); + LOG_ERR("Failed to end comm window during initialization: %d", ret); return ret; } @@ -402,6 +411,8 @@ static int iqs5xx_init(const struct device *dev) { .press_and_hold = DT_INST_PROP(n, press_and_hold), \ .two_finger_tap = DT_INST_PROP(n, two_finger_tap), \ .scroll = DT_INST_PROP(n, scroll), \ + .natural_scroll_x = DT_INST_PROP(n, natural_scroll_x), \ + .natural_scroll_y = DT_INST_PROP(n, natural_scroll_y), \ .press_and_hold_time = DT_INST_PROP_OR(n, press_and_hold_time, 250), \ .switch_xy = DT_INST_PROP(n, switch_xy), \ .flip_x = DT_INST_PROP(n, flip_x), \