summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/touchscreen/exc3000.c148
1 files changed, 147 insertions, 1 deletions
diff --git a/drivers/input/touchscreen/exc3000.c b/drivers/input/touchscreen/exc3000.c
index 203f50acaab7..a6597f026980 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 @@ out:
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;
}