Blob Blame History Raw
From: Takashi Iwai <tiwai@suse.de>
Subject: [PATCH 2/2] input: Add LED support to Synaptics device
Patch-mainline: Submitted
References: bnc#547370,bnc#582529,bnc#589014

The new Synaptics devices have an LED on the top-left corner.
This patch adds a new LED class device to control it.  It's created
dynamically upon synaptics device probing.

The LED is controlled via the command 0x0a with parameters 0x88 or 0x10.
This seems only on/off control although other value might be accepted.

The detection of the LED isn't clear yet.  It should have been the new
capability bits that indicate the presence, but on real machines, it
doesn't fit.  So, for the time being, the driver checks the product id
in the ext capability bits and assumes that LED exists on the known
devices.

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

---
 drivers/input/mouse/Kconfig     |    8 ++
 drivers/input/mouse/synaptics.c |  124 ++++++++++++++++++++++++++++++++++++++++
 drivers/input/mouse/synaptics.h |    3 
 3 files changed, 135 insertions(+)

--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -68,6 +68,14 @@ config MOUSE_PS2_SYNAPTICS
 
 	  If unsure, say Y.
 
+config MOUSE_PS2_SYNAPTICS_LED
+	bool "Support embedded LED on Synaptics devices"
+	depends on MOUSE_PS2_SYNAPTICS
+	depends on LEDS_CLASS=y || LEDS_CLASS=MOUSE_PS2
+	help
+	  Say Y here if you have a Synaptics device with an embedded LED.
+	  This will enable LED class driver to control the LED device.
+
 config MOUSE_PS2_LIFEBOOK
 	bool "Fujitsu Lifebook PS/2 mouse protocol extension" if EXPERT
 	default y
--- 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/leds.h>
 #include <linux/slab.h>
 #include "psmouse.h"
 #include "synaptics.h"
@@ -431,6 +432,123 @@ static void synaptics_pt_create(struct p
 	serio_register_port(serio);
 }
 
+#ifdef CONFIG_MOUSE_PS2_SYNAPTICS_LED
+/*
+ * LED handling:
+ * Some Synaptics devices have an embeded LED at the top-left corner.
+ */
+
+struct synaptics_led {
+	struct psmouse *psmouse;
+	struct work_struct work;
+	struct led_classdev cdev;
+};
+
+static void synaptics_set_led(struct psmouse *psmouse, int on)
+{
+	int i;
+	unsigned char cmd = on ? 0x88 : 0x10;
+
+	ps2_begin_command(&psmouse->ps2dev);
+	if (__ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11))
+		goto out;
+	for (i = 6; i >= 0; i -= 2) {
+		unsigned char d = (cmd >> i) & 3;
+		if (__ps2_command(&psmouse->ps2dev, &d, PSMOUSE_CMD_SETRES))
+			goto out;
+	}
+	cmd = 0x0a;
+	__ps2_command(&psmouse->ps2dev, &cmd, PSMOUSE_CMD_SETRATE);
+ out:
+	ps2_end_command(&psmouse->ps2dev);
+}
+
+static void synaptics_led_work(struct work_struct *work)
+{
+	struct synaptics_led *led;
+
+	led = container_of(work, struct synaptics_led, work);
+	synaptics_set_led(led->psmouse, led->cdev.brightness);
+}
+
+static void synaptics_led_cdev_brightness_set(struct led_classdev *cdev,
+					      enum led_brightness value)
+{
+	struct synaptics_led *led;
+
+	led = container_of(cdev, struct synaptics_led, cdev);
+	schedule_work(&led->work);
+}
+
+static void synaptics_sync_led(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	if (priv->led)
+		synaptics_set_led(psmouse, priv->led->cdev.brightness);
+}
+
+static bool synaptics_has_led(struct synaptics_data *priv)
+{
+	if (!priv->ext_cap_0c)
+		return false;
+	/* 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 false;
+	if (!(priv->ext_cap_0c & 0x2000) &&
+	    (priv->capabilities & 0xd00ff) != 0xd0073)
+		return false;
+	return true;
+}
+
+static int synaptics_init_led(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	struct synaptics_led *led;
+	int err;
+
+	if (!synaptics_has_led(priv))
+		return 0;
+	printk(KERN_INFO "synaptics: support LED control\n");
+	led = kzalloc(sizeof(struct synaptics_led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+	led->psmouse = psmouse;
+	INIT_WORK(&led->work, synaptics_led_work);
+	led->cdev.name = "psmouse::synaptics";
+	led->cdev.brightness_set = synaptics_led_cdev_brightness_set;
+	led->cdev.flags = LED_CORE_SUSPENDRESUME;
+	err = led_classdev_register(NULL, &led->cdev);
+	if (err < 0) {
+		kfree(led);
+		return err;
+	}
+	priv->led = led;
+	return 0;
+}
+
+static void synaptics_free_led(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	if (!priv->led)
+		return;
+	cancel_work_sync(&priv->led->work);
+	synaptics_set_led(psmouse, 0);
+	led_classdev_unregister(&priv->led->cdev);
+	kfree(priv->led);
+}
+#else
+#define synaptics_init_led(ps)	0
+#define synaptics_free_led(ps)	do {} while (0)
+#define synaptics_sync_led(ps)	do {} while (0)
+#endif
+
 /*****************************************************************************
  *	Functions to interpret the absolute mode packets
  ****************************************************************************/
@@ -1276,6 +1394,7 @@ static void synaptics_disconnect(struct
 		device_remove_file(&psmouse->ps2dev.serio->dev,
 				   &psmouse_attr_disable_gesture.dattr);
 
+	synaptics_free_led(psmouse);
 	synaptics_reset(psmouse);
 	kfree(priv);
 	psmouse->private = NULL;
@@ -1332,6 +1451,8 @@ static int synaptics_reconnect(struct ps
 		return -1;
 	}
 
+	synaptics_sync_led(psmouse);
+
 	return 0;
 }
 
@@ -1441,6 +1562,9 @@ static int __synaptics_init(struct psmou
 		     priv->model_id,
 		     priv->capabilities, priv->ext_cap, priv->ext_cap_0c);
 
+	if (synaptics_init_led(psmouse) < 0)
+		goto init_fail;
+
 	set_input_params(psmouse->dev, priv);
 
 	/*
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -145,6 +145,8 @@ struct synaptics_hw_state {
 	struct synaptics_mt_state mt_state;
 };
 
+struct synaptics_led;
+
 struct synaptics_data {
 	/* Data read from the touchpad */
 	unsigned long int model_id;		/* Model-ID */
@@ -174,6 +176,7 @@ struct synaptics_data {
 	 */
 	struct synaptics_hw_state agm;
 	bool agm_pending;			/* new AGM packet received */
+	struct synaptics_led *led;
 };
 
 void synaptics_module_init(void);