Input: edt-ft5x06: Poll the device if no interrupt is configured.

Not all systems have the interrupt line wired up, so switch to
polling the touchscreen off a timer if no interrupt line is
configured.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: edt-ft5x06: Handle unreliable TOUCH_UP events

The ft5x06 is unreliable in sending touch up events, so some
touch IDs can become stuck in the detected state.

Ensure that IDs that are unreported by the controller are
released.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: edt-ft5x06: Only look at the number of points reported

Register 0x02 in the FT5x06 is TD_STATUS containing the number
of valid touch points being reported.

Iterate over that number of points rather than all that are
supported on the device.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: edt-ft5x06: Only read data for number of points reported

Rather than always reading the maximum number of points supported
by the chip (which may be as high as 10), read the number of
active points first, and read data for just those.
In most cases this will result in less data on the I2C bus,
with only the maximum touch points taking more due to a second
read that has to configure the start address.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: edt-ft5x06: Fix patch reading only the number of points reported

Fix bad conflict resolution from upstream updates. Need to read
from tsdata->tdata_offset bytes, not from tsdata->offset.
Also fix logging of i2c read errors to cover both transactions.

Fixes: 7216fcfe2e ("input: edt-ft5x06: Only read data for number of points reported")
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

Input: edt-ft54x6: Clean up timer and workqueue on remove

If no interrupt is defined then a timer and workqueue are used
to poll the controller.
On remove these were not being cleaned up correctly.

Fixes: ca61fdaba7 "Input: edt-ft5x06: Poll the device if no interrupt is
configured."

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: touchscreen: edt-ft5x06: Suppress bogus data on startup

When polled without the use of IRQ, FT5x06 registers may return
undefined initial data, causing unwanted touches or event spamming.
A simple way to filter this out is to suppress touches until the
TD_STATUS register changes for the first time.

Increase the delay before first polling to 300ms, to avoid
transient I2C read flakiness that seems to occur after reset.

Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>

input: edt-ft5x06: Include I2C details in names for the devices

libinput uses the input device name alone. If you have two
identical input devices, then there is no way to differentiate
between them, and in the case of touchscreens that means no
way to associate them with the appropriate display device.

Add the I2C bus and address to the start of the input device
name so that the name is always unique within the system.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>

input: edt-ft5x06: Correct prefix length in snprintf

snprintf takes the length of the array that we can print into,
and has to fit the NULL terminator in there too.
Printing the prefix is generally "12-3456 " which is 8 desired
characters (the length of EDT_NAME_PREFIX_LEN) and the NULL.
The space is therefore being truncated to fit the NULL in.

Increase the length snprintf is allowed to use.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
This commit is contained in:
Dave Stevenson
2020-11-06 18:45:10 +00:00
committed by Dom Cobley
parent 5391f4cc0f
commit 9620ec4a40

View File

