summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGUI <[email protected]>2026-08-14 12:41:00 +0800
committerGitHub <[email protected]>2026-08-14 12:41:00 +0800
commitc1418dbd2e3b9596b4082b111b80bd5f86e76028 (patch)
tree1b590fef35b5971a9d1f2b433cb4fd7035726c72
parent4cf70e176c10548d929c1e4b2124cdfb0899c384 (diff)
[DM/Input] Add ADC joystick, keyboard, and touchscreen drivers (#11679)
* [DM/Input] Add ADC joystick, keyboard, and touchscreen drivers Add polling input drivers for ADC-connected joysticks and resistor- ladder keys. Parse Linux-compatible device-tree bindings, acquire ADC channels through the IIO framework, and report absolute-axis and key events. Add an SPI touchscreen driver for ADS7843, ADS7845, ADS7846, ADS7873, TSC2046, and XPT2046-compatible controllers. Support pen interrupts, coordinate and pressure reporting, sample filtering, regulator control, power management, and common touchscreen properties. Add an I2C touchscreen driver for Goodix GT9xx controllers with multi-touch reporting, GPIO-based reset and address selection, interrupt handling with polling fallback, regulator control, panel configuration, and alternate-address recovery. Adapt the drivers to the RT-Thread device model and input framework, following the corresponding Linux input drivers and device-tree bindings. Signed-off-by: GuEe-GUI <[email protected]> * style: format code with clang-format * drivers: input: address review feedback * style: format input changes for current master --------- Signed-off-by: GuEe-GUI <[email protected]>
-rw-r--r--components/drivers/input/joystick/Kconfig7
-rw-r--r--components/drivers/input/joystick/SConscript3
-rw-r--r--components/drivers/input/joystick/js-adc.c238
-rw-r--r--components/drivers/input/keyboard/Kconfig7
-rw-r--r--components/drivers/input/keyboard/SConscript3
-rw-r--r--components/drivers/input/keyboard/keys-adc.c227
-rw-r--r--components/drivers/input/touchscreen/Kconfig18
-rw-r--r--components/drivers/input/touchscreen/SConscript6
-rw-r--r--components/drivers/input/touchscreen/ts-ads7846.c1194
-rw-r--r--components/drivers/input/touchscreen/ts-goodix.c1200
10 files changed, 2903 insertions, 0 deletions
diff --git a/components/drivers/input/joystick/Kconfig b/components/drivers/input/joystick/Kconfig
index d218d1d16b..c0e68fd291 100644
--- a/components/drivers/input/joystick/Kconfig
+++ b/components/drivers/input/joystick/Kconfig
@@ -2,6 +2,13 @@ menuconfig RT_INPUT_JOYSTICK
bool "Joystick"
default n
+config RT_INPUT_JOYSTICK_ADC
+ bool "Simple joystick connected over ADC"
+ depends on RT_INPUT_JOYSTICK
+ depends on RT_USING_ADC
+ depends on RT_USING_OFW
+ default n
+
if RT_INPUT_JOYSTICK
osource "$(SOC_DM_INPUT_JOYSTICK_DIR)/Kconfig"
endif
diff --git a/components/drivers/input/joystick/SConscript b/components/drivers/input/joystick/SConscript
index 1b68c7eb65..288a9c5dfe 100644
--- a/components/drivers/input/joystick/SConscript
+++ b/components/drivers/input/joystick/SConscript
@@ -10,6 +10,9 @@ CPPPATH = [cwd + '/../../include']
src = []
+if GetDepend(['RT_INPUT_JOYSTICK_ADC']):
+ src += ['js-adc.c']
+
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
diff --git a/components/drivers/input/joystick/js-adc.c b/components/drivers/input/joystick/js-adc.c
new file mode 100644
index 0000000000..91b53343f2
--- /dev/null
+++ b/components/drivers/input/joystick/js-adc.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2022-3-08 GuEe-GUI the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "input.js.adc"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+struct adc_joystick_axis
+{
+ rt_uint32_t code;
+ rt_uint32_t range[2];
+ rt_uint32_t fuzz;
+ rt_uint32_t flat;
+ rt_uint32_t channel;
+ rt_uint32_t inverted;
+
+ struct rt_adc_device *adc_dev;
+};
+
+struct adc_joysticks
+{
+ struct rt_input_device parent;
+
+ rt_uint32_t num_axis;
+ struct adc_joystick_axis axis[];
+};
+
+static void adc_joysticks_poll(struct rt_input_device *idev)
+{
+ int value;
+ struct adc_joysticks *aj = rt_container_of(idev, struct adc_joysticks, parent);
+
+ for (int i = 0; i < aj->num_axis; ++i)
+ {
+ struct adc_joystick_axis *axis = &aj->axis[i];
+
+ value = rt_adc_read(axis->adc_dev, axis->channel);
+ if (value < 0)
+ {
+ return;
+ }
+
+ if (axis->inverted)
+ {
+ value = (int)(axis->range[0] + axis->range[1]) - value;
+ }
+
+ rt_input_report_abs(&aj->parent, axis->code, value);
+ }
+
+ rt_input_sync(&aj->parent);
+}
+
+static rt_err_t adc_joystick_probe(struct rt_platform_device *pdev)
+{
+ rt_err_t err;
+ rt_uint32_t interval;
+ rt_uint32_t num_axis;
+ struct adc_joysticks *aj;
+ struct adc_joystick_axis *axis;
+ struct rt_device *dev = &pdev->parent;
+ struct rt_ofw_node *np = dev->ofw_node, *axis_np;
+
+ num_axis = rt_ofw_get_child_count(np);
+
+ if (!num_axis)
+ {
+ LOG_E("Keymap is missing");
+
+ return -RT_EINVAL;
+ }
+
+ aj = rt_calloc(1, sizeof(*aj) + sizeof(struct adc_joystick_axis) * num_axis);
+
+ if (!aj)
+ {
+ return -RT_ENOMEM;
+ }
+
+ rt_ofw_foreach_child_node(np, axis_np)
+ {
+ rt_uint32_t reg;
+ const char *propname;
+
+ if (rt_ofw_prop_read_u32(axis_np, "reg", &reg))
+ {
+ err = -RT_EINVAL;
+ rt_ofw_node_put(axis_np);
+ goto _fail;
+ }
+
+ if (reg >= num_axis)
+ {
+ LOG_E("%s: reg %u out of range (num_axis %u)",
+ rt_ofw_node_full_name(axis_np), reg, num_axis);
+ rt_ofw_node_put(axis_np);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ axis = &aj->axis[reg];
+
+ if (axis->adc_dev)
+ {
+ LOG_E("%s: duplicate reg %u", rt_ofw_node_full_name(axis_np), reg);
+ rt_ofw_node_put(axis_np);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ axis->adc_dev = rt_iio_channel_get_by_index(dev, reg, &axis->channel);
+
+ if (!axis->adc_dev)
+ {
+ rt_ofw_node_put(axis_np);
+
+ LOG_E("ADC device not found");
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ if (rt_ofw_prop_read_u32(axis_np, "abs-flat", &axis->flat))
+ {
+ axis->flat = 0;
+ }
+
+ if (rt_ofw_prop_read_u32(axis_np, "abs-fuzz", &axis->fuzz))
+ {
+ axis->fuzz = 0;
+ }
+
+ if (rt_ofw_prop_read_u32_array_index(axis_np, "abs-range", 0, 2, axis->range))
+ {
+ LOG_E("%s: Axis[%d] missing %s", rt_ofw_node_full_name(axis_np), reg, "abs-range");
+ rt_ofw_node_put(axis_np);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ axis->inverted = 0;
+ if (axis->range[0] > axis->range[1])
+ {
+ rt_uint32_t t = axis->range[0];
+
+ axis->range[0] = axis->range[1];
+ axis->range[1] = t;
+ axis->inverted = 1;
+ }
+
+ if (!(propname = rt_ofw_get_prop_fuzzy_name(axis_np, ",code$")) ||
+ rt_ofw_prop_read_u32(axis_np, propname, &axis->code))
+ {
+ LOG_E("%s: Axis[%d] missing %s", rt_ofw_node_full_name(axis_np), reg, "*,code");
+ rt_ofw_node_put(axis_np);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ rt_input_set_absinfo(&aj->parent, axis->code,
+ axis->range[0], axis->range[1], axis->fuzz, axis->flat);
+ rt_input_set_capability(&aj->parent, EV_ABS, axis->code);
+ }
+
+ for (int i = 0; i < num_axis; ++i)
+ {
+ if (!aj->axis[i].adc_dev)
+ {
+ LOG_E("Axis[%d] is missing", i);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+ }
+
+ aj->num_axis = num_axis;
+
+ if (rt_ofw_prop_read_u32(np, "poll-interval", &interval))
+ {
+ interval = 200;
+ }
+
+ if ((err = rt_input_setup_polling(&aj->parent, adc_joysticks_poll)))
+ {
+ goto _fail;
+ }
+
+ rt_input_set_poll_interval(&aj->parent, interval);
+
+ if ((err = rt_input_device_register(&aj->parent)))
+ {
+ goto _fail;
+ }
+
+ dev->user_data = aj;
+
+ return RT_EOK;
+
+_fail:
+ rt_input_remove_config(&aj->parent);
+ rt_free(aj);
+
+ return err;
+}
+
+static rt_err_t adc_joystick_remove(struct rt_platform_device *pdev)
+{
+ struct adc_joysticks *aj = pdev->parent.user_data;
+
+ pdev->parent.user_data = RT_NULL;
+ rt_input_device_unregister(&aj->parent);
+
+ rt_free(aj);
+
+ return RT_EOK;
+}
+
+static const struct rt_ofw_node_id adc_joystick_ofw_ids[] = {
+ { .compatible = "adc-joystick" },
+ { /* sentinel */ }
+};
+
+static struct rt_platform_driver adc_joystick_driver = {
+ .name = "adc-joystick",
+ .ids = adc_joystick_ofw_ids,
+
+ .probe = adc_joystick_probe,
+ .remove = adc_joystick_remove,
+};
+RT_PLATFORM_DRIVER_EXPORT(adc_joystick_driver);
diff --git a/components/drivers/input/keyboard/Kconfig b/components/drivers/input/keyboard/Kconfig
index 6cce9d514b..b5b4ae722d 100644
--- a/components/drivers/input/keyboard/Kconfig
+++ b/components/drivers/input/keyboard/Kconfig
@@ -2,6 +2,13 @@ menuconfig RT_INPUT_KEYBOARD
bool "Keyboards"
default n
+config RT_INPUT_KEYBOARD_ADC
+ bool "ADC Ladder Buttons"
+ depends on RT_INPUT_KEYBOARD
+ depends on RT_USING_OFW
+ depends on RT_USING_ADC
+ default n
+
config RT_INPUT_KEYBOARD_GPIO
bool "GPIO"
depends on RT_INPUT_KEYBOARD
diff --git a/components/drivers/input/keyboard/SConscript b/components/drivers/input/keyboard/SConscript
index 67c4713e79..af465ce355 100644
--- a/components/drivers/input/keyboard/SConscript
+++ b/components/drivers/input/keyboard/SConscript
@@ -10,6 +10,9 @@ CPPPATH = [cwd + '/../../include']
src = []
+if GetDepend(['RT_INPUT_KEYBOARD_ADC']):
+ src += ['keys-adc.c']
+
if GetDepend(['RT_INPUT_KEYBOARD_GPIO']):
src += ['keys-gpio.c']
diff --git a/components/drivers/input/keyboard/keys-adc.c b/components/drivers/input/keyboard/keys-adc.c
new file mode 100644
index 0000000000..20608ced4a
--- /dev/null
+++ b/components/drivers/input/keyboard/keys-adc.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2022-3-08 GuEe-GUI the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "input.keyboard.adc"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+struct adc_keys_button
+{
+ rt_uint32_t voltage;
+ rt_uint32_t keycode;
+};
+
+struct adc_keys
+{
+ struct rt_input_device parent;
+ struct rt_adc_device *adc_dev;
+
+ int channel;
+ rt_uint32_t num_keys;
+ rt_uint32_t last_key;
+ rt_uint32_t keyup_voltage;
+ struct adc_keys_button kbtn[];
+};
+
+static void adc_keys_poll(struct rt_input_device *idev)
+{
+ int value, keycode = 0;
+ rt_uint32_t diff, closest = 0xffffffff;
+ struct adc_keys *tk = rt_container_of(idev, struct adc_keys, parent);
+
+ value = rt_adc_read(tk->adc_dev, tk->channel);
+
+ if (value < 0)
+ {
+ /* Forcibly release key if any was pressed */
+ value = tk->keyup_voltage;
+ }
+ else
+ {
+ for (int i = 0; i < tk->num_keys; ++i)
+ {
+ rt_uint32_t sample = (rt_uint32_t)value;
+
+ diff = tk->kbtn[i].voltage > sample ? tk->kbtn[i].voltage - sample : sample - tk->kbtn[i].voltage;
+
+ if (diff < closest)
+ {
+ closest = diff;
+ keycode = tk->kbtn[i].keycode;
+ }
+ }
+ }
+
+ if (value >= 0)
+ {
+ rt_uint32_t sample = (rt_uint32_t)value;
+ rt_uint32_t keyup_diff = tk->keyup_voltage > sample ? tk->keyup_voltage - sample : sample - tk->keyup_voltage;
+
+ if (keyup_diff < closest)
+ {
+ keycode = 0;
+ }
+ }
+
+ if (tk->last_key && tk->last_key != keycode)
+ {
+ rt_input_report_key(&tk->parent, tk->last_key, 0);
+ }
+
+ if (keycode && tk->last_key != keycode)
+ {
+ rt_input_report_key(&tk->parent, keycode, 1);
+ }
+
+ if (tk->last_key != keycode)
+ {
+ rt_input_sync(&tk->parent);
+ }
+
+ tk->last_key = keycode;
+}
+
+static rt_err_t adc_key_probe(struct rt_platform_device *pdev)
+{
+ int i = 0;
+ rt_err_t err;
+ rt_uint32_t interval;
+ rt_uint32_t num_keys;
+ struct adc_keys *tk;
+ struct rt_device *dev = &pdev->parent;
+ struct rt_ofw_node *np = dev->ofw_node, *key_np;
+
+ num_keys = rt_ofw_get_child_count(np);
+
+ if (!num_keys)
+ {
+ LOG_E("Keymap is missing");
+
+ return -RT_EINVAL;
+ }
+
+ tk = rt_calloc(1, sizeof(*tk) + sizeof(struct adc_keys_button) * num_keys);
+
+ if (!tk)
+ {
+ return -RT_ENOMEM;
+ }
+
+ tk->adc_dev = rt_iio_channel_get_by_name(dev, "buttons", &tk->channel);
+
+ if (!tk->adc_dev)
+ {
+ LOG_E("ADC device not found");
+
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ rt_ofw_foreach_child_node(np, key_np)
+ {
+ const char *propname;
+
+ if (rt_ofw_prop_read_u32(key_np, "press-threshold-microvolt",
+ &tk->kbtn[i].voltage))
+ {
+ LOG_E("%s: Key with invalid or missing %s",
+ rt_ofw_node_full_name(key_np), "press-threshold-microvolt");
+ rt_ofw_node_put(key_np);
+
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ tk->kbtn[i].voltage /= 1000;
+
+ if (!(propname = rt_ofw_get_prop_fuzzy_name(key_np, ",code$")) ||
+ rt_ofw_prop_read_u32(key_np, propname, &tk->kbtn[i].keycode))
+ {
+ LOG_E("%s: Key with invalid or missing %s",
+ rt_ofw_node_full_name(key_np), "*,code");
+ rt_ofw_node_put(key_np);
+
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ rt_input_set_capability(&tk->parent, EV_KEY, tk->kbtn[i].keycode);
+
+ ++i;
+ }
+
+ tk->num_keys = num_keys;
+
+ if (rt_ofw_prop_read_u32(np, "keyup-threshold-microvolt", &tk->keyup_voltage))
+ {
+ LOG_E("Invalid or missing keyup voltage");
+
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ tk->keyup_voltage /= 1000;
+
+ if (rt_ofw_prop_read_u32(np, "poll-interval", &interval))
+ {
+ interval = 200;
+ }
+
+ if ((err = rt_input_setup_polling(&tk->parent, adc_keys_poll)))
+ {
+ goto _fail;
+ }
+
+ rt_input_set_poll_interval(&tk->parent, interval);
+
+ if ((err = rt_input_device_register(&tk->parent)))
+ {
+ goto _fail;
+ }
+
+ dev->user_data = tk;
+
+ return RT_EOK;
+
+_fail:
+ rt_input_remove_config(&tk->parent);
+ rt_free(tk);
+
+ return err;
+}
+
+static rt_err_t adc_key_remove(struct rt_platform_device *pdev)
+{
+ struct adc_keys *tk = pdev->parent.user_data;
+
+ pdev->parent.user_data = RT_NULL;
+ rt_input_device_unregister(&tk->parent);
+
+ rt_free(tk);
+
+ return RT_EOK;
+}
+
+static const struct rt_ofw_node_id adc_key_ofw_ids[] = {
+ { .compatible = "adc-keys" },
+ { /* sentinel */ }
+};
+
+static struct rt_platform_driver adc_key_driver = {
+ .name = "adc-keys",
+ .ids = adc_key_ofw_ids,
+
+ .probe = adc_key_probe,
+ .remove = adc_key_remove,
+};
+RT_PLATFORM_DRIVER_EXPORT(adc_key_driver);
diff --git a/components/drivers/input/touchscreen/Kconfig b/components/drivers/input/touchscreen/Kconfig
index 63ce6d9665..4710c6f29f 100644
--- a/components/drivers/input/touchscreen/Kconfig
+++ b/components/drivers/input/touchscreen/Kconfig
@@ -3,6 +3,24 @@ menuconfig RT_INPUT_TOUCHSCREEN
select RT_USING_TOUCH
default n
+config RT_INPUT_TOUCHSCREEN_ADS7846
+ bool "TI ADS7843/45/46/73 XPT/TSC2046 touch screen support"
+ depends on RT_INPUT_TOUCHSCREEN
+ depends on RT_USING_SPI
+ depends on RT_USING_OFW
+ depends on RT_USING_PIN
+ depends on RT_USING_REGULATOR
+ default n
+
+config RT_INPUT_TOUCHSCREEN_GOODIX
+ bool "Goodix GT9xx capacitive touch screen support"
+ depends on RT_INPUT_TOUCHSCREEN
+ depends on RT_USING_I2C
+ depends on RT_USING_OFW
+ depends on RT_USING_PIN
+ depends on RT_USING_REGULATOR
+ default n
+
if RT_INPUT_TOUCHSCREEN
osource "$(SOC_DM_INPUT_TOUCHSCREEN_DIR)/Kconfig"
endif
diff --git a/components/drivers/input/touchscreen/SConscript b/components/drivers/input/touchscreen/SConscript
index 1465692553..6efa769291 100644
--- a/components/drivers/input/touchscreen/SConscript
+++ b/components/drivers/input/touchscreen/SConscript
@@ -10,6 +10,12 @@ CPPPATH = [cwd + '/../../include']
src = []
+if GetDepend(['RT_INPUT_TOUCHSCREEN_ADS7846']):
+ src += ['ts-ads7846.c']
+
+if GetDepend(['RT_INPUT_TOUCHSCREEN_GOODIX']):
+ src += ['ts-goodix.c']
+
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
diff --git a/components/drivers/input/touchscreen/ts-ads7846.c b/components/drivers/input/touchscreen/ts-ads7846.c
new file mode 100644
index 0000000000..e529727367
--- /dev/null
+++ b/components/drivers/input/touchscreen/ts-ads7846.c
@@ -0,0 +1,1194 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2022-11-26 GuEe-GUI the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <drivers/byteorder.h>
+
+#define DBG_TAG "input.ts.ads7846"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+/*
+ * The ADS7846 has touchscreen and other sensors.
+ * Earlier ads784x chips are somewhat compatible.
+ */
+#define ADS_START (1 << 7)
+#define ADS_A2A1A0_d_y (1 << 4) /* Differential */
+#define ADS_A2A1A0_d_z1 (3 << 4) /* Differential */
+#define ADS_A2A1A0_d_z2 (4 << 4) /* Differential */
+#define ADS_A2A1A0_d_x (5 << 4) /* Differential */
+#define ADS_A2A1A0_temp0 (0 << 4) /* Non-differential */
+#define ADS_A2A1A0_vbatt (2 << 4) /* Non-differential */
+#define ADS_A2A1A0_vaux (6 << 4) /* Non-differential */
+#define ADS_A2A1A0_temp1 (7 << 4) /* Non-differential */
+#define ADS_8_BIT (1 << 3)
+#define ADS_12_BIT (0 << 3)
+#define ADS_SER (1 << 2) /* Non-differential */
+#define ADS_DFR (0 << 2) /* Differential */
+#define ADS_PD10_PDOWN (0 << 0) /* Low power mode + penirq */
+#define ADS_PD10_ADC_ON (1 << 0) /* ADC on */
+#define ADS_PD10_REF_ON (2 << 0) /* vREF on + penirq */
+#define ADS_PD10_ALL_ON (3 << 0) /* ADC + vREF on */
+
+#define MAX_12BIT ((1 << 12) - 1)
+
+/* Leave ADC powered up (disables penirq) between differential samples */
+#define READ_12BIT_DFR(x, adc, vref) (ADS_START | ADS_A2A1A0_d_##x | ADS_12_BIT | ADS_DFR | \
+ (adc ? ADS_PD10_ADC_ON : 0) | (vref ? ADS_PD10_REF_ON : 0))
+
+#define READ_Y(vref) (READ_12BIT_DFR(y, 1, vref))
+#define READ_Z1(vref) (READ_12BIT_DFR(z1, 1, vref))
+#define READ_Z2(vref) (READ_12BIT_DFR(z2, 1, vref))
+#define READ_X(vref) (READ_12BIT_DFR(x, 1, vref))
+#define PWRDOWN (READ_12BIT_DFR(y, 0, 0)) /* LAST */
+
+/*
+ * Single-ended samples need to first power up reference voltage;
+ * We leave both ADC and VREF powered
+ */
+#define READ_12BIT_SER(x) (ADS_START | ADS_A2A1A0_##x | ADS_12_BIT | ADS_SER)
+
+#define REF_ON (READ_12BIT_DFR(x, 1, 1))
+#define REF_OFF (READ_12BIT_DFR(y, 0, 0))
+
+#define TS_POLL_DELAY 1 /* ms delay before the first sample */
+#define TS_POLL_PERIOD 5 /* ms delay between samples */
+
+/* This driver doesn't aim at the peak continuous sample rate */
+#define SAMPLE_BITS (8 /*cmd*/ + 16 /*sample*/ + 2 /* before, after */)
+
+#define NSEC_PER_SEC 1000000000L
+#define NSEC_PER_USEC 1000L
+
+/*
+ * Order commands in the most optimal way to reduce Vref switching and
+ * settling time:
+ * Measure: X; Vref: X+, X-; IN: Y+
+ * Measure: Y; Vref: Y+, Y-; IN: X+
+ * Measure: Z1; Vref: Y+, X-; IN: X+
+ * Measure: Z2; Vref: Y+, X-; IN: Y-
+ */
+enum ads7846_cmds
+{
+ ADS7846_X,
+ ADS7846_Y,
+ ADS7846_Z1,
+ ADS7846_Z2,
+ ADS7846_PWDOWN,
+};
+
+rt_packed(struct ads7846_buf {
+ rt_uint8_t cmd;
+ rt_be16_t data;
+});
+
+struct ads7846_ser_req
+{
+ rt_uint8_t ref_on;
+ rt_uint8_t command;
+ rt_uint8_t ref_off;
+ rt_uint16_t scratch;
+ struct rt_spi_message msg[6];
+
+ rt_be16_t sample;
+};
+
+struct ads7845_ser_req
+{
+ rt_uint8_t command[3];
+ struct rt_spi_message msg[1];
+ rt_uint8_t sample[3];
+};
+
+struct ads7846_buf_layout
+{
+ rt_uint32_t offset;
+ rt_uint32_t count;
+ rt_uint32_t skip;
+};
+
+struct ads7846_packet
+{
+ rt_uint32_t count;
+ rt_uint32_t count_skip;
+ rt_uint32_t cmds;
+ rt_uint32_t last_cmd_idx;
+ struct ads7846_buf_layout layout[5];
+ struct ads7846_buf *rx;
+ struct ads7846_buf *tx;
+
+ struct ads7846_buf pwrdown_cmd;
+
+ rt_bool_t ignore;
+ rt_uint16_t x, y, z1, z2;
+};
+
+struct ads7846_platform_data
+{
+ rt_uint16_t model; /* 7843, 7845, 7846, 7873. */
+ rt_uint16_t vref_delay_usecs; /* 0 for external vref; etc */
+ rt_uint16_t vref_mv; /* external vref value, milliVolts ads7846: if 0, use internal vref */
+ rt_bool_t keep_vref_on; /* set to keep vref on for differential measurements as well */
+ rt_bool_t swap_xy; /* swap x and y axes */
+
+ /*
+ * Settling time of the analog signals; a function of Vcc and the
+ * capacitance on the X/Y drivers. If set to non-zero, two samples
+ * are taken with settle_delay us apart, and the second one is used.
+ * ~150 uSec with 0.01uF caps.
+ */
+ rt_uint16_t settle_delay_usecs;
+
+ /*
+ * If set to non-zero, after samples are taken this delay is applied
+ * and penirq is rechecked, to help avoid false events. This value
+ * is affected by the material used to build the touch layer.
+ */
+ rt_uint16_t penirq_recheck_delay_usecs;
+
+ rt_uint16_t x_plate_ohms;
+ rt_uint16_t y_plate_ohms;
+
+ rt_uint16_t x_min, x_max;
+ rt_uint16_t y_min, y_max;
+ rt_uint16_t pressure_min, pressure_max;
+
+ rt_uint16_t debounce_max; /* max number of additional readings per sample */
+ rt_uint16_t debounce_tol; /* tolerance used for filtering */
+ rt_uint16_t debounce_rep; /* additional consecutive good readings required after the first two */
+
+ /* platform specific debounce time for the gpio_pendown */
+ rt_uint32_t gpio_pendown_debounce;
+};
+
+enum ads7846_filter
+{
+ ADS7846_FILTER_OK,
+ ADS7846_FILTER_REPEAT,
+ ADS7846_FILTER_IGNORE,
+};
+
+struct ads7846
+{
+ struct rt_input_device parent;
+
+ int irq;
+ rt_ubase_t gpio_pendown;
+ rt_uint8_t gpio_pendown_active;
+
+ int read_cnt;
+ int read_rep;
+ int last_read;
+
+ struct rt_spi_message msg[5];
+
+ rt_bool_t pendown;
+ rt_bool_t stopped; /* P: lock */
+ rt_bool_t suspended; /* P: lock */
+
+ struct rt_spi_device *spi;
+ struct rt_thread *ts_task;
+ struct rt_regulator *supply;
+
+ struct ads7846_packet packet;
+ struct ads7846_platform_data pdata;
+
+ rt_bool_t use_internal;
+
+ void *filter_data;
+ int (*filter)(void *data, int data_idx, int *val);
+};
+
+/*
+ * Prefix of struct input_touch_properties in input_touch.c — must stay
+ * in sync (ads7846 is single-slot; only used to apply ti,swap-xy).
+ */
+struct ads7846_input_touch_prop
+{
+ rt_uint32_t max_x;
+ rt_uint32_t max_y;
+ rt_bool_t invert_x;
+ rt_bool_t invert_y;
+ rt_bool_t swap_x_y;
+ rt_uint16_t track_id;
+ rt_uint32_t num_slots;
+ void *touch_dev;
+};
+
+static void ads7846_apply_legacy_swap_xy(struct rt_input_device *idev,
+ rt_bool_t vendor_swap_xy)
+{
+ struct ads7846_input_touch_prop *prop;
+ rt_uint32_t axis_x;
+ rt_uint32_t axis_y;
+ struct rt_input_absinfo tmp;
+
+ if (!vendor_swap_xy || !idev || !idev->touch)
+ {
+ return;
+ }
+
+ prop = idev->touch;
+ if (prop->swap_x_y)
+ {
+ return;
+ }
+
+ axis_x = prop->num_slots ? ABS_MT_POSITION_X : ABS_X;
+ axis_y = prop->num_slots ? ABS_MT_POSITION_Y : ABS_Y;
+
+ rt_memcpy(&tmp, &idev->absinfo[axis_x], sizeof(tmp));
+ rt_memcpy(&idev->absinfo[axis_x], &idev->absinfo[axis_y], sizeof(tmp));
+ rt_memcpy(&idev->absinfo[axis_y], &tmp, sizeof(tmp));
+
+ prop->max_x = idev->absinfo[axis_x].maximum;
+ prop->max_y = idev->absinfo[axis_y].maximum;
+ prop->swap_x_y = RT_TRUE;
+
+ if (prop->touch_dev)
+ {
+ struct rt_touch_device *tdev = prop->touch_dev;
+
+ tdev->info.range_x = prop->max_x;
+ tdev->info.range_y = prop->max_y;
+ }
+}
+
+rt_inline rt_uint16_t get_unaligned_be16(const void *p)
+{
+ const rt_packed(struct { rt_be16_t v; }) *ptr = (typeof(ptr))(p);
+
+ return rt_be16_to_cpu(ptr->v);
+}
+
+static void ads7846_report_pen_up(struct ads7846 *ts);
+
+static rt_bool_t ads7846_pendown_active(struct ads7846 *ts)
+{
+ return ts->gpio_pendown == PIN_NONE ||
+ rt_pin_read(ts->gpio_pendown) == ts->gpio_pendown_active;
+}
+
+static void ads7846_stop(struct ads7846 *ts)
+{
+ if (!ts->suspended)
+ {
+ ts->stopped = RT_TRUE;
+ rt_hw_wmb();
+ }
+}
+
+static void ads7846_restart(struct ads7846 *ts)
+{
+ if (!ts->suspended)
+ {
+ /* Check if pen was released since last stop */
+ if (ts->pendown && !ads7846_pendown_active(ts))
+ {
+ ads7846_report_pen_up(ts);
+ }
+
+ ts->stopped = RT_FALSE;
+ rt_hw_wmb();
+ }
+}
+
+rt_inline void ads7846_disable(struct ads7846 *ts)
+{
+ ads7846_stop(ts);
+ if (ts->supply)
+ {
+ rt_regulator_disable(ts->supply);
+ }
+}
+
+rt_inline void ads7846_enable(struct ads7846 *ts)
+{
+ if (ts->supply && rt_regulator_enable(ts->supply))
+ {
+ LOG_E("%s: Failed to enable supply", rt_dm_dev_get_name(&ts->spi->parent));
+ }
+
+ ads7846_restart(ts);
+}
+
+static int ads7846_debounce_filter(void *ads, int data_idx, int *val)
+{
+ struct ads7846 *ts = ads;
+
+ if (!ts->read_cnt || (rt_abs(ts->last_read - *val) > ts->pdata.debounce_tol))
+ {
+ /* Start over collecting consistent readings. */
+
+ ts->read_rep = 0;
+ /*
+ * Repeat it, if this was the first read or the read
+ * wasn't consistent enough.
+ */
+ if (ts->read_cnt < ts->pdata.debounce_max)
+ {
+ ts->last_read = *val;
+ ts->read_cnt++;
+
+ return ADS7846_FILTER_REPEAT;
+ }
+ else
+ {
+ /*
+ * Maximum number of debouncing reached and still
+ * not enough number of consistent readings. Abort
+ * the whole sample, repeat it in the next sampling
+ * period.
+ */
+ ts->read_cnt = 0;
+
+ return ADS7846_FILTER_IGNORE;
+ }
+ }
+ else
+ {
+ if (++ts->read_rep > ts->pdata.debounce_rep)
+ {
+ /* Got a good reading for this coordinate, go for the next one. */
+ ts->read_cnt = 0;
+ ts->read_rep = 0;
+ return ADS7846_FILTER_OK;
+ }
+ else
+ {
+ /* Read more values that are consistent. */
+ ts->read_cnt++;
+ return ADS7846_FILTER_REPEAT;
+ }
+ }
+}
+
+static int ads7846_no_filter(void *ads, int data_idx, int *val)
+{
+ return ADS7846_FILTER_OK;
+}
+
+static int ads7846_read12_ser(struct ads7846 *ts, unsigned command)
+{
+ int status;
+ struct ads7846_ser_req req;
+
+ rt_memset(&req, 0, sizeof(req));
+
+ /*
+ * Internal VREF (7846 with vref-mv unset): turn ref on, discard one
+ * conversion, then wait ti,vref-delay-usecs. Linux does this in one
+ * SPI transaction with inter-xfer delay; RT-Thread SPI has no delay
+ * field, so CS may deassert between phases — same risk as a gap after
+ * the dummy read on some hosts.
+ */
+ if (ts->use_internal)
+ {
+ req.ref_on = REF_ON;
+ req.msg[0].send_buf = &req.ref_on;
+ req.msg[0].length = 1;
+ req.msg[0].cs_take = 1;
+ req.msg[0].cs_release = 0;
+ req.msg[1].recv_buf = &req.scratch;
+ req.msg[1].length = 2;
+ rt_spi_message_append(&req.msg[0], &req.msg[1]);
+
+ ads7846_stop(ts);
+ status = rt_spi_transfer_message(ts->spi, &req.msg[0]) ? -RT_EIO : RT_EOK;
+ ads7846_restart(ts);
+
+ if (status)
+ {
+ return status;
+ }
+
+ rt_hw_us_delay(ts->pdata.vref_delay_usecs);
+ command |= ADS_PD10_REF_ON;
+ }
+
+ /* Enable ADC in every case */
+ command |= ADS_PD10_ADC_ON;
+
+ rt_memset(req.msg, 0, sizeof(req.msg));
+
+ /* Take sample */
+ req.command = (rt_uint8_t)command;
+ req.msg[0].send_buf = &req.command;
+ req.msg[0].length = 1;
+ req.msg[0].cs_take = 1;
+
+ req.msg[1].recv_buf = &req.sample;
+ req.msg[1].length = 2;
+ rt_spi_message_append(&req.msg[0], &req.msg[1]);
+
+ /* Converter in low power mode & enable PENIRQ */
+ req.ref_off = PWRDOWN;
+ req.msg[2].send_buf = &req.ref_off;
+ req.msg[2].length = 1;
+ rt_spi_message_append(&req.msg[0], &req.msg[2]);
+
+ req.msg[3].recv_buf = &req.scratch;
+ req.msg[3].length = 2;
+ req.msg[3].cs_take = 0;
+ rt_spi_message_append(&req.msg[0], &req.msg[3]);
+
+ ads7846_stop(ts);
+ status = rt_spi_transfer_message(ts->spi, &req.msg[0]) ? -RT_EIO : RT_EOK;
+ ads7846_restart(ts);
+
+ if (status == 0)
+ {
+ /* On-wire is a must-ignore bit, a BE12 value, then padding */
+ status = rt_be16_to_cpu(req.sample);
+ status = status >> 3;
+ status &= 0x0fff;
+ }
+
+ return status;
+}
+
+static int ads7845_read12_ser(struct ads7846 *ts, unsigned command)
+{
+ int status;
+ struct ads7845_ser_req req;
+
+ rt_memset(&req, 0, sizeof(req));
+
+ req.command[0] = (rt_uint8_t)command;
+ req.msg[0].send_buf = req.command;
+ req.msg[0].recv_buf = req.sample;
+ req.msg[0].length = 3;
+
+ ads7846_stop(ts);
+ status = rt_spi_transfer_message(ts->spi, &req.msg[0]) ? -RT_EIO : RT_EOK;
+ ads7846_restart(ts);
+
+ if (status == 0)
+ {
+ /* BE12 value, then padding */
+ status = get_unaligned_be16(&req.sample[1]);
+ status = status >> 3;
+ status &= 0x0fff;
+ }
+
+ return status;
+}
+
+static rt_bool_t ads7846_cmd_need_settle(enum ads7846_cmds cmd_idx)
+{
+ switch (cmd_idx)
+ {
+ case ADS7846_X:
+ case ADS7846_Y:
+ case ADS7846_Z1:
+ case ADS7846_Z2:
+ return RT_TRUE;
+
+ case ADS7846_PWDOWN:
+ return RT_FALSE;
+
+ default:
+ break;
+ }
+
+ return RT_FALSE;
+}
+
+static int ads7846_get_value(struct ads7846_buf *buf)
+{
+ int value;
+
+ value = rt_be16_to_cpu(buf->data);
+
+ /* Enforce ADC output is 12 bits width */
+ return (value >> 3) & 0xfff;
+}
+
+static void ads7846_set_cmd_val(struct ads7846 *ts,
+ enum ads7846_cmds cmd_idx, rt_uint16_t val)
+{
+ struct ads7846_packet *packet = &ts->packet;
+
+ switch (cmd_idx)
+ {
+ case ADS7846_Y:
+ packet->y = val;
+ break;
+
+ case ADS7846_X:
+ packet->x = val;
+ break;
+
+ case ADS7846_Z1:
+ packet->z1 = val;
+ break;
+
+ case ADS7846_Z2:
+ packet->z2 = val;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static rt_uint8_t ads7846_get_cmd(enum ads7846_cmds cmd_idx, int vref)
+{
+ switch (cmd_idx)
+ {
+ case ADS7846_Y:
+ return READ_Y(vref);
+
+ case ADS7846_X:
+ return READ_X(vref);
+
+ /* 7846 specific commands */
+ case ADS7846_Z1:
+ return READ_Z1(vref);
+
+ case ADS7846_Z2:
+ return READ_Z2(vref);
+
+ case ADS7846_PWDOWN:
+ return PWRDOWN;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static rt_err_t ads7846_setup_spi_msg(struct ads7846 *ts)
+{
+ rt_size_t size = 0, time;
+ int vref = ts->pdata.keep_vref_on;
+ rt_uint32_t count, offset = 0;
+ struct rt_spi_message *m = &ts->msg[0];
+ struct ads7846_packet *packet = &ts->packet;
+
+ /* Time per bit */
+ time = NSEC_PER_SEC / ts->spi->config.max_hz;
+
+ count = ts->pdata.settle_delay_usecs * NSEC_PER_USEC / time;
+ packet->count_skip = RT_DIV_ROUND_UP(count, 24);
+
+ if (ts->pdata.debounce_max && ts->pdata.debounce_rep)
+ {
+ /*
+ * ads7846_debounce_filter() is making ts->debounce_rep + 2
+ * reads. So we need to get all samples for normal case.
+ */
+ packet->count = ts->pdata.debounce_rep + 2;
+ }
+ else
+ {
+ packet->count = 1;
+ }
+
+ if (ts->pdata.model == 7846)
+ {
+ packet->cmds = 5; /* x, y, z1, z2, pwdown */
+ }
+ else
+ {
+ packet->cmds = 3; /* x, y, pwdown */
+ }
+
+ for (rt_uint32_t cmd_idx = 0; cmd_idx < packet->cmds; ++cmd_idx)
+ {
+ rt_uint32_t max_count;
+ struct ads7846_buf_layout *layout = &packet->layout[cmd_idx];
+
+ if (cmd_idx == packet->cmds - 1)
+ {
+ cmd_idx = ADS7846_PWDOWN;
+ }
+
+ if (ads7846_cmd_need_settle(cmd_idx))
+ {
+ max_count = packet->count + packet->count_skip;
+ }
+ else
+ {
+ max_count = packet->count;
+ }
+
+ layout->offset = offset;
+ offset += max_count;
+ layout->count = max_count;
+ layout->skip = packet->count_skip;
+ size += sizeof(*packet->tx) * max_count;
+ }
+
+ if (!(packet->tx = rt_calloc(1, size)))
+ {
+ return -RT_ENOMEM;
+ }
+
+ if (!(packet->rx = rt_calloc(1, size)))
+ {
+ rt_free(packet->tx);
+ packet->tx = RT_NULL;
+
+ return -RT_ENOMEM;
+ }
+
+ if (ts->pdata.model == 7873)
+ {
+ /*
+ * The AD7873 is almost identical to the ADS7846
+ * keep VREF off during differential/ratiometric conversion modes.
+ */
+ ts->pdata.model = 7846;
+ vref = 0;
+ }
+
+ for (rt_uint32_t cmd_idx = 0; cmd_idx < packet->cmds; ++cmd_idx)
+ {
+ rt_uint8_t cmd;
+ struct ads7846_buf_layout *layout = &packet->layout[cmd_idx];
+
+ if (cmd_idx == packet->cmds - 1)
+ {
+ cmd_idx = ADS7846_PWDOWN;
+ }
+
+ cmd = ads7846_get_cmd(cmd_idx, vref);
+
+ for (rt_uint32_t b = 0; b < layout->count; ++b)
+ {
+ packet->tx[layout->offset + b].cmd = cmd;
+ }
+ }
+
+ m->send_buf = packet->tx;
+ m->recv_buf = packet->rx;
+ m->length = size;
+
+ return RT_EOK;
+}
+
+static rt_err_t ads7846_filter(struct ads7846 *ts)
+{
+ int action, val;
+ struct ads7846_packet *packet = &ts->packet;
+
+ packet->ignore = RT_FALSE;
+
+ for (rt_uint32_t cmd_idx = packet->last_cmd_idx; cmd_idx < packet->cmds - 1; ++cmd_idx)
+ {
+ struct ads7846_buf_layout *layout = &packet->layout[cmd_idx];
+
+ packet->last_cmd_idx = cmd_idx;
+
+ for (rt_uint32_t b = layout->skip; b < layout->count; ++b)
+ {
+ val = ads7846_get_value(&packet->rx[layout->offset + b]);
+
+ action = ts->filter(ts->filter_data, cmd_idx, &val);
+
+ if (action == ADS7846_FILTER_REPEAT)
+ {
+ if (b == layout->count - 1)
+ {
+ return -RT_ERROR;
+ }
+ }
+ else if (action == ADS7846_FILTER_OK)
+ {
+ ads7846_set_cmd_val(ts, cmd_idx, val);
+ break;
+ }
+ else
+ {
+ packet->ignore = RT_TRUE;
+ return RT_EOK;
+ }
+ }
+ }
+
+ return RT_EOK;
+}
+
+static void ads7846_report_pen_up(struct ads7846 *ts)
+{
+ struct rt_input_device *idev = &ts->parent;
+
+ rt_input_report_key(idev, BTN_TOUCH, 0);
+ rt_input_report_abs(idev, ABS_PRESSURE, 0);
+ rt_input_sync(idev);
+
+ ts->pendown = RT_FALSE;
+}
+
+static void ads7846_read_state(struct ads7846 *ts)
+{
+ rt_uint32_t msg_idx = 0;
+ struct rt_spi_message *m;
+ struct ads7846_packet *packet = &ts->packet;
+
+ packet->last_cmd_idx = 0;
+
+ while (RT_TRUE)
+ {
+ m = &ts->msg[msg_idx];
+
+ if (rt_spi_transfer_message(ts->spi, m))
+ {
+ packet->ignore = RT_TRUE;
+ return;
+ }
+
+ if (ads7846_filter(ts))
+ {
+ continue;
+ }
+
+ return;
+ }
+}
+
+static void ads7846_report_state(struct ads7846 *ts)
+{
+ rt_uint32_t Rt;
+ rt_uint16_t x, y, z1, z2;
+ struct ads7846_packet *packet = &ts->packet;
+
+ x = packet->x;
+ y = packet->y;
+
+ if (ts->pdata.model == 7845)
+ {
+ z1 = 0;
+ z2 = 0;
+ }
+ else
+ {
+ z1 = packet->z1;
+ z2 = packet->z2;
+ }
+
+ /* Range filtering */
+ if (x == MAX_12BIT)
+ {
+ x = 0;
+ }
+
+ if (ts->pdata.model == 7843 || ts->pdata.model == 7845)
+ {
+ Rt = ts->pdata.pressure_max / 2;
+ }
+ else if (x && z1)
+ {
+ /* compute touch pressure resistance using equation #2 */
+ Rt = z2;
+ Rt -= z1;
+ Rt *= ts->pdata.x_plate_ohms;
+ Rt = RT_DIV_ROUND_CLOSEST(Rt, 16);
+ Rt *= x;
+ Rt /= z1;
+ Rt = RT_DIV_ROUND_CLOSEST(Rt, 256);
+ }
+ else
+ {
+ Rt = 0;
+ }
+
+ if (!(packet->ignore || Rt > ts->pdata.pressure_max))
+ {
+ if (ts->pdata.penirq_recheck_delay_usecs)
+ {
+ rt_hw_us_delay(ts->pdata.penirq_recheck_delay_usecs);
+
+ if (!ads7846_pendown_active(ts))
+ {
+ Rt = 0;
+ }
+ }
+
+ if (Rt)
+ {
+ struct rt_input_device *idev = &ts->parent;
+
+ if (!ts->pendown)
+ {
+ rt_input_report_key(idev, BTN_TOUCH, 1);
+ ts->pendown = RT_TRUE;
+ }
+
+ rt_input_report_touch_inactive(idev, RT_TRUE);
+ rt_input_report_touch_position(idev, x, y, RT_FALSE);
+ rt_input_report_abs(idev, ABS_PRESSURE, ts->pdata.pressure_max - Rt);
+
+ rt_input_sync(idev);
+ }
+ }
+}
+
+static void ads7846_ts_task(void *param)
+{
+ rt_tick_t timeout;
+ struct ads7846 *ts = param;
+
+ while (RT_TRUE)
+ {
+ rt_thread_suspend(ts->ts_task);
+ rt_schedule();
+
+ rt_thread_mdelay(TS_POLL_DELAY);
+
+ while (!ts->stopped && ads7846_pendown_active(ts))
+ {
+ /* Pen is down, continue with the measurement */
+ ads7846_read_state(ts);
+
+ if (!ts->stopped)
+ {
+ ads7846_report_state(ts);
+ }
+
+ timeout = rt_tick_from_millisecond(TS_POLL_PERIOD);
+ timeout += rt_tick_get();
+
+ while (timeout > rt_tick_get() && !ts->stopped)
+ {
+ rt_thread_yield();
+ }
+ }
+
+ if (ts->pendown && !ts->stopped)
+ {
+ ads7846_report_pen_up(ts);
+ }
+ }
+}
+
+static void ads7846_isr(int irq, void *param)
+{
+ struct ads7846 *ts = param;
+
+ if (ads7846_pendown_active(ts))
+ {
+ rt_thread_resume(ts->ts_task);
+ }
+}
+
+#ifdef RT_USING_PM
+static rt_err_t ads7846_pm_suspend(const struct rt_device *device, rt_uint8_t mode)
+{
+ struct ads7846 *ts = rt_container_of(device, struct ads7846, parent.parent);
+
+ if (!ts->suspended)
+ {
+ ads7846_disable(ts);
+
+ ts->suspended = RT_TRUE;
+ }
+
+ return RT_EOK;
+}
+
+static void ads7846_pm_resume(const struct rt_device *device, rt_uint8_t mode)
+{
+ struct ads7846 *ts = rt_container_of(device, struct ads7846, parent.parent);
+
+ if (ts->suspended)
+ {
+ ts->suspended = RT_FALSE;
+
+ ads7846_enable(ts);
+ }
+}
+
+static const struct rt_device_pm_ops ads7846_pm_ops = {
+ .suspend = ads7846_pm_suspend,
+ .resume = ads7846_pm_resume,
+};
+#endif /* RT_USING_PM */
+
+static void ads7846_get_platform_data(struct rt_spi_device *spi_dev,
+ struct ads7846_platform_data *pdata)
+{
+ rt_uint32_t value;
+ struct rt_device *dev = &spi_dev->parent;
+
+ pdata->model = (rt_ubase_t)rt_spi_device_id_data(spi_dev);
+
+ rt_dm_dev_prop_read_u16(dev, "ti,vref-delay-usecs", &pdata->vref_delay_usecs);
+ rt_dm_dev_prop_read_u16(dev, "ti,vref-mv", &pdata->vref_mv);
+
+ pdata->keep_vref_on = rt_dm_dev_prop_read_bool(dev, "ti,keep-vref-on");
+
+ pdata->swap_xy = rt_dm_dev_prop_read_bool(dev, "ti,swap-xy");
+
+ rt_dm_dev_prop_read_u16(dev, "ti,settle-delay-usec",
+ &pdata->settle_delay_usecs);
+ rt_dm_dev_prop_read_u16(dev, "ti,penirq-recheck-delay-usecs",
+ &pdata->penirq_recheck_delay_usecs);
+
+ rt_dm_dev_prop_read_u16(dev, "ti,x-plate-ohms", &pdata->x_plate_ohms);
+ rt_dm_dev_prop_read_u16(dev, "ti,y-plate-ohms", &pdata->y_plate_ohms);
+
+ rt_dm_dev_prop_read_u16(dev, "ti,x-min", &pdata->x_min);
+ rt_dm_dev_prop_read_u16(dev, "ti,y-min", &pdata->y_min);
+ rt_dm_dev_prop_read_u16(dev, "ti,x-max", &pdata->x_max);
+ rt_dm_dev_prop_read_u16(dev, "ti,y-max", &pdata->y_max);
+
+ /*
+ * touchscreen-max-pressure gets parsed during
+ * touchscreen_parse_properties()
+ */
+ rt_dm_dev_prop_read_u16(dev, "ti,pressure-min", &pdata->pressure_min);
+ if (!rt_dm_dev_prop_read_u32(dev, "touchscreen-min-pressure", &value))
+ {
+ pdata->pressure_min = (rt_uint16_t)value;
+ }
+ rt_dm_dev_prop_read_u16(dev, "ti,pressure-max", &pdata->pressure_max);
+
+ rt_dm_dev_prop_read_u16(dev, "ti,debounce-max", &pdata->debounce_max);
+ if (!rt_dm_dev_prop_read_u32(dev, "touchscreen-average-samples", &value))
+ {
+ pdata->debounce_max = (rt_uint16_t)value;
+ }
+ rt_dm_dev_prop_read_u16(dev, "ti,debounce-tol", &pdata->debounce_tol);
+ rt_dm_dev_prop_read_u16(dev, "ti,debounce-rep", &pdata->debounce_rep);
+
+ rt_dm_dev_prop_read_u32(dev, "ti,pendown-gpio-debounce",
+ &pdata->gpio_pendown_debounce);
+}
+
+static rt_err_t ads7846_probe(struct rt_spi_device *spi_dev)
+{
+ rt_err_t err = RT_EOK;
+ struct rt_touch_info touch_info = {};
+ struct rt_device *dev = &spi_dev->parent;
+ struct ads7846 *ts = rt_calloc(1, sizeof(*ts));
+
+ if (!ts)
+ {
+ return -RT_ENOMEM;
+ }
+ ts->spi = spi_dev;
+ ts->parent.parent.ofw_node = dev->ofw_node;
+
+ ads7846_get_platform_data(spi_dev, &ts->pdata);
+ ts->pdata.vref_delay_usecs = ts->pdata.vref_delay_usecs ?: 100;
+ ts->pdata.x_plate_ohms = ts->pdata.x_plate_ohms ?: 400;
+
+ /* Linux ads784x_hwmon_register: internal ref when 7846 and no ti,vref-mv */
+ if (ts->pdata.model == 7846 && ts->pdata.vref_mv == 0)
+ {
+ ts->use_internal = RT_TRUE;
+ }
+
+ if (ts->pdata.debounce_max)
+ {
+ if (ts->pdata.debounce_max < 2)
+ {
+ ts->pdata.debounce_max = 2;
+ }
+ ts->filter_data = ts;
+ ts->filter = ads7846_debounce_filter;
+ }
+ else
+ {
+ ts->filter = ads7846_no_filter;
+ }
+
+ ts->gpio_pendown = rt_pin_get_named_pin(dev, "pendown", 0,
+ RT_NULL, &ts->gpio_pendown_active);
+
+ if (ts->gpio_pendown < 0 && ts->gpio_pendown != PIN_NONE)
+ {
+ err = ts->gpio_pendown;
+ goto _free;
+ }
+
+ if (ts->pdata.gpio_pendown_debounce)
+ {
+ rt_pin_debounce(ts->gpio_pendown, ts->pdata.gpio_pendown_debounce);
+ }
+
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_X);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_Y);
+ err |= rt_input_set_capability(&ts->parent, EV_KEY, BTN_TOUCH);
+
+ if (err)
+ {
+ goto _free_input_config;
+ }
+
+ rt_input_set_absinfo(&ts->parent, ABS_X,
+ ts->pdata.x_min ?: 0, ts->pdata.x_max ?: MAX_12BIT, 0, 0);
+ rt_input_set_absinfo(&ts->parent, ABS_Y,
+ ts->pdata.y_min ?: 0, ts->pdata.y_max ?: MAX_12BIT, 0, 0);
+
+ if (ts->pdata.model != 7845)
+ {
+ rt_input_set_absinfo(&ts->parent, ABS_PRESSURE,
+ ts->pdata.pressure_min, ts->pdata.pressure_max, 0, 0);
+ }
+
+ touch_info.type = RT_TOUCH_TYPE_RESISTANCE;
+ touch_info.vendor = RT_TOUCH_VENDOR_UNKNOWN;
+
+ if ((err = rt_input_setup_touch(&ts->parent, 0, &touch_info)))
+ {
+ goto _free_input_config;
+ }
+
+ ts->pdata.pressure_max = ts->parent.absinfo[ABS_PRESSURE].maximum ?: ~0;
+
+ /*
+ * Legacy ti,swap-xy when generic touchscreen-swapped-x-y was not set
+ * (Linux ads7846_probe after touchscreen_parse_properties).
+ */
+ ads7846_apply_legacy_swap_xy(&ts->parent, ts->pdata.swap_xy);
+
+ if ((err = ads7846_setup_spi_msg(ts)))
+ {
+ goto _free_input_config;
+ }
+
+ ts->supply = rt_regulator_get(dev, "vcc");
+
+ if (rt_is_err(ts->supply))
+ {
+ err = rt_ptr_err(ts->supply);
+ goto _free_msg;
+ }
+
+ if (ts->supply && (err = rt_regulator_enable(ts->supply)))
+ {
+ goto _free_regulator;
+ }
+
+ if ((ts->irq = rt_dm_dev_get_irq(dev, 0)) < 0)
+ {
+ err = ts->irq;
+ goto _free_disable_regulator;
+ }
+
+ if ((err = rt_input_device_register(&ts->parent)))
+ {
+ goto _free_disable_regulator;
+ }
+
+ spi_dev->parent.user_data = ts;
+
+#ifdef RT_USING_PM
+ rt_pm_device_register(&ts->parent.parent, &ads7846_pm_ops);
+#endif
+
+ ts->ts_task = rt_thread_create(rt_dm_dev_get_name(dev), &ads7846_ts_task,
+ ts, DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 2, 10);
+
+ if (!ts->ts_task)
+ {
+ rt_input_device_unregister(&ts->parent);
+ goto _free_disable_regulator;
+ }
+
+ if (ts->pdata.model == 7845)
+ {
+ ads7845_read12_ser(ts, PWRDOWN);
+ }
+ else
+ {
+ ads7846_read12_ser(ts, READ_12BIT_SER(vaux));
+ }
+
+ rt_hw_interrupt_install(ts->irq, ads7846_isr, ts, rt_dm_dev_get_name(dev));
+ rt_hw_interrupt_umask(ts->irq);
+
+ rt_thread_startup(ts->ts_task);
+
+ return RT_EOK;
+
+_free_disable_regulator:
+ if (ts->supply)
+ {
+ rt_regulator_disable(ts->supply);
+ }
+
+_free_regulator:
+ rt_regulator_put(ts->supply);
+
+_free_msg:
+ rt_free(ts->packet.tx);
+ rt_free(ts->packet.rx);
+
+_free_input_config:
+ rt_input_remove_config(&ts->parent);
+
+_free:
+ rt_free(ts);
+
+ return err;
+}
+
+static rt_err_t ads7846_remove(struct rt_spi_device *spi_dev)
+{
+ struct ads7846 *ts = spi_dev->parent.user_data;
+
+#ifdef RT_USING_PM
+ rt_pm_device_unregister(&ts->parent.parent);
+#endif
+
+ rt_input_device_unregister(&ts->parent);
+
+ ads7846_disable(ts);
+
+ rt_hw_interrupt_mask(ts->irq);
+ rt_pic_detach_irq(ts->irq, ts);
+
+ rt_thread_delete(ts->ts_task);
+
+ rt_regulator_put(ts->supply);
+
+ rt_free(ts->packet.tx);
+ rt_free(ts->packet.rx);
+
+ rt_free(ts);
+
+ return RT_EOK;
+}
+
+static const struct rt_spi_device_id ads7846_ids[] = {
+ { .name = "xpt2046", .data = (void *)7846 },
+ { .name = "tsc2046", .data = (void *)7846 },
+ { .name = "ads7843", .data = (void *)7843 },
+ { .name = "ads7845", .data = (void *)7845 },
+ { .name = "ads7846", .data = (void *)7846 },
+ { .name = "ads7873", .data = (void *)7873 },
+ { /* sentinel */ },
+};
+
+static const struct rt_ofw_node_id ads7846_ofw_ids[] = {
+ { .compatible = "ti,xpt2046", .data = (void *)7846 },
+ { .compatible = "ti,tsc2046", .data = (void *)7846 },
+ { .compatible = "ti,ads7843", .data = (void *)7843 },
+ { .compatible = "ti,ads7845", .data = (void *)7845 },
+ { .compatible = "ti,ads7846", .data = (void *)7846 },
+ { .compatible = "ti,ads7873", .data = (void *)7873 },
+ { /* sentinel */ },
+};
+
+static struct rt_spi_driver ads7846_driver = {
+ .ids = ads7846_ids,
+ .ofw_ids = ads7846_ofw_ids,
+
+ .probe = ads7846_probe,
+ .remove = ads7846_remove,
+};
+RT_SPI_DRIVER_EXPORT(ads7846_driver);
diff --git a/components/drivers/input/touchscreen/ts-goodix.c b/components/drivers/input/touchscreen/ts-goodix.c
new file mode 100644
index 0000000000..ec74ab3883
--- /dev/null
+++ b/components/drivers/input/touchscreen/ts-goodix.c
@@ -0,0 +1,1200 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-06-19 GuEe-GUI the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include <drivers/input.h>
+#include <drivers/byteorder.h>
+
+#define DBG_TAG "input.ts.goodix"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define GOODIX_MAX_HEIGHT 4096
+#define GOODIX_MAX_WIDTH 4096
+#define GOODIX_MAX_CONTACTS 10
+#define GOODIX_CONTACT_SIZE 8
+#define GOODIX_BUFFER_STATUS_READY RT_BIT(7)
+#define GOODIX_HAVE_KEY RT_BIT(4)
+#define GOODIX_BUFFER_STATUS_TIMEOUT 20
+#define GOODIX_I2C_TEST_RETRIES 2
+#define GOODIX_I2C_RETRY_DELAY_MS 20
+#define GOODIX_POLL_INTERVAL_MS 8
+#define GOODIX_CONFIG_WRITE_RETRIES 3
+#define GOODIX_CONFIG_APPLY_DELAY_MS 20
+
+#define GOODIX_CONFIG_MIN_LENGTH 186
+#define GOODIX_CONFIG_911_LENGTH 186
+#define GOODIX_CONFIG_GT9X_LENGTH 240
+
+#define GOODIX_REG_ID 0x8140
+#define GOODIX_READ_COOR_ADDR 0x814E
+#define GOODIX_GT9X_REG_CONFIG_DATA 0x8047
+#define GOODIX_GT1X_REG_CONFIG_DATA 0x8050
+
+#define GOODIX_ID_MAX_LEN 4
+
+#define RESOLUTION_LOC 1
+#define MAX_CONTACTS_LOC 5
+#define TRIGGER_LOC 6
+#define GOODIX_CONFIG_HEADER_LENGTH (TRIGGER_LOC + 1)
+
+#define GOODIX_TP_SIZE_GT9112 9112
+
+struct goodix_chip_info
+{
+ rt_uint16_t config_addr;
+ rt_uint16_t config_len;
+};
+
+struct goodix_ts
+{
+ struct rt_input_device parent;
+
+ struct rt_i2c_client *client;
+ struct rt_regulator *supply;
+
+ rt_base_t irq_pin;
+ rt_base_t rst_pin;
+ rt_uint8_t irq_mode;
+ rt_uint8_t reset_assert_level;
+ rt_uint8_t address_select_level;
+ rt_uint8_t reset_release_level;
+ rt_uint8_t int_sync_level;
+
+ const struct goodix_chip_info *chip;
+ rt_uint8_t max_touch_num;
+ rt_uint8_t contact_size;
+ rt_bool_t use_irq;
+ rt_bool_t force_polling;
+ rt_bool_t irq_sem_inited;
+ rt_bool_t active_slots[GOODIX_MAX_CONTACTS];
+
+ struct rt_thread *worker;
+ struct rt_semaphore irq_sem;
+ volatile rt_uint32_t irq_count;
+ volatile rt_uint32_t handle_count;
+
+ char id[GOODIX_ID_MAX_LEN + 1];
+};
+
+static const struct goodix_chip_info gt9x_chip = {
+ .config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_GT9X_LENGTH,
+};
+
+static const struct goodix_chip_info gt911_chip = {
+ .config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_911_LENGTH,
+};
+
+static const struct goodix_chip_info gt1x_chip = {
+ .config_addr = GOODIX_GT1X_REG_CONFIG_DATA,
+ .config_len = GOODIX_CONFIG_GT9X_LENGTH,
+};
+
+/* Radxa Display 8HD: CJ080258 GT911, 800x1280. */
+static const rt_uint8_t goodix_gt9112_config[] = {
+ 0x62, 0x20, 0x03, 0x00, 0x05, 0x0A, 0x05, 0x00, 0x01, 0x08, 0x28, 0x05,
+ 0x50, 0x32, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x8C, 0x2A, 0x0E, 0x17, 0x15, 0x31, 0x0D, 0x00, 0x00,
+ 0x01, 0x9A, 0x04, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x64, 0x32,
+ 0x00, 0x00, 0x00, 0x0F, 0x36, 0x94, 0xC5, 0x02, 0x07, 0x00, 0x00, 0x04,
+ 0x9B, 0x11, 0x00, 0x7B, 0x16, 0x00, 0x64, 0x1C, 0x00, 0x4F, 0x25, 0x00,
+ 0x41, 0x2F, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1A, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0E,
+ 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x22,
+ 0x21, 0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x18, 0x16, 0x14, 0x13, 0x12, 0x10,
+ 0x0F, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x72, 0x01
+};
+
+static const rt_uint8_t goodix_irq_modes[] = {
+ PIN_IRQ_MODE_RISING,
+ PIN_IRQ_MODE_FALLING,
+ PIN_IRQ_MODE_LOW_LEVEL,
+ PIN_IRQ_MODE_HIGH_LEVEL,
+};
+
+static rt_uint16_t goodix_get_le16(const rt_uint8_t *p)
+{
+ rt_uint16_t val;
+
+ rt_memcpy(&val, p, sizeof(val));
+
+ return rt_le16_to_cpu(val);
+}
+
+static rt_ssize_t goodix_get_named_pin(struct rt_device *dev, const char *name,
+ const char *alt_name, rt_uint8_t *out_mode)
+{
+ rt_ssize_t pin;
+
+ pin = rt_pin_get_named_pin(dev, name, 0, out_mode, RT_NULL);
+
+ if (pin == PIN_NONE && alt_name)
+ {
+ pin = rt_pin_get_named_pin(dev, alt_name, 0, out_mode, RT_NULL);
+ }
+
+ return pin;
+}
+
+static rt_err_t goodix_i2c_read(struct rt_i2c_client *client, rt_uint16_t reg,
+ rt_uint8_t *buf, rt_size_t len)
+{
+ rt_uint8_t addr[2] = { reg >> 8, reg & 0xff };
+ struct rt_i2c_msg msgs[2];
+ rt_ssize_t res;
+
+ msgs[0].addr = client->client_addr;
+ msgs[0].flags = RT_I2C_WR;
+ msgs[0].buf = addr;
+ msgs[0].len = 2;
+
+ msgs[1].addr = client->client_addr;
+ msgs[1].flags = RT_I2C_RD;
+ msgs[1].buf = buf;
+ msgs[1].len = len;
+
+ res = rt_i2c_transfer(client->bus, msgs, 2);
+
+ if (res < 0)
+ {
+ return res;
+ }
+
+ return res == 2 ? RT_EOK : -RT_EIO;
+}
+
+static rt_err_t goodix_i2c_write(struct rt_i2c_client *client, rt_uint16_t reg,
+ const rt_uint8_t *buf, rt_size_t len)
+{
+ rt_uint8_t stack_buf[32];
+ rt_uint8_t *tx;
+ struct rt_i2c_msg msg;
+ rt_ssize_t res;
+
+ if (len + 2 > sizeof(stack_buf))
+ {
+ tx = rt_malloc(len + 2);
+
+ if (!tx)
+ {
+ return -RT_ENOMEM;
+ }
+ }
+ else
+ {
+ tx = stack_buf;
+ }
+
+ tx[0] = reg >> 8;
+ tx[1] = reg & 0xff;
+ rt_memcpy(&tx[2], buf, len);
+
+ msg.addr = client->client_addr;
+ msg.flags = RT_I2C_WR;
+ msg.buf = tx;
+ msg.len = len + 2;
+
+ res = rt_i2c_transfer(client->bus, &msg, 1);
+
+ if (tx != stack_buf)
+ {
+ rt_free(tx);
+ }
+
+ if (res < 0)
+ {
+ return res;
+ }
+
+ return res == 1 ? RT_EOK : -RT_EIO;
+}
+
+static rt_err_t goodix_i2c_write_u8(struct rt_i2c_client *client, rt_uint16_t reg,
+ rt_uint8_t value)
+{
+ return goodix_i2c_write(client, reg, &value, 1);
+}
+
+static rt_err_t goodix_i2c_read_confirm(struct rt_i2c_client *client,
+ rt_uint16_t reg, rt_uint8_t *buf, rt_size_t len)
+{
+ rt_uint8_t stack_buf[32];
+ rt_uint8_t *confirm = stack_buf;
+ rt_err_t err;
+
+ if (len > sizeof(stack_buf))
+ {
+ confirm = rt_malloc(len);
+
+ if (!confirm)
+ {
+ return -RT_ENOMEM;
+ }
+ }
+
+ err = goodix_i2c_read(client, reg, buf, len);
+
+ if (!err)
+ {
+ err = goodix_i2c_read(client, reg, confirm, len);
+
+ if (!err && rt_memcmp(buf, confirm, len))
+ {
+ err = -RT_EIO;
+ }
+ }
+
+ if (confirm != stack_buf)
+ {
+ rt_free(confirm);
+ }
+
+ return err;
+}
+
+static rt_err_t goodix_irq_direction_output(struct goodix_ts *ts, rt_uint8_t value)
+{
+ /* Preload the output latch so changing direction cannot glitch INT. */
+ rt_pin_write(ts->irq_pin, value);
+ rt_pin_mode(ts->irq_pin, PIN_MODE_OUTPUT);
+
+ return RT_EOK;
+}
+
+static rt_err_t goodix_irq_direction_input(struct goodix_ts *ts)
+{
+ rt_pin_mode(ts->irq_pin, PIN_MODE_INPUT);
+
+ return RT_EOK;
+}
+
+static rt_err_t goodix_int_sync(struct goodix_ts *ts)
+{
+ goodix_irq_direction_output(ts, 0);
+ rt_thread_mdelay(50);
+ ts->int_sync_level = rt_pin_read(ts->irq_pin);
+ goodix_irq_direction_input(ts);
+
+ return RT_EOK;
+}
+
+static rt_err_t goodix_reset(struct goodix_ts *ts)
+{
+ /* Preload the output latch so changing direction cannot pulse reset. */
+ rt_pin_write(ts->rst_pin, 0);
+ rt_pin_mode(ts->rst_pin, PIN_MODE_OUTPUT);
+ rt_thread_mdelay(20);
+ ts->reset_assert_level = rt_pin_read(ts->rst_pin);
+
+ goodix_irq_direction_output(ts, ts->client->client_addr == 0x14);
+ rt_thread_mdelay(2);
+ ts->address_select_level = rt_pin_read(ts->irq_pin);
+
+ rt_pin_write(ts->rst_pin, 1);
+ rt_thread_mdelay(6);
+ ts->reset_release_level = rt_pin_read(ts->rst_pin);
+ rt_pin_mode(ts->rst_pin, PIN_MODE_INPUT);
+
+ return goodix_int_sync(ts);
+}
+
+static void goodix_i2c_failure_diagnose(struct goodix_ts *ts, rt_err_t id_err)
+{
+ rt_uint16_t address = ts->client->client_addr;
+ rt_uint8_t value;
+ rt_err_t cfg_err, alt_err = -RT_ENOSYS;
+
+ cfg_err = goodix_i2c_read(ts->client, GOODIX_GT9X_REG_CONFIG_DATA,
+ &value, 1);
+
+ if (address != 0x5d)
+ {
+ ts->client->client_addr = 0x5d;
+ alt_err = goodix_i2c_read(ts->client, GOODIX_REG_ID, &value, 1);
+ ts->client->client_addr = address;
+ }
+
+ LOG_E("diagnostic: id@0x%02x=%s cfg@0x%02x=%s id@0x5d=%s "
+ "seq(rst0/intsel/rst1/sync0)=%u/%u/%u/%u "
+ "irq=%ld level=%ld reset=%ld level=%ld",
+ address, rt_strerror(id_err), address, rt_strerror(cfg_err),
+ rt_strerror(alt_err), (unsigned int)ts->reset_assert_level,
+ (unsigned int)ts->address_select_level,
+ (unsigned int)ts->reset_release_level,
+ (unsigned int)ts->int_sync_level, (long)ts->irq_pin,
+ (long)rt_pin_read(ts->irq_pin), (long)ts->rst_pin,
+ (long)rt_pin_read(ts->rst_pin));
+}
+
+static rt_err_t goodix_i2c_test(struct rt_i2c_client *client)
+{
+ rt_uint8_t test;
+ rt_err_t err = -RT_EIO;
+ int retry = 0;
+
+ while (retry++ < GOODIX_I2C_TEST_RETRIES)
+ {
+ err = goodix_i2c_read(client, GOODIX_REG_ID, &test, 1);
+
+ if (!err)
+ {
+ return RT_EOK;
+ }
+
+ rt_thread_mdelay(GOODIX_I2C_RETRY_DELAY_MS);
+ }
+
+ return err;
+}
+
+static rt_bool_t goodix_product_id_valid(const rt_uint8_t *id)
+{
+ rt_size_t length = 0;
+ int i;
+
+ for (i = 0; i < GOODIX_ID_MAX_LEN && id[i]; i++)
+ {
+ if (!((id[i] >= '0' && id[i] <= '9') ||
+ (id[i] >= 'A' && id[i] <= 'Z') ||
+ (id[i] >= 'a' && id[i] <= 'z')))
+ {
+ return RT_FALSE;
+ }
+
+ length++;
+ }
+
+ return length >= 2;
+}
+
+static rt_err_t goodix_try_alternate_address(struct goodix_ts *ts)
+{
+ rt_uint16_t address = ts->client->client_addr;
+ rt_uint16_t alternate;
+ rt_uint8_t id[GOODIX_ID_MAX_LEN + 1] = { 0 };
+ rt_err_t err;
+
+ if (address == 0x14)
+ {
+ alternate = 0x5d;
+ }
+ else if (address == 0x5d)
+ {
+ alternate = 0x14;
+ }
+ else
+ {
+ return -RT_EINVAL;
+ }
+
+ /*
+ * Select the alternate address deliberately. Merely probing it is not
+ * enough because the address is sampled from INT while reset is released.
+ */
+ ts->client->client_addr = alternate;
+
+ if (ts->rst_pin >= 0 && ts->irq_pin >= 0)
+ {
+ err = goodix_reset(ts);
+ }
+ else
+ {
+ err = RT_EOK;
+ }
+
+ if (!err)
+ {
+ err = goodix_i2c_read(ts->client, GOODIX_REG_ID, id,
+ GOODIX_ID_MAX_LEN);
+ }
+
+ if (!err && !goodix_product_id_valid(id))
+ {
+ err = -RT_EIO;
+ }
+
+ if (err)
+ {
+ ts->client->client_addr = address;
+
+ return err;
+ }
+
+ LOG_D("device selected alternate I2C address 0x%02x (DT 0x%02x), ID %s",
+ alternate, address, id);
+ ts->force_polling = RT_TRUE;
+
+ return RT_EOK;
+}
+
+static rt_err_t goodix_read_version(struct goodix_ts *ts)
+{
+ rt_uint8_t buf[6];
+ rt_err_t err;
+
+ err = goodix_i2c_read(ts->client, GOODIX_REG_ID, buf, sizeof(buf));
+
+ if (err)
+ {
+ return err;
+ }
+
+ rt_memcpy(ts->id, buf, GOODIX_ID_MAX_LEN);
+ ts->id[GOODIX_ID_MAX_LEN] = '\0';
+
+ LOG_I("ID %s, version: %04x", ts->id, goodix_get_le16(&buf[4]));
+
+ return RT_EOK;
+}
+
+static rt_bool_t goodix_config_is_valid(const rt_uint8_t *config, rt_size_t len)
+{
+ rt_uint32_t x_max, y_max;
+ rt_uint8_t contacts;
+
+ if (len <= TRIGGER_LOC)
+ {
+ return RT_FALSE;
+ }
+
+ x_max = goodix_get_le16(&config[RESOLUTION_LOC]);
+ y_max = goodix_get_le16(&config[RESOLUTION_LOC + 2]);
+ contacts = config[MAX_CONTACTS_LOC] & 0x0f;
+
+ return x_max && x_max <= GOODIX_MAX_WIDTH &&
+ y_max && y_max <= GOODIX_MAX_HEIGHT &&
+ contacts && contacts <= GOODIX_MAX_CONTACTS;
+}
+
+static rt_err_t goodix_load_gt9112_config(struct goodix_ts *ts)
+{
+ rt_uint8_t config[sizeof(goodix_gt9112_config)];
+ rt_uint8_t verify[GOODIX_CONFIG_HEADER_LENGTH] = {};
+ rt_uint8_t checksum = 0;
+ rt_uint32_t tp_size;
+ rt_size_t i;
+ rt_err_t err = -RT_EIO;
+ int retry;
+
+ if (rt_dm_dev_prop_read_u32(&ts->client->parent, "tp-size", &tp_size) ||
+ tp_size != GOODIX_TP_SIZE_GT9112 ||
+ rt_strncmp(ts->id, "911", 3))
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_memcpy(config, goodix_gt9112_config, sizeof(config));
+
+ /* A zero version forces GT9xx controllers to accept the new config. */
+ config[0] = 0;
+
+ for (i = 0; i < sizeof(config) - 2; ++i)
+ {
+ checksum += config[i];
+ }
+
+ config[sizeof(config) - 2] = (rt_uint8_t)(0U - checksum);
+ config[sizeof(config) - 1] = 1;
+
+ for (retry = 0; retry < GOODIX_CONFIG_WRITE_RETRIES; ++retry)
+ {
+ /*
+ * Config_Fresh is the final byte of the packet. Keep the complete
+ * configuration in one I2C message so the controller never observes
+ * a partially updated table.
+ */
+ err = goodix_i2c_write(ts->client, GOODIX_GT9X_REG_CONFIG_DATA,
+ config, sizeof(config));
+
+ if (err)
+ {
+ continue;
+ }
+
+ rt_thread_mdelay(GOODIX_CONFIG_APPLY_DELAY_MS);
+ err = goodix_i2c_read_confirm(ts->client, GOODIX_GT9X_REG_CONFIG_DATA,
+ verify, sizeof(verify));
+
+ if (!err &&
+ !rt_memcmp(&verify[RESOLUTION_LOC], &config[RESOLUTION_LOC],
+ GOODIX_CONFIG_HEADER_LENGTH - RESOLUTION_LOC))
+ {
+ LOG_D("loaded GT9112 800x1280 configuration");
+ return RT_EOK;
+ }
+
+ err = -RT_EIO;
+ rt_thread_mdelay(GOODIX_I2C_RETRY_DELAY_MS);
+ }
+
+ LOG_E("GT9112 configuration write did not verify: "
+ "%02x %02x %02x %02x %02x %02x %02x",
+ verify[0], verify[1], verify[2], verify[3],
+ verify[4], verify[5], verify[6]);
+
+ return err;
+}
+
+static rt_err_t goodix_read_config(struct goodix_ts *ts)
+{
+ rt_uint8_t config[GOODIX_CONFIG_HEADER_LENGTH];
+ rt_uint32_t x_max, y_max;
+ rt_err_t err;
+
+ err = goodix_i2c_read(ts->client, ts->chip->config_addr,
+ config, sizeof(config));
+
+ if (err)
+ {
+ ts->max_touch_num = GOODIX_MAX_CONTACTS;
+ return err;
+ }
+
+ if (!goodix_config_is_valid(config, sizeof(config)))
+ {
+ if ((err = goodix_load_gt9112_config(ts)))
+ {
+ return err;
+ }
+
+ err = goodix_i2c_read(ts->client, ts->chip->config_addr,
+ config, sizeof(config));
+
+ if (err)
+ {
+ return err;
+ }
+
+ if (!goodix_config_is_valid(config, sizeof(config)))
+ {
+ return -RT_EIO;
+ }
+ }
+
+ ts->max_touch_num = config[MAX_CONTACTS_LOC] & 0x0f;
+ ts->irq_mode = goodix_irq_modes[config[TRIGGER_LOC] & 0x03];
+
+ if (!ts->max_touch_num)
+ {
+ ts->max_touch_num = GOODIX_MAX_CONTACTS;
+ }
+
+ x_max = goodix_get_le16(&config[RESOLUTION_LOC]);
+ y_max = goodix_get_le16(&config[RESOLUTION_LOC + 2]);
+
+ if (x_max && y_max)
+ {
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_X, 0, x_max - 1, 0, 0);
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_Y, 0, y_max - 1, 0, 0);
+ }
+
+ return RT_EOK;
+}
+
+static void goodix_parse_panel_size(struct goodix_ts *ts, struct rt_device *dev)
+{
+ rt_uint32_t max_x = 0, max_y = 0;
+
+ if (!rt_dm_dev_prop_read_u32(dev, "max-x", &max_x) && max_x)
+ {
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_X, 0, max_x - 1, 0, 0);
+ }
+
+ if (!rt_dm_dev_prop_read_u32(dev, "max-y", &max_y) && max_y)
+ {
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_Y, 0, max_y - 1, 0, 0);
+ }
+}
+
+static rt_err_t goodix_ts_read_input_report(struct goodix_ts *ts, rt_uint8_t *data,
+ int *touch_num_out)
+{
+ rt_uint16_t addr = GOODIX_READ_COOR_ADDR;
+ rt_size_t header_size = 1 + ts->contact_size;
+ rt_tick_t timeout;
+ rt_uint8_t touch_num;
+ rt_err_t err;
+
+ timeout = rt_tick_get() + rt_tick_from_millisecond(GOODIX_BUFFER_STATUS_TIMEOUT);
+
+ do
+ {
+ err = goodix_i2c_read(ts->client, addr, data, header_size);
+
+ if (err)
+ {
+ return err;
+ }
+
+ if (data[0] & GOODIX_BUFFER_STATUS_READY)
+ {
+ touch_num = data[0] & 0x0f;
+
+ if (touch_num > ts->max_touch_num)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (touch_num > 1)
+ {
+ addr += header_size;
+ data += header_size;
+
+ err = goodix_i2c_read(ts->client, addr, data,
+ ts->contact_size * (touch_num - 1));
+
+ if (err)
+ {
+ return err;
+ }
+ }
+
+ *touch_num_out = touch_num;
+ return RT_EOK;
+ }
+
+ if (!ts->use_irq)
+ {
+ break;
+ }
+
+ rt_thread_mdelay(1);
+ } while (rt_tick_get() < timeout);
+
+ return -RT_ETIMEOUT;
+}
+
+static void goodix_release_unused_slots(struct goodix_ts *ts,
+ const rt_bool_t *seen_slots)
+{
+ struct rt_input_device *idev = &ts->parent;
+ int i;
+
+ for (i = 0; i < GOODIX_MAX_CONTACTS; ++i)
+ {
+ if (ts->active_slots[i] && !seen_slots[i])
+ {
+ rt_input_report_touch_slot(idev, i);
+ rt_input_event(idev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ ts->active_slots[i] = RT_FALSE;
+ }
+ }
+}
+
+static void goodix_report_touch(struct goodix_ts *ts, rt_uint8_t *coor_data)
+{
+ struct rt_input_device *idev = &ts->parent;
+ rt_uint32_t id = coor_data[0] & 0x0f;
+ rt_uint32_t x = goodix_get_le16(&coor_data[1]);
+ rt_uint32_t y = goodix_get_le16(&coor_data[3]);
+ rt_uint32_t w = goodix_get_le16(&coor_data[5]);
+
+ if (id >= GOODIX_MAX_CONTACTS)
+ {
+ return;
+ }
+
+ rt_input_report_touch_slot(idev, id);
+ rt_input_event(idev, EV_ABS, ABS_MT_TRACKING_ID, id);
+ ts->active_slots[id] = RT_TRUE;
+ rt_input_report_touch_position(idev, x, y, RT_TRUE);
+ rt_input_report_abs(idev, ABS_MT_TOUCH_MAJOR, w);
+ rt_input_report_abs(idev, ABS_MT_WIDTH_MAJOR, w);
+}
+
+static rt_err_t goodix_process_events(struct goodix_ts *ts)
+{
+ rt_uint8_t point_data[2 + GOODIX_MAX_CONTACTS * GOODIX_CONTACT_SIZE];
+ rt_bool_t seen_slots[GOODIX_MAX_CONTACTS] = { RT_FALSE };
+ rt_uint8_t *coor_data;
+ rt_uint32_t id;
+ int touch_num = 0;
+ int i;
+ rt_err_t err;
+
+ err = goodix_ts_read_input_report(ts, point_data, &touch_num);
+
+ if (err)
+ {
+ return err;
+ }
+
+ for (i = 0; i < touch_num; ++i)
+ {
+ coor_data = &point_data[1 + ts->contact_size * i];
+ id = coor_data[0] & 0x0f;
+
+ if (id < GOODIX_MAX_CONTACTS)
+ {
+ seen_slots[id] = RT_TRUE;
+ goodix_report_touch(ts, coor_data);
+ }
+ }
+
+ goodix_release_unused_slots(ts, seen_slots);
+ rt_input_sync(&ts->parent);
+
+ return RT_EOK;
+}
+
+static void goodix_handle_events(struct goodix_ts *ts)
+{
+ rt_err_t err;
+
+ err = goodix_process_events(ts);
+
+ if (err)
+ {
+ return;
+ }
+
+ ++ts->handle_count;
+ err = goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0);
+
+ if (err)
+ {
+ LOG_D("failed to clear coordinate status: %s", rt_strerror(err));
+ }
+}
+
+static void goodix_worker_entry(void *param)
+{
+ struct goodix_ts *ts = param;
+
+ while (RT_TRUE)
+ {
+ rt_sem_take(&ts->irq_sem, RT_WAITING_FOREVER);
+
+ goodix_handle_events(ts);
+ }
+}
+
+static void goodix_irq_callback(void *param)
+{
+ struct goodix_ts *ts = param;
+
+ ++ts->irq_count;
+ rt_sem_release(&ts->irq_sem);
+}
+
+static void goodix_poll(struct rt_input_device *idev)
+{
+ struct goodix_ts *ts = rt_container_of(idev, struct goodix_ts, parent);
+
+ goodix_handle_events(ts);
+}
+
+static rt_err_t goodix_setup_polling(struct goodix_ts *ts)
+{
+ rt_err_t err;
+
+ err = rt_input_setup_polling(&ts->parent, goodix_poll);
+
+ if (err)
+ {
+ return err;
+ }
+
+ err = rt_input_set_poll_interval(&ts->parent, GOODIX_POLL_INTERVAL_MS);
+
+ if (!err)
+ {
+ LOG_D("poll interval set to %d ms", GOODIX_POLL_INTERVAL_MS);
+ }
+
+ return err;
+}
+
+static rt_err_t goodix_request_irq(struct goodix_ts *ts)
+{
+ rt_err_t err;
+
+ if (ts->irq_pin < 0 || ts->irq_pin == PIN_NONE)
+ {
+ return -RT_ENOSYS;
+ }
+
+ /*
+ * An old ready flag keeps INT asserted and prevents a new edge from
+ * arriving after the GPIO interrupt is enabled.
+ */
+ err = goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0);
+
+ if (err)
+ {
+ return err;
+ }
+
+ err = rt_pin_attach_irq(ts->irq_pin, ts->irq_mode, goodix_irq_callback, ts);
+
+ if (err)
+ {
+ return err;
+ }
+
+ err = rt_pin_irq_enable(ts->irq_pin, RT_TRUE);
+
+ if (err)
+ {
+ rt_pin_detach_irq(ts->irq_pin);
+ return err;
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t goodix_probe(struct rt_i2c_client *client)
+{
+ rt_err_t err;
+ struct rt_device *dev = &client->parent;
+ struct rt_touch_info touch_info = {};
+ struct goodix_ts *ts = rt_calloc(1, sizeof(*ts));
+
+ if (!ts)
+ {
+ return -RT_ENOMEM;
+ }
+
+ ts->client = client;
+ ts->contact_size = GOODIX_CONTACT_SIZE;
+ ts->irq_mode = PIN_IRQ_MODE_FALLING;
+ ts->chip = rt_i2c_client_id_data(client);
+
+ if (!ts->chip)
+ {
+ ts->chip = &gt9x_chip;
+ }
+
+ ts->parent.parent.ofw_node = dev->ofw_node;
+ dev->user_data = ts;
+
+ ts->irq_pin = goodix_get_named_pin(dev, "irq", "touch", RT_NULL);
+ ts->rst_pin = goodix_get_named_pin(dev, "reset", RT_NULL, RT_NULL);
+
+ if (ts->irq_pin < 0 && ts->irq_pin != PIN_NONE)
+ {
+ err = ts->irq_pin;
+ goto _free;
+ }
+
+ if (ts->rst_pin < 0 && ts->rst_pin != PIN_NONE)
+ {
+ err = ts->rst_pin;
+ goto _free;
+ }
+
+ ts->supply = rt_regulator_get(dev, "tp");
+
+ if (rt_is_err(ts->supply))
+ {
+ err = rt_ptr_err(ts->supply);
+ ts->supply = RT_NULL;
+ goto _free;
+ }
+
+ if (!ts->supply && rt_dm_dev_prop_read_bool(dev, "tp-supply"))
+ {
+ LOG_E("tp-supply is present but its regulator is unavailable");
+ err = -RT_EIO;
+ goto _free;
+ }
+
+ if (ts->supply)
+ {
+ err = rt_regulator_enable(ts->supply);
+
+ if (err)
+ {
+ goto _put_supply;
+ }
+
+ rt_thread_mdelay(20);
+ }
+ else
+ {
+ ts->supply = RT_NULL;
+ }
+
+ if (ts->rst_pin >= 0 && ts->irq_pin >= 0)
+ {
+ if ((err = goodix_reset(ts)))
+ {
+ goto _disable_supply;
+ }
+ }
+ else if (ts->rst_pin >= 0)
+ {
+ LOG_W("reset GPIO present without IRQ GPIO, skip hardware reset");
+ }
+
+ if ((err = goodix_i2c_test(client)))
+ {
+ if (ts->rst_pin >= 0 && ts->irq_pin >= 0)
+ {
+ LOG_D("initial I2C test failed: %s, retry after reset",
+ rt_strerror(err));
+
+ if (!(err = goodix_reset(ts)))
+ {
+ err = goodix_i2c_test(client);
+ }
+ }
+ }
+
+ if (err && !goodix_try_alternate_address(ts))
+ {
+ err = RT_EOK;
+ }
+
+ if (err)
+ {
+ goodix_i2c_failure_diagnose(ts, err);
+ LOG_E("I2C communication failed at 0x%02x: %s (irq=%ld reset=%ld)",
+ client->client_addr, rt_strerror(err),
+ (long)ts->irq_pin, (long)ts->rst_pin);
+ goto _disable_supply;
+ }
+
+ if ((err = goodix_read_version(ts)))
+ {
+ LOG_E("failed to read controller version: %s", rt_strerror(err));
+ goto _disable_supply;
+ }
+
+ err = rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_SLOT);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_TRACKING_ID);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_POSITION_X);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_POSITION_Y);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_TOUCH_MAJOR);
+ err |= rt_input_set_capability(&ts->parent, EV_ABS, ABS_MT_WIDTH_MAJOR);
+
+ if (err)
+ {
+ goto _disable_supply;
+ }
+
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_X, 0, GOODIX_MAX_WIDTH - 1, 0, 0);
+ rt_input_set_absinfo(&ts->parent, ABS_MT_POSITION_Y, 0, GOODIX_MAX_HEIGHT - 1, 0, 0);
+ rt_input_set_absinfo(&ts->parent, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ rt_input_set_absinfo(&ts->parent, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+
+ goodix_parse_panel_size(ts, dev);
+
+ touch_info.type = RT_TOUCH_TYPE_CAPACITANCE;
+ touch_info.vendor = RT_TOUCH_VENDOR_GT;
+ touch_info.point_num = GOODIX_MAX_CONTACTS;
+
+ if ((err = rt_input_setup_touch(&ts->parent, GOODIX_MAX_CONTACTS, &touch_info)))
+ {
+ goto _remove_input;
+ }
+
+ if ((err = goodix_read_config(ts)))
+ {
+ LOG_W("failed to read configuration: %s, use falling-edge IRQ",
+ rt_strerror(err));
+ ts->irq_mode = PIN_IRQ_MODE_FALLING;
+ }
+
+ err = rt_sem_init(&ts->irq_sem, "goodix", 0, RT_IPC_FLAG_FIFO);
+
+ if (err)
+ {
+ goto _remove_input;
+ }
+ ts->irq_sem_inited = RT_TRUE;
+
+ if (ts->force_polling)
+ {
+ LOG_W("INT address selection failed, using %d ms polling at 0x%02x",
+ GOODIX_POLL_INTERVAL_MS, ts->client->client_addr);
+ err = -RT_ENOSYS;
+ }
+ else
+ {
+ err = goodix_request_irq(ts);
+ }
+
+ if (err)
+ {
+ if (!ts->force_polling)
+ {
+ LOG_W("IRQ setup failed on pin %ld: %s, using polling mode",
+ (long)ts->irq_pin, rt_strerror(err));
+ }
+
+ rt_sem_detach(&ts->irq_sem);
+ ts->irq_sem_inited = RT_FALSE;
+
+ if ((err = goodix_setup_polling(ts)))
+ {
+ goto _remove_input;
+ }
+
+ ts->use_irq = RT_FALSE;
+ }
+ else
+ {
+ ts->use_irq = RT_TRUE;
+
+ ts->worker = rt_thread_create(rt_dm_dev_get_name(dev), goodix_worker_entry,
+ ts, DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 2, 10);
+
+ if (!ts->worker)
+ {
+ rt_pin_irq_enable(ts->irq_pin, RT_FALSE);
+ rt_pin_detach_irq(ts->irq_pin);
+ rt_sem_detach(&ts->irq_sem);
+ ts->irq_sem_inited = RT_FALSE;
+ ts->use_irq = RT_FALSE;
+
+ if ((err = goodix_setup_polling(ts)))
+ {
+ goto _remove_input;
+ }
+ }
+ }
+
+ if ((err = rt_input_device_register(&ts->parent)))
+ {
+ goto _remove_input;
+ }
+
+ if (ts->worker)
+ {
+ rt_thread_startup(ts->worker);
+ }
+
+ return RT_EOK;
+
+_remove_input:
+ if (ts->use_irq && ts->irq_pin >= 0)
+ {
+ rt_pin_irq_enable(ts->irq_pin, RT_FALSE);
+ rt_pin_detach_irq(ts->irq_pin);
+ }
+
+ if (ts->worker)
+ {
+ rt_thread_delete(ts->worker);
+ ts->worker = RT_NULL;
+ }
+
+ if (ts->irq_sem_inited)
+ {
+ rt_sem_detach(&ts->irq_sem);
+ ts->irq_sem_inited = RT_FALSE;
+ }
+
+ rt_input_remove_config(&ts->parent);
+
+_disable_supply:
+ if (ts->supply)
+ {
+ rt_regulator_disable(ts->supply);
+ }
+
+_put_supply:
+ if (ts->supply)
+ {
+ rt_regulator_put(ts->supply);
+ }
+
+_free:
+ if (dev->user_data == ts)
+ {
+ dev->user_data = RT_NULL;
+ }
+
+ rt_free(ts);
+
+ return err;
+}
+
+static rt_err_t goodix_remove(struct rt_i2c_client *client)
+{
+ struct goodix_ts *ts = client->parent.user_data;
+
+ if (!ts)
+ {
+ return RT_EOK;
+ }
+
+ client->parent.user_data = RT_NULL;
+
+ if (ts->use_irq && ts->irq_pin >= 0)
+ {
+ rt_pin_irq_enable(ts->irq_pin, RT_FALSE);
+ rt_pin_detach_irq(ts->irq_pin);
+ }
+
+ if (ts->worker)
+ {
+ rt_thread_delete(ts->worker);
+ }
+
+ if (ts->irq_sem_inited)
+ {
+ rt_sem_detach(&ts->irq_sem);
+ }
+
+ rt_input_device_unregister(&ts->parent);
+
+ if (ts->supply)
+ {
+ rt_regulator_disable(ts->supply);
+ rt_regulator_put(ts->supply);
+ }
+
+ rt_free(ts);
+
+ return RT_EOK;
+}
+
+static const struct rt_i2c_device_id goodix_ts_ids[] = {
+ { .name = "gt9xx" },
+ { .name = "gt911" },
+ { .name = "gt967" },
+ { /* sentinel */ },
+};
+
+static const struct rt_ofw_node_id goodix_ts_ofw_ids[] = {
+ { .compatible = "goodix,gt9xx", .data = (void *)&gt9x_chip },
+ { .compatible = "goodix,gt911", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt9110", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt927", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt9271", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt928", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt912", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt9147", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt967", .data = (void *)&gt911_chip },
+ { .compatible = "goodix,gt917s", .data = (void *)&gt1x_chip },
+ { .compatible = "goodix,gt9286", .data = (void *)&gt1x_chip },
+ { /* sentinel */ },
+};
+
+static struct rt_i2c_driver goodix_ts_driver = {
+ .ids = goodix_ts_ids,
+ .ofw_ids = goodix_ts_ofw_ids,
+
+ .probe = goodix_probe,
+ .remove = goodix_remove,
+};
+RT_I2C_DRIVER_EXPORT(goodix_ts_driver);