[PATCH v2 1/2] Input:Elan I2C touchpad driver for HID over I2C

From: Tom Lin
Date: Sun May 06 2012 - 23:07:09 EST


This patch adds driver for Elan I2C touchpad. These protocol of HID over I2C
was defined by Microsoft. The kernel driver would use the multi-toucpad
protrocol format B.

Cc: Daniel Kurtz <djkurtz@xxxxxxxxxxxx>
Signed-off-by: Tom Lin(Lin Yen Yu) <tom_lin@xxxxxxxxxx>
---
drivers/input/mouse/Kconfig | 13 +-
drivers/input/mouse/Makefile | 1 +
drivers/input/mouse/elantech_i2c.c | 488 ++++++++++++++++++++++++++++++++++++
3 files changed, 501 insertions(+), 1 deletion(-)
create mode 100644 drivers/input/mouse/elantech_i2c.c

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 9b8db82..be931be 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -304,6 +304,18 @@ config MOUSE_MAPLE
To compile this driver as a module choose M here: the module will be
called maplemouse.

+config MOUSE_ELANTECH_I2C
+ tristate "Elan I2C Touchpad support"
+ depends on I2C
+ help
+ This driver supports Elan I2C touchpad controller and follows the HID over
+ I2C data format.
+
+ Say Y here if you want to use a Elan HID-I2C touchpad.
+
+ To compile this driver as a module, choose M here: the
+ module will be called elantech_i2c.
+
config MOUSE_SYNAPTICS_I2C
tristate "Synaptics I2C Touchpad support"
depends on I2C
@@ -338,5 +350,4 @@ config MOUSE_SYNAPTICS_USB