@@ -69,6 +69,7 @@
#define TOUCH_EVENT_RESERVED 0x03 #define TOUCH_EVENT_RESERVED 0x03
#define EDT_NAME_LEN 23 #define EDT_NAME_LEN 23
#define EDT_NAME_PREFIX_LEN 8
#define EDT_SWITCH_MODE_RETRIES 10 #define EDT_SWITCH_MODE_RETRIES 10
#define EDT_SWITCH_MODE_DELAY 5 /* msec */ #define EDT_SWITCH_MODE_DELAY 5 /* msec */
#define EDT_RAW_DATA_RETRIES 100 #define EDT_RAW_DATA_RETRIES 100
@@ -80,6 +81,10 @@
#define M06_REG_CMD(factory) ((factory) ? 0xf3 : 0xfc) #define M06_REG_CMD(factory) ((factory) ? 0xf3 : 0xfc)
#define M06_REG_ADDR(factory, addr) ((factory) ? (addr) & 0x7f : (addr) & 0x3f) #define M06_REG_ADDR(factory, addr) ((factory) ? (addr) & 0x7f : (addr) & 0x3f)
#define RESET_DELAY_MS 300 /* reset deassert to I2C */
#define FIRST_POLL_DELAY_MS 300 /* in addition to the above */
#define POLL_INTERVAL_MS 17 /* 17ms = 60fps */
enum edt_pmode { enum edt_pmode {
EDT_PMODE_NOT_SUPPORTED, EDT_PMODE_NOT_SUPPORTED,
EDT_PMODE_HIBERNATE, EDT_PMODE_HIBERNATE,
@@ -138,14 +143,19 @@ struct edt_ft5x06_ts_data {
u8 tdata_cmd; u8 tdata_cmd;
int tdata_len; int tdata_len;
int tdata_offset; int tdata_offset;
unsigned int known_ids;
char name[EDT_NAME_LEN]; char name[EDT_NAME_PREFIX_LEN + EDT_NAME_LEN];
char fw_version[EDT_NAME_LEN]; char fw_version[EDT_NAME_LEN];
int init_td_status;
struct edt_reg_addr reg_addr; struct edt_reg_addr reg_addr;
enum edt_ver version; enum edt_ver version;
unsigned int crc_errors; unsigned int crc_errors;
unsigned int header_errors; unsigned int header_errors;
struct timer_list timer;
struct work_struct work_i2c_poll;
}; };
struct edt_i2c_chip_data { struct edt_i2c_chip_data {
@@ -302,17 +312,49 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
u8 rdbuf[63]; u8 rdbuf[63];
int i, type, x, y, id; int i, type, x, y, id;
int error; int error;
int num_points;
unsigned int active_ids = 0, known_ids = tsdata->known_ids;
long released_ids;
int b = 0;
memset(rdbuf, 0, sizeof(rdbuf)); memset(rdbuf, 0, sizeof(rdbuf));
error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd, rdbuf, error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd, rdbuf,
tsdata->tdata_len); tsdata->tdata_len);
if (tsdata->version == EDT_M06) {
num_points = tsdata->max_support_points;
} else {
/* Register 2 is TD_STATUS, containing the number of touch
* points.
*/
num_points = min(rdbuf[2] & 0xf, tsdata->max_support_points);
/* When polling FT5x06 without IRQ: initial register contents
* could be stale or undefined; discard all readings until
* TD_STATUS changes for the first time (or num_points is 0).
*/
if (tsdata->init_td_status) {
if (tsdata->init_td_status < 0)
tsdata->init_td_status = rdbuf[2];
if (num_points && rdbuf[2] == tsdata->init_td_status)
goto out;
tsdata->init_td_status = 0;
}
if (!error && num_points)
error = regmap_bulk_read(tsdata->regmap,
tsdata->tdata_offset,
&rdbuf[tsdata->tdata_offset],
tsdata->point_len * num_points);
}
if (error) { if (error) {
dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n", dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
error); error);
goto out; goto out;
} }
for (i = 0; i < tsdata->max_support_points; i++) { for (i = 0; i < num_points; i++) {
u8 *buf = &rdbuf[i * tsdata->point_len + tsdata->tdata_offset]; u8 *buf = &rdbuf[i * tsdata->point_len + tsdata->tdata_offset];
type = buf[0] >> 6; type = buf[0] >> 6;
@@ -334,10 +376,25 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
input_mt_slot(tsdata->input, id); input_mt_slot(tsdata->input, id);
if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER,
type != TOUCH_EVENT_UP)) type != TOUCH_EVENT_UP)) {
touchscreen_report_pos(tsdata->input, &tsdata->prop, touchscreen_report_pos(tsdata->input, &tsdata->prop,
x, y, true); x, y, true);
active_ids |= BIT(id);
} else {
known_ids &= ~BIT(id);
} }
}
/* One issue with the device is the TOUCH_UP message is not always
* returned. Instead track which ids we know about and report when they
* are no longer updated
*/
released_ids = known_ids & ~active_ids;
for_each_set_bit_from(b, &released_ids, tsdata->max_support_points) {
input_mt_slot(tsdata->input, b);
input_mt_report_slot_inactive(tsdata->input);
}
tsdata->known_ids = active_ids;
input_mt_report_pointer_emulation(tsdata->input, true); input_mt_report_pointer_emulation(tsdata->input, true);
input_sync(tsdata->input); input_sync(tsdata->input);
@@ -346,6 +403,22 @@ out:
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static void edt_ft5x06_ts_irq_poll_timer(struct timer_list *t)
{
struct edt_ft5x06_ts_data *tsdata = timer_container_of(tsdata, t, timer);
schedule_work(&tsdata->work_i2c_poll);
mod_timer(&tsdata->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS));
}
static void edt_ft5x06_ts_work_i2c_poll(struct work_struct *work)
{
struct edt_ft5x06_ts_data *tsdata = container_of(work,
struct edt_ft5x06_ts_data, work_i2c_poll);
edt_ft5x06_ts_isr(0, tsdata);
}
struct edt_ft5x06_attribute { struct edt_ft5x06_attribute {
struct device_attribute dattr; struct device_attribute dattr;
size_t field_offset; size_t field_offset;
@@ -858,6 +931,9 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
char *model_name = tsdata->name; char *model_name = tsdata->name;
char *fw_version = tsdata->fw_version; char *fw_version = tsdata->fw_version;
snprintf(model_name, EDT_NAME_PREFIX_LEN + 1, "%s ", dev_name(&client->dev));
model_name += strlen(model_name);
/* see what we find if we assume it is a M06 * /* see what we find if we assume it is a M06 *
* if we get less than EDT_NAME_LEN, we don't want * if we get less than EDT_NAME_LEN, we don't want
* to have garbage in there * to have garbage in there
@@ -1046,20 +1122,23 @@ static void edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
static void edt_ft5x06_ts_set_tdata_parameters(struct edt_ft5x06_ts_data *tsdata) static void edt_ft5x06_ts_set_tdata_parameters(struct edt_ft5x06_ts_data *tsdata)
{ {
int crclen; int crclen;
int points;
if (tsdata->version == EDT_M06) { if (tsdata->version == EDT_M06) {
tsdata->tdata_cmd = 0xf9; tsdata->tdata_cmd = 0xf9;
tsdata->tdata_offset = 5; tsdata->tdata_offset = 5;
tsdata->point_len = 4; tsdata->point_len = 4;
crclen = 1; crclen = 1;
points = tsdata->max_support_points;
} else { } else {
tsdata->tdata_cmd = 0x0; tsdata->tdata_cmd = 0x0;
tsdata->tdata_offset = 3; tsdata->tdata_offset = 3;
tsdata->point_len = 6; tsdata->point_len = 6;
crclen = 0; crclen = 0;
points = 0;
} }
tsdata->tdata_len = tsdata->point_len * tsdata->max_support_points + tsdata->tdata_len = tsdata->point_len * points +
tsdata->tdata_offset + crclen; tsdata->tdata_offset + crclen;
} }
@@ -1254,7 +1333,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
if (tsdata->reset_gpio) { if (tsdata->reset_gpio) {
usleep_range(5000, 6000); usleep_range(5000, 6000);
gpiod_set_value_cansleep(tsdata->reset_gpio, 0); gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
msleep(300); msleep(RESET_DELAY_MS);
} }
input = devm_input_allocate_device(&client->dev); input = devm_input_allocate_device(&client->dev);
@@ -1328,18 +1407,29 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
return error; return error;
} }
if (client->irq) {
irq_flags = irq_get_trigger_type(client->irq); irq_flags = irq_get_trigger_type(client->irq);
if (irq_flags == IRQF_TRIGGER_NONE) if (irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING; irq_flags = IRQF_TRIGGER_FALLING;
irq_flags |= IRQF_ONESHOT; irq_flags |= IRQF_ONESHOT;
error = devm_request_threaded_irq(&client->dev, client->irq, error = devm_request_threaded_irq(&client->dev, client->irq,
NULL, edt_ft5x06_ts_isr, irq_flags, NULL, edt_ft5x06_ts_isr,
client->name, tsdata); irq_flags, client->name,
tsdata);
if (error) { if (error) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return error; return error;
} }
} else {
tsdata->init_td_status = -1; /* filter bogus initial data */
INIT_WORK(&tsdata->work_i2c_poll,
edt_ft5x06_ts_work_i2c_poll);
timer_setup(&tsdata->timer, edt_ft5x06_ts_irq_poll_timer, 0);
tsdata->timer.expires =
jiffies + msecs_to_jiffies(FIRST_POLL_DELAY_MS);
add_timer(&tsdata->timer);
}
error = input_register_device(input); error = input_register_device(input);
if (error) if (error)
@@ -1360,6 +1450,10 @@ static void edt_ft5x06_ts_remove(struct i2c_client *client)
{ {
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
if (!client->irq) {
timer_delete(&tsdata->timer);
cancel_work_sync(&tsdata->work_i2c_poll);
}
edt_ft5x06_ts_teardown_debugfs(tsdata); edt_ft5x06_ts_teardown_debugfs(tsdata);
} }