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

Bug#703607: Please include Cypress PS/2 Trackpad driver in linux-image-3.2.0-*



Control: severity -1 important
Control: tag -1 patch moreinfo

On Thu, 2013-03-21 at 12:21 +0200, Apollon Oikonomopoulos wrote:
> Package: src:linux
> Version: 3.2.39-2
> Severity: wishlist
> 
> Dear Maintainer,
> 
> Please include the driver for the Cypress PS/2 Trackpad found in newer Dell
> laptops (Dell XPS 12 and XPS 13) in wheezy's 3.2 kernel. Without this driver
> the trackpad works as a plain PS/2 mouse with no additional features
> (tap-to-click, scroll, multitouch support etc).
[...]
> The trackpad driver from 3.9rc1 is not trivial to backport since the kernel's
> multitouch input API has changed significantly.

I would still prefer to use a backport of the upstream version, so we
can be sure that the functionality is equivalent and there shouldn't be
a regression when users upgrade to the later upstream version.

Could you test the attached series of patches?

Ben.

-- 
Ben Hutchings
Who are all these weirdos? - David Bowie, about L-Space IRC channel #afp
From: Henrik Rydberg <rydberg@euromail.se>
Date: Sat, 11 Aug 2012 22:07:55 +0200
Subject: Input: MT - Add flags to input_mt_init_slots()

commit b4adbbefc2099476a4f1020041c99f52cf3cd67d upstream.

Preparing to move more repeated code into the mt core, add a flags
argument to the input_mt_slots_init() function.

Reviewed-and-tested-by: Benjamin Tissoires <benjamin.tissoires@enac.fr>
Tested-by: Ping Cheng <pingc@wacom.com>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
[bwh: Backported to wheezy:
 - Introduce struct input_mt and use container_of() to find it from
   input_dev::mt
 - Rename input_mt_init_slot() to input_mt_init_slot_flags() and add a
   backward-compatible wrapper]
---
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -14,6 +14,9 @@
 
 #define TRKID_SGN	((TRKID_MAX + 1) >> 1)
 
+#define input_dev_to_mt(dev)						\
+	((dev)->mt ? container_of((dev)->mt, struct input_mt, slots[0]) : NULL)
+
 /**
  * input_mt_init_slots() - initialize MT input slots
  * @dev: input device supporting MT events and finger tracking
@@ -25,8 +28,10 @@
  * May be called repeatedly. Returns -EINVAL if attempting to
  * reinitialize with a different number of slots.
  */
