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..06606ce3246 --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.conf @@ -0,0 +1,23 @@ +CONFIG_LOG=n +CONFIG_ZMK_USB_LOGGING=n +CONFIG_ZMK_RTT_LOGGING=n +# CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_BLE_PASSKEY_ENTRY=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_GPIO=y +CONFIG_PINCTRL=y +CONFIG_ZMK_MOUSE=y +CONFIG_ZMK_POINTING=y +CONFIG_INPUT=y +CONFIG_INPUT_AZOTEQ_IQS5XX=y \ 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..bbc22f29516 --- /dev/null +++ b/app/boards/shields/iqs5xx_dev/iqs5xx_dev.overlay @@ -0,0 +1,58 @@ +#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: 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>; + + one-finger-tap; + press-and-hold; + press-and-hold-time = <250>; + two-finger-tap; + + scroll; + natural-scroll-y; + natural-scroll-x; + + 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 */ + }; +}; \ 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..c09427dceaf --- /dev/null +++ b/app/dts/bindings/input/azoteq,iqs5xx-common.yaml @@ -0,0 +1,63 @@ +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" + + one-finger-tap: + type: boolean + description: "Register single finger tap gestures and report them as a left click" + + 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" + + 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" + + 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/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..edeae5c875c --- /dev/null +++ b/app/module/drivers/input/iqs5xx.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2024 ZMK Contributors + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT azoteq_iqs5xx + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iqs5xx.h" + +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; + 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_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}; + + 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_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; + 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; + + // 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; + } + + 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"); + // Acknowledge reset. + iqs5xx_write_reg8(dev, IQS5XX_SYSTEM_CONTROL_0, IQS5XX_ACK_RESET); + 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; + } + + 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); + 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; + } + } + + // Handle movement and gestures. + // + // 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); + 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); + + // Press the button immediately. + 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 (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) { + // 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) { + 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; + } + + 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; + } + + if (rel_x != 0 || rel_y != 0) { + 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) { + const struct iqs5xx_config *config = dev->config; + int ret; + + // 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) { + LOG_ERR("Failed to configure event mode: %d", ret); + return ret; + } + + 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; + } + + 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; + } + + // TODO: Expose these through dts bindings. + // 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); + if (ret < 0) { + LOG_ERR("Failed to configure filter settings: %d", ret); + return ret; + } + + 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, config->press_and_hold_time); + if (ret < 0) { + LOG_ERR("Failed to configure the hold time: %d", ret); + return ret; + } + + 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 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. + ret = iqs5xx_end_comm_window(dev); + if (ret < 0) { + LOG_ERR("Failed to end comm window during initialization: %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); + k_work_init_delayable(&data->button_release_work, iqs5xx_button_release_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}), \ + .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), \ + .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), \ + .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); + +DT_INST_FOREACH_STATUS_OKAY(IQS5XX_INIT) diff --git a/app/module/drivers/input/iqs5xx.h b/app/module/drivers/input/iqs5xx.h new file mode 100644 index 00000000000..a37a883edad --- /dev/null +++ b/app/module/drivers/input/iqs5xx.h @@ -0,0 +1,153 @@ +#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; + 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; + + // Sensitivity. 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; +};