diff --git a/components/drivers/input/joystick/Kconfig b/components/drivers/input/joystick/Kconfig index d218d1d16bb0..ea36ddfe47d7 100644 --- a/components/drivers/input/joystick/Kconfig +++ b/components/drivers/input/joystick/Kconfig @@ -2,6 +2,12 @@ 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 + 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 1b68c7eb6563..288a9c5dfe5b 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 000000000000..0d213d1e143d --- /dev/null +++ b/components/drivers/input/joystick/js-adc.c @@ -0,0 +1,240 @@ +/* + * 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 +#include + +#define DBG_TAG "input.js.adc" +#define DBG_LVL DBG_INFO +#include + +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", ®)) + { + 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 6cce9d514b56..b5b4ae722d05 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 67c4713e7953..af465ce35561 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 000000000000..d2557e4668fc --- /dev/null +++ b/components/drivers/input/keyboard/keys-adc.c @@ -0,0 +1,232 @@ +/* + * 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 +#include + +#define DBG_TAG "input.keyboard.adc" +#define DBG_LVL DBG_INFO +#include + +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), "voltage"); + 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 63ce6d966572..e5d1c04ba6e3 100644 --- a/components/drivers/input/touchscreen/Kconfig +++ b/components/drivers/input/touchscreen/Kconfig @@ -3,6 +3,18 @@ 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 + default n + +config RT_INPUT_TOUCHSCREEN_GOODIX + bool "Goodix GT9xx capacitive touch screen support" + depends on RT_INPUT_TOUCHSCREEN + depends on RT_USING_I2C + 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 1465692553a8..6efa769291cd 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 000000000000..d201e70e0bd5 --- /dev/null +++ b/components/drivers/input/touchscreen/ts-ads7846.c @@ -0,0 +1,1193 @@ +/* + * 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 +#include +#include +#include + +#define DBG_TAG "input.ts.ads7846" +#define DBG_LVL DBG_INFO +#include + +/* + * 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); + rt_regulator_disable(ts->supply); +} + +rt_inline void ads7846_enable(struct ads7846 *ts) +{ + if (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_NULL; + 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_NULL; + 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_NULL; + 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 (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 ((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: + 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 000000000000..6d94963846be --- /dev/null +++ b/components/drivers/input/touchscreen/ts-goodix.c @@ -0,0 +1,1209 @@ +/* + * 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 +#include +#include + +#include +#include + +#define DBG_TAG "input.ts.goodix" +#define DBG_LVL DBG_INFO +#include + +#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 + 1; + 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 = >9x_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 *)>9x_chip }, + { .compatible = "goodix,gt911", .data = (void *)>911_chip }, + { .compatible = "goodix,gt9110", .data = (void *)>911_chip }, + { .compatible = "goodix,gt927", .data = (void *)>911_chip }, + { .compatible = "goodix,gt9271", .data = (void *)>911_chip }, + { .compatible = "goodix,gt928", .data = (void *)>911_chip }, + { .compatible = "goodix,gt912", .data = (void *)>911_chip }, + { .compatible = "goodix,gt9147", .data = (void *)>911_chip }, + { .compatible = "goodix,gt967", .data = (void *)>911_chip }, + { .compatible = "goodix,gt917s", .data = (void *)>1x_chip }, + { .compatible = "goodix,gt9286", .data = (void *)>1x_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);