diff --git a/Documentation/ABI/testing/sysfs-driver-input-exc3000 b/Documentation/ABI/testing/sysfs-driver-input-exc3000 new file mode 100644 index 00000000000000..3d316d54f81c22 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-input-exc3000 @@ -0,0 +1,15 @@ +What: /sys/bus/i2c/devices/xxx/fw_version +Date: Aug 2020 +Contact: linux-input@vger.kernel.org +Description: Reports the firmware version provided by the touchscreen, for example "00_T6" on a EXC80H60 + + Access: Read + Valid values: Represented as string + +What: /sys/bus/i2c/devices/xxx/model +Date: Aug 2020 +Contact: linux-input@vger.kernel.org +Description: Reports the model identification provided by the touchscreen, for example "Orion_1320" on a EXC80H60 + + Access: Read + Valid values: Represented as string diff --git a/drivers/input/touchscreen/exc3000.c b/drivers/input/touchscreen/exc3000.c index 203f50acaab7ba..a6597f026980b9 100644 --- a/drivers/input/touchscreen/exc3000.c +++ b/drivers/input/touchscreen/exc3000.c @@ -27,6 +27,9 @@ #define EXC3000_LEN_FRAME 66 #define EXC3000_LEN_POINT 10 +#define EXC3000_LEN_MODEL_NAME 16 +#define EXC3000_LEN_FW_VERSION 16 + #define EXC3000_MT1_EVENT 0x06 #define EXC3000_MT2_EVENT 0x18 @@ -71,6 +74,11 @@ struct exc3000_data { struct gpio_desc *reset; struct timer_list timer; u8 buf[2 * EXC3000_LEN_FRAME]; + struct completion wait_event; + struct mutex query_lock; + int query_result; + char model[EXC3000_LEN_MODEL_NAME]; + char fw_version[EXC3000_LEN_FW_VERSION]; }; static void exc3000_report_slots(struct input_dev *input, @@ -156,6 +164,28 @@ static int exc3000_read_data(struct exc3000_data *data, return 0; } +static int exc3000_query_interrupt(struct exc3000_data *data) +{ + u8 *buf = data->buf; + int error; + + error = i2c_master_recv(data->client, buf, EXC3000_LEN_FRAME); + if (error < 0) + return error; + + if (buf[0] != 'B') + return -EPROTO; + + if (buf[4] == 'E') + strlcpy(data->model, buf + 5, sizeof(data->model)); + else if (buf[4] == 'D') + strlcpy(data->fw_version, buf + 5, sizeof(data->fw_version)); + else + return -EPROTO; + + return 0; +} + static irqreturn_t exc3000_interrupt(int irq, void *dev_id) { struct exc3000_data *data = dev_id; @@ -164,6 +194,12 @@ static irqreturn_t exc3000_interrupt(int irq, void *dev_id) int slots, total_slots; int error; + if (mutex_is_locked(&data->query_lock)) { + data->query_result = exc3000_query_interrupt(data); + complete(&data->wait_event); + goto out; + } + error = exc3000_read_data(data, buf, &total_slots); if (error) { /* Schedule a timer to release "stuck" contacts */ @@ -191,11 +227,91 @@ static irqreturn_t exc3000_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static ssize_t fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct exc3000_data *data = i2c_get_clientdata(client); + static const u8 request[68] = { + 0x67, 0x00, 0x42, 0x00, 0x03, 0x01, 'D', 0x00 + }; + int error; + + mutex_lock(&data->query_lock); + + data->query_result = -ETIMEDOUT; + reinit_completion(&data->wait_event); + + error = i2c_master_send(client, request, sizeof(request)); + if (error < 0) { + mutex_unlock(&data->query_lock); + return error; + } + + wait_for_completion_interruptible_timeout(&data->wait_event, 1 * HZ); + mutex_unlock(&data->query_lock); + + if (data->query_result < 0) + return data->query_result; + + return sprintf(buf, "%s\n", data->fw_version); +} +static DEVICE_ATTR_RO(fw_version); + +static ssize_t exc3000_get_model(struct exc3000_data *data) +{ + static const u8 request[68] = { + 0x67, 0x00, 0x42, 0x00, 0x03, 0x01, 'E', 0x00 + }; + struct i2c_client *client = data->client; + int error; + + mutex_lock(&data->query_lock); + data->query_result = -ETIMEDOUT; + reinit_completion(&data->wait_event); + + error = i2c_master_send(client, request, sizeof(request)); + if (error < 0) { + mutex_unlock(&data->query_lock); + return error; + } + + wait_for_completion_interruptible_timeout(&data->wait_event, 1 * HZ); + mutex_unlock(&data->query_lock); + + return data->query_result; +} + +static ssize_t model_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct exc3000_data *data = i2c_get_clientdata(client); + int error; + + error = exc3000_get_model(data); + if (error < 0) + return error; + + return sprintf(buf, "%s\n", data->model); +} +static DEVICE_ATTR_RO(model); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_fw_version.attr, + &dev_attr_model.attr, + NULL +}; + +static struct attribute_group exc3000_attribute_group = { + .attrs = sysfs_attrs +}; + static int exc3000_probe(struct i2c_client *client) { struct exc3000_data *data; struct input_dev *input; - int error, max_xy; + int error, max_xy, retry; data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -209,6 +325,8 @@ static int exc3000_probe(struct i2c_client *client) data->info = &exc3000_info[eeti_dev_id]; } timer_setup(&data->timer, exc3000_timer, 0); + init_completion(&data->wait_event); + mutex_init(&data->query_lock); data->reset = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); @@ -226,6 +344,7 @@ static int exc3000_probe(struct i2c_client *client) return -ENOMEM; data->input = input; + input_set_drvdata(input, data); input->name = data->info->name; input->id.bustype = BUS_I2C; @@ -251,6 +370,33 @@ static int exc3000_probe(struct i2c_client *client) if (error) return error; + /* + * I²C does not have built-in recovery, so retry on failure. This + * ensures, that the device probe will not fail for temporary issues + * on the bus. This is not needed for the sysfs calls (userspace + * will receive the error code and can start another query) and + * cannot be done for touch events (but that only means loosing one + * or two touch events anyways). + */ + for (retry = 0; retry < 3; retry++) { + error = exc3000_get_model(data); + if (!error) + break; + dev_warn(&client->dev, "Retry %d get EETI EXC3000 model: %d\n", + retry + 1, error); + } + + if (error) + return error; + + dev_dbg(&client->dev, "TS Model: %s", data->model); + + i2c_set_clientdata(client, data); + + error = devm_device_add_group(&client->dev, &exc3000_attribute_group); + if (error) + return error; + return 0; }