[PATCH] input: Elan HID-I2C device driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Signed-off-by:Tom Lin(Lin Yen Yu) <tom_lin@xxxxxxxxxx>
---
 drivers/input/mouse/Kconfig        |    8 +
 drivers/input/mouse/Makefile       |    1 +
 drivers/input/mouse/elantech_i2c.c |  512 ++++++++++++++++++++++++++++++++++++
 3 files changed, 521 insertions(+)
 create mode 100644 drivers/input/mouse/elantech_i2c.c

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 9b8db82..345cf8c 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -339,4 +339,12 @@ config MOUSE_SYNAPTICS_USB
 	  To compile this driver as a module, choose M here: the
 	  module will be called synaptics_usb.
 
+config MOUSE_ELANTECH_I2C
+	tristate "Elan I2C Touchpad support"
+	depends on I2C
+	help
+	 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.
 endif
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 4718eff..6f092a3 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_MOUSE_SERIAL)		+= sermouse.o
 obj-$(CONFIG_MOUSE_SYNAPTICS_I2C)	+= synaptics_i2c.o
 obj-$(CONFIG_MOUSE_SYNAPTICS_USB)	+= synaptics_usb.o
 obj-$(CONFIG_MOUSE_VSXXXAA)		+= vsxxxaa.o
+obj-$(CONFIG_MOUSE_ELANTECH_I2C)	+= elantech_i2c.o
 
 psmouse-objs := psmouse-base.o synaptics.o
 
