From 7842087b0196d674ed877d768de8f2a34d7fdc53 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Sat, 25 Apr 2020 13:06:31 -0700 Subject: Input: mms114 - add extra compatible for mms345l MMS345L is another first generation touch screen from Melfas, which uses mostly the same registers as MMS152. However, there is some garbage printed during initialization. Apparently MMS345L does not have the MMS152_COMPAT_GROUP register that is read+printed during initialization. TSP FW Rev: bootloader 0x6 / core 0x26 / config 0x26, Compat group: \x06 On earlier kernel versions the compat group was actually printed as an ASCII control character, seems like it gets escaped now. But we probably shouldn't print something from a random register. Add a separate "melfas,mms345l" compatible that avoids reading from the MMS152_COMPAT_GROUP register. This might also help in case there is some other device-specific quirk in the future. Signed-off-by: Stephan Gerhold Reviewed-by: Andi Shyti Link: https://lore.kernel.org/r/20200423102431.2715-1-stephan@gerhold.net Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index 69c6d559eeb0..b3dda1722bae 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -54,6 +54,7 @@ enum mms_type { TYPE_MMS114 = 114, TYPE_MMS152 = 152, + TYPE_MMS345L = 345, }; struct mms114_data { @@ -250,6 +251,15 @@ static int mms114_get_version(struct mms114_data *data) int error; switch (data->type) { + case TYPE_MMS345L: + error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); + if (error) + return error; + + dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", + buf[0], buf[1], buf[2]); + break; + case TYPE_MMS152: error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); if (error) @@ -287,8 +297,8 @@ static int mms114_setup_regs(struct mms114_data *data) if (error < 0) return error; - /* MMS152 has no configuration or power on registers */ - if (data->type == TYPE_MMS152) + /* Only MMS114 has configuration and power on registers */ + if (data->type != TYPE_MMS114) return 0; error = mms114_set_active(data, true); @@ -599,6 +609,9 @@ static const struct of_device_id mms114_dt_match[] = { }, { .compatible = "melfas,mms152", .data = (void *)TYPE_MMS152, + }, { + .compatible = "melfas,mms345l", + .data = (void *)TYPE_MMS345L, }, { } }; -- cgit v1.2.3 From 255cdaf73412de13608fb776101402dca68bed2b Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Sat, 9 May 2020 14:34:35 -0700 Subject: Input: edt-ft5x06 - fix get_default register write access Since commit b6eba86030bf ("Input: edt-ft5x06 - add offset support for ev-ft5726") offset-x and offset-y is supported. Devices using those offset parameters don't support the offset parameter so we need to add the NO_REGISTER check for edt_ft5x06_ts_get_defaults(). Fixes: b6eba86030bf ("Input: edt-ft5x06 - add offset support for ev-ft5726") Signed-off-by: Marco Felsch Link: https://lore.kernel.org/r/20200227112819.16754-2-m.felsch@pengutronix.de Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index d2587724c52a..9b8450794a8a 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -938,19 +938,25 @@ static void edt_ft5x06_ts_get_defaults(struct device *dev, error = device_property_read_u32(dev, "offset", &val); if (!error) { - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset, val); + if (reg_addr->reg_offset != NO_REGISTER) + edt_ft5x06_register_write(tsdata, + reg_addr->reg_offset, val); tsdata->offset = val; } error = device_property_read_u32(dev, "offset-x", &val); if (!error) { - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_x, val); + if (reg_addr->reg_offset_x != NO_REGISTER) + edt_ft5x06_register_write(tsdata, + reg_addr->reg_offset_x, val); tsdata->offset_x = val; } error = device_property_read_u32(dev, "offset-y", &val); if (!error) { - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_y, val); + if (reg_addr->reg_offset_y != NO_REGISTER) + edt_ft5x06_register_write(tsdata, + reg_addr->reg_offset_y, val); tsdata->offset_y = val; } } -- cgit v1.2.3 From f4ee52f3ad8c9210030a9fe48bbb74dbc7c9f90e Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Sat, 9 May 2020 12:05:36 -0700 Subject: Input: edt-ft5x06 - move parameter restore into helper We need to restore the parameters if we switch between the factory/work mode and during the resume process if we switched off the power-supply. Therefore refactor edt_ft5x06_work_mode() and move the "restore the parameters" into a helper routine so we can reuse it later. Signed-off-by: Marco Felsch Link: https://lore.kernel.org/r/20200227112819.16754-3-m.felsch@pengutronix.de Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 43 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 9b8450794a8a..bb9107093796 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -527,6 +527,29 @@ static const struct attribute_group edt_ft5x06_attr_group = { .attrs = edt_ft5x06_attrs, }; +static void edt_ft5x06_restore_reg_parameters(struct edt_ft5x06_ts_data *tsdata) +{ + struct edt_reg_addr *reg_addr = &tsdata->reg_addr; + + edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold, + tsdata->threshold); + edt_ft5x06_register_write(tsdata, reg_addr->reg_gain, + tsdata->gain); + if (reg_addr->reg_offset != NO_REGISTER) + edt_ft5x06_register_write(tsdata, reg_addr->reg_offset, + tsdata->offset); + if (reg_addr->reg_offset_x != NO_REGISTER) + edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_x, + tsdata->offset_x); + if (reg_addr->reg_offset_y != NO_REGISTER) + edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_y, + tsdata->offset_y); + if (reg_addr->reg_report_rate != NO_REGISTER) + edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate, + tsdata->report_rate); + +} + #ifdef CONFIG_DEBUG_FS static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata) { @@ -592,7 +615,6 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata) { struct i2c_client *client = tsdata->client; int retries = EDT_SWITCH_MODE_RETRIES; - struct edt_reg_addr *reg_addr = &tsdata->reg_addr; int ret; int error; @@ -624,24 +646,7 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata) kfree(tsdata->raw_buffer); tsdata->raw_buffer = NULL; - /* restore parameters */ - edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold, - tsdata->threshold); - edt_ft5x06_register_write(tsdata, reg_addr->reg_gain, - tsdata->gain); - if (reg_addr->reg_offset != NO_REGISTER) - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset, - tsdata->offset); - if (reg_addr->reg_offset_x != NO_REGISTER) - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_x, - tsdata->offset_x); - if (reg_addr->reg_offset_y != NO_REGISTER) - edt_ft5x06_register_write(tsdata, reg_addr->reg_offset_y, - tsdata->offset_y); - if (reg_addr->reg_report_rate != NO_REGISTER) - edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate, - tsdata->report_rate); - + edt_ft5x06_restore_reg_parameters(tsdata); enable_irq(client->irq); return 0; -- cgit v1.2.3 From 21d1611a83f8aa3907ca653b5851dd01951d4da6 Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Sat, 9 May 2020 12:10:40 -0700 Subject: Input: edt-ft5x06 - improve power management operations It is possible to bring the device into a deep sleep state. To exit this state the reset or wakeup pin must be toggeled as documented in [1]. Because of the poor documentation I used the several downstream kernels [2] and other applications notes [3] to indentify the related registers. Furthermore I added the support to disable the device completely which is obviously the most effective power-saving mechanism. This mechanism needs the reset pin to ensure the power-up/down sequence. We can't apply any of these power-saving mechanism if both pins are missing (not connected) or if it is a wakeup device. [1] https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x26.pdf https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x06.pdf [2] https://github.com/linux-sunxi/linux-sunxi/blob/sunxi-3.4/drivers/input/touchscreen/ft5x_ts.c https://github.com/Pablito2020/focaltech-touch-driver/blob/master/ft5336_driver.c [3] https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x16_registers.pdf Signed-off-by: Marco Felsch Link: https://lore.kernel.org/r/20200227112819.16754-4-m.felsch@pengutronix.de Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 142 ++++++++++++++++++++++++++++++--- 1 file changed, 132 insertions(+), 10 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index bb9107093796..d423bd6bfeda 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -38,6 +38,9 @@ #define WORK_REGISTER_NUM_X 0x33 #define WORK_REGISTER_NUM_Y 0x34 +#define PMOD_REGISTER_ACTIVE 0x00 +#define PMOD_REGISTER_HIBERNATE 0x03 + #define M09_REGISTER_THRESHOLD 0x80 #define M09_REGISTER_GAIN 0x92 #define M09_REGISTER_OFFSET 0x93 @@ -53,6 +56,7 @@ #define WORK_REGISTER_OPMODE 0x3c #define FACTORY_REGISTER_OPMODE 0x01 +#define PMOD_REGISTER_OPMODE 0xa5 #define TOUCH_EVENT_DOWN 0x00 #define TOUCH_EVENT_UP 0x01 @@ -65,6 +69,12 @@ #define EDT_RAW_DATA_RETRIES 100 #define EDT_RAW_DATA_DELAY 1000 /* usec */ +enum edt_pmode { + EDT_PMODE_NOT_SUPPORTED, + EDT_PMODE_HIBERNATE, + EDT_PMODE_POWEROFF, +}; + enum edt_ver { EDT_M06, EDT_M09, @@ -103,6 +113,7 @@ struct edt_ft5x06_ts_data { struct mutex mutex; bool factory_mode; + enum edt_pmode suspend_mode; int threshold; int gain; int offset; @@ -767,9 +778,8 @@ static const struct file_operations debugfs_raw_data_fops = { .read = edt_ft5x06_debugfs_raw_data_read, }; -static void -edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, - const char *debugfs_name) +static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, + const char *debugfs_name) { tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL); @@ -782,8 +792,7 @@ edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, tsdata->debug_dir, tsdata, &debugfs_raw_data_fops); } -static void -edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) +static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) { debugfs_remove_recursive(tsdata->debug_dir); kfree(tsdata->raw_buffer); @@ -791,14 +800,17 @@ edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) #else -static inline void -edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, - const char *debugfs_name) +static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata) { + return -ENOSYS; } -static inline void -edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) +static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, + const char *debugfs_name) +{ +} + +static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) { } @@ -1125,6 +1137,19 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, return error; } + /* + * Check which sleep modes we can support. Power-off requieres the + * reset-pin to ensure correct power-down/power-up behaviour. Start with + * the EDT_PMODE_POWEROFF test since this is the deepest possible sleep + * mode. + */ + if (tsdata->reset_gpio) + tsdata->suspend_mode = EDT_PMODE_POWEROFF; + else if (tsdata->wake_gpio) + tsdata->suspend_mode = EDT_PMODE_HIBERNATE; + else + tsdata->suspend_mode = EDT_PMODE_NOT_SUPPORTED; + if (tsdata->wake_gpio) { usleep_range(5000, 6000); gpiod_set_value_cansleep(tsdata->wake_gpio, 1); @@ -1238,6 +1263,102 @@ static int edt_ft5x06_ts_remove(struct i2c_client *client) return 0; } +static int __maybe_unused edt_ft5x06_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); + struct gpio_desc *reset_gpio = tsdata->reset_gpio; + int ret; + + if (device_may_wakeup(dev)) + return 0; + + if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED) + return 0; + + /* Enter hibernate mode. */ + ret = edt_ft5x06_register_write(tsdata, PMOD_REGISTER_OPMODE, + PMOD_REGISTER_HIBERNATE); + if (ret) + dev_warn(dev, "Failed to set hibernate mode\n"); + + if (tsdata->suspend_mode == EDT_PMODE_HIBERNATE) + return 0; + + /* + * Power-off according the datasheet. Cut the power may leaf the irq + * line in an undefined state depending on the host pull resistor + * settings. Disable the irq to avoid adjusting each host till the + * device is back in a full functional state. + */ + disable_irq(tsdata->client->irq); + + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(1000, 2000); + + ret = regulator_disable(tsdata->vcc); + if (ret) + dev_warn(dev, "Failed to disable vcc\n"); + + return 0; +} + +static int __maybe_unused edt_ft5x06_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); + int ret = 0; + + if (device_may_wakeup(dev)) + return 0; + + if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED) + return 0; + + if (tsdata->suspend_mode == EDT_PMODE_POWEROFF) { + struct gpio_desc *reset_gpio = tsdata->reset_gpio; + + /* + * We can't check if the regulator is a dummy or a real + * regulator. So we need to specify the 5ms reset time (T_rst) + * here instead of the 100us T_rtp time. We also need to wait + * 300ms in case it was a real supply and the power was cutted + * of. Toggle the reset pin is also a way to exit the hibernate + * mode. + */ + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(5000, 6000); + + ret = regulator_enable(tsdata->vcc); + if (ret) { + dev_err(dev, "Failed to enable vcc\n"); + return ret; + } + + usleep_range(1000, 2000); + gpiod_set_value_cansleep(reset_gpio, 0); + msleep(300); + + edt_ft5x06_restore_reg_parameters(tsdata); + enable_irq(tsdata->client->irq); + + if (tsdata->factory_mode) + ret = edt_ft5x06_factory_mode(tsdata); + } else { + struct gpio_desc *wake_gpio = tsdata->wake_gpio; + + gpiod_set_value_cansleep(wake_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(wake_gpio, 1); + } + + + return ret; +} + +static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops, + edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume); + static const struct edt_i2c_chip_data edt_ft5x06_data = { .max_support_points = 5, }; @@ -1276,6 +1397,7 @@ static struct i2c_driver edt_ft5x06_ts_driver = { .driver = { .name = "edt_ft5x06", .of_match_table = edt_ft5x06_of_match, + .pm = &edt_ft5x06_ts_pm_ops, }, .id_table = edt_ft5x06_ts_id, .probe = edt_ft5x06_ts_probe, -- cgit v1.2.3 From 0f58daaacca9fdc078eadfcfaa19e467b264cb28 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 9 May 2020 14:27:23 -0700 Subject: Input: edt-ft5x06 - prefer asynchronous probe Probing the device takes a while, because we sleep for 300 ms after a reset; allow asynchronous probing so this can happen in the background while other devices are being probed. Signed-off-by: Ahmad Fatoum Signed-off-by: Marco Felsch Link: https://lore.kernel.org/r/20200227112819.16754-5-m.felsch@pengutronix.de Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index d423bd6bfeda..3a4f18d3450d 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -1398,6 +1398,7 @@ static struct i2c_driver edt_ft5x06_ts_driver = { .name = "edt_ft5x06", .of_match_table = edt_ft5x06_of_match, .pm = &edt_ft5x06_ts_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = edt_ft5x06_ts_id, .probe = edt_ft5x06_ts_probe, -- cgit v1.2.3 From 3eb66d9f97f386262adbba9ce752ec80b85246ed Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 9 May 2020 22:18:18 -0700 Subject: Input: add driver for the Cypress CY8CTMA140 touchscreen This adds a new driver for the Cypress CY8CTMA140 touchscreen. This driver is inspired by out-of-tree code for the Samsung GT-S7710 mobile phone. I have tried to compare the structure and behaviour of this touchscreen to the existing CYTTSP and CYTTSP4 generics and it seems pretty different. It is also different in character from the cy8ctmg110_ts.c. It appears to rather be vaguely related to the Melfas MMS114 driver, yet distinctly different. Dmitry Torokhov rewrote the key scanning code during the submission process so the driver is a joint work. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20200506123435.187432-2-linus.walleij@linaro.org Signed-off-by: Dmitry Torokhov --- MAINTAINERS | 6 + drivers/input/touchscreen/Kconfig | 12 ++ drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/cy8ctma140.c | 353 +++++++++++++++++++++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 drivers/input/touchscreen/cy8ctma140.c (limited to 'drivers/input/touchscreen') diff --git a/MAINTAINERS b/MAINTAINERS index 8982c6e013b3..faa5bfe8ece0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4578,6 +4578,12 @@ T: git git://linuxtv.org/anttip/media_tree.git S: Maintained F: drivers/media/common/cypress_firmware* +CYPRESS CY8CTMA140 TOUCHSCREEN DRIVER +M: Linus Walleij +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/input/touchscreen/cy8ctma140.c + CYTTSP TOUCHSCREEN DRIVER M: Ferruh Yigit L: linux-input@vger.kernel.org diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index c071f7c407b6..35c867b2d9a7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -201,6 +201,18 @@ config TOUCHSCREEN_CHIPONE_ICN8505 To compile this driver as a module, choose M here: the module will be called chipone_icn8505. +config TOUCHSCREEN_CY8CTMA140 + tristate "cy8ctma140 touchscreen" + depends on I2C + help + Say Y here if you have a Cypress CY8CTMA140 capacitive + touchscreen also just known as "TMA140" + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cy8ctma140. + config TOUCHSCREEN_CY8CTMG110 tristate "cy8ctmg110 touchscreen" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 94c6162409b3..30d1e1b42492 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o obj-$(CONFIG_TOUCHSCREEN_BU21029) += bu21029_ts.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8505) += chipone_icn8505.o +obj-$(CONFIG_TOUCHSCREEN_CY8CTMA140) += cy8ctma140.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C) += cyttsp_i2c.o cyttsp_i2c_common.o diff --git a/drivers/input/touchscreen/cy8ctma140.c b/drivers/input/touchscreen/cy8ctma140.c new file mode 100644 index 000000000000..a9be29139cbf --- /dev/null +++ b/drivers/input/touchscreen/cy8ctma140.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Cypress CY8CTMA140 (TMA140) touchscreen + * (C) 2020 Linus Walleij + * (C) 2007 Cypress + * (C) 2007 Google, Inc. + * + * Inspired by the tma140_skomer.c driver in the Samsung GT-S7710 code + * drop. The GT-S7710 is codenamed "Skomer", the code also indicates + * that the same touchscreen was used in a product called "Lucas". + * + * The code drop for GT-S7710 also contains a firmware downloader and + * 15 (!) versions of the firmware drop from Cypress. But here we assume + * the firmware got downloaded to the touchscreen flash successfully and + * just use it to read the fingers. The shipped vendor driver does the + * same. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CY8CTMA140_NAME "cy8ctma140" + +#define CY8CTMA140_MAX_FINGERS 4 + +#define CY8CTMA140_GET_FINGERS 0x00 +#define CY8CTMA140_GET_FW_INFO 0x19 + +/* This message also fits some bytes for touchkeys, if used */ +#define CY8CTMA140_PACKET_SIZE 31 + +#define CY8CTMA140_INVALID_BUFFER_BIT 5 + +struct cy8ctma140 { + struct input_dev *input; + struct touchscreen_properties props; + struct device *dev; + struct i2c_client *client; + struct regulator_bulk_data regulators[2]; + u8 prev_fingers; + u8 prev_f1id; + u8 prev_f2id; +}; + +static void cy8ctma140_report(struct cy8ctma140 *ts, u8 *data, int n_fingers) +{ + static const u8 contact_offsets[] = { 0x03, 0x09, 0x10, 0x16 }; + u8 *buf; + u16 x, y; + u8 w; + u8 id; + int slot; + int i; + + for (i = 0; i < n_fingers; i++) { + buf = &data[contact_offsets[i]]; + + /* + * Odd contacts have contact ID in the lower nibble of + * the preceding byte, whereas even contacts have it in + * the upper nibble of the following byte. + */ + id = i % 2 ? buf[-1] & 0x0f : buf[5] >> 4; + slot = input_mt_get_slot_by_key(ts->input, id); + if (slot < 0) + continue; + + x = get_unaligned_be16(buf); + y = get_unaligned_be16(buf + 2); + w = buf[4]; + + dev_dbg(ts->dev, "finger %d: ID %02x (%d, %d) w: %d\n", + slot, id, x, y, w); + + input_mt_slot(ts->input, slot); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); + touchscreen_report_pos(ts->input, &ts->props, x, y, true); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, w); + } + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static irqreturn_t cy8ctma140_irq_thread(int irq, void *d) +{ + struct cy8ctma140 *ts = d; + u8 cmdbuf[] = { CY8CTMA140_GET_FINGERS }; + u8 buf[CY8CTMA140_PACKET_SIZE]; + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .len = sizeof(cmdbuf), + .buf = cmdbuf, + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .len = sizeof(buf), + .buf = buf, + }, + }; + u8 n_fingers; + int ret; + + ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + if (ret < 0) + dev_err(ts->dev, "error reading message: %d\n", ret); + else + dev_err(ts->dev, "wrong number of messages\n"); + goto out; + } + + if (buf[1] & BIT(CY8CTMA140_INVALID_BUFFER_BIT)) { + dev_dbg(ts->dev, "invalid event\n"); + goto out; + } + + n_fingers = buf[2] & 0x0f; + if (n_fingers > CY8CTMA140_MAX_FINGERS) { + dev_err(ts->dev, "unexpected number of fingers: %d\n", + n_fingers); + goto out; + } + + cy8ctma140_report(ts, buf, n_fingers); + +out: + return IRQ_HANDLED; +} + +static int cy8ctma140_init(struct cy8ctma140 *ts) +{ + u8 addr[1]; + u8 buf[5]; + int ret; + + addr[0] = CY8CTMA140_GET_FW_INFO; + ret = i2c_master_send(ts->client, addr, 1); + if (ret < 0) { + dev_err(ts->dev, "error sending FW info message\n"); + return ret; + } + ret = i2c_master_recv(ts->client, buf, 5); + if (ret < 0) { + dev_err(ts->dev, "error receiving FW info message\n"); + return ret; + } + if (ret != 5) { + dev_err(ts->dev, "got only %d bytes\n", ret); + return -EIO; + } + + dev_dbg(ts->dev, "vendor %c%c, HW ID %.2d, FW ver %.4d\n", + buf[0], buf[1], buf[3], buf[4]); + + return 0; +} + +static int cy8ctma140_power_up(struct cy8ctma140 *ts) +{ + int error; + + error = regulator_bulk_enable(ARRAY_SIZE(ts->regulators), + ts->regulators); + if (error) { + dev_err(ts->dev, "failed to enable regulators\n"); + return error; + } + + msleep(250); + + return 0; +} + +static void cy8ctma140_power_down(struct cy8ctma140 *ts) +{ + regulator_bulk_disable(ARRAY_SIZE(ts->regulators), + ts->regulators); +} + +/* Called from the registered devm action */ +static void cy8ctma140_power_off_action(void *d) +{ + struct cy8ctma140 *ts = d; + + cy8ctma140_power_down(ts); +} + +static int cy8ctma140_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cy8ctma140 *ts; + struct input_dev *input; + struct device *dev = &client->dev; + int error; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + ts->dev = dev; + ts->client = client; + ts->input = input; + + input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); + /* One byte for width 0..255 so this is the limit */ + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + /* + * This sets up event max/min capabilities and fuzz. + * Some DT properties are compulsory so we do not need + * to provide defaults for X/Y max or pressure max. + * + * We just initialize a very simple MT touchscreen here, + * some devices use the capability of this touchscreen to + * provide touchkeys, and in that case this needs to be + * extended to handle touchkey input. + * + * The firmware takes care of finger tracking and dropping + * invalid ranges. + */ + touchscreen_parse_properties(input, true, &ts->props); + input_abs_set_fuzz(input, ABS_MT_POSITION_X, 0); + input_abs_set_fuzz(input, ABS_MT_POSITION_Y, 0); + + error = input_mt_init_slots(input, CY8CTMA140_MAX_FINGERS, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) + return error; + + input->name = CY8CTMA140_NAME; + input->id.bustype = BUS_I2C; + input_set_drvdata(input, ts); + + /* + * VCPIN is the analog voltage supply + * VDD is the digital voltage supply + * since the voltage range of VDD overlaps that of VCPIN, + * many designs to just supply both with a single voltage + * source of ~3.3 V. + */ + ts->regulators[0].supply = "vcpin"; + ts->regulators[1].supply = "vdd"; + error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->regulators), + ts->regulators); + if (error) { + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get regulators %d\n", + error); + return error; + } + + error = cy8ctma140_power_up(ts); + if (error) + return error; + + error = devm_add_action_or_reset(dev, cy8ctma140_power_off_action, ts); + if (error) { + dev_err(dev, "failed to install power off handler\n"); + return error; + } + + error = devm_request_threaded_irq(dev, client->irq, + NULL, cy8ctma140_irq_thread, + IRQF_ONESHOT, CY8CTMA140_NAME, ts); + if (error) { + dev_err(dev, "irq %d busy? error %d\n", client->irq, error); + return error; + } + + error = cy8ctma140_init(ts); + if (error) + return error; + + error = input_register_device(input); + if (error) + return error; + + i2c_set_clientdata(client, ts); + + return 0; +} + +static int __maybe_unused cy8ctma140_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cy8ctma140 *ts = i2c_get_clientdata(client); + + if (!device_may_wakeup(&client->dev)) + cy8ctma140_power_down(ts); + + return 0; +} + +static int __maybe_unused cy8ctma140_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cy8ctma140 *ts = i2c_get_clientdata(client); + int error; + + if (!device_may_wakeup(&client->dev)) { + error = cy8ctma140_power_up(ts); + if (error) + return error; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(cy8ctma140_pm, cy8ctma140_suspend, cy8ctma140_resume); + +static const struct i2c_device_id cy8ctma140_idtable[] = { + { CY8CTMA140_NAME, 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, cy8ctma140_idtable); + +static const struct of_device_id cy8ctma140_of_match[] = { + { .compatible = "cypress,cy8ctma140", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cy8ctma140_of_match); + +static struct i2c_driver cy8ctma140_driver = { + .driver = { + .name = CY8CTMA140_NAME, + .pm = &cy8ctma140_pm, + .of_match_table = cy8ctma140_of_match, + }, + .id_table = cy8ctma140_idtable, + .probe = cy8ctma140_probe, +}; +module_i2c_driver(cy8ctma140_driver); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("CY8CTMA140 TouchScreen Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 5fc70e350edd30fb22d2f9b4e6d680c5471890ff Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Mon, 11 May 2020 13:12:13 -0700 Subject: Input: introduce input_mt_report_slot_inactive() input_mt_report_slot_state() ignores "tool" argument when the slot is closed, which has caused a bit of confusion. Let's introduce input_mt_report_slot_inactive() to report inactive slot state. Suggested-by: Dmitry Torokhov Signed-off-by: Jiada Wang Link: https://lore.kernel.org/r/20200508055656.96389-2-jiada_wang@mentor.com Signed-off-by: Dmitry Torokhov --- drivers/hid/hid-alps.c | 3 +-- drivers/hid/hid-multitouch.c | 6 ++---- drivers/input/misc/xen-kbdfront.c | 2 +- drivers/input/mouse/elan_i2c_core.c | 2 +- drivers/input/touchscreen/atmel_mxt_ts.c | 7 +++---- drivers/input/touchscreen/cyttsp4_core.c | 5 ++--- drivers/input/touchscreen/cyttsp_core.c | 2 +- drivers/input/touchscreen/melfas_mip4.c | 4 ++-- drivers/input/touchscreen/mms114.c | 2 +- drivers/input/touchscreen/raspberrypi-ts.c | 2 +- drivers/input/touchscreen/stmfts.c | 2 +- include/linux/input/mt.h | 5 +++++ 12 files changed, 21 insertions(+), 21 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index fa704153cb00..28588f74425e 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -387,8 +387,7 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) input_report_abs(hdata->input, ABS_MT_PRESSURE, z); } else { - input_mt_report_slot_state(hdata->input, - MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(hdata->input); } } diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 362805ddf377..e2ce790ff4a4 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -896,7 +896,7 @@ static void mt_release_pending_palms(struct mt_device *td, clear_bit(slotnum, app->pending_palm_slots); input_mt_slot(input, slotnum); - input_mt_report_slot_state(input, MT_TOOL_PALM, false); + input_mt_report_slot_inactive(input); need_sync = true; } @@ -1640,9 +1640,7 @@ static void mt_release_contacts(struct hid_device *hid) if (mt) { for (i = 0; i < mt->num_slots; i++) { input_mt_slot(input_dev, i); - input_mt_report_slot_state(input_dev, - MT_TOOL_FINGER, - false); + input_mt_report_slot_inactive(input_dev); } input_mt_sync_frame(input_dev); input_sync(input_dev); diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c index 24bc5c5d876f..a1bba722b234 100644 --- a/drivers/input/misc/xen-kbdfront.c +++ b/drivers/input/misc/xen-kbdfront.c @@ -146,7 +146,7 @@ static void xenkbd_handle_mt_event(struct xenkbd_info *info, break; case XENKBD_MT_EV_UP: - input_mt_report_slot_state(info->mtouch, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(info->mtouch); break; case XENKBD_MT_EV_SYN: diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c index 8719da540383..3f9354baac4b 100644 --- a/drivers/input/mouse/elan_i2c_core.c +++ b/drivers/input/mouse/elan_i2c_core.c @@ -938,7 +938,7 @@ static void elan_report_contact(struct elan_tp_data *data, input_report_abs(input, ABS_MT_TOUCH_MINOR, minor); } else { input_mt_slot(input, contact_num); - input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(input); } } diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index ae60442efda0..a2189739e30f 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -822,8 +822,7 @@ static void mxt_proc_t9_message(struct mxt_data *data, u8 *message) * have happened. */ if (status & MXT_T9_RELEASE) { - input_mt_report_slot_state(input_dev, - MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(input_dev); mxt_input_sync(data); } @@ -839,7 +838,7 @@ static void mxt_proc_t9_message(struct mxt_data *data, u8 *message) input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area); } else { /* Touch no longer active, close out slot */ - input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(input_dev); } data->update_input = true; @@ -947,7 +946,7 @@ static void mxt_proc_t100_message(struct mxt_data *data, u8 *message) dev_dbg(dev, "[%u] release\n", id); /* close out slot */ - input_mt_report_slot_state(input_dev, 0, 0); + input_mt_report_slot_inactive(input_dev); } data->update_input = true; diff --git a/drivers/input/touchscreen/cyttsp4_core.c b/drivers/input/touchscreen/cyttsp4_core.c index 6bcffc930384..02a73d9a4def 100644 --- a/drivers/input/touchscreen/cyttsp4_core.c +++ b/drivers/input/touchscreen/cyttsp4_core.c @@ -744,8 +744,7 @@ static void cyttsp4_report_slot_liftoff(struct cyttsp4_mt_data *md, for (t = 0; t < max_slots; t++) { input_mt_slot(md->input, t); - input_mt_report_slot_state(md->input, - MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(md->input); } } @@ -845,7 +844,7 @@ static void cyttsp4_final_sync(struct input_dev *input, int max_slots, int *ids) if (ids[t]) continue; input_mt_slot(input, t); - input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(input); } input_sync(input); diff --git a/drivers/input/touchscreen/cyttsp_core.c b/drivers/input/touchscreen/cyttsp_core.c index 3f5d463dbeed..697aa2c158f7 100644 --- a/drivers/input/touchscreen/cyttsp_core.c +++ b/drivers/input/touchscreen/cyttsp_core.c @@ -340,7 +340,7 @@ static void cyttsp_report_tchdata(struct cyttsp *ts) continue; input_mt_slot(input, i); - input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(input); } input_sync(input); diff --git a/drivers/input/touchscreen/melfas_mip4.c b/drivers/input/touchscreen/melfas_mip4.c index 247c3aaba2d8..f67efdd040b2 100644 --- a/drivers/input/touchscreen/melfas_mip4.c +++ b/drivers/input/touchscreen/melfas_mip4.c @@ -391,7 +391,7 @@ static void mip4_clear_input(struct mip4_ts *ts) /* Screen */ for (i = 0; i < MIP4_MAX_FINGERS; i++) { input_mt_slot(ts->input, i); - input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(ts->input); } /* Keys */ @@ -534,7 +534,7 @@ static void mip4_report_touch(struct mip4_ts *ts, u8 *packet) } else { /* Release event */ input_mt_slot(ts->input, id); - input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(ts->input); } input_mt_sync_frame(ts->input); diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index b3dda1722bae..d5e2d2a139a2 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -559,7 +559,7 @@ static int __maybe_unused mms114_suspend(struct device *dev) /* Release all touch */ for (id = 0; id < MMS114_MAX_TOUCH; id++) { input_mt_slot(input_dev, id); - input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(input_dev); } input_mt_report_pointer_emulation(input_dev, true); diff --git a/drivers/input/touchscreen/raspberrypi-ts.c b/drivers/input/touchscreen/raspberrypi-ts.c index 0e2e08f3f433..ef6aaed217cf 100644 --- a/drivers/input/touchscreen/raspberrypi-ts.c +++ b/drivers/input/touchscreen/raspberrypi-ts.c @@ -100,7 +100,7 @@ static void rpi_ts_poll(struct input_dev *input) released_ids = ts->known_ids & ~modified_ids; for_each_set_bit(i, &released_ids, RPI_TS_MAX_SUPPORTED_POINTS) { input_mt_slot(input, i); - input_mt_report_slot_state(input, MT_TOOL_FINGER, 0); + input_mt_report_slot_inactive(input); modified_ids &= ~(BIT(i)); } ts->known_ids = modified_ids; diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c index b6f95f20f924..b54cc64e4ea6 100644 --- a/drivers/input/touchscreen/stmfts.c +++ b/drivers/input/touchscreen/stmfts.c @@ -198,7 +198,7 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata, u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4; input_mt_slot(sdata->input, slot_id); - input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, false); + input_mt_report_slot_inactive(sdata->input); input_sync(sdata->input); } diff --git a/include/linux/input/mt.h b/include/linux/input/mt.h index 9e409bb13642..3b8580bd33c1 100644 --- a/include/linux/input/mt.h +++ b/include/linux/input/mt.h @@ -100,6 +100,11 @@ static inline bool input_is_mt_axis(int axis) bool input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active); +static inline void input_mt_report_slot_inactive(struct input_dev *dev) +{ + input_mt_report_slot_state(dev, 0, false); +} + void input_mt_report_finger_count(struct input_dev *dev, int count); void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count); void input_mt_drop_unused(struct input_dev *dev); -- cgit v1.2.3 From cf520c64301204afa8272a94cc2e1495a425446a Mon Sep 17 00:00:00 2001 From: Johnny Chuang Date: Tue, 12 May 2020 14:44:39 -0700 Subject: Input: elants_i2c - provide an attribute to show calibration count There is an non-touch case by non-calibration after update firmware. Elan could know calibrate or not by calibration count. The value of '0xffff' means we didn't calibrate after update firmware. If calibrate success, it will plus one and change to '0x0000'. Signed-off-by: Johnny Chuang Link: https://lore.kernel.org/r/1588754932-5902-1-git-send-email-johnny.chuang.emc@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/elants_i2c.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c index 14c577c16b16..6e962b54fea3 100644 --- a/drivers/input/touchscreen/elants_i2c.c +++ b/drivers/input/touchscreen/elants_i2c.c @@ -87,6 +87,7 @@ /* FW read command, 0x53 0x?? 0x0, 0x01 */ #define E_ELAN_INFO_FW_VER 0x00 #define E_ELAN_INFO_BC_VER 0x10 +#define E_ELAN_INFO_REK 0xE0 #define E_ELAN_INFO_TEST_VER 0xE0 #define E_ELAN_INFO_FW_ID 0xF0 #define E_INFO_OSR 0xD6 @@ -1010,7 +1011,7 @@ out: */ static ssize_t calibrate_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) + const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct elants_data *ts = i2c_get_clientdata(client); @@ -1056,8 +1057,32 @@ static ssize_t show_iap_mode(struct device *dev, "Normal" : "Recovery"); } +static ssize_t show_calibration_count(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_REK, 0x00, 0x01 }; + u8 resp[HEADER_SIZE]; + u16 rek_count; + int error; + + error = elants_i2c_execute_command(client, cmd, sizeof(cmd), + resp, sizeof(resp)); + if (error) { + dev_err(&client->dev, + "read ReK status error=%d, buf=%*phC\n", + error, (int)sizeof(resp), resp); + return sprintf(buf, "%d\n", error); + } + + rek_count = get_unaligned_be16(&resp[2]); + + return sprintf(buf, "0x%04x\n", rek_count); +} + static DEVICE_ATTR_WO(calibrate); static DEVICE_ATTR(iap_mode, S_IRUGO, show_iap_mode, NULL); +static DEVICE_ATTR(calibration_count, S_IRUGO, show_calibration_count, NULL); static DEVICE_ATTR(update_fw, S_IWUSR, NULL, write_update_fw); struct elants_version_attribute { @@ -1113,6 +1138,7 @@ static struct attribute *elants_attributes[] = { &dev_attr_calibrate.attr, &dev_attr_update_fw.attr, &dev_attr_iap_mode.attr, + &dev_attr_calibration_count.attr, &elants_ver_attr_fw_version.dattr.attr, &elants_ver_attr_hw_version.dattr.attr, -- cgit v1.2.3 From 6def17b12ba3d30196d4f35a5cf1edf38f791d1d Mon Sep 17 00:00:00 2001 From: Michał Mirosław Date: Sun, 17 May 2020 20:38:43 -0700 Subject: Input: elants - remove unused axes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Driver only ever reports MT events and input_mt_init_slots() sets up emulated axes already. Clear the capabilities not generated directly and move MT axes setup, so they are visible by input_mt_init_slots(). Signed-off-by: Michał Mirosław Reviewed-by: Dmitry Osipenko Tested-by: Dmitry Osipenko Link: https://lore.kernel.org/r/d5eee8cd305adb144a11264d70da94f7b6570366.1587923061.git.mirq-linux@rere.qmqm.pl Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/elants_i2c.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c index 0c4bbd2eb3a2..3abfa3f7db38 100644 --- a/drivers/input/touchscreen/elants_i2c.c +++ b/drivers/input/touchscreen/elants_i2c.c @@ -1316,25 +1316,7 @@ static int elants_i2c_probe(struct i2c_client *client, ts->input->name = "Elan Touchscreen"; ts->input->id.bustype = BUS_I2C; - __set_bit(BTN_TOUCH, ts->input->keybit); - __set_bit(EV_ABS, ts->input->evbit); - __set_bit(EV_KEY, ts->input->evbit); - - /* Single touch input params setup */ - input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0); - input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0); - input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0); - input_abs_set_res(ts->input, ABS_X, ts->x_res); - input_abs_set_res(ts->input, ABS_Y, ts->y_res); - /* Multitouch input params setup */ - error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM, - INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); - if (error) { - dev_err(&client->dev, - "failed to initialize MT slots: %d\n", error); - return error; - } input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0); input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0); @@ -1346,6 +1328,14 @@ static int elants_i2c_probe(struct i2c_client *client, input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res); input_abs_set_res(ts->input, ABS_MT_TOUCH_MAJOR, 1); + error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) { + dev_err(&client->dev, + "failed to initialize MT slots: %d\n", error); + return error; + } + error = input_register_device(ts->input); if (error) { dev_err(&client->dev, -- cgit v1.2.3 From 68334dbab13bc5d637fdf1aa2ccfe7e1932b2af1 Mon Sep 17 00:00:00 2001 From: Michał Mirosław Date: Sun, 17 May 2020 20:39:40 -0700 Subject: Input: elants - override touchscreen info with DT properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow overriding of information from hardware and support additional common DT properties like axis inversion. This is required for eg. Nexus 7 and TF300T where the programmed values in firmware differ from reality. Signed-off-by: Dmitry Osipenko [moved "prop" before DMA buffer] Signed-off-by: Michał Mirosław Link: https://lore.kernel.org/r/49ea996878264f7c8bde25204e4ddf4b1e85ae71.1587923061.git.mirq-linux@rere.qmqm.pl Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/elants_i2c.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c index 3abfa3f7db38..ccaef4031556 100644 --- a/drivers/input/touchscreen/elants_i2c.c +++ b/drivers/input/touchscreen/elants_i2c.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,7 @@ struct elants_data { unsigned int y_res; unsigned int x_max; unsigned int y_max; + struct touchscreen_properties prop; enum elants_state state; enum elants_iap_mode iap_mode; @@ -876,8 +878,7 @@ static void elants_i2c_mt_event(struct elants_data *ts, u8 *buf) input_mt_slot(input, i); input_mt_report_slot_state(input, tool_type, true); - input_event(input, EV_ABS, ABS_MT_POSITION_X, x); - input_event(input, EV_ABS, ABS_MT_POSITION_Y, y); + touchscreen_report_pos(input, &ts->prop, x, y, true); input_event(input, EV_ABS, ABS_MT_PRESSURE, p); input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, w); @@ -1328,6 +1329,8 @@ static int elants_i2c_probe(struct i2c_client *client, input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->y_res); input_abs_set_res(ts->input, ABS_MT_TOUCH_MAJOR, 1); + touchscreen_parse_properties(ts->input, true, &ts->prop); + error = input_mt_init_slots(ts->input, MAX_CONTACT_NUM, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); if (error) { -- cgit v1.2.3 From 918e2844d940da7c624262a7aa327615d3eb5abd Mon Sep 17 00:00:00 2001 From: Michał Mirosław Date: Sun, 17 May 2020 20:41:05 -0700 Subject: Input: elants - refactor elants_i2c_execute_command() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply some DRY-ing to elants_i2c_execute_command() callers. This pulls polling and error printk()s into a single function. Signed-off-by: Michał Mirosław Link: https://lore.kernel.org/r/6c576f688b385235c65b461410a917080d27e825.1587923061.git.mirq-linux@rere.qmqm.pl Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/elants_i2c.c | 200 ++++++++++++++++----------------- 1 file changed, 99 insertions(+), 101 deletions(-) (limited to 'drivers/input/touchscreen') diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c index ccaef4031556..233cb1085bbd 100644 --- a/drivers/input/touchscreen/elants_i2c.c +++ b/drivers/input/touchscreen/elants_i2c.c @@ -192,7 +192,8 @@ static int elants_i2c_read(struct i2c_client *client, void *data, size_t size) static int elants_i2c_execute_command(struct i2c_client *client, const u8 *cmd, size_t cmd_size, - u8 *resp, size_t resp_size) + u8 *resp, size_t resp_size, + int retries, const char *cmd_name) { struct i2c_msg msgs[2]; int ret; @@ -212,30 +213,55 @@ static int elants_i2c_execute_command(struct i2c_client *client, break; default: - dev_err(&client->dev, "%s: invalid command %*ph\n", - __func__, (int)cmd_size, cmd); + dev_err(&client->dev, "(%s): invalid command: %*ph\n", + cmd_name, (int)cmd_size, cmd); return -EINVAL; } - msgs[0].addr = client->addr; - msgs[0].flags = client->flags & I2C_M_TEN; - msgs[0].len = cmd_size; - msgs[0].buf = (u8 *)cmd; + for (;;) { + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = cmd_size; + msgs[0].buf = (u8 *)cmd; + + msgs[1].addr = client->addr; + msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[1].flags |= I2C_M_RD; + msgs[1].len = resp_size; + msgs[1].buf = resp; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + if (--retries > 0) { + dev_dbg(&client->dev, + "(%s) I2C transfer failed: %pe (retrying)\n", + cmd_name, ERR_PTR(ret)); + continue; + } - msgs[1].addr = client->addr; - msgs[1].flags = client->flags & I2C_M_TEN; - msgs[1].flags |= I2C_M_RD; - msgs[1].len = resp_size; - msgs[1].buf = resp; + dev_err(&client->dev, + "(%s) I2C transfer failed: %pe\n", + cmd_name, ERR_PTR(ret)); + return ret; + } - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret < 0) - return ret; + if (ret != ARRAY_SIZE(msgs) || + resp[FW_HDR_TYPE] != expected_response) { + if (--retries > 0) { + dev_dbg(&client->dev, + "(%s) unexpected response: %*ph (retrying)\n", + cmd_name, ret, resp); + continue; + } - if (ret != ARRAY_SIZE(msgs) || resp[FW_HDR_TYPE] != expected_response) - return -EIO; + dev_err(&client->dev, + "(%s) unexpected response: %*ph\n", + cmd_name, ret, resp); + return -EIO; + } - return 0; + return 0; + } } static int elants_i2c_calibrate(struct elants_data *ts) @@ -308,27 +334,21 @@ static u16 elants_i2c_parse_version(u8 *buf) static int elants_i2c_query_hw_version(struct elants_data *ts) { struct i2c_client *client = ts->client; - int error, retry_cnt; + int retry_cnt = MAX_RETRIES; const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_ID, 0x00, 0x01 }; u8 resp[HEADER_SIZE]; + int error; - for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { + while (retry_cnt--) { error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (!error) { - ts->hw_version = elants_i2c_parse_version(resp); - if (ts->hw_version != 0xffff) - return 0; - } - - dev_dbg(&client->dev, "read fw id error=%d, buf=%*phC\n", - error, (int)sizeof(resp), resp); - } + resp, sizeof(resp), 1, + "read fw id"); + if (error) + return error; - if (error) { - dev_err(&client->dev, - "Failed to read fw id: %d\n", error); - return error; + ts->hw_version = elants_i2c_parse_version(resp); + if (ts->hw_version != 0xffff) + return 0; } dev_err(&client->dev, "Invalid fw id: %#04x\n", ts->hw_version); @@ -339,26 +359,27 @@ static int elants_i2c_query_hw_version(struct elants_data *ts) static int elants_i2c_query_fw_version(struct elants_data *ts) { struct i2c_client *client = ts->client; - int error, retry_cnt; + int retry_cnt = MAX_RETRIES; const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_FW_VER, 0x00, 0x01 }; u8 resp[HEADER_SIZE]; + int error; - for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { + while (retry_cnt--) { error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (!error) { - ts->fw_version = elants_i2c_parse_version(resp); - if (ts->fw_version != 0x0000 && - ts->fw_version != 0xffff) - return 0; - } + resp, sizeof(resp), 1, + "read fw version"); + if (error) + return error; + + ts->fw_version = elants_i2c_parse_version(resp); + if (ts->fw_version != 0x0000 && ts->fw_version != 0xffff) + return 0; - dev_dbg(&client->dev, "read fw version error=%d, buf=%*phC\n", - error, (int)sizeof(resp), resp); + dev_dbg(&client->dev, "(read fw version) resp %*phC\n", + (int)sizeof(resp), resp); } - dev_err(&client->dev, - "Failed to read fw version or fw version is invalid\n"); + dev_err(&client->dev, "Invalid fw ver: %#04x\n", ts->fw_version); return -EINVAL; } @@ -366,30 +387,24 @@ static int elants_i2c_query_fw_version(struct elants_data *ts) static int elants_i2c_query_test_version(struct elants_data *ts) { struct i2c_client *client = ts->client; - int error, retry_cnt; + int error; u16 version; const u8 cmd[] = { CMD_HEADER_READ, E_ELAN_INFO_TEST_VER, 0x00, 0x01 }; u8 resp[HEADER_SIZE]; - for (retry_cnt = 0; retry_cnt < MAX_RETRIES; retry_cnt++) { - error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (!error) { - version = elants_i2c_parse_version(resp); - ts->test_version = version >> 8; - ts->solution_version = version & 0xff; - - return 0; - } - - dev_dbg(&client->dev, - "read test version error rc=%d, buf=%*phC\n", - error, (int)sizeof(resp), resp); + error = elants_i2c_execute_command(client, cmd, sizeof(cmd), + resp, sizeof(resp), MAX_RETRIES, + "read test version"); + if (error) { + dev_err(&client->dev, "Failed to read test version\n"); + return error; } - dev_err(&client->dev, "Failed to read test version\n"); + version = elants_i2c_parse_version(resp); + ts->test_version = version >> 8; + ts->solution_version = version & 0xff; - return -EINVAL; + return 0; } static int elants_i2c_query_bc_version(struct elants_data *ts) @@ -401,13 +416,10 @@ static int elants_i2c_query_bc_version(struct elants_data *ts) int error; error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, - "read BC version error=%d, buf=%*phC\n", - error, (int)sizeof(resp), resp); + resp, sizeof(resp), 1, + "read BC version"); + if (error) return error; - } version = elants_i2c_parse_version(resp); ts->bc_version = version >> 8; @@ -439,12 +451,10 @@ static int elants_i2c_query_ts_info(struct elants_data *ts) error = elants_i2c_execute_command(client, get_resolution_cmd, sizeof(get_resolution_cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, "get resolution command failed: %d\n", - error); + resp, sizeof(resp), 1, + "get resolution"); + if (error) return error; - } rows = resp[2] + resp[6] + resp[10]; cols = resp[3] + resp[7] + resp[11]; @@ -452,36 +462,29 @@ static int elants_i2c_query_ts_info(struct elants_data *ts) /* Process mm_to_pixel information */ error = elants_i2c_execute_command(client, get_osr_cmd, sizeof(get_osr_cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, "get osr command failed: %d\n", - error); + resp, sizeof(resp), 1, "get osr"); + if (error) return error; - } osr = resp[3]; error = elants_i2c_execute_command(client, get_physical_scan_cmd, sizeof(get_physical_scan_cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, "get physical scan command failed: %d\n", - error); + resp, sizeof(resp), 1, + "get physical scan"); + if (error) return error; - } phy_x = get_unaligned_be16(&resp[2]); error = elants_i2c_execute_command(client, get_physical_drive_cmd, sizeof(get_physical_drive_cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, "get physical drive command failed: %d\n", - error); + resp, sizeof(resp), 1, + "get physical drive"); + if (error) return error; - } phy_y = get_unaligned_be16(&resp[2]); @@ -636,11 +639,10 @@ static int elants_i2c_validate_remark_id(struct elants_data *ts, /* Compare TS Remark ID and FW Remark ID */ error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, "failed to query Remark ID: %d\n", error); + resp, sizeof(resp), + 1, "read Remark ID"); + if (error) return error; - } ts_remark_id = get_unaligned_be16(&resp[3]); @@ -1075,16 +1077,12 @@ static ssize_t show_calibration_count(struct device *dev, int error; error = elants_i2c_execute_command(client, cmd, sizeof(cmd), - resp, sizeof(resp)); - if (error) { - dev_err(&client->dev, - "read ReK status error=%d, buf=%*phC\n", - error, (int)sizeof(resp), resp); + resp, sizeof(resp), 1, + "read ReK status"); + if (error) return sprintf(buf, "%d\n", error); - } rek_count = get_unaligned_be16(&resp[2]); - return sprintf(buf, "0x%04x\n", rek_count); } -- cgit v1.2.3