To compile this driver as a module, choose M here: the
module will be called synaptics_usb.
-
endif
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 4718eff..ba7cc88 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MOUSE_AMIGA) += amimouse.o
obj-$(CONFIG_MOUSE_APPLETOUCH) += appletouch.o
obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o
obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o
+obj-$(CONFIG_MOUSE_ELANTECH_I2C) += elantech_i2c.o
obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o
obj-$(CONFIG_MOUSE_INPORT) += inport.o
obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o
diff --git a/drivers/input/mouse/elantech_i2c.c b/drivers/input/mouse/elantech_i2c.c
new file mode 100644
index 0000000..5cf2eb8
--- /dev/null
+++ b/drivers/input/mouse/elantech_i2c.c
@@ -0,0 +1,488 @@
+/*
+ * Elantech I2C Touchpad diver
+ *
+ * Copyright (c) 2011-2012 Tom Lin (Lin Yen Yu) <tom_lin@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define DRIVER_NAME "elantech_i2c"
+#define ETP_FINGER_DATA_OFFSET 4
+#define ETP_FINGER_DATA_LEN 5
+#define ETP_I2C_COMMAND_TRIES 3
+#define ETP_I2C_COMMAND_DELAY 500
+/* Length of Elan Touchpad information */
+#define ETP_INF_LENGTH 2
+#define ETP_MAX_FINGERS 5
+#define ETP_PACKET_LENGTH 30
+#define ETP_REPORT_ID 0X5d
+#define ETP_REPORT_MAX_LENGTH 79
+#define ETP_PRESSURE_MAX 0xff
+#define HID_DES_LENGTH_OFFSET 1
+
+static bool elan_i2c_debug;
+module_param_named(debug, elan_i2c_debug, bool, 0600);
+MODULE_PARM_DESC(debug, "Turn Elan i2c TP debugging mode on and off");
+
+enum etd_i2c_command {
+ ETP_HID_DESCR_LENGTH_CMD,
+ ETP_HID_DESCR_CMD,
+ ETP_HID_WAKE_UP_CMD,
+ ETP_HID_RESET_CMD,
+ ETP_HID_REPORT_DESCR_CMD,
+ ETP_TRACE_NUM_CMD,
+ ETP_X_AXIS_MAX_CMD,
+ ETP_Y_AXIS_MAX_CMD,
+ ETP_DPI_VALUE_CMD,
+ ETP_ENABLE_ABS_CMD
+};
+
+static const u8 etp_hid_descriptor_cmd[] = {0x01, 0x00};
+static const u8 etp_hid_wake_up_cmd[] = {0x05, 0x00, 0x00, 0x08};
+static const u8 etp_hid_reset_cmd[] = {0x05, 0x00, 0x00, 0x01};
+static const u8 etp_hid_report_descriptor_cmd[] = {0x02, 0x00};
+static const u8 etp_trace_num_xy_cmd[] = {0x05, 0x01};
+static const u8 etp_x_axis_max_cmd[] = {0x06, 0x01};
+static const u8 etp_y_axis_max_cmd[] = {0x07, 0x01};
+static const u8 etp_dpi_value_cmd[] = {0x08, 0x01};
+static const u8 etp_enable_abs_cmd[] = {0x00, 0x03, 0x01, 0x00};
+
+/* The main device structure */
+struct elantech_i2c {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct work_struct work;
+ unsigned int gpio;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int width_x;
+ unsigned int width_y;
+ int irq;
+ u8 pre_recv[28];
+};
+
+static int elantech_i2c_command(struct i2c_client *client, int command,
+ unsigned char *buf_recv, int data_len)
+{
+ const u8 *cmd = NULL;
+ unsigned char *rec_buf = buf_recv;
+ int ret;
+ int tries = ETP_I2C_COMMAND_TRIES;
+ int length = 0;
+ struct i2c_msg msg[2];
+ int msg_num = 0;
+
+ switch (command) {
+ case ETP_HID_DESCR_LENGTH_CMD:
+ case ETP_HID_DESCR_CMD:
+ cmd = etp_hid_descriptor_cmd;
+ length = sizeof(etp_hid_descriptor_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_HID_WAKE_UP_CMD:
+ cmd = etp_hid_wake_up_cmd;
+ length = sizeof(etp_hid_wake_up_cmd);
+ msg_num = 1;
+ break;
+ case ETP_HID_RESET_CMD:
+ cmd = etp_hid_reset_cmd;
+ length = sizeof(etp_hid_reset_cmd);
+ msg_num = 1;
+ break;
+ case ETP_HID_REPORT_DESCR_CMD:
+ cmd = etp_hid_report_descriptor_cmd;
+ length = sizeof(etp_hid_report_descriptor_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_TRACE_NUM_CMD:
+ cmd = etp_trace_num_xy_cmd;
+ length = sizeof(etp_trace_num_xy_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_X_AXIS_MAX_CMD:
+ cmd = etp_x_axis_max_cmd;
+ length = sizeof(etp_x_axis_max_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_Y_AXIS_MAX_CMD:
+ cmd = etp_y_axis_max_cmd;
+ length = sizeof(etp_y_axis_max_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_DPI_VALUE_CMD:
+ cmd = etp_dpi_value_cmd;
+ length = sizeof(etp_dpi_value_cmd);
+ msg[1].len = data_len;
+ msg_num = 2;
+ break;
+ case ETP_ENABLE_ABS_CMD:
+ cmd = etp_enable_abs_cmd;
+ length = sizeof(etp_enable_abs_cmd);
+ msg_num = 1;
+ break;
+ default:
+ dev_dbg(&client->dev, "%s command=%d unknow.\n",
+ __func__, command);
+ return -1;
+ }
+
+ msg[0].addr = client->addr;
+ msg[0].flags = client->flags & I2C_M_TEN;
+ msg[0].len = length;
+ msg[0].buf = (char *) cmd;
+ msg[1].addr = client->addr;
+ msg[1].flags = client->flags & I2C_M_TEN;
+ msg[1].flags |= I2C_M_RD;
+ msg[1].buf = rec_buf;
+
+ do {
+ ret = i2c_transfer(client->adapter, msg, msg_num);
+ if (ret != msg_num && elan_i2c_debug)
+ print_hex_dump_bytes("Elan I2C Touch data :",
+ DUMP_PREFIX_NONE, rec_buf, msg[1].len);
+ if (ret > 0)
+ break;
+ tries--;
+ dev_dbg(&client->dev, "retrying elantech_i2c_command:%d (%d)\n",
+ command, tries);
+ } while (tries > 0);
+
+ return ret;
+}
+
+static bool elantech_i2c_hw_init(struct i2c_client *client)
+{
+ int rc;
+ uint8_t buf_recv[ETP_REPORT_MAX_LENGTH];
+ int hid_descr_len;
+ int hid_report_len;
+
+ /* Fetch the length of HID description */
+ rc = elantech_i2c_command(client, ETP_HID_DESCR_LENGTH_CMD, buf_recv,
+ HID_DES_LENGTH_OFFSET);
+ if (rc != 2)
+ return false;
+
+ hid_descr_len = buf_recv[0];
+ /* Fetch the lenght of HID report description */
+ rc = elantech_i2c_command(client, ETP_HID_DESCR_CMD, buf_recv, hid_descr_len);
+ if (rc != 2)
+ return false;
+
+ hid_report_len = buf_recv[4];
+ rc = elantech_i2c_command(client, ETP_HID_WAKE_UP_CMD, buf_recv, 0);
+ if (rc != 1)
+ return false;
+
+ rc = elantech_i2c_command(client, ETP_HID_RESET_CMD, buf_recv, 0);
+ if (rc != 1)
+ return false;
+
+ msleep(ETP_I2C_COMMAND_DELAY);
+ rc = i2c_master_recv(client, buf_recv, 2);
+ if (rc != 2 || (buf_recv[0] != 0 && buf_recv[1] != 0))
+ return false;
+
+ rc = elantech_i2c_command(client, ETP_HID_REPORT_DESCR_CMD, buf_recv,
+ hid_report_len);
+ if (rc != 2)
+ return false;
+
+ return true;
+}
+
+static void elantech_i2c_report_absolute(struct elantech_i2c *touch,
+ uint8_t *packet)
+{
+ uint8_t *finger_data = &packet[ETP_FINGER_DATA_OFFSET];
+ struct input_dev *input = touch->input;
+ bool finger_status;
+ int i;
+ int position_x, position_y;
+ int mky_value, mkx_value, h_value;
+
+ for (i = 0 ; i < ETP_MAX_FINGERS ; i++) {
+ finger_status = (packet[3] >> (3+i)) & 0x01;
+ if (finger_status) {
+ position_x = ((finger_data[0] & 0xf0) << 4) | finger_data[1];
+ position_y = touch->max_y -
+ (((finger_data[0] & 0x0f) << 8) | finger_data[2]);
+ mkx_value = (finger_data[3] & 0x0f) * touch->width_x;
+ mky_value = (finger_data[3] >> 4) * touch->width_y;
+ h_value = finger_data[4];
+
+ finger_data += ETP_FINGER_DATA_LEN;
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+ input_report_abs(input, ABS_MT_POSITION_X, position_x);
+ input_report_abs(input, ABS_MT_POSITION_Y, position_y);
+ input_report_abs(input, ABS_MT_PRESSURE, h_value);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, mkx_value);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR, mky_value);
+ } else {
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
+ }
+ }
+ input_report_key(input, BTN_LEFT, ((packet[3] & 0x01) == 1));
+ input_report_key(input, BTN_RIGHT, ((packet[3] & 0x02) == 2));
+ input_report_key(input, BTN_MIDDLE, ((packet[3] & 0x04) == 4));
+ input_mt_report_pointer_emulation(input, true);
+ input_sync(input);
+}
+
+static irqreturn_t elantech_i2c_isr(int irq, void *dev_id)
+{
+ struct elantech_i2c *elantech_i2c_priv = dev_id;
+ unsigned char buf_recv[ETP_PACKET_LENGTH];
+ int rc;
+
+ rc = i2c_master_recv(elantech_i2c_priv->client, buf_recv, ETP_PACKET_LENGTH);
+ if (rc != ETP_PACKET_LENGTH)
+ goto elantech_isr_end;
+
+ memcpy(elantech_i2c_priv->pre_recv, buf_recv, ETP_PACKET_LENGTH);
+ /* Packet check */
+ if (buf_recv[2] == ETP_REPORT_ID && buf_recv[ETP_PACKET_LENGTH - 1] == 0x00)
+ elantech_i2c_report_absolute(elantech_i2c_priv, buf_recv);
+
+
+elantech_isr_end:
+ if (elan_i2c_debug && rc > 0)
+ print_hex_dump_bytes("Elan I2C Touch data :",
+ DUMP_PREFIX_NONE, buf_recv, rc);
+
+ return IRQ_HANDLED;
+}
+
+static struct elantech_i2c *elantech_i2c_priv_create(struct i2c_client *client)
+{
+ struct elantech_i2c *elantech_i2c_priv;
+ struct input_dev *dev;
+ unsigned int gpio;
+ const char *label = "elantech_i2c";
+ int rc = 0;
+ unsigned char buf_recv[2];
+
+ elantech_i2c_priv = kzalloc(sizeof(struct elantech_i2c), GFP_KERNEL);
+ if (!elantech_i2c_priv)
+ return NULL;
+
+ elantech_i2c_priv->client = client;
+ elantech_i2c_priv->irq = client->irq;
+ elantech_i2c_priv->input = input_allocate_device();
+ if (elantech_i2c_priv->input == NULL)
+ goto err_input_allocate_device;
+
+ elantech_i2c_priv->input->name = "ELANTECH_I2C";
+ elantech_i2c_priv->input->phys = client->adapter->name;
+ elantech_i2c_priv->input->id.bustype = BUS_I2C;
+
+ dev = elantech_i2c_priv->input;
+ __set_bit(EV_KEY, dev->evbit);
+ __set_bit(EV_ABS, dev->evbit);
+
+ __set_bit(BTN_LEFT, dev->keybit);
+ __set_bit(BTN_RIGHT, dev->keybit);
+ __set_bit(BTN_MIDDLE, dev->keybit);
+
+ __set_bit(BTN_TOUCH, dev->keybit);
+ __set_bit(BTN_TOOL_FINGER, dev->keybit);
+ __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
+ __set_bit(BTN_TOOL_TRIPLETAP, dev->keybit);
+ __set_bit(BTN_TOOL_QUADTAP, dev->keybit);
+
+ elantech_i2c_command(client, ETP_X_AXIS_MAX_CMD, buf_recv, ETP_INF_LENGTH);
+ elantech_i2c_priv->max_x = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+ elantech_i2c_command(client, ETP_Y_AXIS_MAX_CMD, buf_recv, ETP_INF_LENGTH);
+ elantech_i2c_priv->max_y = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+ dev_info(&client->dev, "%s max_x = %d\n", __func__, elantech_i2c_priv->max_x);
+ dev_info(&client->dev, "%s max_y = %d\n", __func__, elantech_i2c_priv->max_y);
+ elantech_i2c_command(client, ETP_TRACE_NUM_CMD, buf_recv, ETP_INF_LENGTH);
+ elantech_i2c_priv->width_x = elantech_i2c_priv->max_x / (buf_recv[0] - 1);
+ elantech_i2c_priv->width_y = elantech_i2c_priv->max_y / (buf_recv[1] - 1);
+
+ /* X Y Value*/
+ input_set_abs_params(dev, ABS_X, 0, elantech_i2c_priv->max_x, 0, 0);
+ input_set_abs_params(dev, ABS_Y, 0, elantech_i2c_priv->max_y, 0, 0);
+ /* Palme Value */
+ input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0);
+
+ /* Finger Pressure value */
+ input_set_abs_params(dev, ABS_TOOL_WIDTH, 0, ETP_PRESSURE_MAX, 0, 0);
+
+ /* Finger Pressure value */
+ input_mt_init_slots(dev, ETP_MAX_FINGERS);
+ input_set_abs_params(dev, ABS_MT_POSITION_X, 0, elantech_i2c_priv->max_x, 0, 0);
+ input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, elantech_i2c_priv->max_y, 0, 0);
+ input_set_abs_params(dev, ABS_MT_PRESSURE, 0, ETP_PRESSURE_MAX, 0, 0);
+ input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, ETP_PRESSURE_MAX, 0, 0);
+ input_set_abs_params(dev, ABS_MT_TOUCH_MINOR, 0, ETP_PRESSURE_MAX, 0, 0);
+
+ /* Enable ABS mode*/
+ elantech_i2c_command(client, ETP_ENABLE_ABS_CMD, buf_recv, ETP_INF_LENGTH);
+ gpio = irq_to_gpio(client->irq);
+ rc = gpio_request(gpio, label);
+ if (rc < 0) {
+ dev_dbg(&client->dev, "gpio_request failed for input %d rc = %d\n", gpio, rc);
+ goto err_gpio_request;
+ }
+ rc = gpio_direction_input(gpio);
+ if (rc < 0) {
+ dev_dbg(&client->dev, "gpio_direction_input failed for input %d\n", gpio);
+ goto err_gpio_request;
+ }
+ elantech_i2c_priv->gpio = gpio;
+ rc = request_threaded_irq(client->irq, NULL, elantech_i2c_isr,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ client->name, elantech_i2c_priv);
+ if (rc < 0) {
+ dev_dbg(&client->dev, "Could not register for %s interrupt, irq = %d, rc = %d\n",
+ label, client->irq, rc);
+ goto err_gpio_request_irq_fail;
+ }
+
+ return elantech_i2c_priv;
+
+err_gpio_request_irq_fail:
+ gpio_free(gpio);
+err_gpio_request:
+ input_free_device(elantech_i2c_priv->input);
+err_input_allocate_device:
+ kfree(elantech_i2c_priv);
+ return NULL;
+}
+
+static int elantech_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *dev_id)
+
+{
+ int ret = 0;
+ struct elantech_i2c *elantech_i2c_priv;
+ unsigned int gpio;
+
+ if (!elantech_i2c_hw_init(client)) {
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+ elantech_i2c_priv = elantech_i2c_priv_create(client);
+ if (!elantech_i2c_priv) {
+ ret = -EINVAL;
+ goto err_hw_init;
+ }
+
+ ret = input_register_device(elantech_i2c_priv->input);
+ if (ret < 0)
+ goto err_input_register_device;
+
+ i2c_set_clientdata(client, elantech_i2c_priv);
+ device_init_wakeup(&client->dev, 1);
+
+ return 0;
+
+err_input_register_device:
+ gpio = elantech_i2c_priv->gpio;
+ gpio_free(gpio);
+ free_irq(elantech_i2c_priv->irq, elantech_i2c_priv);
+ input_free_device(elantech_i2c_priv->input);
+ kfree(elantech_i2c_priv);
+err_hw_init:
+ return ret;
+}
+
+static int elantech_i2c_remove(struct i2c_client *client)
+{
+ struct elantech_i2c *elantech_i2c_priv = i2c_get_clientdata(client);
+
+ if (elantech_i2c_priv) {
+ if (elantech_i2c_priv->gpio > 0)
+ gpio_free(elantech_i2c_priv->gpio);
+ free_irq(elantech_i2c_priv->irq, elantech_i2c_priv);
+ input_unregister_device(elantech_i2c_priv->input);
+ kfree(elantech_i2c_priv);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elantech_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (device_may_wakeup(&client->dev))
+ enable_irq_wake(client->irq);
+
+ return 0;
+}
+
+static int elantech_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (device_may_wakeup(&client->dev))
+ enable_irq_wake(client->irq);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_i2c_touchpad_pm_ops,
+ elantech_i2c_suspend, elantech_i2c_resume);
+
+static const struct i2c_device_id elantech_i2c_id_table[] = {
+ { "elantech_i2c", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, elantech_i2c_id_table);
+
+static struct i2c_driver elantech_i2c_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &elan_i2c_touchpad_pm_ops,
+ },
+ .probe = elantech_i2c_probe,
+ .remove = __devexit_p(elantech_i2c_remove),
+ .id_table = elantech_i2c_id_table,
+};
+
+static int __init elantech_i2c_init(void)
+{
+ return i2c_add_driver(&elantech_i2c_driver);
+}
+
+static void __exit elantech_i2c_exit(void)
+{
+ i2c_del_driver(&elantech_i2c_driver);
+}
+
+module_init(elantech_i2c_init);
+module_exit(elantech_i2c_exit);
+
+MODULE_AUTHOR("Tom Lin (Lin Yen Yu) <tom_lin@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Elan I2C Touch Pad driver");
+MODULE_LICENSE("GPL");
--
1.7.9.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/