diff --git a/drivers/input/mouse/elantech_i2c.c b/drivers/input/mouse/elantech_i2c.c
new file mode 100644
index 0000000..a82259e
--- /dev/null
+++ b/drivers/input/mouse/elantech_i2c.c
@@ -0,0 +1,512 @@
+/*
+ * Elantech I2C Touchpad diver
+ *
+ * Copyright (c) 2011 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/input.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input/mt.h>
+
+
+#define DRIVER_NAME		"elantech_i2c"
+#define ETP_I2C_COMMAND_TRIES	3
+#define ETP_I2C_COMMAND_DELAY	500
+#define ETP_MAX_FINGERS		5
+
+enum {
+	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,
+	ETP_READ_REPORT_CMD
+} etd_i2c_command;
+
+static int Public_ETP_YMAX_V2;
+static int Public_ETP_XMAX_V2;
+unsigned char hid_descriptor[2] = {0x01, 0x00};
+unsigned char hid_wake_up[4] = {0x05, 0x00, 0x00, 0x08};
+unsigned char hid_reset_cmd[4] = {0x05, 0x00, 0x00, 0x01};
+unsigned char hid_report_descriptor[2] = {0x02, 0x00};
+unsigned char trace_num_xy[2] = {0x05, 0x01};
+unsigned char x_axis_max[2] = {0x06, 0x01};
+unsigned char y_axis_max[2] = {0x07, 0x01};
+unsigned char dpi_value[2] = {0x08, 0x01};
+unsigned char enable_abs[4] = {0x00, 0x03, 0x01, 0x00};
+
+static struct workqueue_struct *elantech_i2c_wq;
+/* The main device structure */
+struct elantech_i2c {
+	struct i2c_client	*client;
+	struct input_dev	*input;
+	struct work_struct	work;
+	struct mutex		input_lock;
+	unsigned int		width_x;
+	unsigned int		width_y;
+	unsigned int		gpio;
+	int			irq;
+};
+
+static int elantech_i2c_command(struct i2c_client *client, int  command,
+				unsigned char *buf_recv)
+{
+	unsigned char *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 index = 0;
+	int msg_num = 0;
+	static unsigned char report_length;
+
+	switch (command) {
+	case ETP_HID_DESCR_CMD:
+		cmd = hid_descriptor;
+		length = ARRAY_SIZE(hid_descriptor);
+		msg[1].len = 30;
+		msg_num = 2;
+		break;
+	case ETP_HID_WAKE_UP_CMD:
+		cmd = hid_wake_up;
+		length = ARRAY_SIZE(hid_wake_up);
+		msg_num = 1;
+		break;
+	case ETP_HID_RESET_CMD:
+		cmd = hid_reset_cmd;
+		length = ARRAY_SIZE(hid_reset_cmd);
+		msg_num = 1;
+		break;
+	case ETP_HID_REPORT_DESCR_CMD:
+		cmd = hid_report_descriptor;
+		length = ARRAY_SIZE(hid_report_descriptor);
+		msg[1].len = report_length;
+		msg_num = 2;
+		break;
+	case ETP_TRACE_NUM_CMD:
+		cmd = trace_num_xy;
+		length = ARRAY_SIZE(trace_num_xy);
+		msg[1].len = 2;
+		msg_num = 2;
+		break;
+	case ETP_X_AXIS_MAX_CMD:
+		cmd = x_axis_max;
+		length = ARRAY_SIZE(x_axis_max);
+		msg[1].len = 2;
+		msg_num = 2;
+		break;
+	case ETP_Y_AXIS_MAX_CMD:
+		cmd = y_axis_max;
+		length = ARRAY_SIZE(y_axis_max);
+		msg[1].len = 2;
+		msg_num = 2;
+		break;
+	case ETP_DPI_VALUE_CMD:
+		cmd = dpi_value;
+		length = ARRAY_SIZE(dpi_value);
+		msg[1].len = 2;
+		msg_num = 2;
+		break;
+	case ETP_ENABLE_ABS_CMD:
+		cmd = enable_abs;
+		length = ARRAY_SIZE(enable_abs);
+		msg_num = 1;
+		break;
+	case ETP_READ_REPORT_CMD:
+		msg_num = 1;
+		break;
+	default:
+		printk(KERN_DEBUG "%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 {
+		if (command != ETP_READ_REPORT_CMD)
+			ret = i2c_transfer(client->adapter, msg, msg_num);
+		else
+			ret = i2c_transfer(client->adapter, &msg[1], msg_num);
+
+		if (ret != msg_num) {
+			if (command != ETP_READ_REPORT_CMD) {
+				printk(KERN_DEBUG "%s ", __func__);
+				for (index = 0; index < msg[1].len ; index++)
+					printk("[%d] = 0x%02x ",
+						index, rec_buf[index]);
+				printk("\n");
+			} else {
+				printk(KERN_DEBUG "ETP_READ_REPORT_CMD fail	!\n");
+			}
+		}
+		if (ret > 0)
+			break;
+
+		tries--;
+		msleep(ETP_I2C_COMMAND_DELAY);
+	} while (tries > 0);
+
+	if (command == ETP_HID_DESCR_CMD)
+		report_length = rec_buf[4];
+
+	return ret;
+}
+
+
+static int elantech_i2c_hw_init(struct i2c_client *client)
+{
+	int rc;
+	uint8_t buf_recv[79] = {0};
+
+	rc = elantech_i2c_command(client, ETP_HID_DESCR_CMD, buf_recv);
+	if (rc != 2)
+		return -EINVAL;
+	rc = elantech_i2c_command(client, ETP_HID_WAKE_UP_CMD, buf_recv);
+	if (rc != 1)
+		return -EINVAL;
+	rc = elantech_i2c_command(client, ETP_HID_RESET_CMD, buf_recv);
+	if (rc != 1)
+		return -EINVAL;
+	rc = i2c_master_recv(client, buf_recv, 2);
+	if (rc != 2)
+		return -EINVAL;
+	rc = elantech_i2c_command(client, ETP_HID_REPORT_DESCR_CMD, buf_recv);
+	if (rc != 2)
+		return -EINVAL;
+
+	return 0;
+}
+
+static irqreturn_t elantech_i2c_irq(int irq, void *dev_id)
+{
+
+	struct elantech_i2c *elantech_i2c = dev_id;
+	queue_work(elantech_i2c_wq, &elantech_i2c->work);
+
+	return IRQ_HANDLED;
+
+}
+
+static void elantech_i2c_report_absolute(struct elantech_i2c *touch,
+				 uint8_t *buf)
+{
+	unsigned char *packet = buf;
+	struct  input_dev *input = touch->input;
+	static int pre_finger_status[5] = {0};
+	int finger_status[5] = {0};
+	int finger_number = 0;
+	int index;
+	short dynamic_date_index;
+	int position_x, position_y;
+	int mky_value, mkx_value, h_value;
+
+	dynamic_date_index = 0;
+	for (index = 0 ; index < ETP_MAX_FINGERS ; index++) {
+		finger_status[index] =
+				(packet[3] & (0x08 << index)) >> (3 + index);
+		finger_number += finger_status[index];
+		if (finger_status[index]) {
+			position_x =
+			((packet[(dynamic_date_index * 5) + 4] & 0xf0) << 4) |
+					packet[(dynamic_date_index * 5) + 5];
+			position_y =
+			Public_ETP_YMAX_V2 -
+			(((packet[(dynamic_date_index * 5) + 4] & 0x0f) << 8) |
+					packet[(dynamic_date_index * 5) + 6]);
+			mkx_value =
+				(packet[(dynamic_date_index * 5) + 7] & 0x0f) * touch->width_x;
+			mky_value =
+				((packet[(dynamic_date_index * 5) + 7] & 0xf0) >> 4) * touch->width_y;
+			h_value = packet[(dynamic_date_index * 5) + 8];
+			dynamic_date_index++;
+
+			input_mt_slot(input, index);
+			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);
+			input_mt_sync(input);
+		} else if (finger_status[index] == 0) {
+			input_mt_slot(input, index);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
+		}
+		pre_finger_status[index] = finger_status[index];
+	}
+
+
+	input_report_key(input, BTN_TOOL_FINGER, finger_number == 1);
+	input_report_key(input, BTN_TOOL_DOUBLETAP, finger_number == 2);
+	input_report_key(input, BTN_TOOL_TRIPLETAP, finger_number == 3);
+	input_report_key(input, BTN_TOOL_QUADTAP, finger_number == 4);
+	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 void elantech_i2c_work_func(struct work_struct *work)
+{
+	unsigned char buf_recv[30] = {0};
+	struct elantech_i2c *elantech_i2c_priv =
+		container_of(work, struct elantech_i2c, work);
+	static unsigned char pre_recv[28] = {0};
+	int rc;
+	int i;
+	int diff_flag;
+	int length;
+
+	mutex_lock(&elantech_i2c_priv->input_lock);
+	rc = i2c_master_recv(elantech_i2c_priv->client, buf_recv, 30);
+	if (rc != 30) {
+		mutex_unlock(&elantech_i2c_priv->input_lock);
+		return ;
+	} else {
+
+		diff_flag = 0;
+		length = (buf_recv[1] << 8) | buf_recv[0];
+		for (i = 0; i < length ; i++) {
+			if (pre_recv[i] != buf_recv[i] && diff_flag == 0)
+				diff_flag = 1;
+			pre_recv[i] = buf_recv[i];
+		}
+
+		if (buf_recv[2] == 0x5d && buf_recv[length - 1] == 0x00 &&
+		   diff_flag == 1)
+			elantech_i2c_report_absolute(elantech_i2c_priv, buf_recv);
+		else if (buf_recv[2] != 0x5d || buf_recv[length - 1] != 0x00) {
+			printk(KERN_DEBUG "%s ", __func__);
+			for (i = 0 ; i < length  ; i++)
+				printk(" 0x%02x ", buf_recv[i]);
+		}
+	}
+	mutex_unlock(&elantech_i2c_priv->input_lock);
+
+}
+
+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;
+	int Pressure_value_data;
+	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);
+	Public_ETP_XMAX_V2 = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+	elantech_i2c_command(client, ETP_Y_AXIS_MAX_CMD, buf_recv);
+	Public_ETP_YMAX_V2 = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+	printk(KERN_INFO "%s Public_ETP_XMAX_V2 = %d\n"
+		, __func__, Public_ETP_XMAX_V2);
+	printk(KERN_INFO "%s Public_ETP_YMAX_V2 = %d\n"
+		, __func__, Public_ETP_YMAX_V2);
+	elantech_i2c_command(client, ETP_TRACE_NUM_CMD, buf_recv);
+	elantech_i2c_priv->width_x = Public_ETP_XMAX_V2 / (buf_recv[0] - 1);
+	elantech_i2c_priv->width_y = Public_ETP_YMAX_V2 / (buf_recv[1] - 1);
+	Pressure_value_data = 0xff;
+
+	/* X Y Value*/
+	input_set_abs_params(dev, ABS_X, 0, Public_ETP_XMAX_V2, 0, 0);
+	input_set_abs_params(dev, ABS_Y, 0, Public_ETP_YMAX_V2, 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, Pressure_value_data, 0, 0);
+
+	/* Finger Pressure value */
+	input_mt_init_slots(dev, ETP_MAX_FINGERS);
+	input_set_abs_params(dev, ABS_MT_POSITION_X, 0, Public_ETP_XMAX_V2, 0, 0);
+	input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, Public_ETP_YMAX_V2, 0, 0);
+	input_set_abs_params(dev, ABS_MT_PRESSURE, 0, Pressure_value_data, 0, 0);
+	input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, Pressure_value_data, 0, 0);
+	input_set_abs_params(dev, ABS_MT_TOUCH_MINOR, 0, Pressure_value_data, 0, 0);
+
+	/* Enable ABS mode*/
+	elantech_i2c_command(client, ETP_ENABLE_ABS_CMD, buf_recv);
+	gpio = irq_to_gpio(client->irq);
+	rc = gpio_request(gpio, label);
+	if (rc < 0) {
+		printk(KERN_DEBUG "gpio_request failed for input %d rc = %d\n"
+			, gpio, rc);
+		goto	err_gpio_request;
+	}
+	rc = gpio_direction_input(gpio);
+	if (rc < 0) {
+		printk(KERN_DEBUG "gpio_direction_input failed for input %d\n"
+		, gpio);
+		goto	err_gpio_request;
+	}
+	elantech_i2c_priv->gpio = gpio;
+	rc = request_irq(client->irq, elantech_i2c_irq, IRQF_TRIGGER_FALLING,
+			label, elantech_i2c_priv);
+	 if (rc < 0) {
+		printk(KERN_DEBUG "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;
+
+	ret = elantech_i2c_hw_init(client);
+	if (ret < 0) {
+		ret = -EINVAL;
+		goto err_hw_init;
+	}
+	elantech_i2c_priv = elantech_i2c_priv_create(client);
+	if (!elantech_i2c_priv) {
+		ret = -ENOMEM;
+		goto err_elantech_i2c_priv;
+	}
+
+	elantech_i2c_wq = create_singlethread_workqueue("elantech_i2c_wq");
+	if (!elantech_i2c_wq) {
+		ret = -ENOMEM;
+		goto err_workqueue;
+	}
+	INIT_WORK(&elantech_i2c_priv->work, elantech_i2c_work_func);
+
+	ret = input_register_device(elantech_i2c_priv->input);
+	if (ret < 0)
+		goto err_input_register_device;
+
+	i2c_set_clientdata(client, elantech_i2c_priv);
+	mutex_init(&elantech_i2c_priv->input_lock);
+	return 0;
+err_input_register_device:
+	destroy_workqueue(elantech_i2c_wq);
+err_workqueue:
+	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_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) {
+		gpio_free(elantech_i2c_priv->gpio);
+		free_irq(elantech_i2c_priv->irq, elantech_i2c_priv);
+		input_free_device(elantech_i2c_priv->input);
+		kfree(elantech_i2c_priv);
+	}
+
+	if (elantech_i2c_wq)
+		destroy_workqueue(elantech_i2c_wq);
+
+	return 0;
+}
+
+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,
+	},
+	.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_DESCRIPTION("Elan I2C Touch Pad driver");
+MODULE_AUTHOR("Tom Lin (Lin Yen Yu) <tom_lin@xxxxxxxxxx>");
+MODULE_LICENSE("GPL");
-- 
1.7.9.2

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


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux