From: Takashi Iwai <tiwai@suse.de>
Subject: Input: Add support of clickpad mode to synaptics mouse driver (v2)
Patch-mainline: Never
References: bnc#547370,bnc#582529,bnc#589014

Add the experimental support of Synatpics Clickpad on new HP laptops.

In this version, button events are no longer handled in the kernel side
but by x11 synaptics driver.  The LED is controlled via evdev events.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 drivers/input/mouse/synaptics.c |   72 ++++++++++++++++++++++++++++++++++++++++
 drivers/input/mouse/synaptics.h |    5 ++
 2 files changed, 77 insertions(+)

--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -29,6 +29,7 @@
 #include <linux/input/mt.h>
 #include <linux/serio.h>
 #include <linux/libps2.h>
+#include <linux/workqueue.h>
 #include <linux/slab.h>
 #include "psmouse.h"
 #include "synaptics.h"
@@ -400,6 +401,59 @@ static void synaptics_pt_create(struct p
  *	Functions to interpret the absolute mode packets
  ****************************************************************************/
 
+static void synaptics_set_led(struct psmouse *psmouse, int on)
+{
+	unsigned char param[1];
+
+	if (psmouse_sliced_command(psmouse, on ? 0x88 : 0x10))
+		return;
+	param[0] = 0x0a;
+	ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_SETRATE);
+}
+
+static void synaptics_led_work(struct work_struct *work)
+{
+	struct synaptics_data *priv = container_of(work, struct synaptics_data, led_work);
+	synaptics_set_led(priv->psmouse, priv->led_status);
+}
+
+/* input event handler: changed by x11 synaptics driver */
+static int synaptics_led_event(struct input_dev *dev, unsigned int type,
+			       unsigned int code, int value)
+{
+	struct synaptics_data *priv = dev->dev.platform_data;
+
+	if (!priv)
+		return 0;
+	if (type == EV_LED && code == LED_MUTE) {
+		priv->led_status = !!value;
+		schedule_work(&priv->led_work);
+	}
+	return 0;
+}
+
+static void synaptics_check_led(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	if (!priv->ext_cap_0c)
+		return;
+	/* FIXME: LED is supposedly detectable in cap0c[1] 0x20, but it seems
+	 * not working on real machines.
+	 * So we check the product id to be sure.
+	 */
+	if (SYN_CAP_PRODUCT_ID(priv->ext_cap) != 0xe4 &&
+	    SYN_CAP_PRODUCT_ID(priv->ext_cap) != 0x64 &&
+	    SYN_CAP_PRODUCT_ID(priv->ext_cap) != 0x84)
+		return;
+	if (!(priv->ext_cap_0c & 0x2000) &&
+	    (priv->capabilities & 0xd00ff) != 0xd0073)
+		return;
+	priv->has_led = 1;
+	printk(KERN_INFO "Synaptics: support LED control\n");
+	INIT_WORK(&priv->led_work, synaptics_led_work);
+}
+
 static int synaptics_parse_hw_state(const unsigned char buf[],
 				    struct synaptics_data *priv,
 				    struct synaptics_hw_state *hw)
@@ -743,10 +797,22 @@ static void set_input_params(struct inpu
 		__clear_bit(BTN_RIGHT, dev->keybit);
 		__clear_bit(BTN_MIDDLE, dev->keybit);
 	}
+	if (priv->has_led) {
+		__set_bit(EV_LED, dev->evbit);
+		__set_bit(LED_MUTE, dev->ledbit);
+		dev->event = synaptics_led_event;
+		dev->dev.platform_data = priv;
+	}
 }
 
 static void synaptics_disconnect(struct psmouse *psmouse)
 {
+	struct synaptics_data *priv = psmouse->private;
+
+	if (priv->has_led) {
+		cancel_work_sync(&priv->led_work);
+		synaptics_set_led(psmouse, 0);
+	}
 	synaptics_reset(psmouse);
 	kfree(psmouse->private);
 	psmouse->private = NULL;
@@ -809,6 +875,9 @@ static int synaptics_reconnect(struct ps
 		return -1;
 	}
 
+	if (priv->has_led)
+		synaptics_set_led(psmouse, priv->led_status);
+
 	return 0;
 }
 
@@ -894,6 +963,7 @@ int synaptics_init(struct psmouse *psmou
 	if (!priv)
 		return -ENOMEM;
 
+	priv->psmouse = psmouse;
 	psmouse_reset(psmouse);
 
 	if (synaptics_query_hardware(psmouse)) {
@@ -918,6 +988,8 @@ int synaptics_init(struct psmouse *psmou
 		SYN_ID_MAJOR(priv->identity), SYN_ID_MINOR(priv->identity),
 		priv->model_id, priv->capabilities, priv->ext_cap, priv->ext_cap_0c);
 
+	synaptics_check_led(psmouse);
+
 	set_input_params(psmouse->dev, priv);
 
 	/*
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -139,6 +139,11 @@ struct synaptics_data {
 	struct serio *pt_port;			/* Pass-through serio port */
 
 	struct synaptics_hw_state mt;		/* current gesture packet */
+
+	unsigned char has_led;
+	unsigned char led_status;
+	struct psmouse *psmouse;
+	struct work_struct led_work;
 };
 
 void synaptics_module_init(void);