-int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots)
+int input_mt_init_slots_flags(struct input_dev *dev, unsigned int num_slots,
+			unsigned int flags)
 {
+	struct input_mt *mt;
 	int i;
 
 	if (!num_slots)
@@ -34,11 +39,12 @@ int input_mt_init_slots(struct input_dev
 	if (dev->mt)
 		return dev->mtsize != num_slots ? -EINVAL : 0;
 
-	dev->mt = kcalloc(num_slots, sizeof(struct input_mt_slot), GFP_KERNEL);
-	if (!dev->mt)
+	mt = kzalloc(sizeof(*mt) + num_slots * sizeof(*mt->slots), GFP_KERNEL);
+	if (!mt)
 		return -ENOMEM;
 
 	dev->mtsize = num_slots;
+	mt->flags = flags;
 	input_set_abs_params(dev, ABS_MT_SLOT, 0, num_slots - 1, 0, 0);
 	input_set_abs_params(dev, ABS_MT_TRACKING_ID, 0, TRKID_MAX, 0, 0);
 	input_set_events_per_packet(dev, 6 * num_slots);
@@ -47,8 +53,15 @@ int input_mt_init_slots(struct input_dev
 	for (i = 0; i < num_slots; i++)
 		input_mt_set_value(&dev->mt[i], ABS_MT_TRACKING_ID, -1);
 
+	dev->mt = &mt->slots[0];
 	return 0;
 }
+EXPORT_SYMBOL(input_mt_init_slots_flags);
+
+int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots)
+{
+	return input_mt_init_slots_flags(dev, num_slots, 0);
+}
 EXPORT_SYMBOL(input_mt_init_slots);
 
 /**
@@ -60,7 +73,7 @@ EXPORT_SYMBOL(input_mt_init_slots);
  */
 void input_mt_destroy_slots(struct input_dev *dev)
 {
-	kfree(dev->mt);
+	kfree(input_dev_to_mt(dev));
 	dev->mt = NULL;
 	dev->mtsize = 0;
 	dev->slot = 0;
--- a/include/linux/input/mt.h
+++ b/include/linux/input/mt.h
@@ -23,6 +23,16 @@ struct input_mt_slot {
 	int abs[ABS_MT_LAST - ABS_MT_FIRST + 1];
 };
 
+/**
+ * struct input_mt - state of tracked contacts
+ * @flags: input_mt operation flags
+ * @slots: array of slots holding current values of tracked contacts
+ */
+struct input_mt {
+	unsigned int flags;
+	struct input_mt_slot slots[];
+};
+
 static inline void input_mt_set_value(struct input_mt_slot *slot,
 				      unsigned code, int value)
 {
@@ -36,6 +46,8 @@ static inline int input_mt_get_value(con
 }
 
 int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots);
+int input_mt_init_slots_flags(struct input_dev *dev, unsigned int num_slots,
+			unsigned int flags);
 void input_mt_destroy_slots(struct input_dev *dev);
 
 static inline int input_mt_new_trkid(struct input_dev *dev)
From: Henrik Rydberg <rydberg@euromail.se>
Date: Wed, 22 Aug 2012 20:43:22 +0200
Subject: Input: MT - Handle frame synchronization in core

commit 55e49089f4589908eb688742d2d7eff33b74ac78 upstream.

Most MT drivers perform the same actions on frame synchronization.
Some actions, like dropping unseen contacts, are also unnecessarily
complex. Collect common frame synchronization tasks in a new function,
input_mt_sync_frame(). Depending on the flags set, it drops unseen
contacts and performs pointer emulation.

With init flags and frame synchronization in place, most MT drivers
can be simplified. First out are the bcm5974 and hid-multitouch
drivers, following this patch.

Reviewed-and-tested-by: Benjamin Tissoires <benjamin.tissoires@enac.fr>
Tested-by: Ping Cheng <pingc@wacom.com>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
[bwh: Backported to wheezy:
 - Add a separate array to track last frame each slot was used]
---
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -17,6 +17,14 @@
 #define input_dev_to_mt(dev)						\
 	((dev)->mt ? container_of((dev)->mt, struct input_mt, slots[0]) : NULL)
 
+static void copy_abs(struct input_dev *dev, unsigned int dst, unsigned int src)
+{
+	if (dev->absinfo && test_bit(src, dev->absbit)) {
+		dev->absinfo[dst] = dev->absinfo[src];
+		dev->absbit[BIT_WORD(dst)] |= BIT_MASK(dst);
+	}
+}
+
 /**
  * input_mt_init_slots() - initialize MT input slots
  * @dev: input device supporting MT events and finger tracking
@@ -39,16 +47,41 @@ int input_mt_init_slots_flags(struct inp
 	if (dev->mt)
 		return dev->mtsize != num_slots ? -EINVAL : 0;
 
-	mt = kzalloc(sizeof(*mt) + num_slots * sizeof(*mt->slots), GFP_KERNEL);
+	mt = kzalloc(sizeof(*mt) + num_slots * sizeof(*mt->slots) +
+		     num_slots * sizeof(*mt->slot_frame), GFP_KERNEL);
 	if (!mt)
 		return -ENOMEM;
 
 	dev->mtsize = num_slots;
+	mt->num_slots = num_slots;
 	mt->flags = flags;
+	mt->slot_frame = (unsigned int *)&mt->slots[num_slots];
 	input_set_abs_params(dev, ABS_MT_SLOT, 0, num_slots - 1, 0, 0);
 	input_set_abs_params(dev, ABS_MT_TRACKING_ID, 0, TRKID_MAX, 0, 0);
 	input_set_events_per_packet(dev, 6 * num_slots);
 
+	if (flags & (INPUT_MT_POINTER | INPUT_MT_DIRECT)) {
+		__set_bit(EV_KEY, dev->evbit);
+		__set_bit(BTN_TOUCH, dev->keybit);
+
+		copy_abs(dev, ABS_X, ABS_MT_POSITION_X);
+		copy_abs(dev, ABS_Y, ABS_MT_POSITION_Y);
+		copy_abs(dev, ABS_PRESSURE, ABS_MT_PRESSURE);
+	}
+	if (flags & INPUT_MT_POINTER) {
+		__set_bit(BTN_TOOL_FINGER, dev->keybit);
+		__set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
+		if (num_slots >= 3)
+			__set_bit(BTN_TOOL_TRIPLETAP, dev->keybit);
+		if (num_slots >= 4)
+			__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
+		if (num_slots >= 5)
+			__set_bit(BTN_TOOL_QUINTTAP, dev->keybit);
+		__set_bit(INPUT_PROP_POINTER, dev->propbit);
+	}
+	if (flags & INPUT_MT_DIRECT)
+		__set_bit(INPUT_PROP_DIRECT, dev->propbit);
+
 	/* Mark slots as 'unused' */
 	for (i = 0; i < num_slots; i++)
 		input_mt_set_value(&dev->mt[i], ABS_MT_TRACKING_ID, -1);
@@ -96,17 +129,23 @@ EXPORT_SYMBOL(input_mt_destroy_slots);
 void input_mt_report_slot_state(struct input_dev *dev,
 				unsigned int tool_type, bool active)
 {
-	struct input_mt_slot *mt;
+	struct input_mt *mt = input_dev_to_mt(dev);
+	struct input_mt_slot *slot;
 	int id;
 
-	if (!dev->mt || !active) {
+	if (!mt)
+		return;
+
+	slot = &mt->slots[dev->slot];
+	mt->slot_frame[dev->slot] = mt->frame;
+
+	if (!active) {
 		input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
 		return;
 	}
 
-	mt = &dev->mt[dev->slot];
-	id = input_mt_get_value(mt, ABS_MT_TRACKING_ID);
-	if (id < 0 || input_mt_get_value(mt, ABS_MT_TOOL_TYPE) != tool_type)
+	id = input_mt_get_value(slot, ABS_MT_TRACKING_ID);
+	if (id < 0 || input_mt_get_value(slot, ABS_MT_TOOL_TYPE) != tool_type)
 		id = input_mt_new_trkid(dev);
 
 	input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, id);
@@ -183,3 +222,34 @@ void input_mt_report_pointer_emulation(s
 	}
 }
 EXPORT_SYMBOL(input_mt_report_pointer_emulation);
+
+/**
+ * input_mt_sync_frame() - synchronize mt frame
+ * @dev: input device with allocated MT slots
+ *
+ * Close the frame and prepare the internal state for a new one.
+ * Depending on the flags, marks unused slots as inactive and performs
+ * pointer emulation.
+ */
+void input_mt_sync_frame(struct input_dev *dev)
+{
+	struct input_mt *mt = input_dev_to_mt(dev);
+	unsigned int i;
+
+	if (!mt)
+		return;
+
+	if (mt->flags & INPUT_MT_DROP_UNUSED) {
+		for (i = 0; i < mt->num_slots; i++) {
+			if (mt->slot_frame[i] == mt->frame)
+				continue;
+			input_mt_slot(dev, i);
+			input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+		}
+	}
+
+	input_mt_report_pointer_emulation(dev, (mt->flags & INPUT_MT_POINTER));
+
+	mt->frame++;
+}
+EXPORT_SYMBOL(input_mt_sync_frame);
--- a/include/linux/input/mt.h
+++ b/include/linux/input/mt.h
@@ -15,6 +15,9 @@
 
 #define TRKID_MAX	0xffff
 
+#define INPUT_MT_POINTER	0x0001	/* pointer device, e.g. trackpad */
+#define INPUT_MT_DIRECT		0x0002	/* direct device, e.g. touchscreen */
+#define INPUT_MT_DROP_UNUSED	0x0004	/* drop contacts not seen in frame */
 /**
  * struct input_mt_slot - represents the state of an input MT slot
  * @abs: holds current values of ABS_MT axes for this slot
@@ -25,11 +28,17 @@ struct input_mt_slot {
 
 /**
  * struct input_mt - state of tracked contacts
+ * @num_slots: number of MT slots the device uses
  * @flags: input_mt operation flags
+ * @frame: increases every time input_mt_sync_frame() is called
+ * @slot_frame: last frame at which input_mt_report_slot_state() was called
  * @slots: array of slots holding current values of tracked contacts
  */
 struct input_mt {
+	int num_slots;
 	unsigned int flags;
+	unsigned int frame;
+	unsigned int *slot_frame;
 	struct input_mt_slot slots[];
 };
 
@@ -72,4 +81,6 @@ void input_mt_report_slot_state(struct i
 void input_mt_report_finger_count(struct input_dev *dev, int count);
 void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count);
 
+void input_mt_sync_frame(struct input_dev *dev);
+
 #endif
From: Henrik Rydberg <rydberg@euromail.se>
Date: Sun, 12 Aug 2012 20:47:05 +0200
Subject: Input: MT - Add in-kernel tracking

commit 7c1a87897c75139dec258eb03e1a24fb73385b73 upstream.

With the INPUT_MT_TRACK flag set, the function input_mt_assign_slots()
can be used to match a new set of contacts against the currently used
slots. The algorithm used is based on Lagrange relaxation, and performs
very well in practice; slower than mtdev for a few corner cases, but
faster in most commonly occuring cases.

Tested-by: Benjamin Tissoires <benjamin.tissoires@enac.fr>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
---
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -50,7 +50,7 @@ int input_mt_init_slots_flags(struct inp
 	mt = kzalloc(sizeof(*mt) + num_slots * sizeof(*mt->slots) +
 		     num_slots * sizeof(*mt->slot_frame), GFP_KERNEL);
 	if (!mt)
-		return -ENOMEM;
+		goto err_mem;
 
 	dev->mtsize = num_slots;
 	mt->num_slots = num_slots;
@@ -81,6 +81,12 @@ int input_mt_init_slots_flags(struct inp
 	}
 	if (flags & INPUT_MT_DIRECT)
 		__set_bit(INPUT_PROP_DIRECT, dev->propbit);
+	if (flags & INPUT_MT_TRACK) {
+		unsigned int n2 = num_slots * num_slots;
+		mt->red = kcalloc(n2, sizeof(*mt->red), GFP_KERNEL);
+		if (!mt->red)
+			goto err_mem;
+	}
 
 	/* Mark slots as 'unused' */
 	for (i = 0; i < num_slots; i++)
@@ -88,6 +94,9 @@ int input_mt_init_slots_flags(struct inp
 
 	dev->mt = &mt->slots[0];
 	return 0;
+err_mem:
+	kfree(mt);
+	return -ENOMEM;
 }
 EXPORT_SYMBOL(input_mt_init_slots_flags);
 
@@ -106,7 +115,11 @@ EXPORT_SYMBOL(input_mt_init_slots);
  */
 void input_mt_destroy_slots(struct input_dev *dev)
 {
-	kfree(input_dev_to_mt(dev));
+	if (dev->mt) {
+		struct input_mt *mt = input_dev_to_mt(dev);
+		kfree(mt->red);
+		kfree(mt);
+	}
 	dev->mt = NULL;
 	dev->mtsize = 0;
 	dev->slot = 0;
@@ -253,3 +266,131 @@ void input_mt_sync_frame(struct input_de
 	mt->frame++;
 }
 EXPORT_SYMBOL(input_mt_sync_frame);
+
+static int adjust_dual(int *begin, int step, int *end, int eq)
+{
+	int f, *p, s, c;
+
+	if (begin == end)
+		return 0;
+
+	f = *begin;
+	p = begin + step;
+	s = p == end ? f + 1 : *p;
+
+	for (; p != end; p += step)
+		if (*p < f)
+			s = f, f = *p;
+		else if (*p < s)
+			s = *p;
+
+	c = (f + s + 1) / 2;
+	if (c == 0 || (c > 0 && !eq))
+		return 0;
+	if (s < 0)
+		c *= 2;
+
+	for (p = begin; p != end; p += step)
+		*p -= c;
+
+	return (c < s && s <= 0) || (f >= 0 && f < c);
+}
+
+static void find_reduced_matrix(int *w, int nr, int nc, int nrc)
+{
+	int i, k, sum;
+
+	for (k = 0; k < nrc; k++) {
+		for (i = 0; i < nr; i++)
+			adjust_dual(w + i, nr, w + i + nrc, nr <= nc);
+		sum = 0;
+		for (i = 0; i < nrc; i += nr)
+			sum += adjust_dual(w + i, 1, w + i + nr, nc <= nr);
+		if (!sum)
+			break;
+	}
+}
+
+static int input_mt_set_matrix(struct input_mt *mt,
+			       const struct input_mt_pos *pos, int num_pos)
+{
+	const struct input_mt_pos *p;
+	struct input_mt_slot *s;
+	int *w = mt->red;
+	int x, y;
+
+	for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
+		if (!input_mt_is_active(s))
+			continue;
+		x = input_mt_get_value(s, ABS_MT_POSITION_X);
+		y = input_mt_get_value(s, ABS_MT_POSITION_Y);
+		for (p = pos; p != pos + num_pos; p++) {
+			int dx = x - p->x, dy = y - p->y;
+			*w++ = dx * dx + dy * dy;
+		}
+	}
+
+	return w - mt->red;
+}
+
+static void input_mt_set_slots(struct input_mt *mt,
+			       int *slots, int num_pos)
+{
+	struct input_mt_slot *s;
+	int *w = mt->red, *p;
+
+	for (p = slots; p != slots + num_pos; p++)
+		*p = -1;
+
+	for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
+		if (!input_mt_is_active(s))
+			continue;
+		for (p = slots; p != slots + num_pos; p++)
+			if (*w++ < 0)
+				*p = s - mt->slots;
+	}
+
+	for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
+		if (input_mt_is_active(s))
+			continue;
+		for (p = slots; p != slots + num_pos; p++)
+			if (*p < 0) {
+				*p = s - mt->slots;
+				break;
+			}
+	}
+}
+
+/**
+ * input_mt_assign_slots() - perform a best-match assignment
+ * @dev: input device with allocated MT slots
+ * @slots: the slot assignment to be filled
+ * @pos: the position array to match
+ * @num_pos: number of positions
+ *
+ * Performs a best match against the current contacts and returns
+ * the slot assignment list. New contacts are assigned to unused
+ * slots.
+ *
+ * Returns zero on success, or negative error in case of failure.
+ */
+int input_mt_assign_slots(struct input_dev *dev, int *slots,
+			  const struct input_mt_pos *pos, int num_pos)
+{
+	struct input_mt *mt = input_dev_to_mt(dev);
+	int nrc;
+
+	if (!mt || !mt->red)
+		return -ENXIO;
+	if (num_pos > mt->num_slots)
+		return -EINVAL;
+	if (num_pos < 1)
+		return 0;
+
+	nrc = input_mt_set_matrix(dev, mt, pos, num_pos);
+	find_reduced_matrix(mt->red, num_pos, nrc / num_pos, nrc);
+	input_mt_set_slots(mt, slots, num_pos);
+
+	return 0;
+}
+EXPORT_SYMBOL(input_mt_assign_slots);
--- a/include/linux/input/mt.h
+++ b/include/linux/input/mt.h
@@ -18,6 +18,8 @@
 #define INPUT_MT_POINTER	0x0001	/* pointer device, e.g. trackpad */
 #define INPUT_MT_DIRECT		0x0002	/* direct device, e.g. touchscreen */
 #define INPUT_MT_DROP_UNUSED	0x0004	/* drop contacts not seen in frame */
+#define INPUT_MT_TRACK		0x0008	/* use in-kernel tracking */
+
 /**
  * struct input_mt_slot - represents the state of an input MT slot
  * @abs: holds current values of ABS_MT axes for this slot
@@ -39,6 +41,7 @@ struct input_mt {
 	unsigned int flags;
 	unsigned int frame;
 	unsigned int *slot_frame;
+	int *red;
 	struct input_mt_slot slots[];
 };
 
@@ -54,6 +57,11 @@ static inline int input_mt_get_value(con
 	return slot->abs[code - ABS_MT_FIRST];
 }
 
+static inline bool input_mt_is_active(const struct input_mt_slot *slot)
+{
+	return input_mt_get_value(slot, ABS_MT_TRACKING_ID) >= 0;
+}
+
 int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots);
 int input_mt_init_slots_flags(struct input_dev *dev, unsigned int num_slots,
 			unsigned int flags);
@@ -83,4 +91,16 @@ void input_mt_report_pointer_emulation(s
 
 void input_mt_sync_frame(struct input_dev *dev);
 
+/**
+ * struct input_mt_pos - contact position
+ * @x: horizontal coordinate
+ * @y: vertical coordinate
+ */
+struct input_mt_pos {
+	s16 x, y;
+};
+
+int input_mt_assign_slots(struct input_dev *dev, int *slots,
+			  const struct input_mt_pos *pos, int num_pos);
+
 #endif
From: Dudley Du <dudl@cypress.com>
Date: Sat, 5 Jan 2013 00:14:22 -0800
Subject: [1/2] Input: add support for Cypress PS/2 Trackpads

commit 0799a924bc93ba46a23e8e7e6b1431ab585fd2ea upstream.

This driver, submitted on behalf of Cypress Semiconductor Corporation and
additional contributors, provides support for the Cypress PS/2 Trackpad.

Original code contributed by Dudley Du (Cypress Semiconductor Corporation),
modified by Kamal Mostafa and Kyle Fazzari.

BugLink: http://launchpad.net/bugs/978807

Signed-off-by: Dudley Du <dudl@cypress.com>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Kyle Fazzari <git@status.e4ward.com>
Signed-off-by: Mario Limonciello <mario_limonciello@dell.com>
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
Acked-by: Herton Krzesinski <herton.krzesinski@canonical.com>
Reviewed-by: Henrik Rydberg <rydberg@euromail.se>
Reviewed-by: Dudley Du <dudl@cypress.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/mouse/Kconfig        |   10 +
 drivers/input/mouse/Makefile       |    1 +
 drivers/input/mouse/cypress_ps2.c  |  725 ++++++++++++++++++++++++++++++++++++
 drivers/input/mouse/cypress_ps2.h  |  191 ++++++++++
 drivers/input/mouse/psmouse-base.c |   32 ++
 drivers/input/mouse/psmouse.h      |    1 +
 6 files changed, 960 insertions(+)
 create mode 100644 drivers/input/mouse/cypress_ps2.c
 create mode 100644 drivers/input/mouse/cypress_ps2.h

--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -68,6 +68,16 @@ config MOUSE_PS2_SYNAPTICS
 
 	  If unsure, say Y.
 
+config MOUSE_PS2_CYPRESS
+       bool "Cypress PS/2 mouse protocol extension" if EXPERT
+       default y
+       depends on MOUSE_PS2
+       help
+         Say Y here if you have a Cypress PS/2 Trackpad connected to
+         your system.
+
+         If unsure, say Y.
+
 config MOUSE_PS2_LIFEBOOK
 	bool "Fujitsu Lifebook PS/2 mouse protocol extension" if EXPERT
 	default y
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -31,3 +31,4 @@ psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK)	+=
 psmouse-$(CONFIG_MOUSE_PS2_SENTELIC)	+= sentelic.o
 psmouse-$(CONFIG_MOUSE_PS2_TRACKPOINT)	+= trackpoint.o
 psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT)	+= touchkit_ps2.o
