diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-10 13:51:47 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-10 13:51:47 -0700 |
| commit | 49f13b0921aaef0d5519358fd9fcc237c1c9124a (patch) | |
| tree | bb8789c9191bc655dc559f540548a8ea0fd0c09c /drivers/input/touchscreen | |
| parent | 09102704c67457c6cdea6c0394c34843484a852c (diff) | |
| parent | 751ad34fbad74c3ed4a9ede24764b4253d4faa84 (diff) | |
| download | linux-49f13b0921aaef0d5519358fd9fcc237c1c9124a.tar.gz linux-49f13b0921aaef0d5519358fd9fcc237c1c9124a.tar.bz2 linux-49f13b0921aaef0d5519358fd9fcc237c1c9124a.zip | |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov:
- a new driver for the Azoteq IQS269A capacitive touch controller
- a new driver for the Cypress CY8CTMA140 touchscreen
- updates to Elan and ft5x06 touchscreen drivers
- assorted driver fixes
- msm-vibrator has been removed as we have a more generic solution
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (28 commits)
Input: adi - work around module name confict
Input: iqs269a - add missing I2C dependency
Input: elants - refactor elants_i2c_execute_command()
Input: elants - override touchscreen info with DT properties
Input: elants - remove unused axes
Input: add support for Azoteq IQS269A
dt-bindings: input: Add bindings for Azoteq IQS269A
Input: imx_sc_key - use devm_add_action_or_reset() to handle all cleanups
Input: remove msm-vibrator driver
dt-bindings: Input: remove msm-vibrator
Input: elants_i2c - provide an attribute to show calibration count
Input: introduce input_mt_report_slot_inactive()
dt-bindings: input: touchscreen: elants_i2c: convert to YAML
Input: add driver for the Cypress CY8CTMA140 touchscreen
dt-bindings: touchscreen: Add CY8CTMA140 bindings
Input: edt-ft5x06 - prefer asynchronous probe
Input: edt-ft5x06 - improve power management operations
Input: edt-ft5x06 - move parameter restore into helper
Input: edt-ft5x06 - fix get_default register write access
Input: atkbd - receive and use physcode->keycode mapping from FW
...
Diffstat (limited to 'drivers/input/touchscreen')
| -rw-r--r-- | drivers/input/touchscreen/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
| -rw-r--r-- | drivers/input/touchscreen/atmel_mxt_ts.c | 7 | ||||
| -rw-r--r-- | drivers/input/touchscreen/cy8ctma140.c | 353 | ||||
| -rw-r--r-- | drivers/input/touchscreen/cyttsp4_core.c | 5 | ||||
| -rw-r--r-- | drivers/input/touchscreen/cyttsp_core.c | 2 | ||||
| -rw-r--r-- | drivers/input/touchscreen/edt-ft5x06.c | 198 | ||||
| -rw-r--r-- | drivers/input/touchscreen/elants_i2c.c | 247 | ||||
| -rw-r--r-- | drivers/input/touchscreen/melfas_mip4.c | 4 | ||||
| -rw-r--r-- | drivers/input/touchscreen/mms114.c | 19 | ||||
| -rw-r--r-- | drivers/input/touchscreen/raspberrypi-ts.c | 2 | ||||
| -rw-r--r-- | drivers/input/touchscreen/stmfts.c | 2 |
12 files changed, 690 insertions, 162 deletions
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/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/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 <linus.walleij@linaro.org> + * (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 <asm/unaligned.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/input/touchscreen.h> +#include <linux/input/mt.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> + +#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 <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("CY8CTMA140 TouchScreen Driver"); +MODULE_LICENSE("GPL v2"); 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/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index d2587724c52a..3a4f18d3450d 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; @@ -527,6 +538,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 +626,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 +657,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; @@ -762,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); @@ -777,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); @@ -786,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 void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata, + const char *debugfs_name) { } -static inline 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) { } @@ -938,19 +955,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; } } @@ -1114,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); @@ -1227,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, }; @@ -1265,6 +1397,8 @@ 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, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = edt_ft5x06_ts_id, .probe = edt_ft5x06_ts_probe, diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c index 2289f9638116..233cb1085bbd 100644 --- a/drivers/input/touchscreen/elants_i2c.c +++ b/drivers/input/touchscreen/elants_i2c.c @@ -33,6 +33,7 @@ #include <linux/slab.h> #include <linux/firmware.h> #include <linux/input/mt.h> +#include <linux/input/touchscreen.h> #include <linux/acpi.h> #include <linux/of.h> #include <linux/gpio/consumer.h> @@ -89,6 +90,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 @@ -136,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; @@ -189,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; @@ -209,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) @@ -305,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); @@ -336,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_ |