+psmouse-$(CONFIG_MOUSE_PS2_CYPRESS)	+= cypress_ps2.o
--- /dev/null
+++ b/drivers/input/mouse/cypress_ps2.c
@@ -0,0 +1,725 @@
+/*
+ * Cypress Trackpad PS/2 mouse driver
+ *
+ * Copyright (c) 2012 Cypress Semiconductor Corporation.
+ *
+ * Author:
+ *   Dudley Du <dudl@cypress.com>
+ *
+ * Additional contributors include:
+ *   Kamal Mostafa <kamal@canonical.com>
+ *   Kyle Fazzari <git@status.e4ward.com>
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/serio.h>
+#include <linux/libps2.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include "cypress_ps2.h"
+
+#undef CYTP_DEBUG_VERBOSE  /* define this and DEBUG for more verbose dump */
+
+static void cypress_set_packet_size(struct psmouse *psmouse, unsigned int n)
+{
+	struct cytp_data *cytp = psmouse->private;
+	cytp->pkt_size = n;
+}
+
+static const unsigned char cytp_rate[] = {10, 20, 40, 60, 100, 200};
+static const unsigned char cytp_resolution[] = {0x00, 0x01, 0x02, 0x03};
+
+static int cypress_ps2_sendbyte(struct psmouse *psmouse, int value)
+{
+	struct ps2dev *ps2dev = &psmouse->ps2dev;
+
+	if (ps2_sendbyte(ps2dev, value & 0xff, CYTP_CMD_TIMEOUT) < 0) {
+		psmouse_dbg(psmouse,
+				"sending command 0x%02x failed, resp 0x%02x\n",
+				value & 0xff, ps2dev->nak);
+		if (ps2dev->nak == CYTP_PS2_RETRY)
+			return CYTP_PS2_RETRY;
+		else
+			return CYTP_PS2_ERROR;
+	}
+
+#ifdef CYTP_DEBUG_VERBOSE
+	psmouse_dbg(psmouse, "sending command 0x%02x succeeded, resp 0xfa\n",
+			value & 0xff);
+#endif
+
+	return 0;
+}
+
+static int cypress_ps2_ext_cmd(struct psmouse *psmouse, unsigned short cmd,
+			       unsigned char data)
+{
+	struct ps2dev *ps2dev = &psmouse->ps2dev;
+	int tries = CYTP_PS2_CMD_TRIES;
+	int rc;
+
+	ps2_begin_command(ps2dev);
+
+	do {
+		/*
+		 * Send extension command byte (0xE8 or 0xF3).
+		 * If sending the command fails, send recovery command
+		 * to make the device return to the ready state.
+		 */
+		rc = cypress_ps2_sendbyte(psmouse, cmd & 0xff);
+		if (rc == CYTP_PS2_RETRY) {
+			rc = cypress_ps2_sendbyte(psmouse, 0x00);
+			if (rc == CYTP_PS2_RETRY)
+				rc = cypress_ps2_sendbyte(psmouse, 0x0a);
+		}
+		if (rc == CYTP_PS2_ERROR)
+			continue;
+
+		rc = cypress_ps2_sendbyte(psmouse, data);
+		if (rc == CYTP_PS2_RETRY)
+			rc = cypress_ps2_sendbyte(psmouse, data);
+		if (rc == CYTP_PS2_ERROR)
+			continue;
+		else
+			break;
+	} while (--tries > 0);
+
+	ps2_end_command(ps2dev);
+
+	return rc;
+}
+
+static int cypress_ps2_read_cmd_status(struct psmouse *psmouse,
+				       unsigned char cmd,
+				       unsigned char *param)
+{
+	int rc;
+	struct ps2dev *ps2dev = &psmouse->ps2dev;
+	enum psmouse_state old_state;
+	int pktsize;
+
+	ps2_begin_command(&psmouse->ps2dev);
+
+	old_state = psmouse->state;
+	psmouse->state = PSMOUSE_CMD_MODE;
+	psmouse->pktcnt = 0;
+
+	pktsize = (cmd == CYTP_CMD_READ_TP_METRICS) ? 8 : 3;
+	memset(param, 0, pktsize);
+
+	rc = cypress_ps2_sendbyte(psmouse, 0xe9);
+	if (rc < 0)
+		goto out;
+
+	wait_event_timeout(ps2dev->wait,
+			(psmouse->pktcnt >= pktsize),
+			msecs_to_jiffies(CYTP_CMD_TIMEOUT));
+
+	memcpy(param, psmouse->packet, pktsize);
+
+	psmouse_dbg(psmouse, "Command 0x%02x response data (0x): %*ph\n",
+			cmd, pktsize, param);
+
+out:
+	psmouse->state = old_state;
+	psmouse->pktcnt = 0;
+
+	ps2_end_command(&psmouse->ps2dev);
+
+	return rc;
+}
+
+static bool cypress_verify_cmd_state(struct psmouse *psmouse,
+				     unsigned char cmd, unsigned char *param)
+{
+	bool rate_match = false;
+	bool resolution_match = false;
+	int i;
+
+	/* callers will do further checking. */
+	if (cmd == CYTP_CMD_READ_CYPRESS_ID ||
+	    cmd == CYTP_CMD_STANDARD_MODE ||
+	    cmd == CYTP_CMD_READ_TP_METRICS)
+		return true;
+
+	if ((~param[0] & DFLT_RESP_BITS_VALID) == DFLT_RESP_BITS_VALID &&
+	    (param[0] & DFLT_RESP_BIT_MODE) == DFLT_RESP_STREAM_MODE) {
+		for (i = 0; i < sizeof(cytp_resolution); i++)
+			if (cytp_resolution[i] == param[1])
+				resolution_match = true;
+
+		for (i = 0; i < sizeof(cytp_rate); i++)
+			if (cytp_rate[i] == param[2])
+				rate_match = true;
+
+		if (resolution_match && rate_match)
+			return true;
+	}
+
+	psmouse_dbg(psmouse, "verify cmd state failed.\n");
+	return false;
+}
+
+static int cypress_send_ext_cmd(struct psmouse *psmouse, unsigned char cmd,
+				unsigned char *param)
+{
+	int tries = CYTP_PS2_CMD_TRIES;
+	int rc;
+
+	psmouse_dbg(psmouse, "send extension cmd 0x%02x, [%d %d %d %d]\n",
+		 cmd, DECODE_CMD_AA(cmd), DECODE_CMD_BB(cmd),
+		 DECODE_CMD_CC(cmd), DECODE_CMD_DD(cmd));
+
+	do {
+		cypress_ps2_ext_cmd(psmouse,
+				    PSMOUSE_CMD_SETRES, DECODE_CMD_DD(cmd));
+		cypress_ps2_ext_cmd(psmouse,
+				    PSMOUSE_CMD_SETRES, DECODE_CMD_CC(cmd));
+		cypress_ps2_ext_cmd(psmouse,
+				    PSMOUSE_CMD_SETRES, DECODE_CMD_BB(cmd));
+		cypress_ps2_ext_cmd(psmouse,
+				    PSMOUSE_CMD_SETRES, DECODE_CMD_AA(cmd));
+
+		rc = cypress_ps2_read_cmd_status(psmouse, cmd, param);
+		if (rc)
+			continue;
+
+		if (cypress_verify_cmd_state(psmouse, cmd, param))
+			return 0;
+
+	} while (--tries > 0);
+
+	return -EIO;
+}
+
+int cypress_detect(struct psmouse *psmouse, bool set_properties)
+{
+	unsigned char param[3];
+
+	if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_CYPRESS_ID, param))
+		return -ENODEV;
+
+	/* Check for Cypress Trackpad signature bytes: 0x33 0xCC */
+	if (param[0] != 0x33 || param[1] != 0xCC)
+		return -ENODEV;
+
+	if (set_properties) {
+		psmouse->vendor = "Cypress";
+		psmouse->name = "Trackpad";
+	}
+
+	return 0;
+}
+
+static int cypress_read_fw_version(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+	unsigned char param[3];
+
+	if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_CYPRESS_ID, param))
+		return -ENODEV;
+
+	/* Check for Cypress Trackpad signature bytes: 0x33 0xCC */
+	if (param[0] != 0x33 || param[1] != 0xCC)
+		return -ENODEV;
+
+	cytp->fw_version = param[2] & FW_VERSION_MASX;
+	cytp->tp_metrics_supported = (param[2] & TP_METRICS_MASK) ? 1 : 0;
+
+	psmouse_dbg(psmouse, "cytp->fw_version = %d\n", cytp->fw_version);
+	psmouse_dbg(psmouse, "cytp->tp_metrics_supported = %d\n",
+		 cytp->tp_metrics_supported);
+
+	return 0;
+}
+
+static int cypress_read_tp_metrics(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+	unsigned char param[8];
+
+	/* set default values for tp metrics. */
+	cytp->tp_width = CYTP_DEFAULT_WIDTH;
+	cytp->tp_high = CYTP_DEFAULT_HIGH;
+	cytp->tp_max_abs_x = CYTP_ABS_MAX_X;
+	cytp->tp_max_abs_y = CYTP_ABS_MAX_Y;
+	cytp->tp_min_pressure = CYTP_MIN_PRESSURE;
+	cytp->tp_max_pressure = CYTP_MAX_PRESSURE;
+	cytp->tp_res_x = cytp->tp_max_abs_x / cytp->tp_width;
+	cytp->tp_res_y = cytp->tp_max_abs_y / cytp->tp_high;
+
+	memset(param, 0, sizeof(param));
+	if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_TP_METRICS, param) == 0) {
+		/* Update trackpad parameters. */
+		cytp->tp_max_abs_x = (param[1] << 8) | param[0];
+		cytp->tp_max_abs_y = (param[3] << 8) | param[2];
+		cytp->tp_min_pressure = param[4];
+		cytp->tp_max_pressure = param[5];
+	}
+
+	if (!cytp->tp_max_pressure ||
+	    cytp->tp_max_pressure < cytp->tp_min_pressure ||
+	    !cytp->tp_width || !cytp->tp_high ||
+	    !cytp->tp_max_abs_x ||
+	    cytp->tp_max_abs_x < cytp->tp_width ||
+	    !cytp->tp_max_abs_y ||
+	    cytp->tp_max_abs_y < cytp->tp_high)
+		return -EINVAL;
+
+	cytp->tp_res_x = cytp->tp_max_abs_x / cytp->tp_width;
+	cytp->tp_res_y = cytp->tp_max_abs_y / cytp->tp_high;
+
+#ifdef CYTP_DEBUG_VERBOSE
+	psmouse_dbg(psmouse, "Dump trackpad hardware configuration as below:\n");
+	psmouse_dbg(psmouse, "cytp->tp_width = %d\n", cytp->tp_width);
+	psmouse_dbg(psmouse, "cytp->tp_high = %d\n", cytp->tp_high);
+	psmouse_dbg(psmouse, "cytp->tp_max_abs_x = %d\n", cytp->tp_max_abs_x);
+	psmouse_dbg(psmouse, "cytp->tp_max_abs_y = %d\n", cytp->tp_max_abs_y);
+	psmouse_dbg(psmouse, "cytp->tp_min_pressure = %d\n", cytp->tp_min_pressure);
+	psmouse_dbg(psmouse, "cytp->tp_max_pressure = %d\n", cytp->tp_max_pressure);
+	psmouse_dbg(psmouse, "cytp->tp_res_x = %d\n", cytp->tp_res_x);
+	psmouse_dbg(psmouse, "cytp->tp_res_y = %d\n", cytp->tp_res_y);
+
+	psmouse_dbg(psmouse, "tp_type_APA = %d\n",
+			(param[6] & TP_METRICS_BIT_APA) ? 1 : 0);
+	psmouse_dbg(psmouse, "tp_type_MTG = %d\n",
+			(param[6] & TP_METRICS_BIT_MTG) ? 1 : 0);
+	psmouse_dbg(psmouse, "tp_palm = %d\n",
+			(param[6] & TP_METRICS_BIT_PALM) ? 1 : 0);
+	psmouse_dbg(psmouse, "tp_stubborn = %d\n",
+			(param[6] & TP_METRICS_BIT_STUBBORN) ? 1 : 0);
+	psmouse_dbg(psmouse, "tp_1f_jitter = %d\n",
+			(param[6] & TP_METRICS_BIT_1F_JITTER) >> 2);
+	psmouse_dbg(psmouse, "tp_2f_jitter = %d\n",
+			(param[6] & TP_METRICS_BIT_2F_JITTER) >> 4);
+	psmouse_dbg(psmouse, "tp_1f_spike = %d\n",
+			param[7] & TP_METRICS_BIT_1F_SPIKE);
+	psmouse_dbg(psmouse, "tp_2f_spike = %d\n",
+			(param[7] & TP_METRICS_BIT_2F_SPIKE) >> 2);
+	psmouse_dbg(psmouse, "tp_abs_packet_format_set = %d\n",
+			(param[7] & TP_METRICS_BIT_ABS_PKT_FORMAT_SET) >> 4);
+#endif
+
+	return 0;
+}
+
+static int cypress_query_hardware(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+	int ret;
+
+	ret = cypress_read_fw_version(psmouse);
+	if (ret)
+		return ret;
+
+	if (cytp->tp_metrics_supported) {
+		ret = cypress_read_tp_metrics(psmouse);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int cypress_set_absolute_mode(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+	unsigned char param[3];
+
+	if (cypress_send_ext_cmd(psmouse, CYTP_CMD_ABS_WITH_PRESSURE_MODE, param) < 0)
+		return -1;
+
+	cytp->mode = (cytp->mode & ~CYTP_BIT_ABS_REL_MASK)
+			| CYTP_BIT_ABS_PRESSURE;
+	cypress_set_packet_size(psmouse, 5);
+
+	return 0;
+}
+
+/*
+ * Reset trackpad device.
+ * This is also the default mode when trackpad powered on.
+ */
+static void cypress_reset(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+
+	cytp->mode = 0;
+
+	psmouse_reset(psmouse);
+}
+
+static int cypress_set_input_params(struct input_dev *input,
+				    struct cytp_data *cytp)
+{
+	int ret;
+
+	if (!cytp->tp_res_x || !cytp->tp_res_y)
+		return -EINVAL;
+
+	__set_bit(EV_ABS, input->evbit);
+	input_set_abs_params(input, ABS_X, 0, cytp->tp_max_abs_x, 0, 0);
+	input_set_abs_params(input, ABS_Y, 0, cytp->tp_max_abs_y, 0, 0);
+	input_set_abs_params(input, ABS_PRESSURE,
+			     cytp->tp_min_pressure, cytp->tp_max_pressure, 0, 0);
+	input_set_abs_params(input, ABS_TOOL_WIDTH, 0, 255, 0, 0);
+
+	/* finger position */
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, cytp->tp_max_abs_x, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cytp->tp_max_abs_y, 0, 0);
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+	ret = input_mt_init_slots(input, CYTP_MAX_MT_SLOTS,
+			INPUT_MT_DROP_UNUSED|INPUT_MT_TRACK);
+	if (ret < 0)
+		return ret;
+
+	__set_bit(INPUT_PROP_SEMI_MT, input->propbit);
+
+	input_abs_set_res(input, ABS_X, cytp->tp_res_x);
+	input_abs_set_res(input, ABS_Y, cytp->tp_res_y);
+
+	input_abs_set_res(input, ABS_MT_POSITION_X, cytp->tp_res_x);
+	input_abs_set_res(input, ABS_MT_POSITION_Y, cytp->tp_res_y);
+
+	__set_bit(BTN_TOUCH, input->keybit);
+	__set_bit(BTN_TOOL_FINGER, input->keybit);
+	__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
+	__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
+	__set_bit(BTN_TOOL_QUADTAP, input->keybit);
+	__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
+
+	__clear_bit(EV_REL, input->evbit);
+	__clear_bit(REL_X, input->relbit);
+	__clear_bit(REL_Y, input->relbit);
+
+	__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(BTN_LEFT, input->keybit);
+	__set_bit(BTN_RIGHT, input->keybit);
+	__set_bit(BTN_MIDDLE, input->keybit);
+
+	input_set_drvdata(input, cytp);
+
+	return 0;
+}
+
+static int cypress_get_finger_count(unsigned char header_byte)
+{
+	unsigned char bits6_7;
+	int finger_count;
+
+	bits6_7 = header_byte >> 6;
+	finger_count = bits6_7 & 0x03;
+
+	if (finger_count == 1)
+		return 1;
+
+	if (header_byte & ABS_HSCROLL_BIT) {
+		/* HSCROLL gets added on to 0 finger count. */
+		switch (finger_count) {
+			case 0:	return 4;
+			case 2: return 5;
+			default:
+				/* Invalid contact (e.g. palm). Ignore it. */
+				return -1;
+		}
+	}
+
+	return finger_count;
+}
+
+
+static int cypress_parse_packet(struct psmouse *psmouse,
+				struct cytp_data *cytp, struct cytp_report_data *report_data)
+{
+	unsigned char *packet = psmouse->packet;
+	unsigned char header_byte = packet[0];
+	int contact_cnt;
+
+	memset(report_data, 0, sizeof(struct cytp_report_data));
+
+	contact_cnt = cypress_get_finger_count(header_byte);
+
+	if (contact_cnt < 0) /* e.g. palm detect */
+		return -EINVAL;
+
+	report_data->contact_cnt = contact_cnt;
+
+	report_data->tap = (header_byte & ABS_MULTIFINGER_TAP) ? 1 : 0;
+
+	if (report_data->contact_cnt == 1) {
+		report_data->contacts[0].x =
+			((packet[1] & 0x70) << 4) | packet[2];
+		report_data->contacts[0].y =
+			((packet[1] & 0x07) << 8) | packet[3];
+		if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
+			report_data->contacts[0].z = packet[4];
+
+	} else if (report_data->contact_cnt >= 2) {
+		report_data->contacts[0].x =
+			((packet[1] & 0x70) << 4) | packet[2];
+		report_data->contacts[0].y =
+			((packet[1] & 0x07) << 8) | packet[3];
+		if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
+			report_data->contacts[0].z = packet[4];
+
+		report_data->contacts[1].x =
+			((packet[5] & 0xf0) << 4) | packet[6];
+		report_data->contacts[1].y =
+			((packet[5] & 0x0f) << 8) | packet[7];
+		if (cytp->mode & CYTP_BIT_ABS_PRESSURE)
+			report_data->contacts[1].z = report_data->contacts[0].z;
+	}
+
+	report_data->left = (header_byte & BTN_LEFT_BIT) ? 1 : 0;
+	report_data->right = (header_byte & BTN_RIGHT_BIT) ? 1 : 0;
+
+	/*
+	 * This is only true if one of the mouse buttons were tapped.  Make
+	 * sure it doesn't turn into a click. The regular tap-to-click
+	 * functionality will handle that on its own. If we don't do this,
+	 * disabling tap-to-click won't affect the mouse button zones.
+	 */
+	if (report_data->tap)
+		report_data->left = 0;
+
+#ifdef CYTP_DEBUG_VERBOSE
+	{
+		int i;
+		int n = report_data->contact_cnt;
+		psmouse_dbg(psmouse, "Dump parsed report data as below:\n");
+		psmouse_dbg(psmouse, "contact_cnt = %d\n",
+			report_data->contact_cnt);
+		if (n > CYTP_MAX_MT_SLOTS)
+		    n = CYTP_MAX_MT_SLOTS;
+		for (i = 0; i < n; i++)
+			psmouse_dbg(psmouse, "contacts[%d] = {%d, %d, %d}\n", i,
+					report_data->contacts[i].x,
+					report_data->contacts[i].y,
+					report_data->contacts[i].z);
+		psmouse_dbg(psmouse, "left = %d\n", report_data->left);
+		psmouse_dbg(psmouse, "right = %d\n", report_data->right);
+		psmouse_dbg(psmouse, "middle = %d\n", report_data->middle);
+	}
+#endif
+
+	return 0;
+}
+
+static void cypress_process_packet(struct psmouse *psmouse, bool zero_pkt)
+{
+	int i;
+	struct input_dev *input = psmouse->dev;
+	struct cytp_data *cytp = psmouse->private;
+	struct cytp_report_data report_data;
+	struct cytp_contact *contact;
+	struct input_mt_pos pos[CYTP_MAX_MT_SLOTS];
+	int slots[CYTP_MAX_MT_SLOTS];
+	int n;
+
+	if (cypress_parse_packet(psmouse, cytp, &report_data))
+		return;
+
+	n = report_data.contact_cnt;
+
+	if (n > CYTP_MAX_MT_SLOTS)
+		n = CYTP_MAX_MT_SLOTS;
+
+	for (i = 0; i < n; i++) {
+		contact = &report_data.contacts[i];
+		pos[i].x = contact->x;
+		pos[i].y = contact->y;
+	}
+
+	input_mt_assign_slots(input, slots, pos, n);
+
+	for (i = 0; i < n; i++) {
+		contact = &report_data.contacts[i];
+		input_mt_slot(input, slots[i]);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+		input_report_abs(input, ABS_MT_POSITION_X, contact->x);
+		input_report_abs(input, ABS_MT_POSITION_Y, contact->y);
+		input_report_abs(input, ABS_MT_PRESSURE, contact->z);
+	}
+
+	input_mt_sync_frame(input);
+
+	input_mt_report_finger_count(input, report_data.contact_cnt);
+
+	input_report_key(input, BTN_LEFT, report_data.left);
+	input_report_key(input, BTN_RIGHT, report_data.right);
+	input_report_key(input, BTN_MIDDLE, report_data.middle);
+
+	input_sync(input);
+}
+
+static psmouse_ret_t cypress_validate_byte(struct psmouse *psmouse)
+{
+	int contact_cnt;
+	int index = psmouse->pktcnt - 1;
+	unsigned char *packet = psmouse->packet;
+	struct cytp_data *cytp = psmouse->private;
+
+	if (index < 0 || index > cytp->pkt_size)
+		return PSMOUSE_BAD_DATA;
+
+	if (index == 0 && (packet[0] & 0xfc) == 0) {
+		/* call packet process for reporting finger leave. */
+		cypress_process_packet(psmouse, 1);
+		return PSMOUSE_FULL_PACKET;
+	}
+
+	/*
+	 * Perform validation (and adjust packet size) based only on the
+	 * first byte; allow all further bytes through.
+	 */
+	if (index != 0)
+		return PSMOUSE_GOOD_DATA;
+
+	/*
+	 * If absolute/relative mode bit has not been set yet, just pass
+	 * the byte through.
+	 */
+	if ((cytp->mode & CYTP_BIT_ABS_REL_MASK) == 0)
+		return PSMOUSE_GOOD_DATA;
+
+	if ((packet[0] & 0x08) == 0x08)
+		return PSMOUSE_BAD_DATA;
+
+	contact_cnt = cypress_get_finger_count(packet[0]);
+
+	if (contact_cnt < 0)
+		return PSMOUSE_BAD_DATA;
+
+	if (cytp->mode & CYTP_BIT_ABS_NO_PRESSURE)
+		cypress_set_packet_size(psmouse, contact_cnt == 2 ? 7 : 4);
+	else
+		cypress_set_packet_size(psmouse, contact_cnt == 2 ? 8 : 5);
+
+	return PSMOUSE_GOOD_DATA;
+}
+
+static psmouse_ret_t cypress_protocol_handler(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp = psmouse->private;
+
+	if (psmouse->pktcnt >= cytp->pkt_size) {
+		cypress_process_packet(psmouse, 0);
+		return PSMOUSE_FULL_PACKET;
+	}
+
+	return cypress_validate_byte(psmouse);
+}
+
+static void cypress_set_rate(struct psmouse *psmouse, unsigned int rate)
+{
+	struct cytp_data *cytp = psmouse->private;
+
+	if (rate >= 80) {
+		psmouse->rate = 80;
+		cytp->mode |= CYTP_BIT_HIGH_RATE;
+	} else {
+		psmouse->rate = 40;
+		cytp->mode &= ~CYTP_BIT_HIGH_RATE;
+	}
+
+	ps2_command(&psmouse->ps2dev, (unsigned char *)&psmouse->rate,
+		    PSMOUSE_CMD_SETRATE);
+}
+
+static void cypress_disconnect(struct psmouse *psmouse)
+{
+	cypress_reset(psmouse);
+	kfree(psmouse->private);
+	psmouse->private = NULL;
+}
+
+static int cypress_reconnect(struct psmouse *psmouse)
+{
+	int tries = CYTP_PS2_CMD_TRIES;
+	int rc;
+
+	do {
+		cypress_reset(psmouse);
+		rc = cypress_detect(psmouse, false);
+	} while (rc && (--tries > 0));
+
+	if (rc) {
+		psmouse_err(psmouse, "Reconnect: unable to detect trackpad.\n");
+		return -1;
+	}
+
+	if (cypress_set_absolute_mode(psmouse)) {
+		psmouse_err(psmouse, "Reconnect: Unable to initialize Cypress absolute mode.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int cypress_init(struct psmouse *psmouse)
+{
+	struct cytp_data *cytp;
+
+	cytp = (struct cytp_data *)kzalloc(sizeof(struct cytp_data), GFP_KERNEL);
+	psmouse->private = (void *)cytp;
+	if (cytp == NULL)
+		return -ENOMEM;
+
+	cypress_reset(psmouse);
+
+	psmouse->pktsize = 8;
+
+	if (cypress_query_hardware(psmouse)) {
+		psmouse_err(psmouse, "Unable to query Trackpad hardware.\n");
+		goto err_exit;
+	}
+
+	if (cypress_set_absolute_mode(psmouse)) {
+		psmouse_err(psmouse, "init: Unable to initialize Cypress absolute mode.\n");
+		goto err_exit;
+	}
+
+	if (cypress_set_input_params(psmouse->dev, cytp) < 0) {
+		psmouse_err(psmouse, "init: Unable to set input params.\n");
+		goto err_exit;
+	}
+
+	psmouse->model = 1;
+	psmouse->protocol_handler = cypress_protocol_handler;
+	psmouse->set_rate = cypress_set_rate;
+	psmouse->disconnect = cypress_disconnect;
+	psmouse->reconnect = cypress_reconnect;
+	psmouse->cleanup = cypress_reset;
+	psmouse->resync_time = 0;
+
+	return 0;
+
+err_exit:
+	/*
+	 * Reset Cypress Trackpad as a standard mouse. Then
+	 * let psmouse driver commmunicating with it as default PS2 mouse.
+	 */
+	cypress_reset(psmouse);
+
+	psmouse->private = NULL;
+	kfree(cytp);
+
+	return -1;
+}
+
+bool cypress_supported(void)
+{
+	return true;
+}
--- /dev/null
+++ b/drivers/input/mouse/cypress_ps2.h
@@ -0,0 +1,191 @@
+#ifndef _CYPRESS_PS2_H
+#define _CYPRESS_PS2_H
+
+#include "psmouse.h"
+
+#define CMD_BITS_MASK 0x03
+#define COMPOSIT(x, s) (((x) & CMD_BITS_MASK) << (s))
+
+#define ENCODE_CMD(aa, bb, cc, dd) \
+	(COMPOSIT((aa), 6) | COMPOSIT((bb), 4) | COMPOSIT((cc), 2) | COMPOSIT((dd), 0))
+#define CYTP_CMD_ABS_NO_PRESSURE_MODE       ENCODE_CMD(0, 1, 0, 0)
+#define CYTP_CMD_ABS_WITH_PRESSURE_MODE     ENCODE_CMD(0, 1, 0, 1)
+#define CYTP_CMD_SMBUS_MODE                 ENCODE_CMD(0, 1, 1, 0)
+#define CYTP_CMD_STANDARD_MODE              ENCODE_CMD(0, 2, 0, 0)  /* not implemented yet. */
+#define CYTP_CMD_CYPRESS_REL_MODE           ENCODE_CMD(1, 1, 1, 1)  /* not implemented yet. */
+#define CYTP_CMD_READ_CYPRESS_ID            ENCODE_CMD(0, 0, 0, 0)
+#define CYTP_CMD_READ_TP_METRICS            ENCODE_CMD(0, 0, 0, 1)
+#define CYTP_CMD_SET_HSCROLL_WIDTH(w)       ENCODE_CMD(1, 1, 0, (w))
+#define     CYTP_CMD_SET_HSCROLL_MASK       ENCODE_CMD(1, 1, 0, 0)
+#define CYTP_CMD_SET_VSCROLL_WIDTH(w)       ENCODE_CMD(1, 2, 0, (w))
+#define     CYTP_CMD_SET_VSCROLL_MASK       ENCODE_CMD(1, 2, 0, 0)
+#define CYTP_CMD_SET_PALM_GEOMETRY(e)       ENCODE_CMD(1, 2, 1, (e))
+#define     CYTP_CMD_PALM_GEMMETRY_MASK     ENCODE_CMD(1, 2, 1, 0)
+#define CYTP_CMD_SET_PALM_SENSITIVITY(s)    ENCODE_CMD(1, 2, 2, (s))
+#define     CYTP_CMD_PALM_SENSITIVITY_MASK  ENCODE_CMD(1, 2, 2, 0)
+#define CYTP_CMD_SET_MOUSE_SENSITIVITY(s)   ENCODE_CMD(1, 3, ((s) >> 2), (s))
+#define     CYTP_CMD_MOUSE_SENSITIVITY_MASK ENCODE_CMD(1, 3, 0, 0)
+#define CYTP_CMD_REQUEST_BASELINE_STATUS    ENCODE_CMD(2, 0, 0, 1)
+#define CYTP_CMD_REQUEST_RECALIBRATION      ENCODE_CMD(2, 0, 0, 3)
+
+#define DECODE_CMD_AA(x) (((x) >> 6) & CMD_BITS_MASK)
+#define DECODE_CMD_BB(x) (((x) >> 4) & CMD_BITS_MASK)
+#define DECODE_CMD_CC(x) (((x) >> 2) & CMD_BITS_MASK)
+#define DECODE_CMD_DD(x) ((x) & CMD_BITS_MASK)
+
+/* Cypress trackpad working mode. */
+#define CYTP_BIT_ABS_PRESSURE    (1 << 3)
+#define CYTP_BIT_ABS_NO_PRESSURE (1 << 2)
+#define CYTP_BIT_CYPRESS_REL     (1 << 1)
+#define CYTP_BIT_STANDARD_REL    (1 << 0)
+#define CYTP_BIT_REL_MASK (CYTP_BIT_CYPRESS_REL | CYTP_BIT_STANDARD_REL)
+#define CYTP_BIT_ABS_MASK (CYTP_BIT_ABS_PRESSURE | CYTP_BIT_ABS_NO_PRESSURE)
+#define CYTP_BIT_ABS_REL_MASK (CYTP_BIT_ABS_MASK | CYTP_BIT_REL_MASK)
+
+#define CYTP_BIT_HIGH_RATE       (1 << 4)
+/*
+ * report mode bit is set, firmware working in Remote Mode.
+ * report mode bit is cleared, firmware working in Stream Mode.
+ */
+#define CYTP_BIT_REPORT_MODE     (1 << 5)
+
+/* scrolling width values for set HSCROLL and VSCROLL width command. */
+#define SCROLL_WIDTH_NARROW 1
+#define SCROLL_WIDTH_NORMAL 2
+#define SCROLL_WIDTH_WIDE   3
+
+#define PALM_GEOMETRY_ENABLE  1
+#define PALM_GEOMETRY_DISABLE 0
+
+#define TP_METRICS_MASK  0x80
+#define FW_VERSION_MASX    0x7f
+#define FW_VER_HIGH_MASK 0x70
+#define FW_VER_LOW_MASK  0x0f
+
+/* Times to retry a ps2_command and millisecond delay between tries. */
+#define CYTP_PS2_CMD_TRIES 3
+#define CYTP_PS2_CMD_DELAY 500
+
+/* time out for PS/2 command only in milliseconds. */
+#define CYTP_CMD_TIMEOUT  200
+#define CYTP_DATA_TIMEOUT 30
+
+#define CYTP_EXT_CMD   0xe8
+#define CYTP_PS2_RETRY 0xfe
+#define CYTP_PS2_ERROR 0xfc
+
+#define CYTP_RESP_RETRY 0x01
+#define CYTP_RESP_ERROR 0xfe
+
+
+#define CYTP_105001_WIDTH  97   /* Dell XPS 13 */
+#define CYTP_105001_HIGH   59
+#define CYTP_DEFAULT_WIDTH (CYTP_105001_WIDTH)
+#define CYTP_DEFAULT_HIGH  (CYTP_105001_HIGH)
+
+#define CYTP_ABS_MAX_X     1600
+#define CYTP_ABS_MAX_Y     900
+#define CYTP_MAX_PRESSURE  255
+#define CYTP_MIN_PRESSURE  0
+
+/* header byte bits of relative package. */
+#define BTN_LEFT_BIT   0x01
+#define BTN_RIGHT_BIT  0x02
+#define BTN_MIDDLE_BIT 0x04
+#define REL_X_SIGN_BIT 0x10
+#define REL_Y_SIGN_BIT 0x20
+
+/* header byte bits of absolute package. */
+#define ABS_VSCROLL_BIT 0x10
+#define ABS_HSCROLL_BIT 0x20
+#define ABS_MULTIFINGER_TAP 0x04
+#define ABS_EDGE_MOTION_MASK 0x80
+
+#define DFLT_RESP_BITS_VALID     0x88  /* SMBus bit should not be set. */
+#define DFLT_RESP_SMBUS_BIT      0x80
+#define   DFLT_SMBUS_MODE        0x80
+#define   DFLT_PS2_MODE          0x00
+#define DFLT_RESP_BIT_MODE       0x40
+#define   DFLT_RESP_REMOTE_MODE  0x40
+#define   DFLT_RESP_STREAM_MODE  0x00
+#define DFLT_RESP_BIT_REPORTING  0x20
+#define DFLT_RESP_BIT_SCALING    0x10
+
+#define TP_METRICS_BIT_PALM               0x80
+#define TP_METRICS_BIT_STUBBORN           0x40
+#define TP_METRICS_BIT_2F_JITTER          0x30
+#define TP_METRICS_BIT_1F_JITTER          0x0c
+#define TP_METRICS_BIT_APA                0x02
+#define TP_METRICS_BIT_MTG                0x01
+#define TP_METRICS_BIT_ABS_PKT_FORMAT_SET 0xf0
+#define TP_METRICS_BIT_2F_SPIKE           0x0c
+#define TP_METRICS_BIT_1F_SPIKE           0x03
+
+/* bits of first byte response of E9h-Status Request command. */
+#define RESP_BTN_RIGHT_BIT  0x01
+#define RESP_BTN_MIDDLE_BIT 0x02
+#define RESP_BTN_LEFT_BIT   0x04
+#define RESP_SCALING_BIT    0x10
+#define RESP_ENABLE_BIT     0x20
+#define RESP_REMOTE_BIT     0x40
+#define RESP_SMBUS_BIT      0x80
+
+#define CYTP_MAX_MT_SLOTS 2
+
+struct cytp_contact {
+	int x;
+	int y;
+	int z;  /* also named as touch pressure. */
+};
+
+/* The structure of Cypress Trackpad event data. */
+struct cytp_report_data {
+	int contact_cnt;
+	struct cytp_contact contacts[CYTP_MAX_MT_SLOTS];
+	unsigned int left:1;
+	unsigned int right:1;
+	unsigned int middle:1;
+	unsigned int tap:1;  /* multi-finger tap detected. */
+};
+
+/* The structure of Cypress Trackpad device private data. */
+struct cytp_data {
+	int fw_version;
+
+	int pkt_size;
+	int mode;
+
+	int tp_min_pressure;
+	int tp_max_pressure;
+	int tp_width;  /* X direction physical size in mm. */
+	int tp_high;  /* Y direction physical size in mm. */
+	int tp_max_abs_x;  /* Max X absolute units that can be reported. */
+	int tp_max_abs_y;  /* Max Y absolute units that can be reported. */
+
+	int tp_res_x;  /* X resolution in units/mm. */
+	int tp_res_y;  /* Y resolution in units/mm. */
+
+	int tp_metrics_supported;
+};
+
+
+#ifdef CONFIG_MOUSE_PS2_CYPRESS
+int cypress_detect(struct psmouse *psmouse, bool set_properties);
+int cypress_init(struct psmouse *psmouse);
+bool cypress_supported(void);
+#else
+inline int cypress_detect(struct psmouse *psmouse, bool set_properties)
+{
+	return -ENOSYS;
+}
+inline int cypress_init(struct psmouse *psmouse)
+{
+	return -ENOSYS;
+}
+inline bool cypress_supported(void)
+{
+	return 0;
+}
+#endif /* CONFIG_MOUSE_PS2_CYPRESS */
+
+#endif  /* _CYPRESS_PS2_H */
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -34,6 +34,7 @@
 #include "touchkit_ps2.h"
 #include "elantech.h"
 #include "sentelic.h"
+#include "cypress_ps2.h"
 
 #define DRIVER_DESC	"PS/2 mouse driver"
 
@@ -663,6 +664,28 @@ static int psmouse_extensions(struct psm
 	}
 
 /*
+ * Try Cypress Trackpad.
+ * Must try it before Finger Sensing Pad because Finger Sensing Pad probe
+ * upsets some modules of Cypress Trackpads.
+ */
+	if (max_proto > PSMOUSE_IMEX &&
+			cypress_detect(psmouse, set_properties) == 0) {
+		if (cypress_supported()) {
+			if (cypress_init(psmouse) == 0)
+				return PSMOUSE_CYPRESS;
+
+			/*
+			 * Finger Sensing Pad probe upsets some modules of
+			 * Cypress Trackpad, must avoid Finger Sensing Pad
+			 * probe if Cypress Trackpad device detected.
+			 */
+			return PSMOUSE_PS2;
+		}
+
+		max_proto = PSMOUSE_IMEX;
+	}
+
+/*
  * Try ALPS TouchPad
  */
 	if (max_proto > PSMOUSE_IMEX) {
@@ -789,6 +812,15 @@ static const struct psmouse_protocol psm
 		.alias		= "thinkps",
 		.detect		= thinking_detect,
 	},
+#ifdef CONFIG_MOUSE_PS2_CYPRESS
+	{
+		.type		= PSMOUSE_CYPRESS,
+		.name		= "CyPS/2",
+		.alias		= "cypress",
+		.detect		= cypress_detect,
+		.init		= cypress_init,
+	},
+#endif
 	{
 		.type		= PSMOUSE_GENPS,
 		.name		= "GenPS/2",
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -94,6 +94,7 @@ enum psmouse_type {
 	PSMOUSE_HGPK,
 	PSMOUSE_ELANTECH,
 	PSMOUSE_FSP,
+	PSMOUSE_CYPRESS,
 	PSMOUSE_AUTO		/* This one should always be last */
 };
 
From: Kamal Mostafa <kamal@canonical.com>
Date: Thu, 21 Feb 2013 11:55:05 -0800
Subject: Input: cypress_ps2 - fix trackpadi found in Dell XPS12

commit 81bb5d31fbf3893a8e041c649dea704dd11d5272 upstream.

Avoid firmware glitch in Cypress PS/2 Trackpad firmware version 11
(as observed in Dell XPS12) which prevents driver from recognizing
the trackpad.

BugLink: http://launchpad.net/bugs/1103594

Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Cc: Dudley Du <dudl@cypress.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/mouse/cypress_ps2.c |   19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/input/mouse/cypress_ps2.c b/drivers/input/mouse/cypress_ps2.c
index 1673dc6..f51765f 100644
--- a/drivers/input/mouse/cypress_ps2.c
+++ b/drivers/input/mouse/cypress_ps2.c
@@ -236,6 +236,13 @@ static int cypress_read_fw_version(struct psmouse *psmouse)
 	cytp->fw_version = param[2] & FW_VERSION_MASX;
 	cytp->tp_metrics_supported = (param[2] & TP_METRICS_MASK) ? 1 : 0;
 
+	/*
+	 * Trackpad fw_version 11 (in Dell XPS12) yields a bogus response to
+	 * CYTP_CMD_READ_TP_METRICS so do not try to use it. LP: #1103594.
+	 */
+	if (cytp->fw_version >= 11)
+		cytp->tp_metrics_supported = 0;
+
 	psmouse_dbg(psmouse, "cytp->fw_version = %d\n", cytp->fw_version);
 	psmouse_dbg(psmouse, "cytp->tp_metrics_supported = %d\n",
 		 cytp->tp_metrics_supported);
@@ -258,6 +265,9 @@ static int cypress_read_tp_metrics(struct psmouse *psmouse)
 	cytp->tp_res_x = cytp->tp_max_abs_x / cytp->tp_width;
 	cytp->tp_res_y = cytp->tp_max_abs_y / cytp->tp_high;
 
+	if (!cytp->tp_metrics_supported)
+		return 0;
+
 	memset(param, 0, sizeof(param));
 	if (cypress_send_ext_cmd(psmouse, CYTP_CMD_READ_TP_METRICS, param) == 0) {
 		/* Update trackpad parameters. */
@@ -315,18 +325,15 @@ static int cypress_read_tp_metrics(struct psmouse *psmouse)
 
 static int cypress_query_hardware(struct psmouse *psmouse)
 {
-	struct cytp_data *cytp = psmouse->private;
 	int ret;
 
 	ret = cypress_read_fw_version(psmouse);
 	if (ret)
 		return ret;
 
-	if (cytp->tp_metrics_supported) {
-		ret = cypress_read_tp_metrics(psmouse);
-		if (ret)
-			return ret;
-	}
+	ret = cypress_read_tp_metrics(psmouse);
+	if (ret)
+		return ret;
 
 	return 0;
 }

Attachment: signature.asc
Description: This is a digitally signed message part


Reply to: