Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [patch]GPIO button is supposed to wake the system up if the wakeup attribute is set
From: Li, Aubrey @ 2014-04-15 16:18 UTC (permalink / raw)
  To: Laxman Dewangan, One Thousand Gnomes
  Cc: dmitry.torokhov@gmail.com, sachin.kamat@linaro.org,
	linux-input@vger.kernel.org
In-Reply-To: <534D282B.50301@nvidia.com>

On 2014/4/15 20:38, Laxman Dewangan wrote:
> On Monday 14 April 2014 09:12 PM, Li, Aubrey wrote:
>> ping...
>>
>> On 2014/4/10 18:48, One Thousand Gnomes wrote:
>>> On Thu, 10 Apr 2014 10:11:09 +0800
>>> "Li, Aubrey" <aubrey.li@linux.intel.com> wrote:
>>>
>>>> When the wakeup attribute is set, GPIO button is supposed to set
>>>> irqflag - IRQF_NO_SUSPEND to request irq. So when the system enters
>>>> the suspend sleep mode, the GPIO irq keeps enabled and is able to
>>>> wake the system up.
>>>>
> 
> I think when we say irq_wake_enable() then based on underlying HW, it
> should not turn off the irq if it is require for the wakeup. I mean it
> need to be handle in the hw specific callbacks to keep enabling the
> wakeup irq on suspend also.

I failed to see why this can't be generic to all of the GPIO buttons for
suspend wakeup. Do you see any cases broken by this proposal?

> For me, I have key which is interrupt based from PMIC, not based on GPIO
> and on that if I set it to IRQF_EARLY_RESUME then it works fine.
> 
IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
resume time.

IRQF_NO_SUSPEND is exactly what I want, instead of IRQF_EARLY_RESUME.
Can you please send your proposal/code to help me understand why this
has to hw specific and why IRQF_EARLY_RESUME is better than IRQF_NO_SUSPEND?

Thanks,
-Aubrey


> 
> 
> -----------------------------------------------------------------------------------
> 
> This email message is for the sole use of the intended recipient(s) and
> may contain
> confidential information.  Any unauthorized review, use, disclosure or
> distribution
> is prohibited.  If you are not the intended recipient, please contact
> the sender by
> reply email and destroy all copies of the original message.
> -----------------------------------------------------------------------------------
> 
> -- 
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 


^ permalink raw reply

* spende /Donation
From: isabelle @ 2014-04-15 19:47 UTC (permalink / raw)


Hallo
Wenn ich diese Nachricht zu senden wollte, ist dies nicht einfach Zufall. Dies ist, weil Ihre e-Mail vom elektronischen Roboter gesichert meine WX.7AR BW ausgewählt wurde.
Zunächst möchte ich mich für dieses Eindringen in Ihr Leben zu entschuldigen, obwohl ich zugeben, dass es mir sehr wichtig. Ich bin Isabelle Vasudev. Ich leide an Krebs im Hals seit nun mehr als 3 Jahre und eine halbe und es leider, mein Arzt hat gerade informiert mich, dass ich bin voller unheilbar und, dass meine Tage, wegen meinen etwas gezählt sind abgebaut Zustand. Ich bin eine Witwe und ich habe keine Kind, das ich beginne zu bedauern.
In der Tat ist der Grund, warum ich Sie kontaktieren bin, möchte ich einen Teil von meinem Grundstück zu spenden, weil ich niemand, wer die Erben konnte. Ich habe fast mein ganzes Zeug, darunter ein Unternehmen der Export von Holz, Gummi und Stahl-Industrie in Afrika, wo ich wohne nun mehr 10 Jahren, verkauft. Ein großer Teil der Gelder gesammelt wurde mit unterschiedlichen Verbänden humanitären Charakter überall in der Welt, aber besonders hier in Afrika bezahlt.
Im Hinblick auf den Rest der Summe genau in Höhe von 750.000, 00euros (sieben hundert und fünfzig tausend Euro) auf eine gesperrte Mitarbeiter-Account, meine letzte wünschen würde Sie es spenden, so dass Sie in Ihrer Branche und vor allem den humanitären investieren können. Ich bin ganz bewusst was ich zu tun beabsichtigen, und ich denke, trotz der Tatsache, die wir nicht wissen, werdet ihr diese Summe gut nutzen. Ich bitte Sie, bitte dieses Erbe zu akzeptieren, ohne jedoch Fragen Sie alles, was in zurückgeben wenn es nicht immer denken, gutes zu tun, um dich herum, was ich nicht getan habe, in meiner Existenz.
Das heißt, wird auf einer verantwortlichen Person und besonders gutem Glauben fallen zu lassen beruhigt, ich möchte bitten, dass Sie bitte mich bei den meisten schnell kontaktieren, um weitere Erklärung über die Gründe für meine Geste und den Verlauf der Dinge zu geben. Bitte kontaktieren Sie mich so bald wie möglich, wenn Sie mein Angebot akzeptieren.
Gott möge mit dir sein!
Ich fordere Sie auf, mich über meine persönliche e-Mail-Adresse zu kontaktieren:
Isabelle.claude654@laposte.net
Der Frieden und Barmherzigkeit Gottes möge mit dir sein.
Mrs Isabelle

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

^ permalink raw reply

* Re: Fwd: Re: [PATCH v2] input: misc: Add driver for Intel Bay Trail GPIO buttons
From: Pavel Machek @ 2014-04-15 22:20 UTC (permalink / raw)
  To: Zhu, Lejun
  Cc: Dmitry Torokhov, One Thousand Gnomes, linux-kernel, linux-input,
	yu-sheng Chen
In-Reply-To: <5338D410.9060406@linux.intel.com>

Hi!
> 
> Input: misc - Add driver for Intel Bay Trail GPIO buttons
> 
> From: Lejun Zhu <lejun.zhu@linux.intel.com>
> 
> This patch adds support for the GPIO buttons on some Intel Bay Trail
> tablets originally running Windows 8. The ACPI description of these
> buttons follows "Windows ACPI Design Guide for SoC Platforms".

Hmm. Is it time for x86 to adopt device tree? Because this is 200
lines of C code which should really have been 10 lines of .dts...

> +
> +/*
> + * Some of the buttons like volume up/down are auto repeat, while others
> + * are not. To support both, we register two platform devices, and put
> + * buttons into them based on whether the key should be auto repeat.
> + */
> +#define BUTTON_TYPES	2
> +
> +struct soc_button_data {
> +	struct platform_device *children[BUTTON_TYPES];
> +};

Would it be possible to extend device description so that this hack is
not needed?
								Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply

* [PATCH] Input: evdev - add event-mask API
From: David Herrmann @ 2014-04-15 22:31 UTC (permalink / raw)
  To: linux-input
  Cc: Dmitry Torokhov, Benjamin Tissoires, Peter Hutterer,
	Lennart Poettering, David Herrmann

Hardware manufacturers group keys in the weirdest way possible. This may
cause a power-key to be grouped together with normal keyboard keys and
thus be reported on the same kernel interface.

However, user-space is often only interested in specific sets of events.
For instance, daemons dealing with system-reboot (like systemd-logind)
listen for KEY_POWER, but are not interested in any main keyboard keys.
Usually, power keys are reported via separate interfaces, however,
some i8042 boards report it in the AT matrix. To avoid waking up those
system daemons on each key-press, we had two ideas:
 - split off KEY_POWER into a separate interface unconditionally
 - allow masking a specific set of events on evdev FDs

Splitting of KEY_POWER is a rather weird way to deal with this and may
break backwards-compatibility. It is also specific to KEY_POWER and might
be required for other stuff, too. Moreover, we might end up with a huge
set of input-devices just to have them properly split.

Hence, this patchset implements the second idea: An event-mask to specify
which events you're interested in. Two ioctls allow setting this mask for
each event-type. If not set, all events are reported. The type==0 entry is
used same as in EVIOCGBIT to set the actual EV_* mask of masked events.
This way, you have a two-level filter.

We are heavily forward-compatible to new event-types and event-codes. So
new user-space will be able to run on an old kernel which doesn't know the
given event-codes or event event-types.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 drivers/input/evdev.c      | 155 +++++++++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/input.h |   8 +++
 2 files changed, 163 insertions(+)

diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 398648b..86778c3 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -51,10 +51,139 @@ struct evdev_client {
 	struct list_head node;
 	int clkid;
 	bool revoked;
+	unsigned long *evmasks[EV_CNT];
 	unsigned int bufsize;
 	struct input_event buffer[];
 };
 
+static size_t evdev_get_mask_cnt(unsigned int type)
+{
+	switch (type) {
+	case 0:
+		/* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */
+		return EV_CNT;
+	case EV_KEY:
+		return KEY_CNT;
+	case EV_REL:
+		return REL_CNT;
+	case EV_ABS:
+		return ABS_CNT;
+	case EV_MSC:
+		return MSC_CNT;
+	case EV_SW:
+		return SW_CNT;
+	case EV_LED:
+		return LED_CNT;
+	case EV_SND:
+		return SND_CNT;
+	case EV_FF:
+		return FF_CNT;
+	}
+
+	return 0;
+}
+
+/* must be called with evdev-mutex held */
+static int evdev_set_mask(struct evdev_client *client,
+			  unsigned int type,
+			  const void __user *codes,
+			  u32 codes_size)
+{
+	unsigned long flags, *mask, *oldmask;
+	size_t cnt, size;
+
+	/* unknown masks are simply ignored for forward-compat */
+	cnt = evdev_get_mask_cnt(type);
+	if (!cnt)
+		return 0;
+
+	/* we allow 'codes_size > size' for forward-compat */
+	size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
+
+	mask = kzalloc(size, GFP_KERNEL);
+	if (!mask)
+		return -ENOMEM;
+
+	if (copy_from_user(mask, codes, min_t(size_t, codes_size, size))) {
+		kfree(mask);
+		return -EFAULT;
+	}
+
+	spin_lock_irqsave(&client->buffer_lock, flags);
+	oldmask = client->evmasks[type];
+	client->evmasks[type] = mask;
+	spin_unlock_irqrestore(&client->buffer_lock, flags);
+
+	kfree(oldmask);
+
+	return 0;
+}
+
+/* must be called with evdev-mutex held */
+static int evdev_get_mask(struct evdev_client *client,
+			  unsigned int type,
+			  void __user *codes,
+			  u32 codes_size)
+{
+	unsigned long *mask;
+	size_t cnt, size, min, i;
+	u8 __user *out;
+
+	/* we allow unknown types and 'codes_size > size' for forward-compat */
+	cnt = evdev_get_mask_cnt(type);
+	size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
+	min = min_t(size_t, codes_size, size);
+
+	if (cnt > 0) {
+		mask = client->evmasks[type];
+		if (mask) {
+			if (copy_to_user(codes, mask, min))
+				return -EFAULT;
+		} else {
+			/* fake mask with all bits set */
+			out = (u8 __user*)codes;
+			for (i = 0; i < min; ++i) {
+				if (put_user((u8)0xff,  out + i))
+					return -EFAULT;
+			}
+		}
+	}
+
+	codes = (u8 __user*)codes + min;
+	codes_size -= min;
+
+	if (codes_size > 0 && clear_user(codes, codes_size))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* requires the buffer lock to be held */
+static bool __evdev_is_masked(struct evdev_client *client,
+			      unsigned int type,
+			      unsigned int code)
+{
+	unsigned long *mask;
+	size_t cnt;
+
+	/* EV_SYN and unknown codes are never masked */
+	if (!type || type >= EV_CNT)
+		return false;
+
+	/* first test whether the type is masked */
+	mask = client->evmasks[0];
+	if (mask && !test_bit(type, mask))
+		return true;
+
+	/* unknown values are never masked */
+	cnt = evdev_get_mask_cnt(type);
+	if (!cnt || code >= cnt)
+		return false;
+
+	mask = client->evmasks[type];
+	return mask && !test_bit(code, mask);
+}
+
 /* Flush queued events of given type @type and code @code. A negative code
  * is interpreted as catch-all. Caller must hold client->buffer_lock. */
 static void __evdev_flush_queue(struct evdev_client *client,
@@ -137,6 +266,9 @@ static void evdev_queue_syn_dropped(struct evdev_client *client)
 static void __pass_event(struct evdev_client *client,
 			 const struct input_event *event)
 {
+	if (__evdev_is_masked(client, event->type, event->code))
+		return;
+
 	client->buffer[client->head++] = *event;
 	client->head &= client->bufsize - 1;
 
@@ -368,6 +500,7 @@ static int evdev_release(struct inode *inode, struct file *file)
 {
 	struct evdev_client *client = file->private_data;
 	struct evdev *evdev = client->evdev;
+	unsigned int i;
 
 	mutex_lock(&evdev->mutex);
 	evdev_ungrab(evdev, client);
@@ -375,6 +508,9 @@ static int evdev_release(struct inode *inode, struct file *file)
 
 	evdev_detach_client(evdev, client);
 
+	for (i = 0; i < EV_CNT; ++i)
+		kfree(client->evmasks[i]);
+
 	if (is_vmalloc_addr(client))
 		vfree(client);
 	else
@@ -866,6 +1002,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
 	struct evdev *evdev = client->evdev;
 	struct input_dev *dev = evdev->handle.dev;
 	struct input_absinfo abs;
+	struct input_mask mask;
 	struct ff_effect effect;
 	int __user *ip = (int __user *)p;
 	unsigned int i, t, u, v;
@@ -927,6 +1064,24 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
 		else
 			return evdev_revoke(evdev, client, file);
 
+	case EVIOCGMASK:
+		if (copy_from_user(&mask, p, sizeof(mask)))
+			return -EFAULT;
+
+		return evdev_get_mask(client,
+				      mask.type,
+				      (void*)(long)mask.codes_ptr,
+				      mask.codes_size);
+
+	case EVIOCSMASK:
+		if (copy_from_user(&mask, p, sizeof(mask)))
+			return -EFAULT;
+
+		return evdev_set_mask(client,
+				      mask.type,
+				      (const void*)(long)mask.codes_ptr,
+				      mask.codes_size);
+
 	case EVIOCSCLOCKID:
 		if (copy_from_user(&i, p, sizeof(unsigned int)))
 			return -EFAULT;
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index bd24470..5b73712 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -97,6 +97,12 @@ struct input_keymap_entry {
 	__u8  scancode[32];
 };
 
+struct input_mask {
+	u32 type;
+	u32 codes_size;
+	u64 codes_ptr;
+};
+
 #define EVIOCGVERSION		_IOR('E', 0x01, int)			/* get driver version */
 #define EVIOCGID		_IOR('E', 0x02, struct input_id)	/* get device ID */
 #define EVIOCGREP		_IOR('E', 0x03, unsigned int[2])	/* get repeat settings */
@@ -153,6 +159,8 @@ struct input_keymap_entry {
 
 #define EVIOCGRAB		_IOW('E', 0x90, int)			/* Grab/Release device */
 #define EVIOCREVOKE		_IOW('E', 0x91, int)			/* Revoke device access */
+#define EVIOCGMASK		_IOR('E', 0x92, struct input_mask)	/* Get event-masks */
+#define EVIOCSMASK		_IOW('E', 0x93, struct input_mask)	/* Set event-masks */
 
 #define EVIOCSCLOCKID		_IOW('E', 0xa0, int)			/* Set clockid to be used for timestamps */
 
-- 
1.9.2


^ permalink raw reply related

* [PATCH 0/6] input: cyapa: integrated with gen5 trackpad supported in one driver.
From: Dudley Du @ 2014-04-16  8:33 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 1160 bytes --]

This patch set is made based on kernel 3.14.0. It's aimed to re-architecture the cyapa driver to support the old gen3 trackpad device and new gen5 trackpad device in one cyapa driver for easily products support based on customers' requirements, and add sysfs functions and interfaces supported that required by users and customers.

Beside this patch, it has 6 patches listed as below.
For these patches each one is patched based on previous one.

patch 1/6: change the architecture of cyapa driver to support function pointers and applying the device proble function in async thread to speed up system boot time.

patch 2/6: add gen5 trackpad devices supported in cyapa driver.

patch 3/6: add full power mode and runtime power mode supported.

patch 4/6: enable/disable trackpad device based on LID state.

patch 5/6: add sysfs interfaces supported for gen3 trackpad device.

patch 6/6: add sysfs interfaces supported for gen5 trackpad device.
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 4441 bytes --]

^ permalink raw reply

* [PATCH 1/6] input: cyapa: rearchitecture driver to support function pointers
From: Dudley Du @ 2014-04-16  8:35 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 38008 bytes --]

Re-architecture the driver to support function pointers, so it can support and integrate new devices later in one driver.
Including use async thread in device proble to speed up system boot time.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index b409c3d..4361ee1 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -14,15 +14,19 @@
  * more details.
  */

+#include <linux/async.h>
 #include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/input/mt.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/completion.h>
 #include <linux/slab.h>

 /* APA trackpad firmware generation */
+#define CYAPA_GEN_UNKNOWN   0x00   /* unknown protocol. */
 #define CYAPA_GEN3   0x03   /* support MT-protocol B with tracking ID. */

 #define CYAPA_NAME   "Cypress APA Trackpad (cyapa)"
@@ -85,14 +89,19 @@
  * bit 7: Busy
  * bit 6 - 5: Reserved
  * bit 4: Bootloader running
- * bit 3 - 1: Reserved
+ * bit 3 - 2: Reserved
+ * bit 1: Watchdog Reset
  * bit 0: Checksum valid
  */
 #define REG_BL_STATUS        0x01
+#define BL_STATUS_REV_6_5    0x60
 #define BL_STATUS_BUSY       0x80
 #define BL_STATUS_RUNNING    0x10
-#define BL_STATUS_DATA_VALID 0x08
+#define BL_STATUS_REV_3_2    0x0c
+#define BL_STATUS_WATCHDOG   0x02
 #define BL_STATUS_CSUM_VALID 0x01
+#define BL_STATUS_REV_MASK (BL_STATUS_WATCHDOG | BL_STATUS_REV_3_2 | \
+                           BL_STATUS_REV_6_5)

 /*
  * Bootloader Error Register
@@ -112,10 +121,14 @@
 #define BL_ERROR_CMD_CSUM    0x10
 #define BL_ERROR_FLASH_PROT  0x08
 #define BL_ERROR_FLASH_CSUM  0x04
+#define BL_ERROR_RESERVED    0x03

 #define BL_STATUS_SIZE  3  /* length of bootloader status registers */
 #define BLK_HEAD_BYTES 32

+/* Macro for register map group offset. */
+#define CYAPA_REG_MAP_SIZE  256
+
 #define PRODUCT_ID_SIZE  16
 #define QUERY_DATA_SIZE  27
 #define REG_PROTOCOL_GEN_QUERY_OFFSET  20
@@ -134,17 +147,27 @@
 #define CYAPA_OFFSET_SOFT_RESET  REG_OFFSET_COMMAND_BASE

 #define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
+#define SET_POWER_MODE_DELAY   10000  /* unit: us */
+#define SET_POWER_MODE_TRIES   5

 #define PWR_MODE_MASK   0xfc
 #define PWR_MODE_FULL_ACTIVE (0x3f << 2)
-#define PWR_MODE_IDLE        (0x05 << 2) /* default sleep time is 50 ms. */
+#define PWR_MODE_IDLE        (0x03 << 2) /* default rt suspend scanrate: 30ms */
+#define PWR_MODE_SLEEP       (0x05 << 2) /* default suspend scanrate: 50ms */
+#define PWR_MODE_BTN_ONLY    (0x01 << 2)
 #define PWR_MODE_OFF         (0x00 << 2)

+#define BTN_ONLY_MODE_NAME   "buttononly"
+#define OFF_MODE_NAME        "off"
+
 #define PWR_STATUS_MASK      0x0c
 #define PWR_STATUS_ACTIVE    (0x03 << 2)
 #define PWR_STATUS_IDLE      (0x02 << 2)
+#define PWR_STATUS_BTN_ONLY  (0x01 << 2)
 #define PWR_STATUS_OFF       (0x00 << 2)

+#define AUTOSUSPEND_DELAY   2000 /* unit : ms */
+
 /*
  * CYAPA trackpad device states.
  * Used in register 0x00, bit1-0, DeviceStatus field.
@@ -153,6 +176,26 @@
 #define CYAPA_DEV_NORMAL  0x03
 #define CYAPA_DEV_BUSY    0x01

+#define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)
+
+struct cyapa;
+typedef void (*irq_handler_func)(struct cyapa *);
+typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
+typedef int (*bl_enter_func)(struct cyapa *);
+typedef int (*bl_activate_func)(struct cyapa *);
+typedef int (*bl_verify_app_integrity_func)(struct cyapa *);
+typedef int (*bl_deactivate_func)(struct cyapa *);
+typedef int (*bl_read_fw_func)(struct cyapa *);
+typedef int (*check_fw_func)(struct cyapa *, const struct firmware *);
+typedef int (*bl_initiate_func)(struct cyapa *, const struct firmware *);
+typedef int (*update_fw_func)(struct cyapa *, const struct firmware *);
+typedef ssize_t (*show_baseline_func)(
+       struct device *, struct device_attribute *, char *);
+typedef ssize_t (*calibrate_store_func)(
+       struct device *, struct device_attribute *, const char *, size_t);
+typedef bool (*func_sort)(struct cyapa *, u8 *, int);
+typedef int (*read_raw_data_func)(struct cyapa *);
+
 enum cyapa_state {
        CYAPA_STATE_OP,
        CYAPA_STATE_BL_IDLE,
@@ -210,14 +253,41 @@ struct cyapa {
        bool irq_wake;  /* irq wake is enabled */
        bool smbus;

+       /* power mode settings */
+       u8 suspend_power_mode;
+       u16 suspend_sleep_time;
+       bool suspended;
+
        /* read from query data region. */
        char product_id[16];
+       u8 fw_maj_ver;  /* firmware major version. */
+       u8 fw_min_ver;  /* firmware minor version. */
        u8 btn_capability;
        u8 gen;
        int max_abs_x;
        int max_abs_y;
        int physical_size_x;
        int physical_size_y;
+
+       u8 gen_detecting;
+
+       atomic_t in_detecting;
+
+       u8 tmp_buf[MAX_TMP_BUF_SIZE];
+
+       check_fw_func cyapa_check_fw;
+       bl_enter_func cyapa_bl_enter;
+       bl_activate_func cyapa_bl_activate;
+       bl_initiate_func cyapa_bl_initiate;
+       update_fw_func cyapa_update_fw;
+       bl_verify_app_integrity_func cyapa_bl_verify_app_integrity;
+       bl_deactivate_func cyapa_bl_deactivate;
+       show_baseline_func cyapa_show_baseline;
+       calibrate_store_func cyapa_calibrate_store;
+       irq_handler_func cyapa_irq_handler;
+       set_power_mode_func cyapa_set_power_mode;
+       bl_read_fw_func cyapa_read_fw;
+       read_raw_data_func cyapa_read_raw_data;
 };

 static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -316,6 +386,12 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
        { CYAPA_SMBUS_BLK_HEAD, 16 },
 };

+static const char unique_str[] = "CYTRA";
+
+static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
+static void cyapa_detect(struct cyapa *cyapa);
+static void cyapa_detect_async(void *data, async_cookie_t cookie);
+
 static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
                                        u8 *values)
 {
@@ -417,88 +493,57 @@ static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
 }

 /*
- * Query device for its current operating state.
+ * Determine the Gen3 trackpad device's current operating state.
  *
  */
-static int cyapa_get_state(struct cyapa *cyapa)
+static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
 {
-       int ret;
-       u8 status[BL_STATUS_SIZE];
-
-       cyapa->state = CYAPA_STATE_NO_DEVICE;
-
-       /*
-        * Get trackpad status by reading 3 registers starting from 0.
-        * If the device is in the bootloader, this will be BL_HEAD.
-        * If the device is in operation mode, this will be the DATA regs.
-        *
-        */
-       ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
-                                      status);
-
-       /*
-        * On smbus systems in OP mode, the i2c_reg_read will fail with
-        * -ETIMEDOUT.  In this case, try again using the smbus equivalent
-        * command.  This should return a BL_HEAD indicating CYAPA_STATE_OP.
-        */
-       if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO))
-               ret = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
-
-       if (ret != BL_STATUS_SIZE)
-               goto error;
-
-       if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) {
-               switch (status[REG_OP_STATUS] & OP_STATUS_DEV) {
-               case CYAPA_DEV_NORMAL:
-               case CYAPA_DEV_BUSY:
+       /* Parse based on Gen3 characteristic regiters and bits */
+       if (reg_data[0] == 0x00 && reg_data[2] == 0x00 &&
+               (reg_data[1] == 0x11 || reg_data[1] == 0x10)) {
+               /* normal state after power on or reset,
+                * reg_data[1] == 0x11, firmware image checksum is valid.
+                * reg_data[1] == 0x10, firmware image checksum is invalid. */
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_BL_IDLE;
+               return 0;
+       } else if (reg_data[0] == 0x00 &&
+               (reg_data[1] & 0x10) == 0x10) {
+               cyapa->gen = CYAPA_GEN3;
+               if (reg_data[1] & BL_STATUS_BUSY) {
+                       cyapa->state = CYAPA_STATE_BL_BUSY;
+               } else {
+                       if ((reg_data[2] & 0x20) == 0x00)
+                               cyapa->state = CYAPA_STATE_BL_IDLE;
+                       else
+                               cyapa->state = CYAPA_STATE_BL_ACTIVE;
+               }
+               return 0;
+       } else if ((reg_data[0] & 0x80) && (reg_data[1] & 0x08)) {
+               /* normal state when running in operaitonal mode,
+                * may also not in full power state or
+                * busying in command process. */
+               if (((reg_data[1] >> 4) & 0x07) <= 5) {
+                       /* only finger number data is valid. */
+                       cyapa->gen = CYAPA_GEN3;
                        cyapa->state = CYAPA_STATE_OP;
-                       break;
-               default:
-                       ret = -EAGAIN;
-                       goto error;
+                       return 0;
                }
-       } else {
-               if (status[REG_BL_STATUS] & BL_STATUS_BUSY)
-                       cyapa->state = CYAPA_STATE_BL_BUSY;
-               else if (status[REG_BL_ERROR] & BL_ERROR_BOOTLOADING)
-                       cyapa->state = CYAPA_STATE_BL_ACTIVE;
-               else
-                       cyapa->state = CYAPA_STATE_BL_IDLE;
+       } else if (reg_data[0] == 0x0C && reg_data[1] == 0x08) {
+               /* Op state when first two registers overwritten with 0x00 */
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_OP;
+               return 0;
+       } else if (reg_data[1] & (BL_STATUS_RUNNING | BL_STATUS_BUSY)) {
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_BL_BUSY;
+               return 0;
        }

-       return 0;
-error:
-       return (ret < 0) ? ret : -EAGAIN;
+       return -EAGAIN;
 }

-/*
- * Poll device for its status in a loop, waiting up to timeout for a response.
- *
- * When the device switches state, it usually takes ~300 ms.
- * However, when running a new firmware image, the device must calibrate its
- * sensors, which can take as long as 2 seconds.
- *
- * Note: The timeout has granularity of the polling rate, which is 100 ms.
- *
- * Returns:
- *   0 when the device eventually responds with a valid non-busy state.
- *   -ETIMEDOUT if device never responds (too many -EAGAIN)
- *   < 0    other errors
- */
-static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
-{
-       int ret;
-       int tries = timeout / 100;
-
-       ret = cyapa_get_state(cyapa);
-       while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
-               msleep(100);
-               ret = cyapa_get_state(cyapa);
-       }
-       return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
-}
-
-static int cyapa_bl_deactivate(struct cyapa *cyapa)
+static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
 {
        int ret;

@@ -530,7 +575,7 @@ static int cyapa_bl_deactivate(struct cyapa *cyapa)
  *   -EAGAIN device is stuck in bootloader, b/c it has invalid firmware
  *   0       device is supported and in operational mode
  */
-static int cyapa_bl_exit(struct cyapa *cyapa)
+static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
 {
        int ret;

@@ -546,9 +591,10 @@ static int cyapa_bl_exit(struct cyapa *cyapa)
        /*
         * In addition, when a device boots for the first time after being
         * updated to new firmware, it must first calibrate its sensors, which
-        * can take up to an additional 2 seconds.
+        * can take up to an additional 2 seconds. If the device power is
+        * running low, this may take even longer.
         */
-       ret = cyapa_poll_state(cyapa, 2000);
+       ret = cyapa_poll_state(cyapa, 4000);
        if (ret < 0)
                return ret;
        if (cyapa->state != CYAPA_STATE_OP)
@@ -557,33 +603,104 @@ static int cyapa_bl_exit(struct cyapa *cyapa)
        return 0;
 }

+static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
+{
+       u8 encoded_time = pwr_mode >> 2;
+       return (encoded_time < 10) ? encoded_time * 10
+                                  : (encoded_time - 5) * 20;
+}
+
+/*
+ * cyapa_get_wait_time_for_pwr_cmd
+ *
+ * Compute the amount of time we need to wait after updating the touchpad
+ * power mode. The touchpad needs to consume the incoming power mode set
+ * command at the current clock rate.
+ */
+
+static u16 cyapa_get_wait_time_for_pwr_cmd(u8 pwr_mode)
+{
+       switch (pwr_mode) {
+       case PWR_MODE_FULL_ACTIVE: return 20;
+       case PWR_MODE_BTN_ONLY: return 20;
+       case PWR_MODE_OFF: return 20;
+       default: return cyapa_pwr_cmd_to_sleep_time(pwr_mode) + 50;
+       }
+}
+
 /*
  * Set device power mode
  *
+ * Write to the field to configure power state. Power states include :
+ *   Full : Max scans and report rate.
+ *   Idle : Report rate set by user specified time.
+ *   ButtonOnly : No scans for fingers. When the button is triggered,
+ *     a slave interrupt is asserted to notify host to wake up.
+ *   Off : Only awake for i2c commands from host. No function for button
+ *     or touch sensors.
+ *
+ * The power_mode command should conform to the following :
+ *   Full : 0x3f
+ *   Idle : Configurable from 20 to 1000ms. See note below for
+ *     cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *   ButtonOnly : 0x01
+ *   Off : 0x00
+ *
+ * Device power mode can only be set when device is in operational mode.
  */
-static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode)
+static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
+               u16 reverved)
 {
-       struct device *dev = &cyapa->client->dev;
        int ret;
        u8 power;
+       int tries = SET_POWER_MODE_TRIES;
+       u16 sleep_time;

+       /* Specific parameter for Gen4 and later trackpad devices.
+        * Avoid compile warning.
+        */
+       reverved = 0;
        if (cyapa->state != CYAPA_STATE_OP)
                return 0;

-       ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+       while (true) {
+               ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+               if (ret >= 0 || --tries < 1)
+                       break;
+               usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+       }
        if (ret < 0)
                return ret;

-       power = ret & ~PWR_MODE_MASK;
+       /*
+        * Return early if the power mode to set is the same as the current
+        * one.
+        */
+       if ((ret & PWR_MODE_MASK) == power_mode)
+               return 0;
+
+       sleep_time = cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK);
+       power = ret;
+       power &= ~PWR_MODE_MASK;
        power |= power_mode & PWR_MODE_MASK;
-       ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
-       if (ret < 0)
-               dev_err(dev, "failed to set power_mode 0x%02x err = %d\n",
-                       power_mode, ret);
+       while (true) {
+               ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
+               if (!ret || --tries < 1)
+                       break;
+               usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+       }
+
+       /*
+        * Wait for the newly set power command to go in at the previous
+        * clock speed (scanrate) used by the touchpad firmware. Not
+        * doing so before issuing the next command may result in errors
+        * depending on the command's content.
+        */
+       msleep(sleep_time);
        return ret;
 }

-static int cyapa_get_query_data(struct cyapa *cyapa)
+static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
 {
        u8 query_data[QUERY_DATA_SIZE];
        int ret;
@@ -592,10 +709,8 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
                return -EBUSY;

        ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data);
-       if (ret < 0)
-               return ret;
        if (ret != QUERY_DATA_SIZE)
-               return -EIO;
+               return (ret < 0) ? ret : -EIO;

        memcpy(&cyapa->product_id[0], &query_data[0], 5);
        cyapa->product_id[5] = '-';
@@ -604,6 +719,9 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
        memcpy(&cyapa->product_id[13], &query_data[11], 2);
        cyapa->product_id[15] = '\0';

+       cyapa->fw_maj_ver = query_data[15];
+       cyapa->fw_min_ver = query_data[16];
+
        cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK;

        cyapa->gen = query_data[20] & 0x0f;
@@ -633,30 +751,38 @@ static int cyapa_get_query_data(struct cyapa *cyapa)
  *   -EINVAL device is in operational mode, but not supported by this driver
  *   0       device is supported
  */
-static int cyapa_check_is_operational(struct cyapa *cyapa)
+static int cyapa_gen3_do_operational_check(struct cyapa *cyapa)
 {
        struct device *dev = &cyapa->client->dev;
-       static const char unique_str[] = "CYTRA";
        int ret;

-       ret = cyapa_poll_state(cyapa, 2000);
-       if (ret < 0)
-               return ret;
        switch (cyapa->state) {
        case CYAPA_STATE_BL_ACTIVE:
-               ret = cyapa_bl_deactivate(cyapa);
-               if (ret)
+               ret = cyapa_gen3_bl_deactivate(cyapa);
+               if (ret) {
+                       dev_err(dev, "failed to bl_deactivate. %d\n", ret);
                        return ret;
+               }

        /* Fallthrough state */
        case CYAPA_STATE_BL_IDLE:
-               ret = cyapa_bl_exit(cyapa);
-               if (ret)
+               ret = cyapa_gen3_bl_exit(cyapa);
+               if (ret) {
+                       dev_err(dev, "failed to bl_exit. %d\n", ret);
                        return ret;
+               }

        /* Fallthrough state */
        case CYAPA_STATE_OP:
-               ret = cyapa_get_query_data(cyapa);
+               /*
+                * Reading query data before going back to the full mode
+                * may cause problems, so we set the power mode first here.
+                */
+               ret = cyapa_gen3_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0);
+               if (ret)
+                       dev_err(dev, "%s: set full power mode failed, (%d)\n",
+                               __func__, ret);
+               ret = cyapa_gen3_get_query_data(cyapa);
                if (ret < 0)
                        return ret;

@@ -682,27 +808,25 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
        return 0;
 }

-static irqreturn_t cyapa_irq(int irq, void *dev_id)
+static void cyapa_gen3_irq_handler(struct cyapa *cyapa)
 {
-       struct cyapa *cyapa = dev_id;
-       struct device *dev = &cyapa->client->dev;
        struct input_dev *input = cyapa->input;
        struct cyapa_reg_data data;
        int i;
        int ret;
        int num_fingers;

-       if (device_may_wakeup(dev))
-               pm_wakeup_event(dev, 0);
-
        ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
-       if (ret != sizeof(data))
-               goto out;
+       if (ret != sizeof(data)) {
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }

        if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC ||
            (data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL ||
            (data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) {
-               goto out;
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
        }

        num_fingers = (data.finger_btn >> 4) & 0x0f;
@@ -724,22 +848,271 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)

        if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK)
                input_report_key(input, BTN_LEFT,
-                                data.finger_btn & OP_DATA_LEFT_BTN);
-
+                                !!(data.finger_btn & OP_DATA_LEFT_BTN));
        if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK)
                input_report_key(input, BTN_MIDDLE,
-                                data.finger_btn & OP_DATA_MIDDLE_BTN);
-
+                                !!(data.finger_btn & OP_DATA_MIDDLE_BTN));
        if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK)
                input_report_key(input, BTN_RIGHT,
-                                data.finger_btn & OP_DATA_RIGHT_BTN);
-
+                                !!(data.finger_btn & OP_DATA_RIGHT_BTN));
        input_sync(input);
+}
+
+/* Returns the number of read bytes or a negative errno code. */
+static ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len,
+                                       u8 *values)
+{
+       int ret;
+       struct i2c_client *client = cyapa->client;
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = client->addr,
+                       .flags = 0,
+                       .len = 1,
+                       .buf = &reg,
+               },
+               {
+                       .addr = client->addr,
+                       .flags = I2C_M_RD,
+                       .len = len,
+                       .buf = values,
+               },
+       };
+
+       ret = i2c_transfer(client->adapter, msgs, 2);
+
+       return (ret == 2) ? len : ((ret < 0) ? ret : -EIO);
+}
+
+/**
+ * cyapa_i2c_write - execute i2c block data write operation
+ * @cyapa: Handle to this driver
+ * @ret: Offset of the data to written in the register map
+ * @len: the data length of bytes to written.
+ * @values: Data to be written
+ *
+ * This executes returns a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
+                                        size_t len, const u8 *values)
+{
+       int ret;
+       struct i2c_client *client = cyapa->client;
+
+       cyapa->tmp_buf[0] = reg;
+       memcpy(&cyapa->tmp_buf[1], values, len);
+       ret = i2c_master_send(client, cyapa->tmp_buf, len + 1);
+
+       return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
+}
+
+static void cyapa_default_irq_handler(struct cyapa *cyapa)
+{
+       async_schedule(cyapa_detect_async, cyapa);
+}
+
+/*
+ * Check if device is operational.
+ *
+ * An operational device is responding, has exited bootloader, and has
+ * firmware supported by this driver.
+ *
+ * Returns:
+ *   -EBUSY  no device or in bootloader
+ *   -EIO    failure while reading from device
+ *   -EAGAIN device is still in bootloader
+ *           if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware
+ *   -EINVAL device is in operational mode, but not supported by this driver
+ *   0       device is supported
+ */
+static int cyapa_check_is_operational(struct cyapa *cyapa)
+{
+       int ret;
+
+       ret = cyapa_poll_state(cyapa, 4000);
+       if (ret)
+               return ret;
+
+       switch (cyapa->gen) {
+       case CYAPA_GEN3:
+               cyapa->cyapa_check_fw = NULL;
+               cyapa->cyapa_bl_enter = NULL;
+               cyapa->cyapa_bl_activate = NULL;
+               cyapa->cyapa_bl_initiate = NULL;
+               cyapa->cyapa_update_fw = NULL;
+               cyapa->cyapa_bl_verify_app_integrity = NULL;
+               cyapa->cyapa_bl_deactivate = cyapa_gen3_bl_deactivate;
+               cyapa->cyapa_show_baseline = NULL;
+               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
+               cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
+               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_raw_data = NULL;
+
+               ret = cyapa_gen3_do_operational_check(cyapa);
+               break;
+       default:
+               cyapa->gen = CYAPA_GEN_UNKNOWN;
+               cyapa->cyapa_check_fw = NULL;
+               cyapa->cyapa_bl_enter = NULL;
+               cyapa->cyapa_bl_activate = NULL;
+               cyapa->cyapa_bl_initiate = NULL;
+               cyapa->cyapa_update_fw = NULL;
+               cyapa->cyapa_bl_verify_app_integrity = NULL;
+               cyapa->cyapa_show_baseline = NULL;
+               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_irq_handler = cyapa_default_irq_handler;
+               cyapa->cyapa_set_power_mode = NULL;
+               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_raw_data = NULL;
+
+               ret = -EAGAIN;
+               break;
+       }
+
+       return ret;
+}
+
+static irqreturn_t cyapa_irq(int irq, void *dev_id)
+{
+       struct cyapa *cyapa = dev_id;
+       struct device *dev = &cyapa->client->dev;
+       struct input_dev *input = cyapa->input;
+       int length;
+
+       if (device_may_wakeup(dev))
+               pm_wakeup_event(dev, 0);
+
+       /*
+        * Don't read input if input device has not been configured.
+        * This check solves a race during probe() between irq_request()
+        * and irq_disable(), since there is no way to request an irq that is
+        * initially disabled.
+        */
+       if (!input || atomic_read(&cyapa->in_detecting))
+               goto out;
+
+       if (cyapa->cyapa_irq_handler)
+               cyapa->cyapa_irq_handler(cyapa);

 out:
        return IRQ_HANDLED;
 }

+/*
+ * Query device for its current operating state.
+ *
+ */
+static int cyapa_get_state(struct cyapa *cyapa)
+{
+       int ret;
+       u8 status[BL_STATUS_SIZE];
+       u8 cmd[32];
+       /* The i2c address of gen4 and gen5 trackpad device must be even. */
+       bool even_addr = ((cyapa->client->addr & 0x0001) == 0);
+       bool smbus = false;
+       int retires = 2;
+
+       cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+       /*
+        * Get trackpad status by reading 3 registers starting from 0.
+        * If the device is in the bootloader, this will be BL_HEAD.
+        * If the device is in operation mode, this will be the DATA regs.
+        *
+        */
+       ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
+                                      status);
+
+       /*
+        * On smbus systems in OP mode, the i2c_reg_read will fail with
+        * -ETIMEDOUT.  In this case, try again using the smbus equivalent
+        * command.  This should return a BL_HEAD indicating CYAPA_STATE_OP.
+        */
+       if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO)) {
+               if (!even_addr)
+                       ret = cyapa_read_block(cyapa,
+                                       CYAPA_CMD_BL_STATUS, status);
+               smbus = true;
+       }
+       if (ret != BL_STATUS_SIZE)
+               goto error;
+
+       /*
+        * detect trackpad protocol based on characristic registers and bits.
+        */
+       do {
+               if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
+                               cyapa->gen == CYAPA_GEN3) {
+                       cyapa->gen_detecting = CYAPA_GEN3;
+                       ret = cyapa_gen3_state_parse(cyapa,
+                                       status, BL_STATUS_SIZE);
+                       if (ret == 0)
+                               goto out_detected;
+                       cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+               }
+
+               /*
+                * cannot detect communication protocol based on current
+                * charateristic registers and bits.
+                * So write error command to do further detection.
+                * this method only valid on I2C bus.
+                * for smbus interface, it won't have overwrite issue.
+                */
+               if (!smbus) {
+                       cmd[0] = 0x00;
+                       cmd[1] = 0x00;
+                       ret = cyapa_i2c_write(cyapa, 0x00, 2, cmd);
+                       if (ret)
+                               goto error;
+
+                       msleep(50);
+
+                       ret = cyapa_i2c_read(cyapa, BL_HEAD_OFFSET,
+                                       BL_STATUS_SIZE, status);
+                       if (ret != BL_STATUS_SIZE)
+                               goto error;
+               }
+       } while (--retires > 0 && !smbus);
+
+       goto error;
+
+out_detected:
+       return 0;
+
+error:
+       return (ret < 0) ? ret : -EAGAIN;
+}
+
+/*
+ * Poll device for its status in a loop, waiting up to timeout for a response.
+ *
+ * When the device switches state, it usually takes ~300 ms.
+ * However, when running a new firmware image, the device must calibrate its
+ * sensors, which can take as long as 2 seconds.
+ *
+ * Note: The timeout has granularity of the polling rate, which is 100 ms.
+ *
+ * Returns:
+ *   0 when the device eventually responds with a valid non-busy state.
+ *   -ETIMEDOUT if device never responds (too many -EAGAIN)
+ *   < 0    other errors
+ */
+static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
+{
+       int ret;
+       int tries = timeout / 100;
+
+       ret = cyapa_get_state(cyapa);
+
+       while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
+               msleep(100);
+               ret = cyapa_get_state(cyapa);
+       }
+
+       return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
+}
+
 static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
 {
        u8 ret = CYAPA_ADAPTER_FUNC_NONE;
@@ -823,6 +1196,62 @@ err_free_device:
        return ret;
 }

+static void cyapa_detect(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       char *envp[] = {"ERROR=1", NULL};
+       int ret;
+
+       ret = cyapa_check_is_operational(cyapa);
+       if (ret == -ETIMEDOUT)
+               dev_err(dev, "no device detected, %d\n", ret);
+       else if (ret)
+               dev_err(dev, "device detected, but not operational, %d\n", ret);
+
+       if (ret) {
+               kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+               return;
+       }
+
+       if (!cyapa->input) {
+               ret = cyapa_create_input_dev(cyapa);
+               if (ret)
+                       dev_err(dev, "create input_dev instance failed, %d\n",
+                               ret);
+
+               enable_irq(cyapa->irq);
+               /*
+                * On some systems, a system crash / warm boot does not reset
+                * the device's current power mode to FULL_ACTIVE.
+                * If such an event happens during suspend, after the device
+                * has been put in a low power mode, the device will still be
+                * in low power mode on a subsequent boot, since there was
+                * never a matching resume().
+                * Handle this by always forcing full power here, when a
+                * device is first detected to be in operational mode.
+                */
+               if (cyapa->cyapa_set_power_mode) {
+                       ret = cyapa->cyapa_set_power_mode(cyapa,
+                                       PWR_MODE_FULL_ACTIVE, 0);
+                       if (ret)
+                               dev_warn(dev, "set active power failed, %d\n",
+                                               ret);
+               }
+       }
+}
+
+static void cyapa_detect_async(void *data, async_cookie_t cookie)
+{
+       struct cyapa *cyapa = (struct cyapa *)data;
+
+       if (atomic_read(&cyapa->in_detecting))
+               return;
+
+       atomic_inc(&cyapa->in_detecting);
+       cyapa_detect(cyapa);
+       atomic_dec(&cyapa->in_detecting);
+}
+
 static int cyapa_probe(struct i2c_client *client,
                       const struct i2c_device_id *dev_id)
 {
@@ -830,6 +1259,7 @@ static int cyapa_probe(struct i2c_client *client,
        u8 adapter_func;
        struct cyapa *cyapa;
        struct device *dev = &client->dev;
+       union i2c_smbus_data dummy;

        adapter_func = cyapa_check_adapter_functionality(client);
        if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) {
@@ -837,13 +1267,18 @@ static int cyapa_probe(struct i2c_client *client,
                return -EIO;
        }

+       /* Make sure there is something at this address */
+       if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
+                       I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
+               return -ENODEV;
+
        cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
        if (!cyapa) {
                dev_err(dev, "allocate memory for cyapa failed\n");
                return -ENOMEM;
        }

-       cyapa->gen = CYAPA_GEN3;
+       cyapa->gen = CYAPA_GEN_UNKNOWN;
        cyapa->client = client;
        i2c_set_clientdata(client, cyapa);
        sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr,
@@ -853,23 +1288,7 @@ static int cyapa_probe(struct i2c_client *client,
        if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
                cyapa->smbus = true;
        cyapa->state = CYAPA_STATE_NO_DEVICE;
-       ret = cyapa_check_is_operational(cyapa);
-       if (ret) {
-               dev_err(dev, "device not operational, %d\n", ret);
-               goto err_mem_free;
-       }
-
-       ret = cyapa_create_input_dev(cyapa);
-       if (ret) {
-               dev_err(dev, "create input_dev instance failed, %d\n", ret);
-               goto err_mem_free;
-       }
-
-       ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
-       if (ret) {
-               dev_err(dev, "set active power failed, %d\n", ret);
-               goto err_unregister_device;
-       }
+       cyapa->suspend_power_mode = PWR_MODE_SLEEP;

        cyapa->irq = client->irq;
        ret = request_threaded_irq(cyapa->irq,
@@ -882,12 +1301,14 @@ static int cyapa_probe(struct i2c_client *client,
                dev_err(dev, "IRQ request failed: %d\n, ", ret);
                goto err_unregister_device;
        }
+       disable_irq(cyapa->irq);

+       async_schedule(cyapa_detect_async, cyapa);
        return 0;

 err_unregister_device:
        input_unregister_device(cyapa->input);
-err_mem_free:
+       i2c_set_clientdata(client, NULL);
        kfree(cyapa);

        return ret;
@@ -899,7 +1320,10 @@ static int cyapa_remove(struct i2c_client *client)

        free_irq(cyapa->irq, cyapa);
        input_unregister_device(cyapa->input);
-       cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+
+       if (cyapa->cyapa_set_power_mode)
+               cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_OFF, 0);
+       i2c_set_clientdata(client, NULL);
        kfree(cyapa);

        return 0;
@@ -913,16 +1337,21 @@ static int cyapa_suspend(struct device *dev)
        struct cyapa *cyapa = dev_get_drvdata(dev);

        disable_irq(cyapa->irq);
+       cyapa->suspended = true;

        /*
         * Set trackpad device to idle mode if wakeup is allowed,
         * otherwise turn off.
         */
-       power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE
+       power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode
                                            : PWR_MODE_OFF;
-       ret = cyapa_set_power_mode(cyapa, power_mode);
-       if (ret < 0)
-               dev_err(dev, "set power mode failed, %d\n", ret);
+       if (cyapa->cyapa_set_power_mode) {
+               ret = cyapa->cyapa_set_power_mode(cyapa, power_mode,
+                               cyapa->suspend_sleep_time);
+               if (ret < 0)
+                       dev_err(dev, "suspend set power mode failed, %d\n",
+                                       ret);
+       }

        if (device_may_wakeup(dev))
                cyapa->irq_wake = (enable_irq_wake(cyapa->irq) == 0);
@@ -936,12 +1365,18 @@ static int cyapa_resume(struct device *dev)

        if (device_may_wakeup(dev) && cyapa->irq_wake)
                disable_irq_wake(cyapa->irq);
+       enable_irq(cyapa->irq);

-       ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
-       if (ret)
-               dev_warn(dev, "resume active power failed, %d\n", ret);
+       if (cyapa->cyapa_set_power_mode) {
+               ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
+                               cyapa->suspend_sleep_time);
+               if (ret)
+                       dev_warn(dev, "resume active power failed, %d\n", ret);
+       }

-       enable_irq(cyapa->irq);
+       async_schedule(cyapa_detect_async, cyapa);
+
+       cyapa->suspended = false;
        return 0;
 }
 #endif /* CONFIG_PM_SLEEP */
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 17605 bytes --]

^ permalink raw reply related

* [PATCH 2/6] input: cyapa: add gen5 trackpad device supported in one driver
From: Dudley Du @ 2014-04-16  8:36 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 77012 bytes --]

Add variable, macros, fucntions and interfaces to support gen5 trackpad device,
The gen5 trackpad device is different protocol with gen3 trackpad device.
And in order to keep compatible with old products and easy customer support,
these two type devices must be integrated and supported in one driver.
So this cyapa driver is rearchitecture to support multi-type devices.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 4361ee1..7b269d8 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -22,12 +22,16 @@
 #include <linux/input/mt.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/completion.h>
 #include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/unaligned/access_ok.h>

 /* APA trackpad firmware generation */
 #define CYAPA_GEN_UNKNOWN   0x00   /* unknown protocol. */
 #define CYAPA_GEN3   0x03   /* support MT-protocol B with tracking ID. */
+#define CYAPA_GEN5   0x05   /* support TrueTouch GEN5 trackpad device. */

 #define CYAPA_NAME   "Cypress APA Trackpad (cyapa)"

@@ -178,6 +182,144 @@

 #define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)

+/* mcros of Gen5 */
+#define RECORD_EVENT_NONE        0
+#define RECORD_EVENT_TOUCHDOWN  1
+#define RECORD_EVENT_DISPLACE    2
+#define RECORD_EVENT_LIFTOFF     3
+
+#define CYAPA_TSG_FLASH_MAP_BLOCK_SIZE      0x80
+#define CYAPA_TSG_IMG_FW_HDR_SIZE           13
+#define CYAPA_TSG_FW_ROW_SIZE               (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE)
+#define CYAPA_TSG_IMG_START_ROW_NUM         0x002e
+#define CYAPA_TSG_IMG_END_ROW_NUM           0x01fe
+#define CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM 0x01ff
+#define CYAPA_TSG_IMG_MAX_RECORDS           (CYAPA_TSG_IMG_END_ROW_NUM - \
+                               CYAPA_TSG_IMG_START_ROW_NUM + 1 + 1)
+#define CYAPA_TSG_IMG_READ_SIZE             (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE / 2)
+#define CYAPA_TSG_START_OF_APPLICATION      0x1700
+#define CYAPA_TSG_APP_INTEGRITY_SIZE        60
+#define CYAPA_TSG_FLASH_MAP_METADATA_SIZE   60
+#define CYAPA_TSG_BL_KEY_SIZE               8
+
+/* Macro definitions for Gen5 trackpad device. */
+#define GEN5_TOUCH_REPORT_HEAD_SIZE     7
+#define GEN5_TOUCH_REPORT_MAX_SIZE      127
+#define GEN5_BTN_REPORT_HEAD_SIZE       6
+#define GEN5_BTN_REPORT_MAX_SIZE        14
+#define GEN5_WAKEUP_EVENT_SIZE          4
+#define GEN5_RAW_DATA_HEAD_SIZE         24
+
+#define GEN5_BL_CMD_REPORT_ID           0x40
+#define GEN5_BL_RESP_REPORT_ID          0x30
+#define GEN5_APP_CMD_REPORT_ID          0x2f
+#define GEN5_APP_RESP_REPORT_ID         0x1f
+
+#define GEN5_APP_DEEP_SLEEP_REPORT_ID   0xf0
+#define GEN5_DEEP_SLEEP_RESP_LENGTH     5
+
+#define GEN5_PARAMETER_ACT_INTERVL_ID        0x4d
+#define GEN5_PARAMETER_ACT_INTERVL_SIZE      1
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_ID    0x4f
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE  2
+#define GEN5_PARAMETER_LP_INTRVL_ID          0x4c
+#define GEN5_PARAMETER_LP_INTRVL_SIZE        2
+
+#define GEN5_PARAMETER_DISABLE_PIP_REPORT    0x08
+
+#define GEN5_POWER_STATE_ACTIVE              0x01
+#define GEN5_POWER_STATE_LOOK_FOR_TOUCH      0x02
+#define GEN5_POWER_STATE_READY               0x03
+#define GEN5_POWER_STATE_IDLE                0x04
+#define GEN5_POWER_STATE_BTN_ONLY            0x05
+#define GEN5_POWER_STATE_OFF                 0x06
+
+#define GEN5_DEEP_SLEEP_STATE_MASK  0x03
+#define GEN5_DEEP_SLEEP_STATE_ON    0x00
+#define GEN5_DEEP_SLEEP_STATE_OFF   0x01
+
+#define GEN5_DEEP_SLEEP_OPCODE      0x08
+#define GEN5_DEEP_SLEEP_OPCODE_MASK 0x0f
+
+#define GEN5_POWER_READY_MAX_INTRVL_TIME  50   /* unit: ms */
+#define GEN5_POWER_IDLE_MAX_INTRVL_TIME   250  /* unit: ms */
+
+#define GEN5_CMD_REPORT_ID_OFFSET       4
+
+#define GEN5_RESP_REPORT_ID_OFFSET      2
+#define GEN5_RESP_RSVD_OFFSET           3
+#define     GEN5_RESP_RSVD_KEY          0x00
+#define GEN5_RESP_BL_SOP_OFFSET         4
+#define     GEN5_SOP_KEY                0x01  /* Start of Packet */
+#define     GEN5_EOP_KEY                0x17  /* End of Packet */
+#define GEN5_RESP_APP_CMD_OFFSET        4
+#define     GET_GEN5_CMD_CODE(reg)      ((reg) & 0x7f)
+
+#define GEN5_MIN_BL_CMD_LENGTH           13
+#define GEN5_MIN_BL_RESP_LENGTH          11
+#define GEN5_MIN_APP_CMD_LENGTH          7
+#define GEN5_MIN_APP_RESP_LENGTH         5
+#define GEN5_UNSUPPORTED_CMD_RESP_LENGTH 6
+
+#define GEN5_RESP_LENGTH_SIZE 2
+
+#define GEN5_HID_DESCRIPTOR_SIZE      32
+#define GEN5_BL_HID_REPORT_ID         0xff
+#define GEN5_APP_HID_REPORT_ID        0xf7
+#define GEN5_BL_MAX_OUTPUT_LENGTH     0x0100
+#define GEN5_APP_MAX_OUTPUT_LENGTH    0x00fe
+
+#define GEN5_BL_REPORT_DESCRIPTOR_SIZE            0x1d
+#define GEN5_BL_REPORT_DESCRIPTOR_ID              0xfe
+#define GEN5_APP_REPORT_DESCRIPTOR_SIZE           0xee
+#define GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE  0xfa
+#define GEN5_APP_REPORT_DESCRIPTOR_ID             0xf6
+
+#define GEN5_TOUCH_REPORT_ID         0x01
+#define GEN5_BTN_REPORT_ID           0x03
+#define GEN5_WAKEUP_EVENT_REPORT_ID  0x04
+#define GEN5_OLD_PUSH_BTN_REPORT_ID  0x05
+#define GEN5_PUSH_BTN_REPORT_ID      0x06
+
+#define GEN5_CMD_COMPLETE_SUCCESS(status) ((status) == 0x00)
+
+#define GEN5_BL_INITIATE_RESP_LEN            11
+#define GEN5_BL_FAIL_EXIT_RESP_LEN           11
+#define GEN5_BL_FAIL_EXIT_STATUS_CODE        0x0c
+#define GEN5_BL_VERIFY_INTEGRITY_RESP_LEN    12
+#define GEN5_BL_INTEGRITY_CHEKC_PASS         0x00
+#define GEN5_BL_BLOCK_WRITE_RESP_LEN         11
+#define GEN5_BL_READ_APP_INFO_RESP_LEN       31
+#define GEN5_CMD_CALIBRATE                   0x28
+#define CYAPA_SENSING_MODE_MUTUAL_CAP_FINE   0x00
+#define CYAPA_SENSING_MODE_SELF_CAP          0x02
+
+#define GEN5_CMD_RETRIEVE_DATA_STRUCTURE     0x24
+#define GEN5_RETRIEVE_MUTUAL_PWC_DATA        0x00
+#define GEN5_RETRIEVE_SELF_CAP_PWC_DATA      0x01
+
+#define GEN5_RETRIEVE_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_CMD_EXECUTE_PANEL_SCAN          0x2a
+#define GEN5_CMD_RETRIEVE_PANEL_SCAN         0x2b
+#define GEN5_PANEL_SCAN_MUTUAL_RAW_DATA      0x00
+#define GEN5_PANEL_SCAN_MUTUAL_BASELINE      0x01
+#define GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT     0x02
+#define GEN5_PANEL_SCAN_SELF_RAW_DATA        0x03
+#define GEN5_PANEL_SCAN_SELF_BASELINE        0x04
+#define GEN5_PANEL_SCAN_SELF_DIFFCOUNT       0x05
+
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK      0x07
+
+#define GEN5_NUMBER_OF_TOUCH_MASK    0x1f
+#define GEN5_GET_EVENT_ID(reg)       (((reg) >> 5) & 0x03)
+#define GEN5_GET_TOUCH_ID(reg)       ((reg) & 0x1f)
+
+#define GEN5_PRODUCT_FAMILY_MASK        0xf000
+#define GEN5_PRODUCT_FAMILY_TRACKPAD    0x1000
+
+#define TSG_INVALID_CMD   0xff
+
 struct cyapa;
 typedef void (*irq_handler_func)(struct cyapa *);
 typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
@@ -200,6 +342,8 @@ enum cyapa_state {
        CYAPA_STATE_OP,
        CYAPA_STATE_BL_IDLE,
        CYAPA_STATE_BL_ACTIVE,
+       CYAPA_STATE_GEN5_BL,
+       CYAPA_STATE_GEN5_APP,
        CYAPA_STATE_BL_BUSY,
        CYAPA_STATE_NO_DEVICE,
 };
@@ -242,6 +386,109 @@ struct cyapa_reg_data {
        struct cyapa_touch touches[5];
 } __packed;

+struct cyapa_gen5_touch_record {
+       /*
+        * bit 7 - 3: reserved
+        * bit 2 - 0: touch type;
+        *            0 : standard finger;
+        *            1 - 15 : reserved.
+        */
+       u8 touch_type;
+
+       /*
+        * bit 7: indicates touch liftoff status.
+        *              0 : touch is currently on the panel.
+        *              1 : touch record indicates a liftoff.
+        * bit 6 - 5: indicates an event associated with this touch instance
+        *              0 : no event
+        *              1 : touchdown
+        *              2 : significant displacement (> active distance)
+        *              3 : liftoff (record reports last known coordinates)
+        * bit 4 - 0: An arbitrary ID tag associated with a finger
+        *              to alow tracking a touch as it moves around the panel.
+        */
+       u8 touch_tip_event_id;
+
+       /* bit 7 - 0 of X-axis corrinate of the touch in pixel. */
+       u8 x_lo;
+
+       /* bit 15 - 8 of X-axis corrinate of the touch in pixel. */
+       u8 x_hi;
+
+       /* bit 7 - 0 of Y-axis corrinate of the touch in pixel. */
+       u8 y_lo;
+
+       /* bit 15 - 8 of Y-axis corrinate of the touch in pixel. */
+       u8 y_hi;
+
+       /* touch intensity in counts, pressure value. */
+       u8 z;
+
+       /*
+        * The length of the major axis of the ellipse of contact between
+        * the finger and the panel (ABS_MT_TOUCH_MAJOR).
+        */
+       u8 major_axis_len;
+
+       /*
+        * The length of the minor axis of the ellipse of contact between
+        * the finger and the panel (ABS_MT_TOUCH_MINOR).
+        */
+       u8 minor_axis_len;
+
+       /*
+        * The length of the major axis of the approaching tool.
+        * (ABS_MT_WIDTH_MAJOR)
+        */
+       u8 major_tool_len;
+
+       /*
+        * The length of the minor axis of the approaching tool.
+        * (ABS_MT_WIDTH_MINOR)
+        */
+       u8 minor_tool_len;
+
+       /*
+        * The angle between the panel vertical axis and
+        * the major axis of the contact ellipse. This value is an 8-bit
+        * signed integer. The range is -127 to +127 (corresponding to
+        * -90 degree and +90 degree respectively).
+        * The positive direction is clockwise from the vertical axis.
+        * If the ellipse of contact degenerates into a circle,
+        * orientation is reported as 0.
+        */
+       u8 orientation;
+} __packed;
+
+
+struct cyapa_gen5_report_data {
+       u8 report_head[GEN5_TOUCH_REPORT_HEAD_SIZE];
+       struct cyapa_gen5_touch_record touch_records[10];
+} __packed;
+
+struct cyapa_tsg_bin_image_head {
+       u8 head_size;  /* in bytes, including itself. */
+       u8 ttda_driver_major_version;  /* reserved as 0. */
+       u8 ttda_driver_minor_version;  /* reserved as 0. */
+       u8 fw_major_version;
+       u8 fw_minor_version;
+       u8 fw_revision_control_number[8];
+} __packed;
+
+struct cyapa_tsg_bin_image_data_record {
+       u8 flash_array_id;
+       __be16 row_number;
+       /* the number of bytes of flash data contained in this record. */
+       __be16 record_len;
+       /* the flash program data. */
+       u8 record_data[CYAPA_TSG_FW_ROW_SIZE];
+} __packed;
+
+struct cyapa_tsg_bin_image {
+       struct cyapa_tsg_bin_image_head image_head;
+       struct cyapa_tsg_bin_image_data_record records[0];
+} __packed;
+
 /* The main device structure */
 struct cyapa {
        enum cyapa_state state;
@@ -256,6 +503,8 @@ struct cyapa {
        /* power mode settings */
        u8 suspend_power_mode;
        u16 suspend_sleep_time;
+       u8 real_power_mode;
+       u16 real_sleep_time;
        bool suspended;

        /* read from query data region. */
@@ -269,10 +518,33 @@ struct cyapa {
        int physical_size_x;
        int physical_size_y;

-       u8 gen_detecting;
+       /* used in ttsp and truetouch based trackpad devices. */
+       u8 x_origin;  /* X Axis Origin: 0 = left side; 1 = rigth side. */
+       u8 y_origin;  /* Y Axis Origin: 0 = top; 1 = bottom. */
+       int electrodes_x;  /* Number of electrodes on the X Axis*/
+       int electrodes_y;  /* Number of electrodes on the Y Axis*/
+       int electrodes_rx;  /* Number of Rx electrodes */
+       int max_z;

+       u8 gen_detecting;
+       u8 in_progress_cmd;
+       func_sort resp_sort_func;
+       u8 *resp_data;
+       int *resp_len;
+
+       /* trackpad is ready for next command */
+       struct completion cmd_ready;
+       atomic_t cmd_issued;
        atomic_t in_detecting;

+       /* record irq disabled/enable state. */
+       struct mutex irq_state_lock;
+       bool irq_enabled;
+       bool prev_irq_enabled;
+       struct mutex cmd_lock;
+
+       /* temple buffer to read all data out. */
+       u8 tmp_irq_buf[MAX_TMP_BUF_SIZE];
        u8 tmp_buf[MAX_TMP_BUF_SIZE];

        check_fw_func cyapa_check_fw;
@@ -288,6 +560,8 @@ struct cyapa {
        set_power_mode_func cyapa_set_power_mode;
        bl_read_fw_func cyapa_read_fw;
        read_raw_data_func cyapa_read_raw_data;
+
+       struct cyapa_tsg_bin_image_head fw_img_head;
 };

 static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -392,6 +666,71 @@ static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
 static void cyapa_detect(struct cyapa *cyapa);
 static void cyapa_detect_async(void *data, async_cookie_t cookie);

+void cyapa_enable_irq(struct cyapa *cyapa)
+{
+       mutex_lock(&cyapa->irq_state_lock);
+       if (!cyapa->irq_enabled)
+               enable_irq(cyapa->irq);
+       cyapa->irq_enabled = true;
+       cyapa->prev_irq_enabled = true;
+       mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq(struct cyapa *cyapa)
+{
+       mutex_lock(&cyapa->irq_state_lock);
+       if (cyapa->irq_enabled)
+               disable_irq(cyapa->irq);
+       cyapa->irq_enabled = false;
+       cyapa->prev_irq_enabled = false;
+       mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_enable_irq_save(struct cyapa *cyapa)
+{
+       mutex_lock(&cyapa->irq_state_lock);
+       if (!cyapa->irq_enabled) {
+               enable_irq(cyapa->irq);
+               cyapa->irq_enabled = true;
+       }
+       mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq_save(struct cyapa *cyapa)
+{
+       mutex_lock(&cyapa->irq_state_lock);
+       if (cyapa->irq_enabled) {
+               disable_irq(cyapa->irq);
+               cyapa->irq_enabled = false;
+       }
+       mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_irq_restore(struct cyapa *cyapa)
+{
+       mutex_lock(&cyapa->irq_state_lock);
+       if (cyapa->irq_enabled != cyapa->prev_irq_enabled) {
+               if (cyapa->prev_irq_enabled) {
+                       enable_irq(cyapa->irq);
+                       cyapa->irq_enabled = true;
+               } else {
+                       disable_irq(cyapa->irq);
+                       cyapa->irq_enabled = false;
+               }
+       }
+       mutex_unlock(&cyapa->irq_state_lock);
+}
+
+bool cyapa_is_irq_enabled(struct cyapa *cyapa)
+{
+       bool enabled;
+
+       mutex_lock(&cyapa->irq_state_lock);
+       enabled = cyapa->irq_enabled;
+       mutex_unlock(&cyapa->irq_state_lock);
+       return enabled;
+}
+
 static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
                                        u8 *values)
 {
@@ -603,6 +942,33 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
        return 0;
 }

+/*
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *
+ * These are helper functions that convert to and from integer idle
+ * times and register settings to write to the PowerMode register.
+ * The trackpad supports between 20ms to 1000ms scan intervals.
+ * The time will be increased in increments of 10ms from 20ms to 100ms.
+ * From 100ms to 1000ms, time will be increased in increments of 20ms.
+ *
+ * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is:
+ *   Idle_Command = Idle Time / 10;
+ * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is:
+ *   Idle_Command = Idle Time / 20 + 5;
+ */
+static u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time)
+{
+       if (sleep_time < 20)
+               sleep_time = 20;     /* minimal sleep time. */
+       else if (sleep_time > 1000)
+               sleep_time = 1000;   /* maximal sleep time. */
+
+       if (sleep_time < 100)
+               return ((sleep_time / 10) << 2) & PWR_MODE_MASK;
+       else
+               return ((sleep_time / 20 + 5) << 2) & PWR_MODE_MASK;
+}
+
 static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
 {
        u8 encoded_time = pwr_mode >> 2;
@@ -734,6 +1100,8 @@ static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
        cyapa->physical_size_y =
                ((query_data[24] & 0x0f) << 8) | query_data[26];

+       cyapa->max_z = 255;
+
        return 0;
 }

@@ -906,6 +1274,1209 @@ static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
        return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
 }

+
+/*******************************************************************
+ * Functions defined for Gen5 trackapd device.
+ *******************************************************************/
+
+/* Return negative errno, or else the number of bytes read. */
+static ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+       int ret;
+
+       if (size == 0)
+               return 0;
+
+       if (!buf || size > CYAPA_REG_MAP_SIZE)
+               return -EINVAL;
+
+       ret = i2c_master_recv(cyapa->client, buf, size);
+
+       if (ret != size)
+               return (ret < 0) ? ret : -EIO;
+
+       return size;
+}
+
+/**
+ * Return a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+       int ret;
+
+       if (!buf || !size)
+               return -EINVAL;
+
+       ret = i2c_master_send(cyapa->client, buf, size);
+
+       if (ret != size)
+               return (ret < 0) ? ret : -EIO;
+
+       return 0;
+}
+
+static int cyapa_do_i2c_pip_cmd_irq_sync(
+               struct cyapa *cyapa,
+               u8 *cmd, size_t cmd_len,
+               unsigned long timeout)
+{
+       int ret;
+
+       /* wait for interrupt to set ready completion */
+       init_completion(&cyapa->cmd_ready);
+
+       atomic_inc(&cyapa->cmd_issued);
+       ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+       if (ret) {
+               atomic_dec(&cyapa->cmd_issued);
+               return (ret < 0) ? ret : -EIO;
+       }
+
+       /* wait for interrupt to indicate command is completed. */
+       timeout = wait_for_completion_timeout(&cyapa->cmd_ready,
+                               msecs_to_jiffies(timeout));
+       if (timeout == 0) {
+               atomic_dec(&cyapa->cmd_issued);
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+/**
+ * this function is aimed to dump all not read data in Gen5 trackpad
+ * before send any command, otherwise, the interrupt line will be blocked.
+ */
+static int cyapa_empty_pip_output_data(struct cyapa *cyapa,
+               u8 *buf, int *len, func_sort func)
+{
+       int ret;
+       int length;
+       int report_count;
+       int empty_count;
+       int buf_len;
+
+       buf_len = 0;
+       if (len) {
+               buf_len = (*len < MAX_TMP_BUF_SIZE) ? *len : MAX_TMP_BUF_SIZE;
+               *len = 0;
+       }
+
+       report_count = 8;  /* max 7 pending data before command response data */
+       empty_count = 0;
+       do {
+               /*
+                * Depnding on testing in cyapa driver, there are max 5 "02 00"
+                * packets between two valid bufferred data report in firmware.
+                * So in order to dump all buffered data out and
+                * make interrupt line release for reassert again,
+                * we must set the empty_count check value bigger than 5 to
+                * make it work. Otherwise, in some situation,
+                * the interrupt line may unable to reactive again,
+                * which will cause trackpad device unable to
+                * report data any more.
+                * for example, it may happen in EFT and ESD testing.
+                */
+               if (empty_count > 5)
+                       return 0;
+
+               ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+                               GEN5_RESP_LENGTH_SIZE);
+               if (ret < 0)
+                       return ret;
+
+               length = get_unaligned_le16(cyapa->tmp_buf);
+               if (length == GEN5_RESP_LENGTH_SIZE) {
+                       empty_count++;
+                       continue;
+               } else if (length > MAX_TMP_BUF_SIZE) {
+                       /* should not happen */
+                       return -EINVAL;
+               } else if (length == 0) {
+                       /* application or bootloader launch data polled out. */
+                       length = GEN5_RESP_LENGTH_SIZE;
+                       if (buf && buf_len && func &&
+                               func(cyapa, cyapa->tmp_buf, length)) {
+                               length = min(buf_len, length);
+                               memcpy(buf, cyapa->tmp_buf, length);
+                               *len = length;
+                               /* response found, success. */
+                               return 0;
+                       } else {
+                               continue;
+                       }
+               }
+
+               ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+               if (ret < 0)
+                       return ret;
+
+               report_count--;
+               empty_count = 0;
+               length = get_unaligned_le16(cyapa->tmp_buf);
+               if (length <= GEN5_RESP_LENGTH_SIZE) {
+                       empty_count++;
+               } else if (buf && buf_len && func &&
+                       func(cyapa, cyapa->tmp_buf, length)) {
+                       length = min(buf_len, length);
+                       memcpy(buf, cyapa->tmp_buf, length);
+                       *len = length;
+                       /* response found, success. */
+                       return 0;
+               }
+
+               ret = -EINVAL;
+       } while (report_count);
+
+       return ret;
+}
+
+static int cyapa_i2c_pip_cmd_irq_sync(
+               struct cyapa *cyapa,
+               u8 *cmd, int cmd_len,
+               u8 *resp_data, int *resp_len,
+               unsigned long timeout,
+               func_sort func)
+{
+       int ret;
+       int tries;
+       int length;
+
+       if (!cmd || !cmd_len)
+               return -EINVAL;
+
+       mutex_lock(&cyapa->cmd_lock);
+
+       cyapa->resp_sort_func = func;
+       cyapa->resp_data = resp_data;
+       cyapa->resp_len = resp_len;
+
+       if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+                       cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+               /* application command */
+               cyapa->in_progress_cmd = cmd[6] & 0x7f;
+       } else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+                       cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+               /* bootloader command */
+               cyapa->in_progress_cmd = cmd[7];
+       }
+
+       /* send command data, wait and read output response data's length. */
+       if (cyapa_is_irq_enabled(cyapa)) {
+               ret = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+                                                       timeout);
+               if (ret == -ETIMEDOUT && resp_data &&
+                               resp_len && *resp_len != 0 && func) {
+                       /*
+                        * for some old version with some unknown reasons,
+                        * there was no interrupt for the command response data,
+                        * so need to poll here to try to get the response data.
+                        */
+                       ret = cyapa_empty_pip_output_data(cyapa,
+                                       resp_data, resp_len, func);
+                       if (ret || *resp_len == 0)
+                               ret = ret ? ret : -ETIMEDOUT;
+               }
+       } else {
+               ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+               if (ret) {
+                       ret = ret < 0 ? ret : -EIO;
+                       goto out;
+               }
+
+               tries = timeout / 5;
+               length = *resp_len;
+               if (resp_data && resp_len && length != 0 && func) {
+                       do {
+                               usleep_range(3000, 5000);
+                               *resp_len = length;
+                               ret = cyapa_empty_pip_output_data(cyapa,
+                                               resp_data, resp_len, func);
+                               if (ret || *resp_len == 0)
+                                       continue;
+                               else
+                                       break;
+                       } while (--tries > 0);
+                       if ((ret || *resp_len == 0) || tries <= 0)
+                               ret = ret ? ret : -ETIMEDOUT;
+               }
+       }
+
+out:
+       cyapa->resp_sort_func = NULL;
+       cyapa->resp_data = NULL;
+       cyapa->resp_len = NULL;
+       cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
+       mutex_unlock(&cyapa->cmd_lock);
+       return ret;
+}
+
+bool cyapa_gen5_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa,
+               u8 *data, int len)
+{
+       if (!data || len < GEN5_MIN_BL_RESP_LENGTH)
+               return false;
+
+       /* bootloader input report id 30h */
+       if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_RESP_REPORT_ID &&
+                       data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+                       data[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY)
+               return true;
+
+       return false;
+}
+
+bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+               u8 *data, int len)
+{
+       int resp_len;
+
+       if (!data || len < GEN5_MIN_APP_RESP_LENGTH)
+               return false;
+
+       if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID &&
+                       data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) {
+               resp_len = get_unaligned_le16(&data[0]);
+               if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+                       resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+                       data[5] == cyapa->in_progress_cmd) {
+                       /* unsupported command code */
+                       return false;
+               } else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+                               cyapa->in_progress_cmd) {
+                       /* correct command response received */
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+bool cyapa_gen5_sort_application_launch_data(struct cyapa *cyapa,
+               u8 *buf, int len)
+{
+       if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+               return false;
+
+       if (buf[0] == 0 && buf[1] == 0)
+               return true;
+
+       return false;
+}
+
+static bool cyapa_gen5_sort_hid_descriptor_data(struct cyapa *cyapa,
+               u8 *buf, int len)
+{
+       int resp_len;
+       int max_output_len;
+
+       /* check hid descriptor. */
+       if (len != GEN5_HID_DESCRIPTOR_SIZE)
+               return false;
+
+       resp_len = get_unaligned_le16(&buf[0]);
+       max_output_len = get_unaligned_le16(&buf[16]);
+       if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+               if (buf[2] == GEN5_BL_HID_REPORT_ID &&
+                               max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+                       /* BL mode HID Descriptor */
+                       return true;
+               } else if (buf[2] == GEN5_APP_HID_REPORT_ID &&
+                               max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+                       /* APP mode HID Descriptor */
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+static bool cyapa_gen5_sort_deep_sleep_data(struct cyapa *cyapa,
+               u8 *buf, int len)
+{
+       if (len == GEN5_DEEP_SLEEP_RESP_LENGTH &&
+               buf[2] == GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+               (buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+                       GEN5_DEEP_SLEEP_OPCODE)
+               return true;
+       return false;
+}
+
+static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+       int ret;
+       int length;
+       u8 cmd[2];
+       u8 resp_data[32];
+       int max_output_len;
+
+       /* Parse based on Gen5 characteristic regiters and bits */
+       length = get_unaligned_le16(&reg_data[0]);
+       if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+               /* dump all buffered data firstly, specific for the situation
+                * that when trackpad is just power on the cyapa go here. */
+               cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+               memset(resp_data, 0, sizeof(resp_data));
+               ret = cyapa_i2c_pip_read(cyapa, resp_data, 3);
+               if (ret != 3)
+                       return -EAGAIN;
+
+               length = get_unaligned_le16(&resp_data[0]);
+               if (length == GEN5_RESP_LENGTH_SIZE) {
+                       /* normal state of Gen5 with no data to respose */
+                       cyapa->gen = CYAPA_GEN5;
+
+                       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+                       /* read HID Description from trackpad device */
+                       cmd[0] = 0x01;
+                       cmd[1] = 0x00;
+                       length = GEN5_HID_DESCRIPTOR_SIZE;
+                       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                                       cmd, 2,
+                                       resp_data, &length,
+                                       300,
+                                       cyapa_gen5_sort_hid_descriptor_data);
+                       if (ret)
+                               return -EAGAIN;
+
+                       length = get_unaligned_le16(&resp_data[0]);
+                       max_output_len = get_unaligned_le16(&resp_data[16]);
+                       if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+                                       length == GEN5_RESP_LENGTH_SIZE) &&
+                               resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+                               max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+                               /* BL mode HID Description read */
+                               cyapa->state = CYAPA_STATE_GEN5_BL;
+                               return 0;
+                       } else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+                                       length == GEN5_RESP_LENGTH_SIZE) &&
+                               resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+                               max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+                               /* APP mode HID Description read */
+                               cyapa->state = CYAPA_STATE_GEN5_APP;
+                               return 0;
+                       } else {
+                               /* should not happen!!! */
+                               cyapa->state = CYAPA_STATE_NO_DEVICE;
+                               return -EAGAIN;
+                       }
+               }
+       } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+                       (reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+                               reg_data[2] == GEN5_APP_HID_REPORT_ID)) {
+               /* 0x20 0x00 0xF7 is Gen5 Application HID Description Header;
+                * 0x20 0x00 0xFF is Gen5 Booloader HID Description Header.
+                *
+                * must read Report Description content through out,
+                * otherwise Gen5 trackpad cannot reponse next command
+                * or report any touch or button data.
+                */
+               ret = cyapa_i2c_pip_read(cyapa, resp_data,
+                               GEN5_HID_DESCRIPTOR_SIZE);
+               if (ret != GEN5_HID_DESCRIPTOR_SIZE)
+                       return -EAGAIN;
+               length = get_unaligned_le16(&resp_data[0]);
+               max_output_len = get_unaligned_le16(&resp_data[16]);
+               if (length == GEN5_RESP_LENGTH_SIZE) {
+                       if (reg_data[2] == GEN5_BL_HID_REPORT_ID) {
+                               /* BL mode HID Description has been previously
+                                * read out */
+                               cyapa->gen = CYAPA_GEN5;
+                               cyapa->state = CYAPA_STATE_GEN5_BL;
+                               return 0;
+                       } else {
+                               /* APP mode HID Description has been previously
+                                * read out */
+                               cyapa->gen = CYAPA_GEN5;
+                               cyapa->state = CYAPA_STATE_GEN5_APP;
+                               return 0;
+                       }
+               } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+                               resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+                               max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+                       /* BL mode HID Description read */
+                       cyapa->gen = CYAPA_GEN5;
+                       cyapa->state = CYAPA_STATE_GEN5_BL;
+                       return 0;
+               } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+                               resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+                               max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+                       /* APP mode HID Description read */
+                       cyapa->gen = CYAPA_GEN5;
+                       cyapa->state = CYAPA_STATE_GEN5_APP;
+                       return 0;
+               } else {
+                       /* should not happen!!! */
+                       cyapa->state = CYAPA_STATE_NO_DEVICE;
+                       return -EAGAIN;
+               }
+       } else if ((length == GEN5_APP_REPORT_DESCRIPTOR_SIZE ||
+                       length == GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE) &&
+                       reg_data[2] == GEN5_APP_REPORT_DESCRIPTOR_ID) {
+               /* 0xEE 0x00 0xF6 is Gen5 APP Report Description header. */
+               cyapa->gen = CYAPA_GEN5;
+               cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+               /*
+                * must read Report Description content through out,
+                * otherwise Gen5 trackpad cannot reponse next command
+                * or report any touch or button data.
+                */
+
+               cyapa->state = CYAPA_STATE_GEN5_APP;
+               return 0;
+       } else if (length == GEN5_BL_REPORT_DESCRIPTOR_SIZE &&
+                       reg_data[2] == GEN5_BL_REPORT_DESCRIPTOR_ID) {
+               /* 0x1D 0x00 0xFE is Gen5 BL Report Descriptior header. */
+               cyapa->gen = CYAPA_GEN5;
+               cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+               /*
+                * must read Report Description content through out,
+                * otherwise Gen5 trackpad cannot reponse next command
+                * or report any touch or button data.
+                */
+               ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+                               GEN5_BL_REPORT_DESCRIPTOR_SIZE);
+               if (ret != GEN5_BL_REPORT_DESCRIPTOR_SIZE)
+                       return -EAGAIN;
+
+               cyapa->state = CYAPA_STATE_GEN5_BL;
+               return 0;
+       } else if (reg_data[2] == GEN5_TOUCH_REPORT_ID ||
+                       reg_data[2] == GEN5_BTN_REPORT_ID ||
+                       reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+                       reg_data[2] == GEN5_PUSH_BTN_REPORT_ID ||
+                       reg_data[2] == GEN5_WAKEUP_EVENT_REPORT_ID) {
+               ret = 0;
+               length = get_unaligned_le16(&reg_data[0]);
+               switch (reg_data[2]) {
+               case GEN5_TOUCH_REPORT_ID:
+                       if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+                               length > GEN5_TOUCH_REPORT_MAX_SIZE)
+                               ret = -EINVAL;
+                       break;
+               case GEN5_BTN_REPORT_ID:
+               case GEN5_OLD_PUSH_BTN_REPORT_ID:
+               case GEN5_PUSH_BTN_REPORT_ID:
+                       if (length < GEN5_BTN_REPORT_HEAD_SIZE ||
+                               length > GEN5_BTN_REPORT_MAX_SIZE)
+                               ret = -EINVAL;
+                       break;
+               case GEN5_WAKEUP_EVENT_REPORT_ID:
+                       if (length != GEN5_WAKEUP_EVENT_SIZE)
+                               ret = -EINVAL;
+                       break;
+               }
+
+               if (ret < 0)
+                       return -EAGAIN;
+
+               cyapa->gen = CYAPA_GEN5;
+               cyapa->state = CYAPA_STATE_GEN5_APP;
+               return 0;
+       } else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+                       reg_data[2] == GEN5_APP_RESP_REPORT_ID) {
+               /*
+                * must read report data through out,
+                * otherwise Gen5 trackpad cannot reponse next command
+                * or report any touch or button data.
+                */
+               length = get_unaligned_le16(reg_data);
+               ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+               if (ret != length)
+                       return -EAGAIN;
+
+               if (length == GEN5_RESP_LENGTH_SIZE) {
+                       /* previous command has read the data through out. */
+                       if (reg_data[2] == GEN5_BL_RESP_REPORT_ID) {
+                               /* Gen5 BL command response data detected */
+                               cyapa->gen = CYAPA_GEN5;
+                               cyapa->state = CYAPA_STATE_GEN5_BL;
+                       } else {
+                               /* Gen5 APP command response data detected */
+                               cyapa->gen = CYAPA_GEN5;
+                               cyapa->state = CYAPA_STATE_GEN5_APP;
+                       }
+               } else if (cyapa->tmp_buf[2] == GEN5_BL_RESP_REPORT_ID &&
+                               cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY &&
+                               cyapa->tmp_buf[4] == GEN5_SOP_KEY &&
+                               cyapa->tmp_buf[length - 1] == GEN5_EOP_KEY) {
+                       /* Gen5 BL command response data detected */
+                       cyapa->gen = CYAPA_GEN5;
+                       cyapa->state = CYAPA_STATE_GEN5_BL;
+               } else if (cyapa->tmp_buf[2] == GEN5_APP_RESP_REPORT_ID &&
+                               cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY) {
+                       /* Gen5 APP command response data detected */
+                       cyapa->gen = CYAPA_GEN5;
+                       cyapa->state = CYAPA_STATE_GEN5_APP;
+               } else {
+                       /* should not happen!!! */
+                       cyapa->state = CYAPA_STATE_NO_DEVICE;
+               }
+
+               if (cyapa->state == CYAPA_STATE_NO_DEVICE)
+                       return -EAGAIN;
+               return 0;
+       }
+
+       return -EAGAIN;
+}
+
+bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+{
+       if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+               return false;
+
+       if (buf[0] == 0 && buf[1] == 0)
+               return true;
+
+       /* exit bootloader failed for some reason. */
+       if (len == GEN5_BL_FAIL_EXIT_RESP_LEN &&
+                       buf[2] == GEN5_BL_RESP_REPORT_ID &&
+                       buf[3] == GEN5_RESP_RSVD_KEY &&
+                       buf[4] == GEN5_SOP_KEY &&
+                       buf[10] == GEN5_EOP_KEY)
+               return true;
+
+       return false;
+}
+static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
+{
+       int ret;
+       u8 resp_data[11];
+       int resp_len;
+       u8 bl_gen5_bl_exit[] = { 0x04, 0x00,
+               0x0B, 0x00, 0x40, 0x00, 0x01, 0x3b, 0x00, 0x00,
+               0x20, 0xc7, 0x17
+       };
+
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       bl_gen5_bl_exit, sizeof(bl_gen5_bl_exit),
+                       resp_data, &resp_len,
+                       5000, cyapa_gen5_sort_bl_exit_data);
+       if (ret)
+               return ret;
+
+       if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+                       resp_data[2] == GEN5_BL_RESP_REPORT_ID)
+               return -EAGAIN;
+
+       if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+               return 0;
+
+       return -EAGAIN;
+}
+
+static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
+{
+       int ret;
+       u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
+       u8 resp_data[6];
+       int resp_len;
+
+       cmd[7] = power_state;
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x08 ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return ret < 0 ? ret : -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+               u8 parameter_id, u16 interval_time)
+{
+       int ret;
+       u8 cmd[13];
+       int cmd_len;
+       u8 resp_data[7];
+       int resp_len;
+       u8 parameter_size;
+
+       switch (parameter_id) {
+       case GEN5_PARAMETER_ACT_INTERVL_ID:
+               parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+               break;
+       case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+               parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+               break;
+       case GEN5_PARAMETER_LP_INTRVL_ID:
+               parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+               break;
+       default:
+               return -EINVAL;
+       }
+       cmd_len = 7 + parameter_size;  /* not incuding 2 bytes address */
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       put_unaligned_le16(cmd_len, &cmd[2]);
+       cmd[4] = 0x2f;
+       cmd[5] = 0x00;
+       cmd[6] = 0x06; /* set parameter command code */
+       cmd[7] = parameter_id;
+       cmd[8] = parameter_size;
+       put_unaligned_le16(interval_time, &cmd[9]);
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len + 2,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+                       resp_data[5] != parameter_id ||
+                       resp_data[6] != parameter_size)
+               return ret < 0 ? ret : -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen5_get_interval_time(struct cyapa *cyapa,
+               u8 parameter_id, u16 *interval_time)
+{
+       int ret;
+       u8 cmd[8];
+       u8 resp_data[11];
+       int resp_len;
+       u8 parameter_size;
+       u16 mask, i;
+
+       *interval_time = 0;
+       switch (parameter_id) {
+       case GEN5_PARAMETER_ACT_INTERVL_ID:
+               parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+               break;
+       case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+               parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+               break;
+       case GEN5_PARAMETER_LP_INTRVL_ID:
+               parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       cmd[2] = 0x06;
+       cmd[3] = 0x00;
+       cmd[4] = 0x2f;
+       cmd[5] = 0x00;
+       cmd[6] = 0x05; /* get parameter command code */
+       cmd[7] = parameter_id;
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x05 ||
+                       resp_data[5] != parameter_id ||
+                       resp_data[6] == 0)
+               return ret < 0 ? ret : -EINVAL;
+
+       mask = 0;
+       for (i = 0; i < parameter_size; i++)
+               mask |= (0xff << (i * 8));
+       *interval_time = get_unaligned_le16(&resp_data[7]) & mask;
+
+       return 0;
+}
+
+static int cyapa_gen5_disable_pip_report(struct cyapa *cyapa)
+{
+       int ret;
+       u8 cmd[10];
+       u8 resp_data[7];
+       int resp_len;
+
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       put_unaligned_le16(8, &cmd[2]);
+       cmd[4] = 0x2f;
+       cmd[5] = 0x00;
+       cmd[6] = 0x06; /* set parameter command code */
+       cmd[7] = GEN5_PARAMETER_DISABLE_PIP_REPORT;
+       cmd[8] = 0x01;
+       cmd[9] = 0x01;
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, 10,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+                       resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT ||
+                       resp_data[6] != 0x01)
+               return ret < 0 ? ret : -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen5_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+       int ret;
+       u8 cmd[4] = { 0x05, 0x00, 0x00, 0x08};
+       u8 resp_data[5];
+       int resp_len;
+
+       cmd[0] = 0x05;
+       cmd[1] = 0x00;
+       cmd[2] = state & GEN5_DEEP_SLEEP_STATE_MASK;
+       cmd[3] = 0x08;
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       100, cyapa_gen5_sort_deep_sleep_data);
+       if (ret || ((resp_data[3] & GEN5_DEEP_SLEEP_STATE_MASK) != state))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
+               u8 power_mode, u16 sleep_time)
+{
+       struct device *dev = &cyapa->client->dev;
+       int ret;
+       u8 power_state;
+
+       if (cyapa->state != CYAPA_STATE_GEN5_APP)
+               return 0;
+
+       /* dump all the report data before do power mode commmands. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       switch (power_mode) {
+       case PWR_MODE_OFF:
+       case PWR_MODE_FULL_ACTIVE:
+       case PWR_MODE_BTN_ONLY:
+               if (power_mode == cyapa->real_power_mode)
+                       return 0;
+
+               if (power_mode == PWR_MODE_OFF) {
+                       ret = cyapa_gen5_deep_sleep(cyapa,
+                                       GEN5_DEEP_SLEEP_STATE_OFF);
+                       if (ret) {
+                               dev_err(dev, "enter deep sleep fail, (%d)\n",
+                                       ret);
+                               return ret;
+                       }
+
+                       cyapa->real_power_mode = PWR_MODE_OFF;
+                       return 0;
+               }
+               break;
+       default:
+               if (sleep_time == cyapa->real_sleep_time &&
+                       power_mode == cyapa->real_power_mode)
+                       return 0;
+       }
+
+       if (cyapa->real_power_mode == PWR_MODE_OFF) {
+               ret = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+               if (ret) {
+                       dev_err(dev, "deep sleep wake fail, (%d)\n", ret);
+                       return ret;
+               }
+               /* set current power state in driver. */
+               cyapa->real_power_mode = cyapa->suspend_power_mode;
+               cyapa->real_sleep_time = cyapa->suspend_sleep_time;
+       }
+
+       if (power_mode == PWR_MODE_FULL_ACTIVE) {
+               ret = cyapa_gen5_change_power_state(cyapa,
+                               GEN5_POWER_STATE_ACTIVE);
+               if (ret) {
+                       dev_err(dev, "change to active fail, (%d)\n", ret);
+                       return ret;
+               }
+
+               cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+       } else if (power_mode == PWR_MODE_BTN_ONLY) {
+               ret = cyapa_gen5_change_power_state(cyapa,
+                               GEN5_POWER_STATE_BTN_ONLY);
+               if (ret) {
+                       dev_err(dev, "fail change to active, (%d)\n", ret);
+                       return ret;
+               }
+
+               cyapa->real_power_mode = PWR_MODE_BTN_ONLY;
+       } else {
+               /* continue to change power mode even failed to set
+                * interval time, it won't affect the power mode change. */
+               cyapa_gen5_set_interval_time(cyapa,
+                       GEN5_PARAMETER_LP_INTRVL_ID, sleep_time);
+
+               if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
+                       power_state = GEN5_POWER_STATE_READY;
+               else
+                       power_state = GEN5_POWER_STATE_IDLE;
+               ret = cyapa_gen5_change_power_state(cyapa, power_state);
+               if (ret) {
+                       dev_err(dev, "set power state %d fail, (%d)\n",
+                               power_state, ret);
+                       cyapa_gen5_set_interval_time(cyapa,
+                                       GEN5_PARAMETER_LP_INTRVL_ID,
+                                       cyapa->real_sleep_time);
+                       return ret;
+               }
+
+               /* disable pip report for a little time, firmware will
+                * re-enable it automatically. It's used to fix the issue
+                * that trackpad unable to report signal to wake system up
+                * in the special situation that system is in suspending, and
+                * at the same time, user touch trackpad to wake system up.
+                * This function can avoid the data to be buffured when system
+                * is suspending which may cause interrput line unable to be
+                * asserted again. */
+               cyapa_gen5_disable_pip_report(cyapa);
+
+               cyapa->real_power_mode =
+                               cyapa_sleep_time_to_pwr_cmd(sleep_time);
+               cyapa->real_sleep_time = sleep_time;
+       }
+
+       return ret;
+}
+
+static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
+               u8 *buf, int len)
+{
+       /* check the report id and command code */
+       if (buf[2] == GEN5_APP_RESP_REPORT_ID &&
+                       GET_GEN5_CMD_CODE(buf[4]) == 0x02)
+               return true;
+
+       return false;
+}
+
+static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
+{
+       int ret;
+       u8 cmd[16];
+       int cmd_len;
+       u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+       int resp_len;
+
+       /* read application information. */
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       cmd[2] = 0x0b;
+       cmd[3] = 0x00;
+       cmd[4] = 0x40;
+       cmd[5] = 0x00;
+       cmd[6] = GEN5_SOP_KEY;
+       cmd[7] = 0x3c;  /* read application information command code */
+       cmd[8] = 0x00;
+       cmd[9] = 0x00;
+       cmd[10] = 0xb0;
+       cmd[11] = 0x42;
+       cmd[12] = GEN5_EOP_KEY;
+       cmd_len = 13;
+       resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, cmd_len,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+       if (ret || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+                       resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return (ret < 0) ? ret : -EIO;
+
+       memcpy(&cyapa->product_id[0], &resp_data[8], 5);
+       cyapa->product_id[5] = '-';
+       memcpy(&cyapa->product_id[6], &resp_data[13], 6);
+       cyapa->product_id[12] = '-';
+       memcpy(&cyapa->product_id[13], &resp_data[19], 2);
+       cyapa->product_id[15] = '\0';
+
+       cyapa->fw_maj_ver = resp_data[22];
+       cyapa->fw_min_ver = resp_data[23];
+
+       return 0;
+}
+
+static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
+{
+       int ret;
+       u8 resp_data[71];
+       int resp_len;
+       u8 get_system_information[] = {
+               0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+       };
+       u16 product_family;
+
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       get_system_information, sizeof(get_system_information),
+                       resp_data, &resp_len,
+                       2000, cyapa_gen5_sort_system_info_data);
+       if (ret || resp_len < sizeof(resp_data))
+               return ret;
+
+       cyapa->fw_img_head.head_size =
+               sizeof(struct cyapa_tsg_bin_image_head) - 1;
+       memcpy(&cyapa->fw_img_head.ttda_driver_major_version,
+               &resp_data[5], cyapa->fw_img_head.head_size);
+
+       product_family = get_unaligned_le16(&resp_data[7]);
+       if ((product_family & GEN5_PRODUCT_FAMILY_MASK) !=
+               GEN5_PRODUCT_FAMILY_TRACKPAD)
+               return -EINVAL;
+
+       cyapa->fw_maj_ver = resp_data[15];
+       cyapa->fw_min_ver = resp_data[16];
+
+       cyapa->electrodes_x = resp_data[52];
+       cyapa->electrodes_y = resp_data[53];
+
+       cyapa->physical_size_x =  get_unaligned_le16(&resp_data[54]) / 100;
+       cyapa->physical_size_y = get_unaligned_le16(&resp_data[56]) / 100;
+
+       cyapa->max_abs_x = get_unaligned_le16(&resp_data[58]);
+       cyapa->max_abs_y = get_unaligned_le16(&resp_data[60]);
+
+       cyapa->max_z = get_unaligned_le16(&resp_data[62]);
+
+       cyapa->x_origin = resp_data[64] & 0x01;
+       cyapa->y_origin = resp_data[65] & 0x01;
+
+       cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK;
+
+       memcpy(&cyapa->product_id[0], &resp_data[33], 5);
+       cyapa->product_id[5] = '-';
+       memcpy(&cyapa->product_id[6], &resp_data[38], 6);
+       cyapa->product_id[12] = '-';
+       memcpy(&cyapa->product_id[13], &resp_data[44], 2);
+       cyapa->product_id[15] = '\0';
+
+       if (!cyapa->electrodes_x || !cyapa->electrodes_y ||
+               !cyapa->physical_size_x || !cyapa->physical_size_y ||
+               !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen5_do_operational_check(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       int ret;
+
+       if (cyapa->gen != CYAPA_GEN5)
+               return -EINVAL;
+
+       switch (cyapa->state) {
+       case CYAPA_STATE_GEN5_BL:
+               ret = cyapa_gen5_bl_exit(cyapa);
+               if (ret) {
+                       /* try to update trackpad product information. */
+                       cyapa_gen5_bl_query_data(cyapa);
+                       return ret;
+               }
+
+               cyapa->state = CYAPA_STATE_GEN5_APP;
+
+       case CYAPA_STATE_GEN5_APP:
+               /* if trackpad device in deep sleep mode,
+                * the app command will fail.
+                * So always reset trackpad device to full active when
+                * the device state is requeried.
+                */
+               if (cyapa->real_power_mode == PWR_MODE_OFF) {
+                       ret = cyapa_gen5_set_power_mode(cyapa,
+                                       PWR_MODE_FULL_ACTIVE, 0);
+                       if (ret)
+                               return ret;
+
+                       /* set initial power state of trackpad device. */
+                       cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+                       cyapa_gen5_get_interval_time(cyapa,
+                               GEN5_PARAMETER_LP_INTRVL_ID,
+                               &cyapa->real_sleep_time);
+                       cyapa->suspend_power_mode =
+                               cyapa_sleep_time_to_pwr_cmd(
+                                       cyapa->real_sleep_time);
+                       cyapa->suspend_sleep_time = cyapa->real_sleep_time;
+               }
+
+               /* Get trackpad product information. */
+               ret = cyapa_gen5_get_query_data(cyapa);
+               if (ret)
+                       return ret;
+               /* only support product ID starting with CYTRA */
+               if (memcmp(cyapa->product_id, unique_str,
+                          sizeof(unique_str) - 1) != 0) {
+                       dev_err(dev, "%s: unknown product ID (%s)\n",
+                               __func__, cyapa->product_id);
+                       return -EINVAL;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+       struct input_dev *input = cyapa->input;
+       struct cyapa_gen5_report_data report_data;
+       int i;
+       int ret;
+       u8 report_id;
+       u8 buttons;
+       unsigned int report_len, touch_num;
+       int x, y;
+
+       if (cyapa->gen != CYAPA_GEN5 ||
+               cyapa->state != CYAPA_STATE_GEN5_APP) {
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }
+
+       ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data,
+                       GEN5_TOUCH_REPORT_HEAD_SIZE);
+       if (ret != GEN5_TOUCH_REPORT_HEAD_SIZE) {
+               /* failed to read report head data. */
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }
+
+       report_len = get_unaligned_le16(&report_data.report_head[0]);
+       if (report_len <= 2) {
+               /*
+                * trackpad power up event or end of one touch packets report,
+                * no data for report.
+                */
+               if (report_len != 2)
+                       async_schedule(cyapa_detect_async, cyapa);
+
+               return;
+       }
+
+       report_id = report_data.report_head[2];
+       if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+                       report_len == GEN5_WAKEUP_EVENT_SIZE) {
+               /* Wake event from deep sleep mode, reset power state. */
+               return;
+       } else if (report_id != GEN5_TOUCH_REPORT_ID &&
+                       report_id != GEN5_BTN_REPORT_ID &&
+                       report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
+                       report_id != GEN5_PUSH_BTN_REPORT_ID) {
+               /* Running in BL mode or unknown response data read. */
+
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }
+
+       if (report_len > GEN5_TOUCH_REPORT_HEAD_SIZE) {
+               /* must make sure to read all data through out before return. */
+               ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len);
+               if (ret != report_len) {
+                       /* failed to read report head data. */
+                       async_schedule(cyapa_detect_async, cyapa);
+                       return;
+               }
+       }
+
+       if (report_id == GEN5_TOUCH_REPORT_ID &&
+               (report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+                       report_len > GEN5_TOUCH_REPORT_MAX_SIZE)) {
+               /* Invald report data length for finger packet. */
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }
+
+       if ((report_id == GEN5_BTN_REPORT_ID ||
+                       report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+                       report_id == GEN5_PUSH_BTN_REPORT_ID) &&
+               (report_len < GEN5_BTN_REPORT_HEAD_SIZE ||
+                       report_len > GEN5_BTN_REPORT_MAX_SIZE)) {
+               /* Invald report data length of button packet. */
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }
+
+       if (report_id == GEN5_BTN_REPORT_ID ||
+                       report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+                       report_id == GEN5_PUSH_BTN_REPORT_ID) {
+               /* button report data */
+               buttons = (report_data.report_head[5] << 3) &
+                                       CAPABILITY_BTN_MASK;
+               if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) {
+                       input_report_key(input, BTN_LEFT,
+                               !!(buttons & CAPABILITY_LEFT_BTN_MASK));
+               }
+               if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) {
+                       input_report_key(input, BTN_MIDDLE,
+                               !!(buttons & CAPABILITY_MIDDLE_BTN_MASK));
+               }
+               if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) {
+                       input_report_key(input, BTN_RIGHT,
+                               !!(buttons & CAPABILITY_RIGHT_BTN_MASK));
+               }
+
+               input_sync(input);
+       } else {
+               /* touch report data */
+               touch_num = report_data.report_head[5] &
+                                       GEN5_NUMBER_OF_TOUCH_MASK;
+
+               for (i = 0; i < touch_num; i++) {
+                       const struct cyapa_gen5_touch_record *touch =
+                               &report_data.touch_records[i];
+                       u8 event_id =
+                               GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+                       int slot = GEN5_GET_TOUCH_ID(touch->touch_tip_event_id);
+
+                       if (event_id == RECORD_EVENT_LIFTOFF)
+                               continue;
+
+                       input_mt_slot(input, slot);
+                       input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+                       x = (touch->x_hi << 8) | touch->x_lo;
+                       if (cyapa->x_origin)
+                               x = cyapa->max_abs_x - x;
+                       input_report_abs(input, ABS_MT_POSITION_X, x);
+                       y = (touch->y_hi << 8) | touch->y_lo;
+                       if (cyapa->y_origin)
+                               y = cyapa->max_abs_y - y;
+                       input_report_abs(input, ABS_MT_POSITION_Y, y);
+                       input_report_abs(input, ABS_MT_PRESSURE,
+                               touch->z);
+                       input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+                               touch->major_axis_len);
+                       input_report_abs(input, ABS_MT_TOUCH_MINOR,
+                               touch->minor_axis_len);
+
+                       input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+                               touch->major_tool_len);
+                       input_report_abs(input, ABS_MT_WIDTH_MINOR,
+                               touch->minor_tool_len);
+
+                       input_report_abs(input, ABS_MT_ORIENTATION,
+                               touch->orientation);
+               }
+
+               input_mt_sync_frame(input);
+
+               input_sync(input);
+       }
+}
+
 static void cyapa_default_irq_handler(struct cyapa *cyapa)
 {
        async_schedule(cyapa_detect_async, cyapa);
@@ -934,6 +2505,26 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
                return ret;

        switch (cyapa->gen) {
+       case CYAPA_GEN5:
+               cyapa->cyapa_check_fw = NULL;
+               cyapa->cyapa_bl_enter = NULL;
+               cyapa->cyapa_bl_activate = NULL;
+               cyapa->cyapa_bl_initiate = NULL;
+               cyapa->cyapa_update_fw = NULL;
+               cyapa->cyapa_bl_verify_app_integrity = NULL;
+               cyapa->cyapa_bl_deactivate = NULL;
+               cyapa->cyapa_show_baseline = NULL;
+               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_irq_handler = cyapa_gen5_irq_handler;
+               cyapa->cyapa_set_power_mode = cyapa_gen5_set_power_mode;
+               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_raw_data = NULL;
+
+               cyapa_enable_irq_save(cyapa);
+               ret = cyapa_gen5_do_operational_check(cyapa);
+               cyapa_irq_restore(cyapa);
+
+               break;
        case CYAPA_GEN3:
                cyapa->cyapa_check_fw = NULL;
                cyapa->cyapa_bl_enter = NULL;
@@ -983,14 +2574,75 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
        if (device_may_wakeup(dev))
                pm_wakeup_event(dev, 0);

+       if (atomic_read(&cyapa->cmd_issued)) {
+               if (cyapa->gen_detecting == CYAPA_GEN5) {
+                       /*
+                        * read out all none command response data.
+                        * these output data may caused by user put finger on
+                        * trackpad when host waiting the command response.
+                        */
+                       cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+                       length = get_unaligned_le16(cyapa->tmp_irq_buf);
+                       length = (length <= 2) ? 2 : length;
+                       if (length > 2)
+                               cyapa_i2c_pip_read(cyapa,
+                                       cyapa->tmp_irq_buf, length);
+                       if (!(cyapa->resp_sort_func &&
+                               cyapa->resp_sort_func(cyapa,
+                                       cyapa->tmp_irq_buf, length))) {
+                               /*
+                                * Cover the Gen5 V1 firmware issue.
+                                * The issue is there is no interrut will be
+                                * asserted to notityf host to read a command
+                                * data out when always has finger touch on
+                                * trackpad during the command is issued to
+                                * trackad device.
+                                * This issue has the scenario is that,
+                                * user always has his fingers touched on
+                                * trackpad device when booting/rebooting
+                                * their chrome book.
+                                */
+                               length = *cyapa->resp_len;
+                               cyapa_empty_pip_output_data(cyapa,
+                                               cyapa->resp_data,
+                                               &length,
+                                               cyapa->resp_sort_func);
+                               if (cyapa->resp_len && length != 0) {
+                                       *cyapa->resp_len = length;
+                                       complete(&cyapa->cmd_ready);
+                                       atomic_dec(&cyapa->cmd_issued);
+                               }
+                               goto out;
+                       }
+
+                       if (cyapa->resp_data && cyapa->resp_len) {
+                               *cyapa->resp_len = (*cyapa->resp_len < length) ?
+                                       *cyapa->resp_len : length;
+                               memcpy(cyapa->resp_data, cyapa->tmp_irq_buf,
+                                       *cyapa->resp_len);
+                       }
+               }
+               complete(&cyapa->cmd_ready);
+               atomic_dec(&cyapa->cmd_issued);
+               goto out;
+       }
+
        /*
         * Don't read input if input device has not been configured.
         * This check solves a race during probe() between irq_request()
         * and irq_disable(), since there is no way to request an irq that is
         * initially disabled.
         */
-       if (!input || atomic_read(&cyapa->in_detecting))
+       if (!input || atomic_read(&cyapa->in_detecting)) {
+               if (cyapa->gen_detecting == CYAPA_GEN5) {
+                       cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+                       length = get_unaligned_le16(cyapa->tmp_irq_buf);
+                       if (length > 2)
+                               cyapa_i2c_pip_read(cyapa,
+                                       cyapa->tmp_irq_buf, length);
+               }
                goto out;
+       }

        if (cyapa->cyapa_irq_handler)
                cyapa->cyapa_irq_handler(cyapa);
@@ -1051,6 +2703,22 @@ static int cyapa_get_state(struct cyapa *cyapa)
                                goto out_detected;
                        cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
                }
+               if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
+                               cyapa->gen == CYAPA_GEN5) &&
+                       !smbus && even_addr) {
+                       cyapa->gen_detecting = CYAPA_GEN5;
+
+                       cyapa_enable_irq_save(cyapa);
+                       ret = cyapa_gen5_state_parse(cyapa,
+                                       status, BL_STATUS_SIZE);
+                       cyapa_irq_restore(cyapa);
+                       if (ret == 0) {
+                               cyapa_empty_pip_output_data(cyapa,
+                                               NULL, NULL, NULL);
+                               goto out_detected;
+                       }
+                       cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+               }

                /*
                 * cannot detect communication protocol based on current
@@ -1157,7 +2825,27 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
                             0);
        input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cyapa->max_abs_y, 0,
                             0);
-       input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+       input_set_abs_params(input, ABS_MT_PRESSURE, 0, cyapa->max_z, 0, 0);
+       if (cyapa->gen > CYAPA_GEN3) {
+               input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+               input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+               /* orientation is the angle between the vertial axis and
+                * the major axis of the contact ellipse.
+                * The range is -127 to 127.
+                * the positive direction is clockwise form the vertical axis.
+                * If the ellipse of contact degenerates into a circle,
+                * orientation is reported as 0.
+                *
+                * Also, for Gen5 trackpad the accurate of this orientation
+                * value is value + (-30 ~ 30).
+                */
+               input_set_abs_params(input, ABS_MT_ORIENTATION,
+                               -127, 127, 0, 0);
+       }
+       if (cyapa->gen >= CYAPA_GEN5) {
+               input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+               input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 255, 0, 0);
+       }

        input_abs_set_res(input, ABS_MT_POSITION_X,
                          cyapa->max_abs_x / cyapa->physical_size_x);
@@ -1202,6 +2890,14 @@ static void cyapa_detect(struct cyapa *cyapa)
        char *envp[] = {"ERROR=1", NULL};
        int ret;

+       /*
+        * Try to dump all bufferred data if it's known gen5 trackpad
+        * before detecting. Because the irq routine may disabled
+        * before enter this routine.
+        */
+       if (cyapa->gen == CYAPA_GEN5)
+               cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
        ret = cyapa_check_is_operational(cyapa);
        if (ret == -ETIMEDOUT)
                dev_err(dev, "no device detected, %d\n", ret);
@@ -1219,7 +2915,8 @@ static void cyapa_detect(struct cyapa *cyapa)
                        dev_err(dev, "create input_dev instance failed, %d\n",
                                ret);

-               enable_irq(cyapa->irq);
+               cyapa_enable_irq(cyapa);
+
                /*
                 * On some systems, a system crash / warm boot does not reset
                 * the device's current power mode to FULL_ACTIVE.
@@ -1238,6 +2935,14 @@ static void cyapa_detect(struct cyapa *cyapa)
                                                ret);
                }
        }
+
+       /*
+        * Try to dump all bufferred data if it's known gen5 trackpad before
+        * detecting. Because the irq routine may disabled before
+        * leave this routine.
+        */
+       if (cyapa->gen == CYAPA_GEN5)
+               cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
 }

 static void cyapa_detect_async(void *data, async_cookie_t cookie)
@@ -1290,7 +2995,16 @@ static int cyapa_probe(struct i2c_client *client,
        cyapa->state = CYAPA_STATE_NO_DEVICE;
        cyapa->suspend_power_mode = PWR_MODE_SLEEP;

+       init_completion(&cyapa->cmd_ready);
+       atomic_set(&cyapa->cmd_issued, 0);
+       mutex_init(&cyapa->irq_state_lock);
+       mutex_init(&cyapa->cmd_lock);
+       atomic_set(&cyapa->in_detecting, 0);
+       cyapa->resp_sort_func = NULL;
+       cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
        cyapa->irq = client->irq;
+       cyapa->irq_enabled = true;
        ret = request_threaded_irq(cyapa->irq,
                                   NULL,
                                   cyapa_irq,
@@ -1301,7 +3015,7 @@ static int cyapa_probe(struct i2c_client *client,
                dev_err(dev, "IRQ request failed: %d\n, ", ret);
                goto err_unregister_device;
        }
-       disable_irq(cyapa->irq);
+       cyapa_disable_irq(cyapa);

        async_schedule(cyapa_detect_async, cyapa);
        return 0;
@@ -1336,7 +3050,7 @@ static int cyapa_suspend(struct device *dev)
        u8 power_mode;
        struct cyapa *cyapa = dev_get_drvdata(dev);

-       disable_irq(cyapa->irq);
+       cyapa_disable_irq(cyapa);
        cyapa->suspended = true;

        /*
@@ -1365,7 +3079,7 @@ static int cyapa_resume(struct device *dev)

        if (device_may_wakeup(dev) && cyapa->irq_wake)
                disable_irq_wake(cyapa->irq);
-       enable_irq(cyapa->irq);
+       cyapa_enable_irq(cyapa);

        if (cyapa->cyapa_set_power_mode) {
                ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 26947 bytes --]

^ permalink raw reply related

* [PATCH 3/6] input: cyapa: add power mode sleep and runtime power mode supported.
From: Dudley Du @ 2014-04-16  8:38 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 11210 bytes --]

In order to save power when the trackpad device is not used, the sleep power mode and runtime power mode must be supported.
And the enter sleep time can be configured in the sysfs system.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 7b269d8..6820b3f 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -27,6 +27,7 @@
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 #include <linux/unaligned/access_ok.h>
+#include <linux/pm_runtime.h>

 /* APA trackpad firmware generation */
 #define CYAPA_GEN_UNKNOWN   0x00   /* unknown protocol. */
@@ -505,6 +506,10 @@ struct cyapa {
        u16 suspend_sleep_time;
        u8 real_power_mode;
        u16 real_sleep_time;
+#ifdef CONFIG_PM_RUNTIME
+       u8 runtime_suspend_power_mode;
+       u16 runtime_suspend_sleep_time;
+#endif /* CONFIG_PM_RUNTIME */
        bool suspended;

        /* read from query data region. */
@@ -1873,6 +1878,13 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
        return -EAGAIN;
 }

+static int cyapa_gen5_sleep_time_check(u16 sleep_time)
+{
+       if (sleep_time > 1000)
+               sleep_time = 1000;
+       return sleep_time;
+}
+
 static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
 {
        int ret;
@@ -2571,6 +2583,9 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
        struct input_dev *input = cyapa->input;
        int length;

+       pm_runtime_get_sync(dev);
+       pm_runtime_mark_last_busy(dev);
+
        if (device_may_wakeup(dev))
                pm_wakeup_event(dev, 0);

@@ -2646,6 +2661,7 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)

        if (cyapa->cyapa_irq_handler)
                cyapa->cyapa_irq_handler(cyapa);
+       pm_runtime_put_sync_autosuspend(dev);

 out:
        return IRQ_HANDLED;
@@ -2945,6 +2961,152 @@ static void cyapa_detect(struct cyapa *cyapa)
                cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
 }

+
+/*
+ * Sysfs Interface.
+ */
+
+#ifdef CONFIG_PM_SLEEP
+static ssize_t cyapa_show_suspend_scanrate(struct device *dev,
+                                          struct device_attribute *attr,
+                                          char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u8 pwr_cmd = cyapa->suspend_power_mode;
+       u16 sleep_time;
+       int len;
+
+       if (pwr_cmd == PWR_MODE_BTN_ONLY)
+               len = scnprintf(buf, PAGE_SIZE, "%s\n", BTN_ONLY_MODE_NAME);
+       else if (pwr_cmd == PWR_MODE_OFF)
+               len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME);
+       else {
+               if (cyapa->gen == CYAPA_GEN3)
+                       sleep_time = cyapa_pwr_cmd_to_sleep_time(pwr_cmd);
+               else
+                       sleep_time = cyapa->suspend_sleep_time;
+               len = scnprintf(buf, PAGE_SIZE, "%u\n", sleep_time);
+       }
+
+       return len;
+}
+
+static ssize_t cyapa_update_suspend_scanrate(struct device *dev,
+                                            struct device_attribute *attr,
+                                            const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u16 sleep_time;
+
+       if (buf == NULL || count == 0)
+               goto invalidparam;
+
+       if (sysfs_streq(buf, BTN_ONLY_MODE_NAME))
+               cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY;
+       else if (sysfs_streq(buf, OFF_MODE_NAME))
+               cyapa->suspend_power_mode = PWR_MODE_OFF;
+       else if (!kstrtou16(buf, 10, &sleep_time)) {
+               cyapa->suspend_power_mode =
+                               cyapa_sleep_time_to_pwr_cmd(sleep_time);
+               if (cyapa->gen > CYAPA_GEN3)
+                       cyapa->suspend_sleep_time =
+                               cyapa_gen5_sleep_time_check(sleep_time);
+       } else
+               goto invalidparam;
+
+       return count;
+
+invalidparam:
+       dev_err(dev, "invalid suspend scanrate ms parameters\n");
+       return -EINVAL;
+}
+
+static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+                  cyapa_show_suspend_scanrate,
+                  cyapa_update_suspend_scanrate);
+
+static struct attribute *cyapa_power_wakeup_entries[] = {
+       &dev_attr_suspend_scanrate_ms.attr,
+       NULL,
+};
+
+static const struct attribute_group cyapa_power_wakeup_group = {
+       .name = power_group_name,
+       .attrs = cyapa_power_wakeup_entries,
+};
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static ssize_t cyapa_show_rt_suspend_scanrate(struct device *dev,
+                                             struct device_attribute *attr,
+                                             char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u8 pwr_cmd = cyapa->runtime_suspend_power_mode;
+
+       if (cyapa->gen == CYAPA_GEN3)
+               return scnprintf(buf, PAGE_SIZE, "%u\n",
+                       cyapa_pwr_cmd_to_sleep_time(pwr_cmd));
+       else
+               return scnprintf(buf, PAGE_SIZE, "%u\n",
+                       cyapa->runtime_suspend_sleep_time);
+}
+
+static ssize_t cyapa_update_rt_suspend_scanrate(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u16 time;
+
+       if (buf == NULL || count == 0 || kstrtou16(buf, 10, &time)) {
+               dev_err(dev, "invalid runtime suspend scanrate ms parameter\n");
+               return -EINVAL;
+       }
+
+       /*
+        * When the suspend scanrate is changed, pm_runtime_get to resume
+        * a potentially suspended device, update to the new pwr_cmd
+        * and then pm_runtime_put to suspend into the new power mode.
+        */
+       pm_runtime_get_sync(dev);
+       cyapa->runtime_suspend_power_mode = cyapa_sleep_time_to_pwr_cmd(time);
+       if (cyapa->gen > CYAPA_GEN3)
+               cyapa->runtime_suspend_sleep_time = time;
+       pm_runtime_put_sync_autosuspend(dev);
+       return count;
+}
+
+static DEVICE_ATTR(runtime_suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+                  cyapa_show_rt_suspend_scanrate,
+                  cyapa_update_rt_suspend_scanrate);
+
+static struct attribute *cyapa_power_runtime_entries[] = {
+       &dev_attr_runtime_suspend_scanrate_ms.attr,
+       NULL,
+};
+
+static const struct attribute_group cyapa_power_runtime_group = {
+       .name = power_group_name,
+       .attrs = cyapa_power_runtime_entries,
+};
+
+static void cyapa_start_runtime(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+
+       cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+       if (sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group))
+               dev_warn(dev, "error creating wakeup runtime entries.\n");
+       pm_runtime_set_active(dev);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+       pm_runtime_enable(dev);
+}
+#else
+static void cyapa_start_runtime(struct cyapa *cyapa) {}
+#endif /* CONFIG_PM_RUNTIME */
+
 static void cyapa_detect_async(void *data, async_cookie_t cookie)
 {
        struct cyapa *cyapa = (struct cyapa *)data;
@@ -2957,6 +3119,15 @@ static void cyapa_detect_async(void *data, async_cookie_t cookie)
        atomic_dec(&cyapa->in_detecting);
 }

+static void cyapa_detect_and_start(void *data, async_cookie_t cookie)
+{
+       struct cyapa *cyapa = data;
+
+       cyapa_detect_async(data, cookie);
+
+       cyapa_start_runtime(cyapa);
+}
+
 static int cyapa_probe(struct i2c_client *client,
                       const struct i2c_device_id *dev_id)
 {
@@ -3017,7 +3188,13 @@ static int cyapa_probe(struct i2c_client *client,
        }
        cyapa_disable_irq(cyapa);

-       async_schedule(cyapa_detect_async, cyapa);
+#ifdef CONFIG_PM_SLEEP
+       if (device_can_wakeup(dev) &&
+           sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
+               dev_warn(dev, "error creating wakeup power entries.\n");
+#endif /* CONFIG_PM_SLEEP */
+
+       async_schedule(cyapa_detect_and_start, cyapa);
        return 0;

 err_unregister_device:
@@ -3032,6 +3209,16 @@ static int cyapa_remove(struct i2c_client *client)
 {
        struct cyapa *cyapa = i2c_get_clientdata(client);

+       pm_runtime_disable(&client->dev);
+
+#ifdef CONFIG_PM_SLEEP
+       sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+       sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
+#endif
+
        free_irq(cyapa->irq, cyapa);
        input_unregister_device(cyapa->input);

@@ -3090,12 +3277,56 @@ static int cyapa_resume(struct device *dev)

        async_schedule(cyapa_detect_async, cyapa);

+       /* runtime set active to reflect active state. */
+       pm_runtime_disable(dev);
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
        cyapa->suspended = false;
        return 0;
 }
 #endif /* CONFIG_PM_SLEEP */

-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+#ifdef CONFIG_PM_RUNTIME
+static int cyapa_runtime_suspend(struct device *dev)
+{
+       int ret;
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+
+       if (cyapa->cyapa_set_power_mode) {
+               /* set trackpad device to idle mode */
+               ret = cyapa->cyapa_set_power_mode(cyapa,
+                               cyapa->runtime_suspend_power_mode,
+                               cyapa->runtime_suspend_sleep_time);
+               if (ret)
+                       dev_err(dev, "runtime suspend failed, %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int cyapa_runtime_resume(struct device *dev)
+{
+       int ret;
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+
+       if (cyapa->cyapa_set_power_mode) {
+               /* resume to full active mode */
+               ret = cyapa->cyapa_set_power_mode(cyapa,
+                               PWR_MODE_FULL_ACTIVE, 0);
+               if (ret)
+                       dev_err(dev, "runtime resume failed, %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+static const struct dev_pm_ops cyapa_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(cyapa_suspend, cyapa_resume)
+       SET_RUNTIME_PM_OPS(cyapa_runtime_suspend, cyapa_runtime_resume, NULL)
+};

 static const struct i2c_device_id cyapa_id_table[] = {
        { "cyapa", 0 },
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 7525 bytes --]

^ permalink raw reply related

* [PATCH 4/6] input: cyapa: enable/disable trackpad device based on LID state
From: Dudley Du @ 2014-04-16  8:39 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 6046 bytes --]

Rely on EV_SW and SW_LID bits to identify a LID device, and hook
up our filter to listen for SW_LID events to enable/disable touchpad when
LID is open/closed.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 6820b3f..da03427 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -523,6 +523,9 @@ struct cyapa {
        int physical_size_x;
        int physical_size_y;

+       bool lid_handler_registered;
+       struct input_handler lid_handler;
+
        /* used in ttsp and truetouch based trackpad devices. */
        u8 x_origin;  /* X Axis Origin: 0 = left side; 1 = rigth side. */
        u8 y_origin;  /* Y Axis Origin: 0 = top; 1 = bottom. */
@@ -3107,6 +3110,125 @@ static void cyapa_start_runtime(struct cyapa *cyapa)
 static void cyapa_start_runtime(struct cyapa *cyapa) {}
 #endif /* CONFIG_PM_RUNTIME */

+
+/*
+ * We rely on EV_SW and SW_LID bits to identify a LID device, and hook
+ * up our filter to listen for SW_LID events to enable/disable touchpad when
+ * LID is open/closed.
+ */
+static const struct input_device_id lid_device_ids[] = {
+       {
+               .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
+                        INPUT_DEVICE_ID_MATCH_SWBIT,
+               .evbit = { BIT_MASK(EV_SW) },
+               .swbit = { BIT_MASK(SW_LID) },
+       },
+       { },
+};
+
+static int lid_device_connect(struct input_handler *handler,
+                             struct input_dev *dev,
+                             const struct input_device_id *id)
+{
+       struct input_handle *lid_handle;
+       int error;
+
+       lid_handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+       if (!lid_handle)
+               return -ENOMEM;
+
+       lid_handle->dev = dev;
+       lid_handle->handler = handler;
+       lid_handle->name = "lid_event_handler";
+       lid_handle->private = handler->private;
+
+       error = input_register_handle(lid_handle);
+       if (error)
+               goto err_free;
+
+       error = input_open_device(lid_handle);
+       if (error)
+               goto err_unregister;
+
+       return 0;
+err_unregister:
+       input_unregister_handle(lid_handle);
+err_free:
+       kfree(lid_handle);
+       return error;
+}
+
+static void lid_device_disconnect(struct input_handle *handle)
+{
+       input_close_device(handle);
+       input_unregister_handle(handle);
+       kfree(handle);
+}
+
+static bool lid_event_filter(struct input_handle *handle,
+                            unsigned int type, unsigned int code, int value)
+{
+       struct cyapa *cyapa = handle->private;
+       struct device *dev = &cyapa->client->dev;
+
+       if (type == EV_SW && code == SW_LID) {
+               if (cyapa->suspended) {
+                       /*
+                        * If the lid event filter is called while suspended,
+                        * there is no guarantee that the underlying i2cs are
+                        * resumed at this point, so it is not safe to issue
+                        * the command to change power modes.
+                        * Instead, rely on cyapa_resume to set us back to
+                        * PWR_MODE_FULL_ACTIVE.
+                        */
+                       return false;
+               }
+               if (value == 0) {
+                       if (cyapa->cyapa_set_power_mode)
+                               cyapa->cyapa_set_power_mode(cyapa,
+                                               PWR_MODE_FULL_ACTIVE, 0);
+                       pm_runtime_set_active(dev);
+                       pm_runtime_enable(dev);
+               } else {
+                       pm_runtime_disable(dev);
+                       if (cyapa->cyapa_set_power_mode)
+                               cyapa->cyapa_set_power_mode(cyapa,
+                                               PWR_MODE_OFF, 0);
+               }
+       }
+
+       return false;
+}
+
+static void lid_event_register_handler(struct cyapa *cyapa)
+{
+       int error;
+       struct input_handler *lid_handler = &cyapa->lid_handler;
+
+       if (cyapa->lid_handler_registered)
+               return;
+
+       lid_handler->filter     = lid_event_filter;
+       lid_handler->connect    = lid_device_connect;
+       lid_handler->disconnect = lid_device_disconnect;
+       lid_handler->name       = "cyapa_lid_event_handler";
+       lid_handler->id_table   = lid_device_ids;
+       lid_handler->private    = cyapa;
+
+       error = input_register_handler(lid_handler);
+       if (error)
+               return;
+       cyapa->lid_handler_registered = true;
+}
+
+static void lid_event_unregister_handler(struct cyapa *cyapa)
+{
+       if (cyapa->lid_handler_registered) {
+               input_unregister_handler(&cyapa->lid_handler);
+               cyapa->lid_handler_registered = false;
+       }
+}
+
 static void cyapa_detect_async(void *data, async_cookie_t cookie)
 {
        struct cyapa *cyapa = (struct cyapa *)data;
@@ -3126,6 +3248,7 @@ static void cyapa_detect_and_start(void *data, async_cookie_t cookie)
        cyapa_detect_async(data, cookie);

        cyapa_start_runtime(cyapa);
+       lid_event_register_handler(cyapa);
 }

 static int cyapa_probe(struct i2c_client *client,
@@ -3221,7 +3344,7 @@ static int cyapa_remove(struct i2c_client *client)

        free_irq(cyapa->irq, cyapa);
        input_unregister_device(cyapa->input);
-
+       lid_event_unregister_handler(cyapa);
        if (cyapa->cyapa_set_power_mode)
                cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_OFF, 0);
        i2c_set_clientdata(client, NULL);
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 6036 bytes --]

^ permalink raw reply related

* [PATCH 5/6] input: cyapa: add sysfs interfaces supported for gen3 trackpad device
From: Dudley Du @ 2014-04-16  8:40 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 38561 bytes --]

Add sysfs interfaces for gen3 trackpad devices that required in production,
including read and update firmware image, report baselines, sensors calibrate,
read product id, read firmware version.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index da03427..66cb5cc 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -15,6 +15,7 @@
  */

 #include <linux/async.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/firmware.h>
 #include <linux/i2c.h>
@@ -29,6 +30,7 @@
 #include <linux/unaligned/access_ok.h>
 #include <linux/pm_runtime.h>

+
 /* APA trackpad firmware generation */
 #define CYAPA_GEN_UNKNOWN   0x00   /* unknown protocol. */
 #define CYAPA_GEN3   0x03   /* support MT-protocol B with tracking ID. */
@@ -50,6 +52,8 @@
 #define CYAPA_CMD_BL_ALL           0x0a
 #define CYAPA_CMD_BLK_PRODUCT_ID   0x0b
 #define CYAPA_CMD_BLK_HEAD         0x0c
+#define CYAPA_CMD_MAX_BASELINE     0x0d
+#define CYAPA_CMD_MIN_BASELINE     0x0e

 /* report data start reg offset address. */
 #define DATA_REG_START_OFFSET  0x0000
@@ -150,6 +154,10 @@
                              CAPABILITY_MIDDLE_BTN_MASK)

 #define CYAPA_OFFSET_SOFT_RESET  REG_OFFSET_COMMAND_BASE
+#define OP_RECALIBRATION_MASK    0x80
+#define OP_REPORT_BASELINE_MASK  0x40
+#define REG_OFFSET_MAX_BASELINE  0x0026
+#define REG_OFFSET_MIN_BASELINE  0x0027

 #define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
 #define SET_POWER_MODE_DELAY   10000  /* unit: us */
@@ -493,6 +501,7 @@ struct cyapa_tsg_bin_image {
 /* The main device structure */
 struct cyapa {
        enum cyapa_state state;
+       u8 status[BL_STATUS_SIZE];

        struct i2c_client *client;
        struct input_dev *input;
@@ -569,13 +578,29 @@ struct cyapa {
        bl_read_fw_func cyapa_read_fw;
        read_raw_data_func cyapa_read_raw_data;

+       size_t read_fw_image_size;
+       size_t tp_raw_data_size;
        struct cyapa_tsg_bin_image_head fw_img_head;
+
+       struct mutex debugfs_mutex;
+
+       /* per-instance debugfs root */
+       struct dentry *dentry_dev;
+
+       /* Buffer to store firmware read using debugfs */
+       u8 *read_fw_image;
+       /* Buffer to store sensors' raw data */
+       u8 *tp_raw_data;
 };

+static const u8 bl_activate[] = { 0x00, 0xff, 0x38, 0x00, 0x01, 0x02, 0x03,
+               0x04, 0x05, 0x06, 0x07 };
 static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
                0x04, 0x05, 0x06, 0x07 };
 static const u8 bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04,
                0x05, 0x06, 0x07 };
+/* global root node of the cyapa debugfs directory. */
+static struct dentry *cyapa_debugfs_root;

 struct cyapa_cmd_len {
        u8 cmd;
@@ -601,10 +626,14 @@ struct cyapa_cmd_len {
 #define CMD_RESET 0
 #define CMD_POWER_MODE 1
 #define CMD_DEV_STATUS 2
+#define CMD_REPORT_MAX_BASELINE 3
+#define CMD_REPORT_MIN_BASELINE 4
 #define SMBUS_BYTE_CMD(cmd) (((cmd) & 0x3f) << 1)
 #define CYAPA_SMBUS_RESET SMBUS_BYTE_CMD(CMD_RESET)
 #define CYAPA_SMBUS_POWER_MODE SMBUS_BYTE_CMD(CMD_POWER_MODE)
 #define CYAPA_SMBUS_DEV_STATUS SMBUS_BYTE_CMD(CMD_DEV_STATUS)
+#define CYAPA_SMBUS_MAX_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MAX_BASELINE)
+#define CYAPA_SMBUS_MIN_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MIN_BASELINE)

  /* for group registers read/write command */
 #define REG_GROUP_DATA 0
@@ -649,7 +678,9 @@ static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
        { BL_DATA_OFFSET, 16 },
        { BL_HEAD_OFFSET, 32 },
        { REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE },
-       { REG_OFFSET_DATA_BASE, 32 }
+       { REG_OFFSET_DATA_BASE, 32 },
+       { REG_OFFSET_MAX_BASELINE, 1 },
+       { REG_OFFSET_MIN_BASELINE, 1 },
 };

 static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
@@ -666,10 +697,30 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
        { CYAPA_SMBUS_BL_ALL, 32 },
        { CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE },
        { CYAPA_SMBUS_BLK_HEAD, 16 },
+       { CYAPA_SMBUS_MAX_BASELINE, 1 },
+       { CYAPA_SMBUS_MIN_BASELINE, 1 },
 };

 static const char unique_str[] = "CYTRA";

+#define CYAPA_DEBUGFS_READ_FW  "read_fw"
+#define CYAPA_DEBUGFS_RAW_DATA "raw_data"
+#define CYAPA_FW_NAME          "cyapa.bin"
+#define CYAPA_FW_BLOCK_SIZE    64
+#define CYAPA_FW_READ_SIZE     16
+#define CYAPA_FW_HDR_START     0x0780
+#define CYAPA_FW_HDR_BLOCK_COUNT  2
+#define CYAPA_FW_HDR_BLOCK_START  (CYAPA_FW_HDR_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_HDR_SIZE      (CYAPA_FW_HDR_BLOCK_COUNT * \
+                                CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_START    0x0800
+#define CYAPA_FW_DATA_BLOCK_COUNT  480
+#define CYAPA_FW_DATA_BLOCK_START  (CYAPA_FW_DATA_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_SIZE     (CYAPA_FW_DATA_BLOCK_COUNT * \
+                                CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_SIZE          (CYAPA_FW_HDR_SIZE + CYAPA_FW_DATA_SIZE)
+#define CYAPA_CMD_LEN          16
+
 static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
 static void cyapa_detect(struct cyapa *cyapa);
 static void cyapa_detect_async(void *data, async_cookie_t cookie);
@@ -890,6 +941,78 @@ static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
        return -EAGAIN;
 }

+/*
+ * Enter bootloader by soft resetting the device.
+ *
+ * If device is already in the bootloader, the function just returns.
+ * Otherwise, reset the device; after reset, device enters bootloader idle
+ * state immediately.
+ *
+ * Also, if device was unregister device from input core.  Device will
+ * re-register after it is detected following resumption of operational mode.
+ *
+ * Returns:
+ *   0 on success
+ *   -EAGAIN  device was reset, but is not now in bootloader idle state
+ *   < 0 if the device never responds within the timeout
+ */
+static int cyapa_gen3_bl_enter(struct cyapa *cyapa)
+{
+       int ret;
+
+       if (cyapa->input) {
+               cyapa_disable_irq(cyapa);
+               input_unregister_device(cyapa->input);
+               cyapa->input = NULL;
+       }
+
+       ret = cyapa_poll_state(cyapa, 500);
+       if (ret < 0)
+               return ret;
+       if (cyapa->state == CYAPA_STATE_BL_IDLE) {
+               /* Already in BL_IDLE. Skipping exit. */
+               return 0;
+       }
+
+       if (cyapa->state != CYAPA_STATE_OP)
+               return -EAGAIN;
+
+       cyapa->state = CYAPA_STATE_NO_DEVICE;
+       ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, 0x01);
+       if (ret < 0)
+               return -EIO;
+
+       usleep_range(25000, 50000);
+       ret = cyapa_poll_state(cyapa, 500);
+       if (ret < 0)
+               return ret;
+       if ((cyapa->state != CYAPA_STATE_BL_IDLE) ||
+               (cyapa->status[REG_BL_STATUS] & BL_STATUS_WATCHDOG))
+               return -EAGAIN;
+
+       return 0;
+}
+
+static int cyapa_gen3_bl_activate(struct cyapa *cyapa)
+{
+       int ret;
+
+       ret = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_activate),
+                                       bl_activate);
+       if (ret < 0)
+               return ret;
+
+       /* Wait for bootloader to activate; takes between 2 and 12 seconds */
+       msleep(2000);
+       ret = cyapa_poll_state(cyapa, 11000);
+       if (ret < 0)
+               return ret;
+       if (cyapa->state != CYAPA_STATE_BL_ACTIVE)
+               return -EAGAIN;
+
+       return 0;
+}
+
 static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
 {
        int ret;
@@ -950,6 +1073,412 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
        return 0;
 }

+/* Used in gen3 bootloader commands. */
+static u16 cyapa_gen3_csum(const u8 *buf, size_t count)
+{
+       int i;
+       u16 csum = 0;
+
+       for (i = 0; i < count; i++)
+               csum += buf[i];
+
+       return csum;
+}
+
+/*
+ * Verify the integrity of a CYAPA firmware image file.
+ *
+ * The firmware image file is 30848 bytes, composed of 482 64-byte blocks.
+ *
+ * The first 2 blocks are the firmware header.
+ * The next 480 blocks are the firmware image.
+ *
+ * The first two bytes of the header hold the header checksum, computed by
+ * summing the other 126 bytes of the header.
+ * The last two bytes of the header hold the firmware image checksum, computed
+ * by summing the 30720 bytes of the image modulo 0xffff.
+ *
+ * Both checksums are stored little-endian.
+ */
+static int cyapa_gen3_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+       struct device *dev = &cyapa->client->dev;
+       u16 csum;
+       u16 csum_expected;
+
+       /* Firmware must match exact 30848 bytes = 482 64-byte blocks. */
+       if (fw->size != CYAPA_FW_SIZE) {
+               dev_err(dev, "invalid firmware size = %zu, expected %u.\n",
+                       fw->size, CYAPA_FW_SIZE);
+               return -EINVAL;
+       }
+
+       /* Verify header block */
+       csum_expected = (fw->data[0] << 8) | fw->data[1];
+       csum = cyapa_gen3_csum(&fw->data[2], CYAPA_FW_HDR_SIZE - 2);
+       if (csum != csum_expected) {
+               dev_err(dev, "%s %04x, expected: %04x\n",
+                       "invalid firmware header checksum = ",
+                       csum, csum_expected);
+               return -EINVAL;
+       }
+
+       /* Verify firmware image */
+       csum_expected = (fw->data[CYAPA_FW_HDR_SIZE - 2] << 8) |
+                        fw->data[CYAPA_FW_HDR_SIZE - 1];
+       csum = cyapa_gen3_csum(&fw->data[CYAPA_FW_HDR_SIZE],
+                       CYAPA_FW_DATA_SIZE);
+       if (csum != csum_expected) {
+               dev_err(dev, "%s %04x, expected: %04x\n",
+                       "invalid firmware header checksum = ",
+                       csum, csum_expected);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+/*
+ * Write a |len| byte long buffer |buf| to the device, by chopping it up into a
+ * sequence of smaller |CYAPA_CMD_LEN|-length write commands.
+ *
+ * The data bytes for a write command are prepended with the 1-byte offset
+ * of the data relative to the start of |buf|.
+ */
+static int cyapa_gen3_write_buffer(struct cyapa *cyapa,
+               const u8 *buf, size_t len)
+{
+       int ret;
+       size_t i;
+       unsigned char cmd[CYAPA_CMD_LEN + 1];
+       size_t cmd_len;
+
+       for (i = 0; i < len; i += CYAPA_CMD_LEN) {
+               const u8 *payload = &buf[i];
+               cmd_len = (len - i >= CYAPA_CMD_LEN) ? CYAPA_CMD_LEN : len - i;
+               cmd[0] = i;
+               memcpy(&cmd[1], payload, cmd_len);
+
+               ret = cyapa_i2c_reg_write_block(cyapa, 0, cmd_len + 1, cmd);
+               if (ret < 0)
+                       return ret;
+       }
+       return 0;
+}
+
+/*
+ * A firmware block write command writes 64 bytes of data to a single flash
+ * page in the device.  The 78-byte block write command has the format:
+ *   <0xff> <CMD> <Key> <Start> <Data> <Data-Checksum> <CMD Checksum>
+ *
+ *  <0xff>  - every command starts with 0xff
+ *  <CMD>   - the write command value is 0x39
+ *  <Key>   - write commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ *  <Block> - Memory Block number (address / 64) (16-bit, big-endian)
+ *  <Data>  - 64 bytes of firmware image data
+ *  <Data Checksum> - sum of 64 <Data> bytes, modulo 0xff
+ *  <CMD Checksum> - sum of 77 bytes, from 0xff to <Data Checksum>
+ *
+ * Each write command is split into 5 i2c write transactions of up to 16 bytes.
+ * Each transaction starts with an i2c register offset: (00, 10, 20, 30, 40).
+ */
+static int cyapa_gen3_write_fw_block(struct cyapa *cyapa,
+               u16 block, const u8 *data)
+{
+       int ret;
+       u8 cmd[78];
+       u8 status[BL_STATUS_SIZE];
+       /* Programming for one block can take about 100ms. */
+       int tries = 11;
+       u8 bl_status, bl_error;
+
+       /* set write command and security key bytes. */
+       cmd[0] = 0xff;
+       cmd[1] = 0x39;
+       cmd[2] = 0x00;
+       cmd[3] = 0x01;
+       cmd[4] = 0x02;
+       cmd[5] = 0x03;
+       cmd[6] = 0x04;
+       cmd[7] = 0x05;
+       cmd[8] = 0x06;
+       cmd[9] = 0x07;
+       cmd[10] = block >> 8;
+       cmd[11] = block;
+       memcpy(&cmd[12], data, CYAPA_FW_BLOCK_SIZE);
+       cmd[76] = cyapa_gen3_csum(data, CYAPA_FW_BLOCK_SIZE);
+       cmd[77] = cyapa_gen3_csum(cmd, sizeof(cmd) - 1);
+
+       ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+       if (ret)
+               return ret;
+
+       /* wait for write to finish */
+       do {
+               usleep_range(10000, 20000);
+
+               /* check block write command result status. */
+               ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET,
+                                              BL_STATUS_SIZE, status);
+               if (ret != BL_STATUS_SIZE)
+                       return (ret < 0) ? ret : -EIO;
+       } while ((status[1] & BL_STATUS_BUSY) && --tries);
+
+       /* ignore WATCHDOG bit and reserved bits. */
+       bl_status = status[1] & ~BL_STATUS_REV_MASK;
+       bl_error = status[2] & ~BL_ERROR_RESERVED;
+
+       if (status[1] & BL_STATUS_BUSY)
+               ret = -ETIMEDOUT;
+       else if (bl_status != BL_STATUS_RUNNING ||
+               bl_error != BL_ERROR_BOOTLOADING)
+               ret = -EIO;
+       else
+               ret = 0;
+
+       return ret;
+}
+
+/*
+ * A firmware block read command reads 16 bytes of data from flash starting
+ * from a given address.  The 12-byte block read command has the format:
+ *   <0xff> <CMD> <Key> <Addr>
+ *
+ *  <0xff>  - every command starts with 0xff
+ *  <CMD>   - the read command value is 0x3c
+ *  <Key>   - read commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ *  <Addr>  - Memory address (16-bit, big-endian)
+ *
+ * The command is followed by an i2c block read to read the 16 bytes of data.
+ */
+static int cyapa_gen3_read_fw_bytes(struct cyapa *cyapa, u16 addr, u8 *data)
+{
+       int ret;
+       u8 cmd[] = { 0xff, 0x3c, 0, 1, 2, 3, 4, 5, 6, 7, addr >> 8, addr };
+
+       ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+       if (ret)
+               return ret;
+
+       /* read data buffer starting from offset 16 */
+       ret = cyapa_i2c_reg_read_block(cyapa, 16, CYAPA_FW_READ_SIZE, data);
+       if (ret != CYAPA_FW_READ_SIZE)
+               return (ret < 0) ? ret : -EIO;
+
+       return 0;
+}
+
+static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
+               const struct firmware *fw)
+{
+       struct device *dev = &cyapa->client->dev;
+       int ret;
+       int i;
+
+       /* First write data, starting at byte 128  of fw->data */
+       for (i = 0; i < CYAPA_FW_DATA_BLOCK_COUNT; i++) {
+               size_t block = CYAPA_FW_DATA_BLOCK_START + i;
+               size_t addr = (i + CYAPA_FW_HDR_BLOCK_COUNT) *
+                               CYAPA_FW_BLOCK_SIZE;
+               const u8 *data = &fw->data[addr];
+               ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+               if (ret) {
+                       dev_err(dev, "FW update aborted, %d\n", ret);
+                       return ret;
+               }
+       }
+
+       /* Then write checksum */
+       for (i = 0; i < CYAPA_FW_HDR_BLOCK_COUNT; i++) {
+               size_t block = CYAPA_FW_HDR_BLOCK_START + i;
+               size_t addr = i * CYAPA_FW_BLOCK_SIZE;
+               const u8 *data = &fw->data[addr];
+               ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+               if (ret) {
+                       dev_err(dev, "FW update aborted, %d\n", ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Read the entire firmware image into ->read_fw_image.
+ * If the ->read_fw_image has already been allocated, then this function
+ * doesn't do anything and just returns 0.
+ * If an error occurs while reading the image, ->read_fw_image is freed, and
+ * the error is returned.
+ *
+ * The firmware is a fixed size (CYAPA_FW_SIZE), and is read out in
+ * fixed length (CYAPA_FW_READ_SIZE) chunks.
+ */
+static int cyapa_gen3_read_fw(struct cyapa *cyapa)
+{
+       int ret;
+       int addr;
+
+       if (cyapa->read_fw_image)
+               return 0;
+
+       ret = cyapa_gen3_bl_enter(cyapa);
+       if (ret)
+               goto err_detect;
+
+       cyapa->read_fw_image = kmalloc(CYAPA_FW_SIZE, GFP_KERNEL);
+       if (!cyapa->read_fw_image) {
+               ret = -ENOMEM;
+               goto err_detect;
+       }
+
+       for (addr = 0; addr < CYAPA_FW_SIZE; addr += CYAPA_FW_READ_SIZE) {
+               ret = cyapa_gen3_read_fw_bytes(cyapa, CYAPA_FW_HDR_START + addr,
+                                         &cyapa->read_fw_image[addr]);
+               if (ret) {
+                       kfree(cyapa->read_fw_image);
+                       cyapa->read_fw_image = NULL;
+                       break;
+               }
+       }
+
+err_detect:
+       if (cyapa->read_fw_image)
+               cyapa->read_fw_image_size = CYAPA_FW_SIZE;
+       cyapa_detect_async(cyapa, 0);
+       return ret;
+}
+
+static ssize_t cyapa_gen3_do_calibrate(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int tries = 20;  /* max recalibration timeout 2s. */
+       int ret;
+
+       cyapa_disable_irq(cyapa);
+
+       ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+       if (ret < 0) {
+               dev_err(dev, "Error reading dev status. err = %d\n", ret);
+               goto out;
+       }
+       if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+               dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n",
+                        ret);
+               ret = -EAGAIN;
+               goto out;
+       }
+
+       ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET,
+                              OP_RECALIBRATION_MASK);
+       if (ret < 0) {
+               dev_err(dev, "Failed to send calibrate command. ret = %d\n",
+                       ret);
+               goto out;
+       }
+
+       do {
+               /*
+                * For this recalibration, the max time will not exceed 2s.
+                * The average time is approximately 500 - 700 ms, and we
+                * will check the status every 100 - 200ms.
+                */
+               usleep_range(100000, 200000);
+
+               ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+               if (ret < 0) {
+                       dev_err(dev, "Error reading dev status. err = %d\n",
+                               ret);
+                       goto out;
+               }
+               if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL)
+                       break;
+       } while (--tries);
+
+       if (tries == 0) {
+               dev_err(dev, "Failed to calibrate. Timeout.\n");
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+       dev_dbg(dev, "Calibration successful.\n");
+
+out:
+       cyapa_enable_irq(cyapa);
+       return ret < 0 ? ret : count;
+}
+
+static ssize_t cyapa_gen3_show_baseline(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int max_baseline, min_baseline;
+       int tries = 3;
+       int ret;
+
+       cyapa_disable_irq(cyapa);
+
+       ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+       if (ret < 0) {
+               dev_err(dev, "Error reading dev status. err = %d\n", ret);
+               goto out;
+       }
+       if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+               dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n",
+                        ret);
+               ret = -EAGAIN;
+               goto out;
+       }
+
+       ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET,
+                              OP_REPORT_BASELINE_MASK);
+       if (ret < 0) {
+               dev_err(dev, "Failed to send report baseline command. %d\n",
+                       ret);
+               goto out;
+       }
+
+       do {
+               usleep_range(10000, 20000);
+
+               ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+               if (ret < 0) {
+                       dev_err(dev, "Error reading dev status. err = %d\n",
+                               ret);
+                       goto out;
+               }
+               if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL)
+                       break;
+       } while (--tries);
+
+       if (tries == 0) {
+               dev_err(dev, "Device timed out going to Normal state.\n");
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       ret = cyapa_read_byte(cyapa, CYAPA_CMD_MAX_BASELINE);
+       if (ret < 0) {
+               dev_err(dev, "Failed to read max baseline. err = %d\n", ret);
+               goto out;
+       }
+       max_baseline = ret;
+
+       ret = cyapa_read_byte(cyapa, CYAPA_CMD_MIN_BASELINE);
+       if (ret < 0) {
+               dev_err(dev, "Failed to read min baseline. err = %d\n", ret);
+               goto out;
+       }
+       min_baseline = ret;
+
+       dev_dbg(dev, "Baseline report successful. Max: %d Min: %d\n",
+               max_baseline, min_baseline);
+       ret = scnprintf(buf, PAGE_SIZE, "%d %d\n", max_baseline, min_baseline);
+
+out:
+       cyapa_enable_irq(cyapa);
+       return ret;
+}
+
 /*
  * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
  *
@@ -2541,18 +3070,18 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)

                break;
        case CYAPA_GEN3:
-               cyapa->cyapa_check_fw = NULL;
-               cyapa->cyapa_bl_enter = NULL;
-               cyapa->cyapa_bl_activate = NULL;
+               cyapa->cyapa_check_fw = cyapa_gen3_check_fw;
+               cyapa->cyapa_bl_enter = cyapa_gen3_bl_enter;
+               cyapa->cyapa_bl_activate = cyapa_gen3_bl_activate;
                cyapa->cyapa_bl_initiate = NULL;
-               cyapa->cyapa_update_fw = NULL;
+               cyapa->cyapa_update_fw = cyapa_gen3_do_fw_update;
                cyapa->cyapa_bl_verify_app_integrity = NULL;
                cyapa->cyapa_bl_deactivate = cyapa_gen3_bl_deactivate;
-               cyapa->cyapa_show_baseline = NULL;
-               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_show_baseline = cyapa_gen3_show_baseline;
+               cyapa->cyapa_calibrate_store = cyapa_gen3_do_calibrate;
                cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
                cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
-               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_fw = cyapa_gen3_read_fw;
                cyapa->cyapa_read_raw_data = NULL;

                ret = cyapa_gen3_do_operational_check(cyapa);
@@ -2713,6 +3242,10 @@ static int cyapa_get_state(struct cyapa *cyapa)
         * detect trackpad protocol based on characristic registers and bits.
         */
        do {
+               cyapa->status[REG_OP_STATUS] = status[REG_OP_STATUS];
+               cyapa->status[REG_BL_STATUS] = status[REG_BL_STATUS];
+               cyapa->status[REG_BL_ERROR] = status[REG_BL_ERROR];
+
                if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
                                cyapa->gen == CYAPA_GEN3) {
                        cyapa->gen_detecting = CYAPA_GEN3;
@@ -2964,6 +3497,286 @@ static void cyapa_detect(struct cyapa *cyapa)
                cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
 }

+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+       struct device *dev = &cyapa->client->dev;
+       int ret;
+       const struct firmware *fw;
+
+       ret = request_firmware(&fw, fw_name, dev);
+       if (ret) {
+               dev_err(dev, "Could not load firmware from %s, %d\n",
+                       fw_name, ret);
+               return ret;
+       }
+
+       if (cyapa->cyapa_check_fw) {
+               ret = cyapa->cyapa_check_fw(cyapa, fw);
+               if (ret) {
+                       dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+                                       fw_name);
+                       goto done;
+               }
+       } else {
+               dev_err(dev, "Unknown status, operation forbidden, gen=%d\n",
+                       cyapa->gen);
+               ret = -EPERM;
+               goto done;
+       }
+
+       /*
+        * Resume the potentially suspended device because doing FW
+        * update on a device not in the FULL mode has a chance to
+        * fail.
+        */
+       pm_runtime_get_sync(dev);
+
+       if (cyapa->cyapa_bl_enter) {
+               ret = cyapa->cyapa_bl_enter(cyapa);
+               if (ret)
+                       goto err_detect;
+       }
+
+       if (cyapa->cyapa_bl_activate) {
+               ret = cyapa->cyapa_bl_activate(cyapa);
+               if (ret)
+                       goto err_detect;
+       }
+
+       if (cyapa->cyapa_bl_initiate) {
+               ret = cyapa->cyapa_bl_initiate(cyapa, fw);
+               if (ret)
+                       goto err_detect;
+       }
+
+       if (cyapa->cyapa_update_fw) {
+               ret = cyapa->cyapa_update_fw(cyapa, fw);
+               if (ret)
+                       goto err_detect;
+       }
+
+       if (cyapa->cyapa_bl_verify_app_integrity) {
+               ret = cyapa->cyapa_bl_verify_app_integrity(cyapa);
+               if (ret)
+                       goto err_detect;
+       }
+
+err_detect:
+       pm_runtime_put_noidle(dev);
+       cyapa_detect_async(cyapa, 0);
+
+done:
+       release_firmware(fw);
+       return ret;
+}
+
+/*
+ **************************************************************
+ * debugfs interface
+ **************************************************************
+*/
+static int cyapa_debugfs_open(struct inode *inode, struct file *file)
+{
+       struct cyapa *cyapa = inode->i_private;
+       int ret;
+
+       if (!cyapa)
+               return -ENODEV;
+
+       ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+       if (ret)
+               return ret;
+
+       if (!kobject_get(&cyapa->client->dev.kobj)) {
+               ret = -ENODEV;
+               goto out;
+       }
+
+       file->private_data = cyapa;
+
+       /*
+        * If firmware hasn't been read yet, read it all in one pass.
+        * Subsequent opens will reuse the data in this same buffer.
+        */
+       if (!cyapa->cyapa_read_fw) {
+               ret = -EPERM;
+               goto out;
+       }
+       ret = cyapa->cyapa_read_fw(cyapa);
+
+out:
+       mutex_unlock(&cyapa->debugfs_mutex);
+       return ret;
+}
+
+static int cyapa_debugfs_release(struct inode *inode, struct file *file)
+{
+       struct cyapa *cyapa = file->private_data;
+       int ret;
+
+       if (!cyapa)
+               return 0;
+
+       ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+       if (ret)
+               return ret;
+       file->private_data = NULL;
+       kobject_put(&cyapa->client->dev.kobj);
+       mutex_unlock(&cyapa->debugfs_mutex);
+
+       return 0;
+}
+
+/* Return some bytes from the buffered firmware image, starting from *ppos */
+static ssize_t cyapa_debugfs_read_fw(struct file *file, char __user *buffer,
+                                    size_t count, loff_t *ppos)
+{
+       struct cyapa *cyapa = file->private_data;
+
+       if (!cyapa->read_fw_image)
+               return -EINVAL;
+
+       if (*ppos >= cyapa->read_fw_image_size)
+               return 0;
+
+       if (count + *ppos > cyapa->read_fw_image_size)
+               count = cyapa->read_fw_image_size - *ppos;
+
+       if (copy_to_user(buffer, &cyapa->read_fw_image[*ppos], count))
+               return -EFAULT;
+
+       *ppos += count;
+       return count;
+}
+
+static const struct file_operations cyapa_read_fw_fops = {
+       .open = cyapa_debugfs_open,
+       .release = cyapa_debugfs_release,
+       .read = cyapa_debugfs_read_fw
+};
+
+static int cyapa_debugfs_raw_data_open(struct inode *inode, struct file *file)
+{
+       struct cyapa *cyapa = inode->i_private;
+       int ret;
+
+       if (!cyapa)
+               return -ENODEV;
+
+       ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+       if (ret)
+               return ret;
+
+       if (!kobject_get(&cyapa->client->dev.kobj)) {
+               ret = -ENODEV;
+               goto out;
+       }
+
+       file->private_data = cyapa;
+
+       if (!cyapa->tp_raw_data) {
+               if (cyapa->state != CYAPA_STATE_GEN5_APP ||
+                       !cyapa->electrodes_x || !cyapa->electrodes_y) {
+                       ret =  -EINVAL;
+                       goto out;
+               }
+
+               cyapa->tp_raw_data_size = sizeof(s32) * (cyapa->electrodes_x *
+                       cyapa->electrodes_y + cyapa->electrodes_x +
+                       cyapa->electrodes_y) + GEN5_RAW_DATA_HEAD_SIZE;
+               /* This buffer will be hold after used until the driver is
+                * unloaded, the purpose of it is to improve the performace
+                * to avoid frequently allocate and release the buffer. */
+               cyapa->tp_raw_data =
+                       kmalloc(cyapa->tp_raw_data_size, GFP_KERNEL);
+               if (!cyapa->tp_raw_data) {
+                       ret =  -ENOMEM;
+                       goto out;
+               }
+               memset(cyapa->tp_raw_data, 0, cyapa->tp_raw_data_size);
+       }
+
+       if (!cyapa->cyapa_read_raw_data) {
+               ret = -EPERM;
+               goto out;
+       }
+       ret = cyapa->cyapa_read_raw_data(cyapa);
+
+out:
+       mutex_unlock(&cyapa->debugfs_mutex);
+       return ret;
+}
+
+static int cyapa_debugfs_raw_data_release(struct inode *inode,
+                               struct file *file)
+{
+       struct cyapa *cyapa = file->private_data;
+       int ret;
+
+       if (!cyapa)
+               return 0;
+
+       ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+       if (ret)
+               return ret;
+       file->private_data = NULL;
+       kobject_put(&cyapa->client->dev.kobj);
+       mutex_unlock(&cyapa->debugfs_mutex);
+
+       return 0;
+}
+
+/* Always return the sensors' latest raw data from trackpad device. */
+static ssize_t cyapa_debugfs_read_raw_data(struct file *file,
+                                    char __user *buffer,
+                                    size_t count, loff_t *ppos)
+{
+       struct cyapa *cyapa = file->private_data;
+
+       if (!cyapa->tp_raw_data)
+               return -EINVAL;
+
+       if (*ppos >= cyapa->tp_raw_data_size)
+               return 0;
+
+       if (count + *ppos > cyapa->tp_raw_data_size)
+               count = cyapa->tp_raw_data_size - *ppos;
+
+       if (copy_to_user(buffer, &cyapa->tp_raw_data[*ppos], count))
+               return -EFAULT;
+
+       *ppos += count;
+       return count;
+}
+
+static const struct file_operations cyapa_read_raw_data_fops = {
+       .open = cyapa_debugfs_raw_data_open,
+       .release = cyapa_debugfs_raw_data_release,
+       .read = cyapa_debugfs_read_raw_data
+};
+
+static int cyapa_debugfs_init(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+
+       if (!cyapa_debugfs_root)
+               return -ENODEV;
+
+       cyapa->dentry_dev = debugfs_create_dir(kobject_name(&dev->kobj),
+                                              cyapa_debugfs_root);
+
+       if (!cyapa->dentry_dev)
+               return -ENODEV;
+
+       mutex_init(&cyapa->debugfs_mutex);
+
+       debugfs_create_file(CYAPA_DEBUGFS_READ_FW, S_IRUSR, cyapa->dentry_dev,
+                           cyapa, &cyapa_read_fw_fops);
+
+       debugfs_create_file(CYAPA_DEBUGFS_RAW_DATA, S_IRUSR, cyapa->dentry_dev,
+                           cyapa, &cyapa_read_raw_data_fops);
+       return 0;
+}

 /*
  * Sysfs Interface.
@@ -3110,6 +3923,93 @@ static void cyapa_start_runtime(struct cyapa *cyapa)
 static void cyapa_start_runtime(struct cyapa *cyapa) {}
 #endif /* CONFIG_PM_RUNTIME */

+static ssize_t cyapa_show_fm_ver(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       return scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+                        cyapa->fw_min_ver);
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       return scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+}
+
+static ssize_t cyapa_update_fw_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       const char *fw_name;
+       int ret;
+
+       /* Do not allow paths that step out of /lib/firmware  */
+       if (strstr(buf, "../") != NULL)
+               return -EINVAL;
+
+       fw_name = !strncmp(buf, "1", count) ||
+                 !strncmp(buf, "1\n", count) ? CYAPA_FW_NAME : buf;
+
+       ret = cyapa_firmware(cyapa, fw_name);
+       if (ret)
+               dev_err(dev, "firmware update failed, %d\n", ret);
+       else
+               dev_dbg(dev, "firmware update succeeded\n");
+
+       return ret ? ret : count;
+}
+
+static ssize_t cyapa_calibrate_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int ret;
+
+       if (!cyapa->cyapa_calibrate_store) {
+               dev_err(dev, "Calibrate operation not permitted.\n");
+               return -EPERM;
+       }
+
+       ret = cyapa->cyapa_calibrate_store(dev, attr, buf, count);
+       return ret < 0 ? ret : count;
+}
+
+static ssize_t cyapa_show_baseline(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+
+       if (!cyapa->cyapa_show_baseline) {
+               dev_err(dev, "Calibrate operation not permitted.\n");
+               return -EPERM;
+       }
+
+       return cyapa->cyapa_show_baseline(dev, attr, buf);
+}
+
+static DEVICE_ATTR(firmware_version, S_IRUGO, cyapa_show_fm_ver, NULL);
+static DEVICE_ATTR(product_id, S_IRUGO, cyapa_show_product_id, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, cyapa_update_fw_store);
+static DEVICE_ATTR(baseline, S_IRUGO, cyapa_show_baseline, NULL);
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, cyapa_calibrate_store);
+
+static struct attribute *cyapa_sysfs_entries[] = {
+       &dev_attr_firmware_version.attr,
+       &dev_attr_product_id.attr,
+       &dev_attr_update_fw.attr,
+       &dev_attr_baseline.attr,
+       &dev_attr_calibrate.attr,
+       NULL,
+};
+
+static const struct attribute_group cyapa_sysfs_group = {
+       .attrs = cyapa_sysfs_entries,
+};
+

 /*
  * We rely on EV_SW and SW_LID bits to identify a LID device, and hook
@@ -3311,6 +4211,16 @@ static int cyapa_probe(struct i2c_client *client,
        }
        cyapa_disable_irq(cyapa);

+       if (sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group))
+               dev_warn(dev, "error creating sysfs entries.\n");
+
+       /* Create a global debugfs root for all cyapa devices */
+       cyapa_debugfs_root = debugfs_create_dir("cyapa", NULL);
+       if (cyapa_debugfs_root == ERR_PTR(-ENODEV))
+               cyapa_debugfs_root = NULL;
+       if (cyapa_debugfs_init(cyapa))
+               dev_warn(dev, "error creating debugfs entries.\n");
+
 #ifdef CONFIG_PM_SLEEP
        if (device_can_wakeup(dev) &&
            sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
@@ -3333,6 +4243,7 @@ static int cyapa_remove(struct i2c_client *client)
        struct cyapa *cyapa = i2c_get_clientdata(client);

        pm_runtime_disable(&client->dev);
+       sysfs_remove_group(&client->dev.kobj, &cyapa_sysfs_group);

 #ifdef CONFIG_PM_SLEEP
        sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
@@ -3342,7 +4253,18 @@ static int cyapa_remove(struct i2c_client *client)
        sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
 #endif

+       kfree(cyapa->read_fw_image);
+       cyapa->read_fw_image = NULL;
+       cyapa->read_fw_image_size = 0;
+       kfree(cyapa->tp_raw_data);
+       cyapa->tp_raw_data = NULL;
+       cyapa->tp_raw_data_size = 0;
        free_irq(cyapa->irq, cyapa);
+
+       debugfs_remove_recursive(cyapa->dentry_dev);
+       debugfs_remove_recursive(cyapa_debugfs_root);
+       mutex_destroy(&cyapa->debugfs_mutex);
+
        input_unregister_device(cyapa->input);
        lid_event_unregister_handler(cyapa);
        if (cyapa->cyapa_set_power_mode)
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 16554 bytes --]

^ permalink raw reply related

* [PATCH 6/6] input: cyapa: add sysfs interfaces supported for gen5 trackpad device
From: Dudley Du @ 2014-04-16  8:41 UTC (permalink / raw)
  To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
  Cc: Benson Leung, Daniel Kurtz, David Solda,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 52899 bytes --]

Add sysfs interfaces for gen5 trackpad devices that required in production,
including read and update firmware image, report baselines, sensors calibrate,
read product id, read firmware version.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index effa9c5..8653ffd 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -205,7 +205,7 @@ config MOUSE_BCM5974

 config MOUSE_CYAPA
        tristate "Cypress APA I2C Trackpad support"
-       depends on I2C
+       depends on I2C && CRC_ITU_T
        help
          This driver adds support for Cypress All Points Addressable (APA)
          I2C Trackpads, including the ones used in 2012 Samsung Chromebooks.
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 66cb5cc..63a2c79 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -29,6 +29,8 @@
 #include <linux/uaccess.h>
 #include <linux/unaligned/access_ok.h>
 #include <linux/pm_runtime.h>
+#include <linux/crc-ccitt.h>
+#include <linux/crc-itu-t.h>


 /* APA trackpad firmware generation */
@@ -2364,6 +2366,86 @@ static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
        return -EAGAIN;
 }

+static int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
+               const struct firmware *fw)
+{
+       int ret = 0;
+       u16 length = 0;
+       u16 data_len = 0;
+       u16 meta_data_crc = 0;
+       u16 cmd_crc = 0;
+       u8 bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + 3];
+       int bl_gen5_activate_size = 0;
+       u8 resp_data[11];
+       int resp_len;
+       struct cyapa_tsg_bin_image *image;
+       int records_num;
+       u8 *data;
+
+       /* Try to dump all bufferred report data before send any command. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       bl_gen5_activate_size = sizeof(bl_gen5_activate);
+       memset(bl_gen5_activate, 0, bl_gen5_activate_size);
+
+       /* Output Report Register Address[15:0] = 0004h */
+       bl_gen5_activate[0] = 0x04;
+       bl_gen5_activate[1] = 0x00;
+
+       /* totoal command length[15:0] */
+       length = bl_gen5_activate_size - 2;
+       put_unaligned_le16(length, &bl_gen5_activate[2]);
+       bl_gen5_activate[4] = 0x40;  /* Report ID = 40h */
+       bl_gen5_activate[5] = 0x00;  /* RSVD = 00h */
+
+       bl_gen5_activate[6] = GEN5_SOP_KEY;  /* SOP = 01h */
+       bl_gen5_activate[7] = 0x48;  /* Command Code = 48h */
+
+       /* 8 Key bytes and block size */
+       data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
+       /* Data Length[15:0] */
+       put_unaligned_le16(data_len, &bl_gen5_activate[8]);
+       bl_gen5_activate[10] = 0xa5;  /* Key Byte 0 */
+       bl_gen5_activate[11] = 0x01;
+       bl_gen5_activate[12] = 0x02;  /*     .      */
+       bl_gen5_activate[13] = 0x03;  /*     .      */
+       bl_gen5_activate[14] = 0xff;  /*     .      */
+       bl_gen5_activate[15] = 0xfe;
+       bl_gen5_activate[16] = 0xfd;
+       bl_gen5_activate[17] = 0x5a;  /* Key Byte 7 */
+
+       /* copy 60 bytes Meta Data Row Parameters */
+       image = (struct cyapa_tsg_bin_image *)fw->data;
+       records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+                               sizeof(struct cyapa_tsg_bin_image_data_record);
+       /* APP_INTEGRITY row is always the last row block */
+       data = image->records[records_num - 1].record_data;
+       memcpy(&bl_gen5_activate[18], data, CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+
+       meta_data_crc = crc_itu_t(0xffff, &bl_gen5_activate[18],
+                               CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+       /* Meta Data CRC[15:0] */
+       put_unaligned_le16(meta_data_crc,
+               &bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_METADATA_SIZE]);
+
+       cmd_crc = crc_itu_t(0xffff, &bl_gen5_activate[6], 4 + data_len);
+       put_unaligned_le16(cmd_crc,
+               &bl_gen5_activate[bl_gen5_activate_size - 3]);  /* CRC[15:0] */
+       bl_gen5_activate[bl_gen5_activate_size - 1] = GEN5_EOP_KEY;
+
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       bl_gen5_activate, sizeof(bl_gen5_activate),
+                       resp_data, &resp_len, 12000,
+                       cyapa_gen5_sort_tsg_pip_bl_resp_data);
+       if (ret || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
+                       resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return (ret < 0) ? ret : -EAGAIN;
+
+       return 0;
+}
+
 bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
 {
        if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
@@ -2410,6 +2492,373 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
        return -EAGAIN;
 }

+static int cyapa_gen5_bl_enter(struct cyapa *cyapa)
+{
+       int ret;
+       u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 };
+       u8 resp_data[2];
+       int resp_len;
+
+       if (cyapa->input) {
+               cyapa_disable_irq(cyapa);
+               input_unregister_device(cyapa->input);
+               cyapa->input = NULL;
+       }
+       cyapa_enable_irq(cyapa);
+
+       ret = cyapa_poll_state(cyapa, 500);
+       if (ret < 0)
+               return ret;
+       if (cyapa->gen != CYAPA_GEN5)
+               return -EINVAL;
+
+       /* Already in Gen5 BL. Skipping exit. */
+       if (cyapa->state == CYAPA_STATE_GEN5_BL)
+               return 0;
+
+       if (cyapa->state != CYAPA_STATE_GEN5_APP)
+               return -EAGAIN;
+
+       /* Try to dump all bufferred report data before send any command. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       /*
+        * send bootloader enter command to trackpad device,
+        * after enter bootloader, the response data is two bytes of 0x00 0x00.
+        */
+       resp_len = sizeof(resp_data);
+       memset(resp_data, 0, resp_len);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       5000, cyapa_gen5_sort_application_launch_data);
+       if (ret || resp_data[0] != 0x00 || resp_data[1] != 0x00)
+               return (ret < 0) ? ret : -EAGAIN;
+
+       cyapa->state = CYAPA_STATE_GEN5_BL;
+       return 0;
+}
+
+static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+       int i;
+       struct cyapa_tsg_bin_image *image;
+       int flash_records_count;
+       u16 expected_app_crc;
+       u16 expected_app_integrity_crc;
+       u16 app_crc = 0;
+       u16 app_integrity_crc = 0;
+       u16 row_num;
+       u8 *data;
+       u32 app_start;
+       u16 app_len;
+       u32 img_start;
+       u16 img_len;
+       int record_index;
+       struct device *dev = &cyapa->client->dev;
+
+       image = (struct cyapa_tsg_bin_image *)fw->data;
+       flash_records_count = (fw->size -
+                       sizeof(struct cyapa_tsg_bin_image_head)) /
+                       sizeof(struct cyapa_tsg_bin_image_data_record);
+
+       /* APP_INTEGRITY row is always the last row block,
+        * and the row id must be 0x01ff */
+       row_num = get_unaligned_be16(
+                       &image->records[flash_records_count - 1].row_number);
+       if (&image->records[flash_records_count - 1].flash_array_id != 0x00 &&
+                       row_num != 0x01ff) {
+               dev_err(dev, "%s: invaid app_integrity data.\n", __func__);
+               return -EINVAL;
+       }
+       data = image->records[flash_records_count - 1].record_data;
+       app_start = get_unaligned_le32(&data[4]);
+       app_len = get_unaligned_le16(&data[8]);
+       expected_app_crc = get_unaligned_le16(&data[10]);
+       img_start = get_unaligned_le32(&data[16]);
+       img_len = get_unaligned_le16(&data[20]);
+       expected_app_integrity_crc = get_unaligned_le16(&data[60]);
+
+       if ((app_start + app_len + img_start + img_len) %
+                       CYAPA_TSG_FW_ROW_SIZE) {
+               dev_err(dev, "%s: invaid image alignment.\n", __func__);
+               return -EINVAL;
+       }
+
+       /* verify app_integrity crc */
+       app_integrity_crc = crc_itu_t(0xffff, data,
+                       CYAPA_TSG_APP_INTEGRITY_SIZE);
+       if (app_integrity_crc != expected_app_integrity_crc) {
+               dev_err(dev, "%s: invaid app_integrity crc.\n", __func__);
+               return -EINVAL;
+       }
+
+       /*
+        * verify application image CRC
+        */
+       record_index = app_start / CYAPA_TSG_FW_ROW_SIZE -
+                               CYAPA_TSG_IMG_START_ROW_NUM;
+       data = (u8 *)&image->records[record_index].record_data;
+       app_crc = crc_itu_t(0xffff, data, CYAPA_TSG_FW_ROW_SIZE);
+       for (i = 1; i < (app_len / CYAPA_TSG_FW_ROW_SIZE); i++) {
+               data = (u8 *)&image->records[++record_index].record_data;
+               app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE);
+       }
+
+       if (app_crc != expected_app_crc) {
+               dev_err(dev, "%s: invaid firmware app crc check.\n", __func__);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
+               struct cyapa_tsg_bin_image_data_record *flash_record)
+{
+       u8 flash_array_id;
+       u16 flash_row_id;
+       u16 record_len;
+       u8 *record_data;
+       u8 cmd[144];  /* 13 + 128+ 3 */
+       u16 cmd_len;
+       u16 data_len;
+       u16 crc;
+       u8 resp_data[11];
+       int resp_len;
+       int ret;
+
+       flash_array_id = flash_record->flash_array_id;
+       flash_row_id = get_unaligned_be16(&flash_record->row_number);
+       record_len = get_unaligned_be16(&flash_record->record_len);
+       record_data = flash_record->record_data;
+
+       cmd_len = sizeof(cmd) - 2; /* not include 2 bytes regisetr address. */
+       memset(cmd, 0, cmd_len + 2);
+       cmd[0] = 0x04;  /* register address */
+       cmd[1] = 0x00;
+
+       put_unaligned_le16(cmd_len, &cmd[2]);
+       cmd[4] = 0x40;  /* report id 40h */
+       cmd[5] = 0x00;
+
+       cmd[6] = GEN5_SOP_KEY;  /* SOP = 01h */
+       cmd[7] = 0x39;  /* command code = 39h */
+       /* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
+       data_len = 3 + record_len;
+       put_unaligned_le16(data_len, &cmd[8]);
+       cmd[10] = flash_array_id;  /* Flash Array ID = 00h */
+       put_unaligned_le16(flash_row_id, &cmd[11]);
+
+       memcpy(&cmd[13], record_data, record_len);
+       crc = crc_itu_t(0xffff, &cmd[6], 4 + data_len);
+       put_unaligned_le16(crc, &cmd[2 + cmd_len - 3]);
+       cmd[2 + cmd_len - 1] = GEN5_EOP_KEY;
+
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+       if (ret || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
+                       resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return ret < 0 ? ret : -EAGAIN;
+
+       return 0;
+}
+
+static int cyapa_gen5_read_fw_bytes(struct cyapa *cyapa, u16 row_num, u8 *data)
+{
+       int ret;
+       u8 cmd[16];
+       size_t cmd_len;
+       u8 resp_data[CYAPA_TSG_FW_ROW_SIZE / 2 + GEN5_MIN_BL_RESP_LENGTH];
+       int resp_len;
+       u16 offset;
+       u16 cmd_crc;
+       struct cyapa_tsg_bin_image_data_record *fw_img_record;
+
+       fw_img_record = (struct cyapa_tsg_bin_image_data_record *)data;
+
+       cmd[0] = 0x04;  /* register address */
+       cmd[1] = 0x00;
+       cmd[2] = 0x0e;
+       cmd[3] = 0x00;
+       cmd[4] = 0x40;  /* report id 40h */
+       cmd[5] = 0x00;
+       cmd[6] = GEN5_SOP_KEY;
+       cmd[7] = 0x3d;  /* read application image command code */
+       cmd[8] = 0x03;
+       cmd[9] = 0x00;
+       offset = row_num * CYAPA_TSG_FW_ROW_SIZE -
+                       CYAPA_TSG_START_OF_APPLICATION;
+       put_unaligned_le16(offset, &cmd[10]);
+       cmd[12] = CYAPA_TSG_IMG_READ_SIZE;
+       cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+       put_unaligned_le16(cmd_crc, &cmd[13]);  /* CRC[15:0] */
+       cmd[15] = GEN5_EOP_KEY;  /* EOP = 17h */
+       cmd_len = 16;
+
+       resp_len = CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH;
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, cmd_len,
+                       resp_data, &resp_len,
+                       50, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+       if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+                       ret || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return (ret < 0) ? ret : -EAGAIN;
+
+       /* copy first 64 bytes in the row. */
+       memcpy(&fw_img_record->record_data[0], &resp_data[8],
+                       CYAPA_TSG_IMG_READ_SIZE);
+
+       if (row_num == CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM) {
+               /* last row's rest 64 bytes are bootloader metadata,
+                * it's not allowed to be read out, will respond with error. */
+               memset(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+                       0, CYAPA_TSG_IMG_READ_SIZE);
+               goto skip_last_row;
+       }
+
+       /* read next 64 bytes in the row. */
+       offset = offset + CYAPA_TSG_IMG_READ_SIZE;
+       put_unaligned_le16(offset, &cmd[10]);
+       cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+       put_unaligned_le16(cmd_crc, &cmd[13]);  /* CRC[15:0] */
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, cmd_len,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+       if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+                       ret || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return (ret < 0) ? ret : -EAGAIN;
+
+       /* copy last 64 bytes in the row. */
+       memcpy(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+               &resp_data[8], CYAPA_TSG_IMG_READ_SIZE);
+
+skip_last_row:
+       fw_img_record->flash_array_id = 0;
+       put_unaligned_be16(row_num, &fw_img_record->row_number);
+       put_unaligned_be16(CYAPA_TSG_FW_ROW_SIZE, &fw_img_record->record_len);
+
+       return 0;
+}
+
+static int cyapa_gen5_read_fw(struct cyapa *cyapa)
+{
+       int ret;
+       int fw_img_head_size;
+       int fw_img_record_size;
+       int row_index;
+       int array_index;
+       u32 img_start;
+       u16 img_len;
+       u16 img_start_row;
+       u16 img_end_row;
+       struct cyapa_tsg_bin_image_data_record app_integrity;
+       u8 *record_data;
+
+       if (cyapa->read_fw_image)
+               return 0;
+
+       ret = cyapa_gen5_bl_enter(cyapa);
+       if (ret)
+               goto err;
+
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       fw_img_head_size = sizeof(struct cyapa_tsg_bin_image_head);
+       fw_img_record_size = sizeof(struct cyapa_tsg_bin_image_data_record);
+
+       /* Read app integrity block data. */
+       row_index = CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM;
+       ret = cyapa_gen5_read_fw_bytes(cyapa, row_index, (u8 *)&app_integrity);
+       if (ret)
+               goto err;
+       img_start = get_unaligned_le32(&app_integrity.record_data[16]);
+       img_len = get_unaligned_le16(&app_integrity.record_data[20]);
+       if ((img_start + img_len) % CYAPA_TSG_FW_ROW_SIZE)
+               goto err;
+       img_start_row = img_start / CYAPA_TSG_FW_ROW_SIZE;
+       img_end_row = (img_start + img_len) / CYAPA_TSG_FW_ROW_SIZE - 1;
+
+       /* allocate memory for image. */
+       cyapa->read_fw_image_size = fw_img_head_size +
+               (img_end_row -  img_start_row + 2) * fw_img_record_size;
+       cyapa->read_fw_image = kmalloc(cyapa->read_fw_image_size, GFP_KERNEL);
+       if (!cyapa->read_fw_image) {
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       /* set image head data. */
+       memcpy(cyapa->read_fw_image, &cyapa->fw_img_head, fw_img_head_size);
+
+       /* read image blocks. */
+       for (row_index = img_start_row, array_index = 0;
+                       row_index <= img_end_row;
+                       row_index++, array_index++) {
+               record_data = &cyapa->read_fw_image[fw_img_head_size +
+                               array_index * fw_img_record_size];
+               ret = cyapa_gen5_read_fw_bytes(cyapa, row_index, record_data);
+               if (ret)
+                       goto err;
+       }
+
+       /* append last app integrity block data. */
+       record_data = &cyapa->read_fw_image[fw_img_head_size +
+                               array_index * fw_img_record_size];
+       memcpy(record_data, &app_integrity, fw_img_record_size);
+
+err:
+       if (ret) {
+               kfree(cyapa->read_fw_image);
+               cyapa->read_fw_image = NULL;
+               cyapa->read_fw_image_size = 0;
+       }
+
+       cyapa_detect_async(cyapa, 0);
+       return ret;
+}
+
+static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
+               const struct firmware *fw)
+{
+       struct device *dev = &cyapa->client->dev;
+       struct cyapa_tsg_bin_image *image =
+               (struct cyapa_tsg_bin_image *)fw->data;
+       struct cyapa_tsg_bin_image_data_record *flash_record;
+       int flash_records_count;
+       int i;
+       int ret;
+
+       /* Try to dump all bufferred data if exists before send commands. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       flash_records_count =
+               (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+                       sizeof(struct cyapa_tsg_bin_image_data_record);
+       /*
+        * the last flash row 0x01ff has been written through bl_initiate
+        *  command, so DO NOT write flash 0x01ff to trackpad device.
+        */
+       for (i = 0; i < (flash_records_count - 1); i++) {
+               flash_record = &image->records[i];
+               ret = cyapa_gen5_write_fw_block(cyapa, flash_record);
+               if (ret) {
+                       dev_err(dev, "%s: Gen5 FW update aborted, %d\n",
+                               __func__, ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
 static int cyapa_gen5_sleep_time_check(u16 sleep_time)
 {
        if (sleep_time > 1000)
@@ -2694,6 +3143,763 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
        return ret;
 }

+static int cyapa_gen5_resume_scanning(struct cyapa *cyapa)
+{
+       u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x04 };
+       u8 resp_data[6];
+       int resp_len;
+       int ret;
+
+       /* Try to dump all bufferred data before doing command. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       resp_len = 6;
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, 7,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x04)
+               return -EINVAL;
+
+       /* Try to dump all bufferred data when resuming scanning. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       return 0;
+}
+
+static int cyapa_gen5_suspend_scanning(struct cyapa *cyapa)
+{
+       u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x03 };
+       u8 resp_data[6];
+       int resp_len;
+       int ret;
+
+       /* Try to dump all bufferred data before doing command. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       resp_len = 6;
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, 7,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       resp_data[3] != GEN5_RESP_RSVD_KEY ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != 0x03)
+               return -EINVAL;
+
+       /* Try to dump all bufferred data when suspending scanning. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       return 0;
+}
+
+static int cyapa_gen5_calibrate_pwcs(struct cyapa *cyapa,
+               u8 calibrate_sensing_mode_type)
+{
+       int ret;
+       u8 cmd[8];
+       u8 resp_data[6];
+       int resp_len;
+
+       /* Try to dump all bufferred data before doing command. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       cmd[2] = 0x06;
+       cmd[3] = 0x00;
+       cmd[4] = GEN5_APP_CMD_REPORT_ID;
+       cmd[5] = 0x00;
+       cmd[6] = GEN5_CMD_CALIBRATE;
+       cmd[7] = calibrate_sensing_mode_type;
+       resp_len = sizeof(resp_data);
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       5000, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) != GEN5_CMD_CALIBRATE ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+               return ret < 0 ? ret : -EAGAIN;
+
+       return 0;
+}
+
+static ssize_t cyapa_gen5_do_calibrate(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int ret, calibrate_ret;
+
+       /* 1. suspend Scanning*/
+       ret = cyapa_gen5_suspend_scanning(cyapa);
+       if (ret)
+               return ret;
+
+       /* 2. do mutual capacitance fine calibrate. */
+       calibrate_ret = cyapa_gen5_calibrate_pwcs(cyapa,
+                               CYAPA_SENSING_MODE_MUTUAL_CAP_FINE);
+       if (calibrate_ret)
+               goto resume_scanning;
+
+       /* 3. do self capacitance calibrate. */
+       calibrate_ret = cyapa_gen5_calibrate_pwcs(cyapa,
+                               CYAPA_SENSING_MODE_SELF_CAP);
+       if (calibrate_ret)
+               goto resume_scanning;
+
+resume_scanning:
+       /* 4. resume Scanning*/
+       ret = cyapa_gen5_resume_scanning(cyapa);
+       if (ret || calibrate_ret)
+               return ret ? ret : calibrate_ret;
+
+       return count;
+}
+
+static s32 two_complement_to_s32(s32 value, int num_bits)
+{
+       if (value >> (num_bits - 1))
+               value |=  -1 << num_bits;
+       return value;
+}
+
+static s32 cyapa_parse_structure_data(u8 data_format, u8 *buf, int buf_len)
+{
+       int data_size;
+       bool big_endian;
+       bool unsigned_type;
+       s32 value;
+
+       data_size = (data_format & 0x07);
+       big_endian = ((data_format & 0x10) == 0x00);
+       unsigned_type = ((data_format & 0x20) == 0x00);
+
+       if (buf_len < data_size)
+               return 0;
+
+       switch (data_size) {
+       case 1:
+               value  = buf[0];
+               break;
+       case 2:
+               if (big_endian)
+                       value = get_unaligned_be16(buf);
+               else
+                       value = get_unaligned_le16(buf);
+               break;
+       case 4:
+               if (big_endian)
+                       value = get_unaligned_be32(buf);
+               else
+                       value = get_unaligned_le32(buf);
+               break;
+       default:
+               /* should not happen, just as default case here. */
+               value = 0;
+               break;
+       }
+
+       if (!unsigned_type)
+               value = two_complement_to_s32(value, data_size * 8);
+
+       return value;
+}
+
+
+/*
+ * Read all the global mutual or self idac data or mutual or self local PWC
+ * data based on the @idac_data_type.
+ * If the input value of @data_size is 0, then means read global mutual or
+ * self idac data. For read global mutual idac data, @idac_max, @idac_min and
+ * @idac_ave are in order used to return the max value of global mutual idac
+ * data, the min value of global mutual idac and the average value of the
+ * global mutual idac data. For read global self idac data, @idac_max is used
+ * to return the global self cap idac data in Rx direction, @idac_min is used
+ * to return the global self cap idac data in Tx direction. @idac_ave is not
+ * used.
+ * If the input value of @data_size is not 0, than means read the mutual or
+ * self local PWC data. The @idac_max, @idac_min and @idac_ave are used to
+ * return the max, min and average value of the mutual or self local PWC data.
+ * Note, in order to raed mutual local PWC data, must read invoke this function
+ * to read the mutual global idac data firstly to set the correct Rx number
+ * value, otherwise, the read mutual idac and PWC data may not correct.
+ */
+static int cyapa_gen5_read_idac_data(struct cyapa *cyapa,
+               u8 cmd_code, u8 idac_data_type, int *data_size,
+               int *idac_max, int *idac_min, int *idac_ave)
+{
+       int ret;
+       int i;
+       u8 cmd[12];
+       u8 resp_data[256];
+       int resp_len;
+       int read_len;
+       int value;
+       u16 offset;
+       int read_elements;
+       bool read_global_idac;
+       int sum, count, max_element_cnt;
+       int tmp_max, tmp_min, tmp_ave, tmp_sum, tmp_count, tmp_max_elements;
+       int electrodes_rx;
+
+       if (cmd_code != GEN5_CMD_RETRIEVE_DATA_STRUCTURE ||
+               (idac_data_type != GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+               idac_data_type != GEN5_RETRIEVE_SELF_CAP_PWC_DATA) ||
+               !data_size || !idac_max || !idac_min || !idac_ave)
+               return -EINVAL;
+
+       *idac_max = INT_MIN;
+       *idac_min = INT_MAX;
+       sum = count = tmp_count = 0;
+       electrodes_rx = 0;
+       tmp_max_elements = 0;
+       if (*data_size == 0) {
+               /* Read global idac values firstly.
+                * Currently, no idac data exceed 4 bytes. */
+               read_global_idac = true;
+               offset = 0;
+               *data_size = 4;
+
+               if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+                       if (cyapa->electrodes_rx == 0) {
+                               if (cyapa->electrodes_y > cyapa->electrodes_x) {
+                                       electrodes_rx = cyapa->electrodes_y;
+                                       tmp_max_elements = cyapa->electrodes_x;
+                               } else {
+                                       electrodes_rx = cyapa->electrodes_x;
+                                       tmp_max_elements = cyapa->electrodes_y;
+                               }
+                       } else {
+                               electrodes_rx = cyapa->electrodes_rx;
+                               tmp_max_elements = 0;  /* disable Rx detect. */
+                       }
+                       max_element_cnt = ((electrodes_rx + 7) / 8) * 8;
+                       tmp_max = INT_MIN;
+                       tmp_min = INT_MAX;
+                       tmp_ave = tmp_sum = tmp_count = 0;
+               } else
+                       max_element_cnt = 2;
+       } else {
+               read_global_idac = false;
+               if (*data_size > 4)
+                       *data_size = 4;
+               /* calculate the start offset in bytes of local PWC data. */
+               if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+                       offset = ((cyapa->electrodes_rx + 7) / 8) * 8
+                                               * (*data_size);
+                       if (cyapa->electrodes_rx == cyapa->electrodes_x)
+                               tmp_count = cyapa->electrodes_y;
+                       else
+                               tmp_count = cyapa->electrodes_x;
+                       max_element_cnt = ((cyapa->electrodes_rx + 7) / 8) *
+                                               8 * tmp_count;
+               } else if (idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+                       offset = 2;
+                       max_element_cnt = cyapa->electrodes_x +
+                                               cyapa->electrodes_y;
+               }
+       }
+
+       do {
+               read_elements = (256 - 10) / (*data_size);
+               read_elements = min(read_elements, max_element_cnt - count);
+               read_len = read_elements * (*data_size);
+
+               cmd[0] = 0x04;
+               cmd[1] = 0x00;
+               cmd[2] = 0x0a;
+               cmd[3] = 0x00;
+               cmd[4] = GEN5_APP_CMD_REPORT_ID;
+               cmd[5] = 0x00;
+               cmd[6] = cmd_code;
+               put_unaligned_le16(offset, &cmd[7]); /* Read Offset[15:0] */
+               put_unaligned_le16(read_len, &cmd[9]); /* Read Length[15:0] */
+               cmd[11] = idac_data_type;
+               resp_len = 10 + read_len;
+               ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                               cmd, 12,
+                               resp_data, &resp_len,
+                               500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+               if (ret || resp_len < 10 || resp_data[2] !=
+                                       GEN5_APP_RESP_REPORT_ID ||
+                               GET_GEN5_CMD_CODE(resp_data[4]) != cmd_code ||
+                               !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+                               resp_data[6] != idac_data_type)
+                       return (ret < 0) ? ret : -EAGAIN;
+               read_len = get_unaligned_le16(&resp_data[7]);
+               if (read_len == 0)
+                       break;
+
+               *data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+               if (read_len < *data_size)
+                       return -EINVAL;
+
+               if (read_global_idac &&
+                       idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+                       /* Rx's self global idac data. */
+                       *idac_max = cyapa_parse_structure_data(
+                                       resp_data[9], &resp_data[10],
+                                       *data_size);
+                       /* Tx's self global idac data. */
+                       *idac_min = cyapa_parse_structure_data(
+                                       resp_data[9],
+                                       &resp_data[10 + *data_size],
+                                       *data_size);
+                       break;
+               }
+
+               /* read mutual global idac or local mutual/self PWC data. */
+               offset += read_len;
+               for (i = 10; i < (read_len + 10); i += *data_size) {
+                       value = cyapa_parse_structure_data(resp_data[9],
+                                       &resp_data[i], *data_size);
+                       *idac_min = min(value, *idac_min);
+                       *idac_max = max(value, *idac_max);
+
+                       if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+                               tmp_count < tmp_max_elements &&
+                               read_global_idac) {
+                               tmp_min = min(value, tmp_min);
+                               tmp_max = max(value, tmp_max);
+                               tmp_sum += value;
+                               tmp_count++;
+                       }
+
+                       sum += value;
+                       count++;
+
+                       if (count >= max_element_cnt)
+                               goto out;
+               }
+       } while (true);
+
+out:
+       *idac_ave = count ? (sum / count) : 0;
+
+       if (read_global_idac &&
+               idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+               if (tmp_count == 0)
+                       return 0;
+               /* algorithm to detect electrodes_rx value. */
+               tmp_ave = tmp_sum / tmp_count;
+               tmp_count = tmp_ave * 15 / 100;
+               if (abs(tmp_ave - *idac_ave) > tmp_count ||
+                       (abs(tmp_ave - *idac_min) > (tmp_count * 2) &&
+                               *idac_min < tmp_min) ||
+                       (abs(*idac_max - tmp_ave) > (tmp_count * 2) &&
+                               *idac_max > tmp_max)) {
+                       /* overcount the mutual global idac values. */
+                       cyapa->electrodes_rx = tmp_max_elements;
+                       *idac_min = tmp_min;
+                       *idac_max = tmp_max;
+                       *idac_ave = tmp_ave;
+               } else
+                       cyapa->electrodes_rx = electrodes_rx;
+       }
+
+       return 0;
+}
+
+static int cyapa_gen5_read_mutual_idac_data(struct cyapa *cyapa,
+       int *gidac_mutual_max, int *gidac_mutual_min, int *gidac_mutual_ave,
+       int *lidac_mutual_max, int *lidac_mutual_min, int *lidac_mutual_ave)
+{
+       int ret;
+       int data_size;
+
+       *gidac_mutual_max = *gidac_mutual_min = *gidac_mutual_ave = 0;
+       *lidac_mutual_max = *lidac_mutual_min = *lidac_mutual_ave = 0;
+
+       data_size = 0;
+       ret = cyapa_gen5_read_idac_data(cyapa,
+               GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+               GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+               &data_size,
+               gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave);
+       if (ret)
+               return ret;
+
+       ret = cyapa_gen5_read_idac_data(cyapa,
+               GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+               GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+               &data_size,
+               lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave);
+       return ret;
+}
+
+static int cyapa_gen5_read_self_idac_data(struct cyapa *cyapa,
+               int *gidac_self_rx, int *gidac_self_tx,
+               int *lidac_self_max, int *lidac_self_min, int *lidac_self_ave)
+{
+       int ret;
+       int data_size;
+
+       *gidac_self_rx = *gidac_self_tx = 0;
+       *lidac_self_max = *lidac_self_min = *lidac_self_ave = 0;
+
+       data_size = 0;
+       ret = cyapa_gen5_read_idac_data(cyapa,
+               GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+               GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+               &data_size,
+               lidac_self_max, lidac_self_min, lidac_self_ave);
+       if (ret)
+               return ret;
+       *gidac_self_rx = *lidac_self_max;
+       *gidac_self_tx = *lidac_self_min;
+
+       ret = cyapa_gen5_read_idac_data(cyapa,
+               GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+               GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+               &data_size,
+               lidac_self_max, lidac_self_min, lidac_self_ave);
+       return ret;
+}
+
+static ssize_t cyapa_gen5_execute_panel_scan(struct cyapa *cyapa)
+{
+       int ret;
+       u8 cmd[7];
+       u8 resp_data[6];
+       int resp_len;
+
+       cmd[0] = 0x04;
+       cmd[1] = 0x00;
+       cmd[2] = 0x05;
+       cmd[3] = 0x00;
+       cmd[4] = GEN5_APP_CMD_REPORT_ID;
+       cmd[5] = 0x00;
+       cmd[6] = GEN5_CMD_EXECUTE_PANEL_SCAN;  /* command code */
+       resp_len = 6;
+       ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, 7,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+       if (ret || resp_len != 6 ||
+                       resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                       GET_GEN5_CMD_CODE(resp_data[4]) !=
+                               GEN5_CMD_EXECUTE_PANEL_SCAN ||
+                       !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5])) {
+               cyapa_gen5_resume_scanning(cyapa);
+               return (ret < 0) ? ret : -EAGAIN;
+       }
+
+       return 0;
+}
+
+static int cyapa_gen5_read_panel_scan_raw_data(struct cyapa *cyapa,
+               u8 cmd_code, u8 raw_data_type, int raw_data_max_num,
+               int *raw_data_max, int *raw_data_min, int *raw_data_ave,
+               u8 *buffer)
+{
+       int ret;
+       int i;
+       u8 cmd[12];
+       u8 resp_data[256];  /* max bytes can transfer one time. */
+       int resp_len;
+       int read_elements;
+       int read_len;
+       u16 offset;
+       s32 value;
+       int sum, count;
+       int data_size;
+       s32 *intp;
+
+       if (cmd_code != GEN5_CMD_RETRIEVE_PANEL_SCAN ||
+               (raw_data_type > GEN5_PANEL_SCAN_SELF_DIFFCOUNT) ||
+               !raw_data_max || !raw_data_min || !raw_data_ave)
+               return -EINVAL;
+
+       intp = (s32 *)buffer;
+       *raw_data_max = INT_MIN;
+       *raw_data_min = INT_MAX;
+       sum = count = 0;
+       offset = 0;
+       read_elements = (256 - 10) / 4;  /* currently, max element size is 4. */
+       read_len = read_elements * 4;
+       do {
+               cmd[0] = 0x04;
+               cmd[1] = 0x00;
+               cmd[2] = 0x0a;
+               cmd[3] = 0x00;
+               cmd[4] = GEN5_APP_CMD_REPORT_ID;
+               cmd[5] = 0x00;
+               cmd[6] = cmd_code;  /* command code */
+               put_unaligned_le16(offset, &cmd[7]);
+               put_unaligned_le16(read_elements, &cmd[9]);
+               cmd[11] = raw_data_type;
+               resp_len = 10 + read_len;
+
+               ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, 12,
+                       resp_data, &resp_len,
+                       500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+               if (ret || resp_len < 10 ||
+                               resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+                               (resp_data[4] & 0x7f) != cmd_code ||
+                               !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+                               resp_data[6] != raw_data_type)
+                       return (ret < 0) ? ret : -EAGAIN;
+
+               read_elements = get_unaligned_le16(&resp_data[7]);
+               if (read_elements == 0)
+                       break;
+
+               data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+               offset += read_elements;
+               if (read_elements) {
+                       for (i = 10;
+                            i < (read_elements * data_size + 10);
+                            i += data_size) {
+                               value = cyapa_parse_structure_data(resp_data[9],
+                                               &resp_data[i], data_size);
+                               *raw_data_min = min(value, *raw_data_min);
+                               *raw_data_max = max(value, *raw_data_max);
+
+                               if (intp)
+                                       put_unaligned_le32(value, &intp[count]);
+
+                               sum += value;
+                               count++;
+
+                       }
+               }
+
+               if (count >= raw_data_max_num)
+                       break;
+
+               read_elements = (sizeof(resp_data) - 10) / data_size;
+               read_len = read_elements * data_size;
+       } while (true);
+
+       *raw_data_ave = count ? (sum / count) : 0;
+
+       return 0;
+}
+
+static ssize_t cyapa_gen5_show_baseline(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       int ret, err;
+       int gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave;
+       int lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave;
+       int gidac_self_rx, gidac_self_tx;
+       int lidac_self_max, lidac_self_min, lidac_self_ave;
+       int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+       int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+       int mutual_diffdata_max, mutual_diffdata_min, mutual_diffdata_ave;
+       int self_diffdata_max, self_diffdata_min, self_diffdata_ave;
+       int mutual_baseline_max, mutual_baseline_min, mutual_baseline_ave;
+       int self_baseline_max, self_baseline_min, self_baseline_ave;
+
+       /* 1. suspend Scanning*/
+       ret = cyapa_gen5_suspend_scanning(cyapa);
+       if (ret)
+               return ret;
+
+       /* 2.  read global and local mutual IDAC data. */
+       gidac_self_rx = gidac_self_tx = 0;
+       err = cyapa_gen5_read_mutual_idac_data(cyapa,
+                               &gidac_mutual_max, &gidac_mutual_min,
+                               &gidac_mutual_ave, &lidac_mutual_max,
+                               &lidac_mutual_min, &lidac_mutual_ave);
+       if (err)
+               goto resume_scanning;
+
+       /* 3.  read global and local self IDAC data. */
+       err = cyapa_gen5_read_self_idac_data(cyapa,
+                               &gidac_self_rx, &gidac_self_tx,
+                               &lidac_self_max, &lidac_self_min,
+                               &lidac_self_ave);
+       if (err)
+               goto resume_scanning;
+
+       /* 4. execuate panel scan. It must be executed before read data. */
+       err = cyapa_gen5_execute_panel_scan(cyapa);
+       if (err)
+               goto resume_scanning;
+
+       /* 5. retrive panel scan, mutual cap raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_MUTUAL_RAW_DATA,
+                               cyapa->electrodes_x * cyapa->electrodes_y,
+                               &raw_cap_mutual_max, &raw_cap_mutual_min,
+                               &raw_cap_mutual_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+       /* 6. retrive panel scan, self cap raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_SELF_RAW_DATA,
+                               cyapa->electrodes_x + cyapa->electrodes_y,
+                               &raw_cap_self_max, &raw_cap_self_min,
+                               &raw_cap_self_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+       /* 7. retrive panel scan, mutual cap diffcount raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+                               cyapa->electrodes_x * cyapa->electrodes_y,
+                               &mutual_diffdata_max, &mutual_diffdata_min,
+                               &mutual_diffdata_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+       /* 8. retrive panel scan, self cap diffcount raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+                               cyapa->electrodes_x + cyapa->electrodes_y,
+                               &self_diffdata_max, &self_diffdata_min,
+                               &self_diffdata_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+       /* 9. retrive panel scan, mutual cap baseline raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_MUTUAL_BASELINE,
+                               cyapa->electrodes_x * cyapa->electrodes_y,
+                               &mutual_baseline_max, &mutual_baseline_min,
+                               &mutual_baseline_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+       /* 10. retrive panel scan, self cap baseline raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_SELF_BASELINE,
+                               cyapa->electrodes_x + cyapa->electrodes_y,
+                               &self_baseline_max, &self_baseline_min,
+                               &self_baseline_ave,
+                               NULL);
+       if (err)
+               goto resume_scanning;
+
+resume_scanning:
+       /* 11. resume Scanning*/
+       ret = cyapa_gen5_resume_scanning(cyapa);
+       if (ret || err)
+               return ret ? ret : err;
+
+       /* 12. output data strings */
+       ret = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d ",
+               gidac_mutual_min, gidac_mutual_max, gidac_mutual_ave,
+               lidac_mutual_min, lidac_mutual_max, lidac_mutual_ave,
+               gidac_self_rx, gidac_self_tx,
+               lidac_self_min, lidac_self_max, lidac_self_ave);
+       err = scnprintf(buf + ret, PAGE_SIZE - ret,
+               "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+               raw_cap_mutual_min, raw_cap_mutual_max, raw_cap_mutual_ave,
+               raw_cap_self_min, raw_cap_self_max, raw_cap_self_ave,
+               mutual_diffdata_min, mutual_diffdata_max, mutual_diffdata_ave,
+               self_diffdata_min, self_diffdata_max, self_diffdata_ave,
+               mutual_baseline_min, mutual_baseline_max, mutual_baseline_ave,
+               self_baseline_min, self_baseline_max, self_baseline_ave);
+       return ret + err;
+}
+
+static int cyapa_gen5_read_raw_data(struct cyapa *cyapa)
+{
+       int ret, err;
+       int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+       int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+       int offset;
+       int data_size, max, min, ave;
+       ktime_t time_mono;
+
+       offset = 0;
+       if (!cyapa->tp_raw_data)
+               return -ENOMEM;
+
+       /* 1. suspend Scanning.
+        * After suspend scanning, the raw data will not be updated,
+        * so the time of the raw data is before scanning suspended. */
+       time_mono = ktime_get();
+       ret = cyapa_gen5_suspend_scanning(cyapa);
+       if (ret)
+               return ret;
+
+       /* 2. get the correct electrodes_rx number. */
+       if (cyapa->electrodes_rx == 0) {
+               /* Through the read global idac interface to get the Rx number.
+                * this value is useful to the raw data map.*/
+               data_size = 0;
+               err = cyapa_gen5_read_idac_data(cyapa,
+                               GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+                               GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+                               &data_size, &max, &min, &ave);
+               if (err || cyapa->electrodes_rx == 0)
+                       goto resume_scanning;
+       }
+
+       /* 3. execuate panel scan. It must be executed before read data. */
+       err = cyapa_gen5_execute_panel_scan(cyapa);
+       if (err)
+               goto resume_scanning;
+
+       /* 4. retrive panel scan, mutual cap raw data. */
+       offset = GEN5_RAW_DATA_HEAD_SIZE;
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+                               cyapa->electrodes_x * cyapa->electrodes_y,
+                               &raw_cap_mutual_max, &raw_cap_mutual_min,
+                               &raw_cap_mutual_ave,
+                               cyapa->tp_raw_data + offset);
+       if (err)
+               goto resume_scanning;
+
+       offset += sizeof(s32) * cyapa->electrodes_x * cyapa->electrodes_y;
+
+       /* 5. retrive panel scan, self cap raw data. */
+       err = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+                               GEN5_CMD_RETRIEVE_PANEL_SCAN,
+                               GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+                               cyapa->electrodes_x + cyapa->electrodes_y,
+                               &raw_cap_self_max, &raw_cap_self_min,
+                               &raw_cap_self_ave,
+                               cyapa->tp_raw_data + offset);
+       if (err)
+               goto resume_scanning;
+
+       offset += sizeof(s32) * (cyapa->electrodes_x + cyapa->electrodes_y);
+
+resume_scanning:
+       /* 6. resume Scanning*/
+       ret = cyapa_gen5_resume_scanning(cyapa);
+       if (ret || err)
+               return ret ? ret : err;
+
+       *((struct timeval *)&cyapa->tp_raw_data[0]) =
+                                               ktime_to_timeval(time_mono);
+       cyapa->tp_raw_data[16] = (u8)cyapa->electrodes_x;
+       cyapa->tp_raw_data[17] = (u8)cyapa->electrodes_y;
+       cyapa->tp_raw_data[18] = (u8)cyapa->x_origin;
+       cyapa->tp_raw_data[19] = (u8)cyapa->y_origin;
+       cyapa->tp_raw_data[20] = (u8)sizeof(s32);
+       cyapa->tp_raw_data[21] = (u8)sizeof(s32);
+       cyapa->tp_raw_data[22] = (u8)cyapa->electrodes_rx;
+       cyapa->tp_raw_data[23] = 0;  /* reserved. */
+
+       cyapa->tp_raw_data_size = offset;
+       return 0;
+}
+
 static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
                u8 *buf, int len)
 {
@@ -3050,19 +4256,19 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)

        switch (cyapa->gen) {
        case CYAPA_GEN5:
-               cyapa->cyapa_check_fw = NULL;
-               cyapa->cyapa_bl_enter = NULL;
+               cyapa->cyapa_check_fw = cyapa_gen5_check_fw;
+               cyapa->cyapa_bl_enter = cyapa_gen5_bl_enter;
                cyapa->cyapa_bl_activate = NULL;
-               cyapa->cyapa_bl_initiate = NULL;
-               cyapa->cyapa_update_fw = NULL;
+               cyapa->cyapa_bl_initiate = cyapa_gen5_bl_initiate;
+               cyapa->cyapa_update_fw = cyapa_gen5_do_fw_update;
                cyapa->cyapa_bl_verify_app_integrity = NULL;
                cyapa->cyapa_bl_deactivate = NULL;
-               cyapa->cyapa_show_baseline = NULL;
-               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_show_baseline = cyapa_gen5_show_baseline;
+               cyapa->cyapa_calibrate_store = cyapa_gen5_do_calibrate;
                cyapa->cyapa_irq_handler = cyapa_gen5_irq_handler;
                cyapa->cyapa_set_power_mode = cyapa_gen5_set_power_mode;
-               cyapa->cyapa_read_fw = NULL;
-               cyapa->cyapa_read_raw_data = NULL;
+               cyapa->cyapa_read_fw = cyapa_gen5_read_fw;
+               cyapa->cyapa_read_raw_data = cyapa_gen5_read_raw_data;

                cyapa_enable_irq_save(cyapa);
                ret = cyapa_gen5_do_operational_check(cyapa);
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.

[-- Attachment #2: winmail.dat --]
[-- Type: application/ms-tnef, Size: 19446 bytes --]

^ permalink raw reply related

* Re: [PATCH v4 1/7] drivers: input: keyboard: st-keyscan: add keyscan driver
From: Gabriel Fernandez @ 2014-04-16  8:49 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Gabriel FERNANDEZ, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Rob Landley, Russell King, Grant Likely,
	devicetree, linux-doc, linux-kernel, linux-arm-kernel,
	linux-input, kernel, Lee Jones, Giuseppe Condorelli
In-Reply-To: <20140413051050.GA7267@core.coreip.homeip.net>

On 13 April 2014 07:10, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> Hi Gabriel,
>
> On Fri, Apr 11, 2014 at 05:07:30PM +0200, Gabriel FERNANDEZ wrote:
>> This patch adds ST Keyscan driver to use the keypad hw a subset
>> of ST boards provide. Specific board setup will be put in the
>> given dt.
>>
>> Signed-off-by: Gabriel Fernandez <gabriel.fernandez@linaro.org>
>> Signed-off-by: Giuseppe Condorelli <giuseppe.condorelli@st.com>
>> ---
>>  .../devicetree/bindings/input/st-keyscan.txt       |  60 +++++
>>  drivers/input/keyboard/Kconfig                     |  12 +
>>  drivers/input/keyboard/Makefile                    |   1 +
>>  drivers/input/keyboard/st-keyscan.c                | 260 +++++++++++++++++++++
>>  4 files changed, 333 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/input/st-keyscan.txt
>>  create mode 100644 drivers/input/keyboard/st-keyscan.c
>>
>> diff --git a/Documentation/devicetree/bindings/input/st-keyscan.txt b/Documentation/devicetree/bindings/input/st-keyscan.txt
>> new file mode 100644
>> index 0000000..51eb428
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/input/st-keyscan.txt
>> @@ -0,0 +1,60 @@
>> +* ST Keyscan controller Device Tree bindings
>> +
>> +The ST keyscan controller Device Tree binding is based on the
>> +matrix-keymap.
>> +
>> +Required properties:
>> +- compatible: "st,sti-keyscan"
>> +
>> +- reg: Register base address and size of st-keyscan controller.
>> +
>> +- interrupts: Interrupt number for the st-keyscan controller.
>> +
>> +- clocks: Must contain one entry, for the module clock.
>> +  See ../clocks/clock-bindings.txt for details.
>> +
>> +- pinctrl: Should specify pin control groups used for this controller.
>> +  See ../pinctrl/pinctrl-bindings.txt for details.
>> +
>> +- linux,keymap: The keymap for keys as described in the binding document
>> +  devicetree/bindings/input/matrix-keymap.txt.
>> +
>> +- keypad,num-rows: Number of row lines connected to the keypad controller.
>> +
>> +- keypad,num-columns: Number of column lines connected to the keypad
>> +  controller.
>> +
>> +Optional property:
>> +- st,debounce_us: Debouncing interval time in microseconds
>> +
>> +Example:
>> +
>> +keyscan: keyscan@fe4b0000 {
>> +     compatible = "st,sti-keyscan";
>> +     reg = <0xfe4b0000 0x2000>;
>> +     interrupts = <GIC_SPI 212 IRQ_TYPE_NONE>;
>> +     clocks  = <&CLK_SYSIN>;
>> +     pinctrl-names = "default";
>> +     pinctrl-0 = <&pinctrl_keyscan>;
>> +
>> +     keypad,num-rows = <4>;
>> +     keypad,num-columns = <4>;
>> +     st,debounce_us = <5000>;
>> +
>> +     linux,keymap = < MATRIX_KEY(0x00, 0x00, KEY_F13)
>> +                      MATRIX_KEY(0x00, 0x01, KEY_F9)
>> +                      MATRIX_KEY(0x00, 0x02, KEY_F5)
>> +                      MATRIX_KEY(0x00, 0x03, KEY_F1)
>> +                      MATRIX_KEY(0x01, 0x00, KEY_F14)
>> +                      MATRIX_KEY(0x01, 0x01, KEY_F10)
>> +                      MATRIX_KEY(0x01, 0x02, KEY_F6)
>> +                      MATRIX_KEY(0x01, 0x03, KEY_F2)
>> +                      MATRIX_KEY(0x02, 0x00, KEY_F15)
>> +                      MATRIX_KEY(0x02, 0x01, KEY_F11)
>> +                      MATRIX_KEY(0x02, 0x02, KEY_F7)
>> +                      MATRIX_KEY(0x02, 0x03, KEY_F3)
>> +                      MATRIX_KEY(0x03, 0x00, KEY_F16)
>> +                      MATRIX_KEY(0x03, 0x01, KEY_F12)
>> +                      MATRIX_KEY(0x03, 0x02, KEY_F8)
>> +                      MATRIX_KEY(0x03, 0x03, KEY_F4) >;
>> +     };
>> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
>> index 76842d7..40a377c 100644
>> --- a/drivers/input/keyboard/Kconfig
>> +++ b/drivers/input/keyboard/Kconfig
>> @@ -524,6 +524,18 @@ config KEYBOARD_STOWAWAY
>>         To compile this driver as a module, choose M here: the
>>         module will be called stowaway.
>>
>> +config KEYBOARD_ST_KEYSCAN
>> +     tristate "STMicroelectronics keyscan support"
>> +     depends on ARCH_STI
>> +     select INPUT_EVDEV
>
> Why are you selecting evdev? Its presence is not essential for the
> driver.
it's historic..
i agree no reason to keep it.

>
>> +     select INPUT_MATRIXKMAP
>> +     help
>> +       Say Y here if you want to use a keypad attached to the keyscan block
>> +       on some STMicroelectronics SoC devices.
>> +
>> +       To compile this driver as a module, choose M here: the
>> +       module will be called st-keyscan.
>> +
>>  config KEYBOARD_SUNKBD
>>       tristate "Sun Type 4 and Type 5 keyboard"
>>       select SERIO
>> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
>> index 11cff7b..7504ae1 100644
>> --- a/drivers/input/keyboard/Makefile
>> +++ b/drivers/input/keyboard/Makefile
>> @@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_SH_KEYSC)             += sh_keysc.o
>>  obj-$(CONFIG_KEYBOARD_SPEAR)         += spear-keyboard.o
>>  obj-$(CONFIG_KEYBOARD_STMPE)         += stmpe-keypad.o
>>  obj-$(CONFIG_KEYBOARD_STOWAWAY)              += stowaway.o
>> +obj-$(CONFIG_KEYBOARD_ST_KEYSCAN)    += st-keyscan.o
>>  obj-$(CONFIG_KEYBOARD_SUNKBD)                += sunkbd.o
>>  obj-$(CONFIG_KEYBOARD_TC3589X)               += tc3589x-keypad.o
>>  obj-$(CONFIG_KEYBOARD_TEGRA)         += tegra-kbc.o
>> diff --git a/drivers/input/keyboard/st-keyscan.c b/drivers/input/keyboard/st-keyscan.c
>> new file mode 100644
>> index 0000000..7ed3b3e
>> --- /dev/null
>> +++ b/drivers/input/keyboard/st-keyscan.c
>> @@ -0,0 +1,260 @@
>> +/*
>> + * STMicroelectronics Key Scanning driver
>> + *
>> + * Copyright (c) 2014 STMicroelectonics Ltd.
>> + * Author: Stuart Menefy <stuart.menefy@st.com>
>> + *
>> + * Based on sh_keysc.c, copyright 2008 Magnus Damm
>> + *
>> + * 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/module.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/clk.h>
>> +#include <linux/io.h>
>> +#include <linux/input/matrix_keypad.h>
>> +
>> +#define ST_KEYSCAN_MAXKEYS 16
>> +
>> +#define KEYSCAN_CONFIG_OFF           0x0
>> +#define KEYSCAN_CONFIG_ENABLE                0x1
>> +#define KEYSCAN_DEBOUNCE_TIME_OFF    0x4
>> +#define KEYSCAN_MATRIX_STATE_OFF     0x8
>> +#define KEYSCAN_MATRIX_DIM_OFF               0xc
>> +#define KEYSCAN_MATRIX_DIM_X_SHIFT   0x0
>> +#define KEYSCAN_MATRIX_DIM_Y_SHIFT   0x2
>> +
>> +struct st_keyscan {
>> +     void __iomem *base;
>> +     int irq;
>> +     struct clk *clk;
>> +     struct input_dev *input_dev;
>> +     unsigned int last_state;
>> +     unsigned int n_rows;
>> +     unsigned int n_cols;
>> +     unsigned int debounce_us;
>> +};
>> +
>> +static irqreturn_t keyscan_isr(int irq, void *dev_id)
>> +{
>> +     struct platform_device *pdev = dev_id;
>> +     struct st_keyscan *keypad = platform_get_drvdata(pdev);
>
> This is not safe, you are assigning platform data at the very end of
> probe; input device may get opened before probe is completed and IRS
> may come early causing OOPS.
>
>> +     unsigned short *keycode = keypad->input_dev->keycode;
>> +     int state;
>> +     int change;
>> +
>> +     state = readl(keypad->base + KEYSCAN_MATRIX_STATE_OFF) & 0xffff;
>> +     change = keypad->last_state ^ state;
>> +
>> +     while (change) {
>> +             int scancode = __ffs(change);
>> +             int down = state & BIT(scancode);
>> +
>> +             input_report_key(keypad->input_dev, keycode[scancode], down);
>> +
>> +             change ^= BIT(scancode);
>> +     };
>> +     input_sync(keypad->input_dev);
>> +
>> +     keypad->last_state = state;
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int keyscan_start(struct st_keyscan *keypad)
>> +{
>> +     clk_enable(keypad->clk);
>
> Should handle clk_enable() failures.
>
ok

>> +
>> +     writel(keypad->debounce_us * (clk_get_rate(keypad->clk) / 1000000),
>> +            keypad->base + KEYSCAN_DEBOUNCE_TIME_OFF);
>> +
>> +     writel(((keypad->n_cols - 1) << KEYSCAN_MATRIX_DIM_X_SHIFT) |
>> +            ((keypad->n_rows - 1) << KEYSCAN_MATRIX_DIM_Y_SHIFT),
>> +            keypad->base + KEYSCAN_MATRIX_DIM_OFF);
>> +
>> +     writel(KEYSCAN_CONFIG_ENABLE, keypad->base + KEYSCAN_CONFIG_OFF);
>> +
>> +     return 0;
>> +}
>> +
>> +static void keyscan_stop(struct st_keyscan *keypad)
>> +{
>> +     writel(0, keypad->base + KEYSCAN_CONFIG_OFF);
>> +
>> +     clk_disable(keypad->clk);
>> +}
>> +
>> +static int keyscan_open(struct input_dev *dev)
>> +{
>> +     struct st_keyscan *keypad = input_get_drvdata(dev);
>> +
>> +     return keyscan_start(keypad);
>> +}
>> +
>> +static void keyscan_close(struct input_dev *dev)
>> +{
>> +     struct st_keyscan *keypad = input_get_drvdata(dev);
>> +
>> +     keyscan_stop(keypad);
>> +}
>> +
>> +static int keypad_matrix_key_parse_dt(struct st_keyscan *keypad_data)
>> +{
>> +     struct device *dev = keypad_data->input_dev->dev.parent;
>> +     struct device_node *np = dev->of_node;
>> +     int error;
>> +
>> +     error = matrix_keypad_parse_of_params(dev, &keypad_data->n_rows,
>> +                     &keypad_data->n_cols);
>> +     if (error) {
>> +             dev_err(dev, "failed to parse keypad params\n");
>> +             return error;
>> +     }
>> +
>> +     of_property_read_u32(np, "st,debounce-us", &keypad_data->debounce_us);
>> +
>> +     dev_info(dev, "n_rows=%d n_col=%d debounce=%d\n",
>> +                     keypad_data->n_rows,
>> +                     keypad_data->n_cols,
>> +                     keypad_data->debounce_us);
>> +
>> +     return 0;
>> +}
>> +
>> +static int __init keyscan_probe(struct platform_device *pdev)
>
> keyscan_probe() should not be marked __init as it may be needed again if
> one were to unbind and rebind the driver to the device through sysfs.
>
ok

>> +{
>> +     struct st_keyscan *keypad_data;
>> +     struct input_dev *input_dev;
>> +     struct resource *res;
>> +     int error;
>> +
>> +     if (!pdev->dev.of_node) {
>> +             dev_err(&pdev->dev, "no keymap defined\n");
>> +             return -EINVAL;
>> +     }
>> +
>> +     keypad_data = devm_kzalloc(&pdev->dev,
>> +                     sizeof(*keypad_data), GFP_KERNEL);
>> +     if (!keypad_data)
>> +             return -ENOMEM;
>> +
>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +     keypad_data->base = devm_ioremap_resource(&pdev->dev, res);
>> +     if (IS_ERR(keypad_data->base))
>> +             return PTR_ERR(keypad_data->base);
>> +
>> +     keypad_data->irq = platform_get_irq(pdev, 0);
>> +     if (keypad_data->irq < 0) {
>> +             dev_err(&pdev->dev, "no IRQ specified\n");
>> +             return -EINVAL;
>> +     }
>> +
>> +     error = devm_request_irq(&pdev->dev, keypad_data->irq, keyscan_isr, 0,
>> +                              pdev->name, pdev);
>> +     if (error) {
>> +             dev_err(&pdev->dev, "failed to request IRQ\n");
>> +             return error;
>> +     }
>> +
>> +     input_dev = devm_input_allocate_device(&pdev->dev);
>> +
>> +     if (!input_dev) {
>> +             dev_err(&pdev->dev, "failed to allocate the input device\n");
>> +             return -ENOMEM;
>> +     }
>> +
>> +     keypad_data->clk = devm_clk_get(&pdev->dev, NULL);
>> +     if (IS_ERR(keypad_data->clk)) {
>> +             dev_err(&pdev->dev, "cannot get clock");
>> +             return PTR_ERR(keypad_data->clk);
>> +     }
>> +
>> +     keypad_data->input_dev = input_dev;
>> +
>> +     input_dev->name = pdev->name;
>> +     input_dev->phys = "keyscan-keys/input0";
>> +     input_dev->dev.parent = &pdev->dev;
>> +     input_dev->open = keyscan_open;
>> +     input_dev->close = keyscan_close;
>> +
>> +     input_dev->id.bustype = BUS_HOST;
>> +
>> +     error = keypad_matrix_key_parse_dt(keypad_data);
>> +     if (error)
>> +             return error;
>> +
>> +     error = matrix_keypad_build_keymap(NULL, NULL,
>> +                     keypad_data->n_rows, keypad_data->n_cols,
>> +                     NULL, input_dev);
>> +     if (error) {
>> +             dev_err(&pdev->dev, "failed to build keymap\n");
>> +             return error;
>> +     }
>> +
>> +     input_set_drvdata(input_dev, keypad_data);
>> +
>> +     error = input_register_device(input_dev);
>> +     if (error) {
>> +             dev_err(&pdev->dev, "failed to register input device\n");
>> +             return error;
>> +     }
>> +
>> +     platform_set_drvdata(pdev, keypad_data);
>> +
>> +     device_set_wakeup_capable(&pdev->dev, 1);
>> +
>> +     return 0;
>> +}
>> +
>> +static int keyscan_suspend(struct device *dev)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +     struct st_keyscan *keypad = platform_get_drvdata(pdev);
>> +
>> +     if (device_may_wakeup(dev))
>> +             enable_irq_wake(keypad->irq);
>> +     else
>> +             keyscan_stop(keypad);
>> +
>> +     return 0;
>> +}
>> +
>> +static int keyscan_resume(struct device *dev)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +     struct st_keyscan *keypad = platform_get_drvdata(pdev);
>> +
>> +     if (device_may_wakeup(dev))
>> +             disable_irq_wake(keypad->irq);
>> +     else
>> +             keyscan_start(keypad);
>
> If devoice has not been opened you should not try to stop it otherwise
> clk counter will be imbalanced.
>
Right

>> +
>> +     return 0;
>> +}
>> +
>> +static SIMPLE_DEV_PM_OPS(keyscan_dev_pm_ops, keyscan_suspend, keyscan_resume);
>> +
>> +static const struct of_device_id keyscan_of_match[] = {
>> +     { .compatible = "st,sti-keyscan" },
>> +     { },
>> +};
>> +MODULE_DEVICE_TABLE(of, keyscan_of_match);
>> +
>> +struct platform_driver keyscan_device_driver = {
>> +     .probe          = keyscan_probe,
>> +     .driver         = {
>> +             .name   = "st-keyscan",
>> +             .pm     = &keyscan_dev_pm_ops,
>> +             .of_match_table = of_match_ptr(keyscan_of_match),
>> +     }
>> +};
>> +
>> +module_platform_driver(keyscan_device_driver);
>> +
>> +MODULE_AUTHOR("Stuart Menefy <stuart.menefy@st.com>");
>> +MODULE_DESCRIPTION("STMicroelectronics keyscan device driver");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.9.1
>>
>
> Does the version of the patch below still work for you?
>
Yes it's was tested on b2000 and b2089 sti boards.

> Thanks.
>
> --
> Dmitry
>
Thanks for yours remarks, i will prepare a v5 versions.
Regards

> Input: add st-keyscan driver
>
> From: Gabriel FERNANDEZ <gabriel.fernandez@st.com>
>
> This patch adds ST Keyscan driver to use the keypad hw a subset of ST
> boards provide. Specific board setup will be put in the given dt.
>
> Signed-off-by: Gabriel Fernandez <gabriel.fernandez@linaro.org>
> Signed-off-by: Giuseppe Condorelli <giuseppe.condorelli@st.com>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
>  .../devicetree/bindings/input/st-keyscan.txt       |   60 ++++
>  drivers/input/keyboard/Kconfig                     |   11 +
>  drivers/input/keyboard/Makefile                    |    1
>  drivers/input/keyboard/st-keyscan.c                |  274 ++++++++++++++++++++
>  4 files changed, 346 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/st-keyscan.txt
>  create mode 100644 drivers/input/keyboard/st-keyscan.c
>
> diff --git a/Documentation/devicetree/bindings/input/st-keyscan.txt b/Documentation/devicetree/bindings/input/st-keyscan.txt
> new file mode 100644
> index 0000000..51eb428
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/st-keyscan.txt
> @@ -0,0 +1,60 @@
> +* ST Keyscan controller Device Tree bindings
> +
> +The ST keyscan controller Device Tree binding is based on the
> +matrix-keymap.
> +
> +Required properties:
> +- compatible: "st,sti-keyscan"
> +
> +- reg: Register base address and size of st-keyscan controller.
> +
> +- interrupts: Interrupt number for the st-keyscan controller.
> +
> +- clocks: Must contain one entry, for the module clock.
> +  See ../clocks/clock-bindings.txt for details.
> +
> +- pinctrl: Should specify pin control groups used for this controller.
> +  See ../pinctrl/pinctrl-bindings.txt for details.
> +
> +- linux,keymap: The keymap for keys as described in the binding document
> +  devicetree/bindings/input/matrix-keymap.txt.
> +
> +- keypad,num-rows: Number of row lines connected to the keypad controller.
> +
> +- keypad,num-columns: Number of column lines connected to the keypad
> +  controller.
> +
> +Optional property:
> +- st,debounce_us: Debouncing interval time in microseconds
> +
> +Example:
> +
> +keyscan: keyscan@fe4b0000 {
> +       compatible = "st,sti-keyscan";
> +       reg = <0xfe4b0000 0x2000>;
> +       interrupts = <GIC_SPI 212 IRQ_TYPE_NONE>;
> +       clocks  = <&CLK_SYSIN>;
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pinctrl_keyscan>;
> +
> +       keypad,num-rows = <4>;
> +       keypad,num-columns = <4>;
> +       st,debounce_us = <5000>;
> +
> +       linux,keymap = < MATRIX_KEY(0x00, 0x00, KEY_F13)
> +                        MATRIX_KEY(0x00, 0x01, KEY_F9)
> +                        MATRIX_KEY(0x00, 0x02, KEY_F5)
> +                        MATRIX_KEY(0x00, 0x03, KEY_F1)
> +                        MATRIX_KEY(0x01, 0x00, KEY_F14)
> +                        MATRIX_KEY(0x01, 0x01, KEY_F10)
> +                        MATRIX_KEY(0x01, 0x02, KEY_F6)
> +                        MATRIX_KEY(0x01, 0x03, KEY_F2)
> +                        MATRIX_KEY(0x02, 0x00, KEY_F15)
> +                        MATRIX_KEY(0x02, 0x01, KEY_F11)
> +                        MATRIX_KEY(0x02, 0x02, KEY_F7)
> +                        MATRIX_KEY(0x02, 0x03, KEY_F3)
> +                        MATRIX_KEY(0x03, 0x00, KEY_F16)
> +                        MATRIX_KEY(0x03, 0x01, KEY_F12)
> +                        MATRIX_KEY(0x03, 0x02, KEY_F8)
> +                        MATRIX_KEY(0x03, 0x03, KEY_F4) >;
> +       };
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index 76842d7..948a303 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -524,6 +524,17 @@ config KEYBOARD_STOWAWAY
>           To compile this driver as a module, choose M here: the
>           module will be called stowaway.
>
> +config KEYBOARD_ST_KEYSCAN
> +       tristate "STMicroelectronics keyscan support"
> +       depends on ARCH_STI || COMPILE_TEST
> +       select INPUT_MATRIXKMAP
> +       help
> +         Say Y here if you want to use a keypad attached to the keyscan block
> +         on some STMicroelectronics SoC devices.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called st-keyscan.
> +
>  config KEYBOARD_SUNKBD
>         tristate "Sun Type 4 and Type 5 keyboard"
>         select SERIO
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 11cff7b..7504ae1 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_SH_KEYSC)               += sh_keysc.o
>  obj-$(CONFIG_KEYBOARD_SPEAR)           += spear-keyboard.o
>  obj-$(CONFIG_KEYBOARD_STMPE)           += stmpe-keypad.o
>  obj-$(CONFIG_KEYBOARD_STOWAWAY)                += stowaway.o
> +obj-$(CONFIG_KEYBOARD_ST_KEYSCAN)      += st-keyscan.o
>  obj-$(CONFIG_KEYBOARD_SUNKBD)          += sunkbd.o
>  obj-$(CONFIG_KEYBOARD_TC3589X)         += tc3589x-keypad.o
>  obj-$(CONFIG_KEYBOARD_TEGRA)           += tegra-kbc.o
> diff --git a/drivers/input/keyboard/st-keyscan.c b/drivers/input/keyboard/st-keyscan.c
> new file mode 100644
> index 0000000..758b487
> --- /dev/null
> +++ b/drivers/input/keyboard/st-keyscan.c
> @@ -0,0 +1,274 @@
> +/*
> + * STMicroelectronics Key Scanning driver
> + *
> + * Copyright (c) 2014 STMicroelectonics Ltd.
> + * Author: Stuart Menefy <stuart.menefy@st.com>
> + *
> + * Based on sh_keysc.c, copyright 2008 Magnus Damm
> + *
> + * 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/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/input/matrix_keypad.h>
> +
> +#define ST_KEYSCAN_MAXKEYS 16
> +
> +#define KEYSCAN_CONFIG_OFF             0x0
> +#define KEYSCAN_CONFIG_ENABLE          0x1
> +#define KEYSCAN_DEBOUNCE_TIME_OFF      0x4
> +#define KEYSCAN_MATRIX_STATE_OFF       0x8
> +#define KEYSCAN_MATRIX_DIM_OFF         0xc
> +#define KEYSCAN_MATRIX_DIM_X_SHIFT     0x0
> +#define KEYSCAN_MATRIX_DIM_Y_SHIFT     0x2
> +
> +struct st_keyscan {
> +       void __iomem *base;
> +       int irq;
> +       struct clk *clk;
> +       struct input_dev *input_dev;
> +       unsigned long last_state;
> +       unsigned int n_rows;
> +       unsigned int n_cols;
> +       unsigned int debounce_us;
> +};
> +
> +static irqreturn_t keyscan_isr(int irq, void *dev_id)
> +{
> +       struct st_keyscan *keypad = dev_id;
> +       unsigned short *keycode = keypad->input_dev->keycode;
> +       unsigned long state, change;
> +       int bit_nr;
> +
> +       state = readl(keypad->base + KEYSCAN_MATRIX_STATE_OFF) & 0xffff;
> +       change = keypad->last_state ^ state;
> +       keypad->last_state = state;
> +
> +       for_each_set_bit(bit_nr, &change, BITS_PER_LONG)
> +               input_report_key(keypad->input_dev,
> +                                keycode[bit_nr], state & BIT(bit_nr));
> +
> +       input_sync(keypad->input_dev);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int keyscan_start(struct st_keyscan *keypad)
> +{
> +       int error;
> +
> +       error = clk_enable(keypad->clk);
> +       if (error)
> +               return error;
> +
> +       writel(keypad->debounce_us * (clk_get_rate(keypad->clk) / 1000000),
> +              keypad->base + KEYSCAN_DEBOUNCE_TIME_OFF);
> +
> +       writel(((keypad->n_cols - 1) << KEYSCAN_MATRIX_DIM_X_SHIFT) |
> +              ((keypad->n_rows - 1) << KEYSCAN_MATRIX_DIM_Y_SHIFT),
> +              keypad->base + KEYSCAN_MATRIX_DIM_OFF);
> +
> +       writel(KEYSCAN_CONFIG_ENABLE, keypad->base + KEYSCAN_CONFIG_OFF);
> +
> +       return 0;
> +}
> +
> +static void keyscan_stop(struct st_keyscan *keypad)
> +{
> +       writel(0, keypad->base + KEYSCAN_CONFIG_OFF);
> +
> +       clk_disable(keypad->clk);
> +}
> +
> +static int keyscan_open(struct input_dev *dev)
> +{
> +       struct st_keyscan *keypad = input_get_drvdata(dev);
> +
> +       return keyscan_start(keypad);
> +}
> +
> +static void keyscan_close(struct input_dev *dev)
> +{
> +       struct st_keyscan *keypad = input_get_drvdata(dev);
> +
> +       keyscan_stop(keypad);
> +}
> +
> +static int keypad_matrix_key_parse_dt(struct st_keyscan *keypad_data)
> +{
> +       struct device *dev = keypad_data->input_dev->dev.parent;
> +       struct device_node *np = dev->of_node;
> +       int error;
> +
> +       error = matrix_keypad_parse_of_params(dev, &keypad_data->n_rows,
> +                                             &keypad_data->n_cols);
> +       if (error) {
> +               dev_err(dev, "failed to parse keypad params\n");
> +               return error;
> +       }
> +
> +       of_property_read_u32(np, "st,debounce-us", &keypad_data->debounce_us);
> +
> +       dev_dbg(dev, "n_rows=%d n_col=%d debounce=%d\n",
> +               keypad_data->n_rows, keypad_data->n_cols,
> +               keypad_data->debounce_us);
> +
> +       return 0;
> +}
> +
> +static int keyscan_probe(struct platform_device *pdev)
> +{
> +       struct st_keyscan *keypad_data;
> +       struct input_dev *input_dev;
> +       struct resource *res;
> +       int error;
> +
> +       if (!pdev->dev.of_node) {
> +               dev_err(&pdev->dev, "no DT data present\n");
> +               return -EINVAL;
> +       }
> +
> +       keypad_data = devm_kzalloc(&pdev->dev, sizeof(*keypad_data),
> +                                  GFP_KERNEL);
> +       if (!keypad_data)
> +               return -ENOMEM;
> +
> +       input_dev = devm_input_allocate_device(&pdev->dev);
> +       if (!input_dev) {
> +               dev_err(&pdev->dev, "failed to allocate the input device\n");
> +               return -ENOMEM;
> +       }
> +
> +       input_dev->name = pdev->name;
> +       input_dev->phys = "keyscan-keys/input0";
> +       input_dev->dev.parent = &pdev->dev;
> +       input_dev->open = keyscan_open;
> +       input_dev->close = keyscan_close;
> +
> +       input_dev->id.bustype = BUS_HOST;
> +
> +       error = keypad_matrix_key_parse_dt(keypad_data);
> +       if (error)
> +               return error;
> +
> +       error = matrix_keypad_build_keymap(NULL, NULL,
> +                                          keypad_data->n_rows,
> +                                          keypad_data->n_cols,
> +                                          NULL, input_dev);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to build keymap\n");
> +               return error;
> +       }
> +
> +       input_set_drvdata(input_dev, keypad_data);
> +
> +       keypad_data->input_dev = input_dev;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       keypad_data->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(keypad_data->base))
> +               return PTR_ERR(keypad_data->base);
> +
> +       keypad_data->clk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(keypad_data->clk)) {
> +               dev_err(&pdev->dev, "cannot get clock\n");
> +               return PTR_ERR(keypad_data->clk);
> +       }
> +
> +       error = clk_enable(keypad_data->clk);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to enable clock\n");
> +               return error;
> +       }
> +
> +       keyscan_stop(keypad_data);
> +
> +       keypad_data->irq = platform_get_irq(pdev, 0);
> +       if (keypad_data->irq < 0) {
> +               dev_err(&pdev->dev, "no IRQ specified\n");
> +               return -EINVAL;
> +       }
> +
> +       error = devm_request_irq(&pdev->dev, keypad_data->irq, keyscan_isr, 0,
> +                                pdev->name, keypad_data);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to request IRQ\n");
> +               return error;
> +       }
> +
> +       error = input_register_device(input_dev);
> +       if (error) {
> +               dev_err(&pdev->dev, "failed to register input device\n");
> +               return error;
> +       }
> +
> +       platform_set_drvdata(pdev, keypad_data);
> +
> +       device_set_wakeup_capable(&pdev->dev, 1);
> +
> +       return 0;
> +}
> +
> +static int keyscan_suspend(struct device *dev)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct st_keyscan *keypad = platform_get_drvdata(pdev);
> +       struct input_dev *input = keypad->input_dev;
> +
> +       mutex_lock(&input->mutex);
> +
> +       if (device_may_wakeup(dev))
> +               enable_irq_wake(keypad->irq);
> +       else if (input->users)
> +               keyscan_stop(keypad);
> +
> +       mutex_unlock(&input->mutex);
> +       return 0;
> +}
> +
> +static int keyscan_resume(struct device *dev)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct st_keyscan *keypad = platform_get_drvdata(pdev);
> +       struct input_dev *input = keypad->input_dev;
> +       int retval = 0;
> +
> +       mutex_lock(&input->mutex);
> +
> +       if (device_may_wakeup(dev))
> +               disable_irq_wake(keypad->irq);
> +       else if (input->users)
> +               retval = keyscan_start(keypad);
> +
> +       mutex_unlock(&input->mutex);
> +       return retval;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(keyscan_dev_pm_ops, keyscan_suspend, keyscan_resume);
> +
> +static const struct of_device_id keyscan_of_match[] = {
> +       { .compatible = "st,sti-keyscan" },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, keyscan_of_match);
> +
> +static struct platform_driver keyscan_device_driver = {
> +       .probe          = keyscan_probe,
> +       .driver         = {
> +               .name   = "st-keyscan",
> +               .pm     = &keyscan_dev_pm_ops,
> +               .of_match_table = of_match_ptr(keyscan_of_match),
> +       }
> +};
> +
> +module_platform_driver(keyscan_device_driver);
> +
> +MODULE_AUTHOR("Stuart Menefy <stuart.menefy@st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics keyscan device driver");
> +MODULE_LICENSE("GPL");

^ permalink raw reply

* Re: [patch]GPIO button is supposed to wake the system up if the wakeup attribute is set
From: Laxman Dewangan @ 2014-04-16 12:35 UTC (permalink / raw)
  To: Li, Aubrey, One Thousand Gnomes
  Cc: dmitry.torokhov@gmail.com, sachin.kamat@linaro.org,
	linux-input@vger.kernel.org
In-Reply-To: <534D5BEA.30906@linux.intel.com>

On Tuesday 15 April 2014 09:48 PM, Li, Aubrey wrote:
> On 2014/4/15 20:38, Laxman Dewangan wrote:
>> On Monday 14 April 2014 09:12 PM, Li, Aubrey wrote:
>>> ping...
>>>
>>> On 2014/4/10 18:48, One Thousand Gnomes wrote:
>>>>
>> I think when we say irq_wake_enable() then based on underlying HW, it
>> should not turn off the irq if it is require for the wakeup. I mean it
>> need to be handle in the hw specific callbacks to keep enabling the
>> wakeup irq on suspend also.
> I failed to see why this can't be generic to all of the GPIO buttons for
> suspend wakeup. Do you see any cases broken by this proposal?

My point here is that if underlying HW needs to have irq enabled for 
wakup then it need to handle in centralized location i.e. the driver 
which is implementing it for the irq callbacks.
Otherwise, we need to change this on multiple places who needs wakeups 
which is vast in nature like sd driver for sdcard insert/remove etc. 
almost all drivers which need wakeups through GPIOs.

>> For me, I have key which is interrupt based from PMIC, not based on GPIO
>> and on that if I set it to IRQF_EARLY_RESUME then it works fine.
>>
> IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
> IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
> resume time.
>
> IRQF_NO_SUSPEND is exactly what I want, instead of IRQF_EARLY_RESUME.
> Can you please send your proposal/code to help me understand why this
> has to hw specific and why IRQF_EARLY_RESUME is better than IRQF_NO_SUSPEND?

IRQF_EARLY_RESUME helps to re-enable mask or irq before parent interrupt 
resume and so parent isr handler sees the irq flag enabled when it try 
to scan source of interrupt. Otherwise parent isr handler treat this as 
spurious interrupt and does not call handler as irq flag disabled for that.

This only happen when on resume, parent inettrupt enabled before the 
child interrupt on irq resume. Because as soon as parent isr re-enabled 
on resume, its hadnler get called before actually child interrupt 
enabled. This is what I observed mainly on PMIC and its sub irq. Not 
observed on SoC level of interrupts.


-----------------------------------------------------------------------------------
This email message is for the sole use of the intended recipient(s) and may contain
confidential information.  Any unauthorized review, use, disclosure or distribution
is prohibited.  If you are not the intended recipient, please contact the sender by
reply email and destroy all copies of the original message.
-----------------------------------------------------------------------------------

^ permalink raw reply

* Re: HID vendor access from user space
From: Jiri Kosina @ 2014-04-16 14:53 UTC (permalink / raw)
  To: Nestor Lopez Casado
  Cc: David Herrmann, Dmitry Torokhov, open list:HID CORE LAYER,
	Olivier Gay, Benjamin Tissoires, Mathieu Meisser
In-Reply-To: <CAE7qMroYKj0rQQ32JMsp-myrESg5V0tkXF2JukhP4aauPapfcQ@mail.gmail.com>

On Tue, 15 Apr 2014, Nestor Lopez Casado wrote:

> > Frankly, I'd really like to avoid any parsing (deciding about collections)
> > whatsoever around hidraw, as that was the whole point of creating it in
> > parallel to hiddev was simply 'no parsing, raw access'.
> 
> We should avoid naming the new nodes "hidrawX" they could be
> "hidvendorX" for instance, or it could also be "hidcolX" where col
> stands for collection. We will be moving the "raw" one level down.

I am still having trouble seeing how this would work together with current 
hidraw.

In your proposal, there will be both hidrawX and hidcolX existing in 
parallel, right? Who will be deciding what goes to hidcolX instead of 
hidrawX? One of the pain points would be -- /dev/hidrawX will have to stay 
anyway for compatibility reasons, so we can't just divide it to multiple 
/dev/hidcolX all of a sudden and never look back.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH v4 1/9] mfd: AXP20x: Add mfd driver for AXP20x PMIC
From: Lee Jones @ 2014-04-16 15:43 UTC (permalink / raw)
  To: Carlo Caione
  Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
	wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	linux-input-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
	boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <1397209093-10077-2-git-send-email-carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>

> This patch introduces the preliminary support for PMICs X-Powers AXP202
> and AXP209. The AXP209 and AXP202 are the PMUs (Power Management Unit)
> used by A10, A13 and A20 SoCs and developed by X-Powers, a sister company
> of Allwinner.
> 
> The core enables support for two subsystems:
> - PEK (Power Enable Key)
> - Regulators
> 
> Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
> ---
>  drivers/mfd/Kconfig        |  12 +++
>  drivers/mfd/Makefile       |   1 +
>  drivers/mfd/axp20x.c       | 249 +++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/axp20x.h | 180 ++++++++++++++++++++++++++++++++
>  4 files changed, 442 insertions(+)
>  create mode 100644 drivers/mfd/axp20x.c
>  create mode 100644 include/linux/mfd/axp20x.h

This looks good to me know.

Once you have all the other Acks you need I'd be happy to create an
immutable branch for the other maintainers to pull from.

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH v4 1/9] mfd: AXP20x: Add mfd driver for AXP20x PMIC
From: Lee Jones @ 2014-04-16 15:44 UTC (permalink / raw)
  To: Carlo Caione
  Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
	wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	linux-input-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
	boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <20140416154350.GF19671@lee--X1>

> > This patch introduces the preliminary support for PMICs X-Powers AXP202
> > and AXP209. The AXP209 and AXP202 are the PMUs (Power Management Unit)
> > used by A10, A13 and A20 SoCs and developed by X-Powers, a sister company
> > of Allwinner.
> > 
> > The core enables support for two subsystems:
> > - PEK (Power Enable Key)
> > - Regulators
> > 
> > Signed-off-by: Carlo Caione <carlo-KA+7E9HrN00dnm+yROfE0A@public.gmane.org>
> > ---
> >  drivers/mfd/Kconfig        |  12 +++
> >  drivers/mfd/Makefile       |   1 +
> >  drivers/mfd/axp20x.c       | 249 +++++++++++++++++++++++++++++++++++++++++++++
> >  include/linux/mfd/axp20x.h | 180 ++++++++++++++++++++++++++++++++
> >  4 files changed, 442 insertions(+)
> >  create mode 100644 drivers/mfd/axp20x.c
> >  create mode 100644 include/linux/mfd/axp20x.h
> 
> This looks good to me know.
> 
> Once you have all the other Acks you need I'd be happy to create an
> immutable branch for the other maintainers to pull from.

Whoops, forgot this:
  Acked-by: Lee Jones <lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* HID: hid-logitech - missing HID_OUTPUT_REPORT 0
From: simon @ 2014-04-16 22:35 UTC (permalink / raw)
  To: Kees Cook; +Cc: linux-input, Jiri Kosina, Elias Vanderstuyft

[-- Attachment #1: Type: text/plain, Size: 1413 bytes --]

Hi Kees and all,
I've got a report from an end user that their Logitech F710 wireless
gamepad is not functioning correctly. This device has a switch to select
between 'X' mode (X-Pad, works OK) and 'D' mode (HID using hid-logitech,
doesn't work).

After some discussion off list, Elias and I think that this is related to
checking the report descriptor (attached).

Kernel log shows
--
[15961.607787] usb 5-5: USB disconnect, device number 12
[15962.235373] usb 5-5: new full-speed USB device number 13 using ohci-pci
[15962.412284] usb 5-5: New USB device found, idVendor=046d, idProduct=c219
[15962.412287] usb 5-5: New USB device strings: Mfr=1, Product=2,
SerialNumber=0
[15962.412289] usb 5-5: Product: Logitech Cordless RumblePad 2
[15962.412291] usb 5-5: Manufacturer: Logitech
[15962.424343] input: Logitech Logitech Cordless RumblePad 2 as
/devices/pci0000:00/0000:00:13.0/usb5/5-5/5-5:1.0/input/input26
[15962.424477] logitech 0003:046D:C219.0012: input,hidraw5: USB HID v1.11
Gamepad
[Logitech Logitech Cordless RumblePad 2] on usb-0000:00:13.0-5/input0
[15962.424480] logitech 0003:046D:C219.0012: missing HID_OUTPUT_REPORT 0
--

Last message comes from here (I believe):
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/hid/hid-lgff.c?id=refs/tags/v3.15-rc1#n137

Can anyone see what might be wrong with the report, to cause this code to
spit out an error?

Cheers,
Simon.

[-- Attachment #2: report_descriptor.txt --]
[-- Type: text/plain, Size: 3751 bytes --]

0x05, 0x01,         /*  Usage Page (Desktop),                   */
0x09, 0x05,         /*  Usage (Gamepad),                        */
0xA1, 0x01,         /*  Collection (Application),               */
0xA1, 0x02,         /*      Collection (Logical),               */
0x85, 0x01,         /*          Report ID (1),                  */
0x75, 0x08,         /*          Report Size (8),                */
0x95, 0x04,         /*          Report Count (4),               */
0x15, 0x00,         /*          Logical Minimum (0),            */
0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
0x35, 0x00,         /*          Physical Minimum (0),           */
0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
0x09, 0x30,         /*          Usage (X),                      */
0x09, 0x31,         /*          Usage (Y),                      */
0x09, 0x32,         /*          Usage (Z),                      */
0x09, 0x35,         /*          Usage (Rz),                     */
0x81, 0x02,         /*          Input (Variable),               */
0x75, 0x04,         /*          Report Size (4),                */
0x95, 0x01,         /*          Report Count (1),               */
0x25, 0x07,         /*          Logical Maximum (7),            */
0x46, 0x3B, 0x01,   /*          Physical Maximum (315),         */
0x66, 0x14, 0x00,   /*          Unit (Degrees),                 */
0x09, 0x39,         /*          Usage (Hat Switch),             */
0x81, 0x42,         /*          Input (Variable, Null State),   */
0x66, 0x00, 0x00,   /*          Unit,                           */
0x75, 0x01,         /*          Report Size (1),                */
0x95, 0x0C,         /*          Report Count (12),              */
0x25, 0x01,         /*          Logical Maximum (1),            */
0x45, 0x01,         /*          Physical Maximum (1),           */
0x05, 0x09,         /*          Usage Page (Button),            */
0x19, 0x01,         /*          Usage Minimum (01h),            */
0x29, 0x0C,         /*          Usage Maximum (0Ch),            */
0x81, 0x02,         /*          Input (Variable),               */
0x95, 0x01,         /*          Report Count (1),               */
0x75, 0x08,         /*          Report Size (8),                */
0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),             */
0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
0x09, 0x00,         /*          Usage (00h),                    */
0x81, 0x02,         /*          Input (Variable),               */
0xC0,               /*      End Collection,                     */
0xA1, 0x02,         /*      Collection (Logical),               */
0x85, 0x02,         /*          Report ID (2),                  */
0x95, 0x07,         /*          Report Count (7),               */
0x75, 0x08,         /*          Report Size (8),                */
0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),             */
0x09, 0x03,         /*          Usage (03h),                    */
0x81, 0x02,         /*          Input (Variable),               */
0xC0,               /*      End Collection,                     */
0xA1, 0x02,         /*      Collection (Logical),               */
0x85, 0x03,         /*          Report ID (3),                  */
0x09, 0x04,         /*          Usage (04h),                    */
0x91, 0x02,         /*          Output (Variable),              */
0xC0,               /*      End Collection,                     */
0xC0                /*  End Collection                          */

^ permalink raw reply

* Re: HID: hid-logitech - missing HID_OUTPUT_REPORT 0
From: Kees Cook @ 2014-04-16 23:04 UTC (permalink / raw)
  To: simon; +Cc: linux-input, Jiri Kosina, Elias Vanderstuyft
In-Reply-To: <6452400bfacafd7e1f0d0f7a98b06248.squirrel@mungewell.org>

On Wed, Apr 16, 2014 at 3:35 PM,  <simon@mungewell.org> wrote:
> Hi Kees and all,
> I've got a report from an end user that their Logitech F710 wireless
> gamepad is not functioning correctly. This device has a switch to select
> between 'X' mode (X-Pad, works OK) and 'D' mode (HID using hid-logitech,
> doesn't work).
>
> After some discussion off list, Elias and I think that this is related to
> checking the report descriptor (attached).
>
> Kernel log shows
> --
> [15961.607787] usb 5-5: USB disconnect, device number 12
> [15962.235373] usb 5-5: new full-speed USB device number 13 using ohci-pci
> [15962.412284] usb 5-5: New USB device found, idVendor=046d, idProduct=c219
> [15962.412287] usb 5-5: New USB device strings: Mfr=1, Product=2,
> SerialNumber=0
> [15962.412289] usb 5-5: Product: Logitech Cordless RumblePad 2
> [15962.412291] usb 5-5: Manufacturer: Logitech
> [15962.424343] input: Logitech Logitech Cordless RumblePad 2 as
> /devices/pci0000:00/0000:00:13.0/usb5/5-5/5-5:1.0/input/input26
> [15962.424477] logitech 0003:046D:C219.0012: input,hidraw5: USB HID v1.11
> Gamepad
> [Logitech Logitech Cordless RumblePad 2] on usb-0000:00:13.0-5/input0
> [15962.424480] logitech 0003:046D:C219.0012: missing HID_OUTPUT_REPORT 0
> --
>
> Last message comes from here (I believe):
> https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/hid/hid-lgff.c?id=refs/tags/v3.15-rc1#n137
>
> Can anyone see what might be wrong with the report, to cause this code to
> spit out an error?

I don't know the lg driver very well, but it looks like it's expecting
a single report ID (0), but the device is showing two report IDs: 1
and 2. So, from the perspective of the driver, this is correct: it
wouldn't know how to deal with things since it is only expecting
Report ID 0. It seems like the driver needs to be updated for this
different device.

-Kees

-- 
Kees Cook
Chrome OS Security

^ permalink raw reply

* Re: [PATCH v4 7/9] ARM: sun7i/sun4i: dt: Add AXP209 support to various boards
From: Carlo Caione @ 2014-04-17 10:06 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
	wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	linux-input-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	lgirdwood-Re5JQEeQqe8AvxtiuMwx3w,
	lee.jones-QSEj5FYQhm4dnm+yROfE0A,
	boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
In-Reply-To: <20140411161813.GF28800-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>

On Fri, Apr 11, 2014 at 6:18 PM, Mark Brown <broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Fri, Apr 11, 2014 at 03:04:32PM +0200, Carlo Caione wrote:
>> On Fri, Apr 11, 2014 at 2:29 PM, Mark Brown <broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
>
>> >> +                             regulators {
>> >> +                                     compatible = "x-powers,axp20x-reg";
>
>> > This compatible isn't part of the driver.
>
>> Yes I know. The problem here is that in v4 I had to fill in the field
>> .of_compatible of the mfd_cell with "x-powers,axp20x-reg". This
>> because the regulator_dev_lookup() checks for dev->of_node when
>> looking for the supply so I needed the compatible string in the DT to
>> have the dev->of_node filled in by mfd_add_device().
>> What do you suggest? Modify the regulator driver?
>
> You're looking for regulator_bulk_register_supply_alias() in the MFD
> driver (via parent_supplies in the MFD cell probably).

Hi Mark,
I'm fighting with a small issue when using the
regulator_bulk_register_supply_alias(). Problem is that when using the
.parent_supplies entry in the MFD driver, I hit the

WARN_ON(!list_empty(&dev->devres_head));

in linux/drivers/base/dd.c#L272, but, apart from the warning,
everything seems to work correctly.
A possible explanation I gave myself is that in the mfd_add_device()
we try to use the devm_* API when the regulator device is not bound to
the driver yet (I found some information here
http://lists.infradead.org/pipermail/linux-arm-kernel/2012-June/104442.html).
Is this the case?

Thanks,

-- 
Carlo Caione

^ permalink raw reply

* [RESEND PATCH] hid: Add custom driver for Lenovo ThinkPad Compact Bluetooth Keyboard
From: Jamie Lentin @ 2014-04-17 10:54 UTC (permalink / raw)
  To: Jamie Lentin, Jiri Kosina; +Cc: linux-input, linux-kernel

This keyboard requires some custom mappings for all keys to be
available, and the Fn-lock toggle needs to be controlled in software.

Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
---
Apologies for nagging, but anyone got any feedback on this? I don't
think there's anything massively contentious, but hasn't been picked up
by anyone yet.

I assume that Linux users want Fn-Lock enabled by default, so they can
get at the function keys. If this is an incorrect assumption then can
change it---so long as I can leave it enabled and use the Fn-Lock key
for something else, I don't particuarly care.

Tested with and applies cleanly to 3.13.6.
---
 drivers/hid/Kconfig                   |  10 ++
 drivers/hid/Makefile                  |   1 +
 drivers/hid/hid-core.c                |   3 +
 drivers/hid/hid-ids.h                 |   1 +
 drivers/hid/hid-lenovo-tpcompactkbd.c | 191 ++++++++++++++++++++++++++++++++++
 5 files changed, 206 insertions(+)
 create mode 100644 drivers/hid/hid-lenovo-tpcompactkbd.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 34e2d39..8e45413 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -335,6 +335,16 @@ config HID_LENOVO_TPKBD
 	sensitivity of the trackpoint, using the microphone mute button or
 	controlling the mute and microphone mute LEDs.
 
+config HID_LENOVO_CBTKBD
+	tristate "Lenovo ThinkPad Compact Bluetooth Keyboard with TrackPoint"
+	depends on HID
+	---help---
+	Support for the Lenovo ThinkPad Compact Bluetooth Keyboard with TrackPoint.
+
+	Say Y here if you have a Lenovo ThinkPad Compact Bluetooth Keyboard with
+	TrackPoint and would like to use the function keys as function keys, as
+	well as letting linux recognise the special functions such as brightness.
+
 config HID_LOGITECH
 	tristate "Logitech devices" if EXPERT
 	depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 30e4431..c0a2f89 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_HID_KEYTOUCH)	+= hid-keytouch.o
 obj-$(CONFIG_HID_KYE)		+= hid-kye.o
 obj-$(CONFIG_HID_LCPOWER)       += hid-lcpower.o
 obj-$(CONFIG_HID_LENOVO_TPKBD)	+= hid-lenovo-tpkbd.o
+obj-$(CONFIG_HID_LENOVO_CBTKBD)	+= hid-lenovo-tpcompactkbd.o
 obj-$(CONFIG_HID_LOGITECH)	+= hid-logitech.o
 obj-$(CONFIG_HID_LOGITECH_DJ)	+= hid-logitech-dj.o
 obj-$(CONFIG_HID_MAGICMOUSE)    += hid-magicmouse.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 253fe23..77bce8f 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1734,6 +1734,9 @@ static const struct hid_device_id hid_have_special_driver[] = {
 #if IS_ENABLED(CONFIG_HID_LENOVO_TPKBD)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
 #endif
+#if IS_ENABLED(CONFIG_HID_LENOVO_CBTKBD)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+#endif
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index f9304cb..6802166 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -539,6 +539,7 @@
 
 #define USB_VENDOR_ID_LENOVO		0x17ef
 #define USB_DEVICE_ID_LENOVO_TPKBD	0x6009
+#define USB_DEVICE_ID_LENOVO_CBTKBD	0x6048
 
 #define USB_VENDOR_ID_LG		0x1fd2
 #define USB_DEVICE_ID_LG_MULTITOUCH	0x0064
diff --git a/drivers/hid/hid-lenovo-tpcompactkbd.c b/drivers/hid/hid-lenovo-tpcompactkbd.c
new file mode 100644
index 0000000..0fd085b
--- /dev/null
+++ b/drivers/hid/hid-lenovo-tpcompactkbd.c
@@ -0,0 +1,191 @@
+/*
+ *  ThinkPad Compact (Bluetooth|USB) Keyboard with TrackPoint
+ *
+ *  Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static unsigned int fnmode;
+module_param(fnmode, uint, 0644);
+MODULE_PARM_DESC(fnmode, "Fn lock mode ([0] = normal (Fn Lock toggles), 1 = Permanently on, 2 = Permanently off)");
+
+struct tpcompactkbd_sc {
+	unsigned int fn_lock;
+};
+
+/* Send a config command to the keyboard */
+static int tpcompactkbd_send_cmd(struct hid_device *hdev,
+			unsigned char byte2, unsigned char byte3)
+{
+	unsigned char buf[] = {0x18, byte2, byte3};
+
+	return hdev->hid_output_raw_report(hdev, buf, sizeof(buf),
+						HID_OUTPUT_REPORT);
+}
+
+/* Toggle fnlock on or off, if fnmode allows */
+static void tpcompactkbd_toggle_fnlock(struct hid_device *hdev)
+{
+	struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
+
+	tpcsc->fn_lock = fnmode == 2 ? 0 : fnmode == 1 ? 1 : !tpcsc->fn_lock;
+	if (tpcompactkbd_send_cmd(hdev, 0x05, tpcsc->fn_lock ? 0x01 : 0x00))
+		hid_err(hdev, "Fn-lock toggle failed\n");
+}
+
+/*
+ * Keyboard sends non-standard reports for most "hotkey" Fn functions.
+ * Map these back to regular keys.
+ *
+ * Esc:	KEY_FN_ESC		FnLock
+ * (F1--F3 are regular keys)
+ * F4:	KEY_MICMUTE		Mic Mute
+ * F5:	KEY_BRIGHTNESSDOWN	Brightness down
+ * F6:	KEY_BRIGHTNESSUP	Brightness up
+ * F7:	KEY_SWITCHVIDEOMODE	External display (projector)
+ * F8:	KEY_FN_F8		Wireless
+ * F9:	KEY_CONFIG		Control panel / settings
+ * F10:	KEY_SEARCH		Search
+ * F11:	KEY_FN_F11		View open applications (3 boxes)
+ * F12:	KEY_FN_F12		Open My computer (6 boxes)
+ */
+
+#define tpckbd_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int tpcompactkbd_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		set_bit(EV_REP, hi->input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x03f1:
+			tpckbd_map_key_clear(KEY_FN_F8);
+			return 1;
+		case 0x0221:
+			tpckbd_map_key_clear(KEY_SEARCH);
+			return 1;
+		case 0x03f2:
+			tpckbd_map_key_clear(KEY_FN_F12);
+			return 1;
+		}
+	}
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) {
+		set_bit(EV_REP, hi->input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x00f0:
+			tpckbd_map_key_clear(KEY_FN_ESC);
+			return 1;
+		case 0x00f1:
+			tpckbd_map_key_clear(KEY_MICMUTE);
+			return 1;
+		case 0x00f2:
+			tpckbd_map_key_clear(KEY_BRIGHTNESSDOWN);
+			return 1;
+		case 0x00f3:
+			tpckbd_map_key_clear(KEY_BRIGHTNESSUP);
+			return 1;
+		case 0x00f4:
+			tpckbd_map_key_clear(KEY_SWITCHVIDEOMODE);
+			return 1;
+		case 0x00f5:
+			tpckbd_map_key_clear(KEY_FN_F8);
+			return 1;
+		case 0x00f6:
+			tpckbd_map_key_clear(KEY_CONFIG);
+			return 1;
+		case 0x00f8:
+			tpckbd_map_key_clear(KEY_FN_F11);
+			return 1;
+		case 0x00fa:
+			tpckbd_map_key_clear(KEY_FN_ESC);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int tpcompactkbd_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	/* Switch fn-lock on fn-esc */
+	if (unlikely(usage->code == KEY_FN_ESC && value))
+		tpcompactkbd_toggle_fnlock(hdev);
+
+	return 0;
+}
+
+static int tpcompactkbd_probe(struct hid_device *hdev,
+			const struct hid_device_id *id)
+{
+	int ret;
+	struct tpcompactkbd_sc *tpcsc;
+
+	tpcsc = devm_kzalloc(&hdev->dev, sizeof(*tpcsc), GFP_KERNEL);
+	if (tpcsc == NULL) {
+		hid_err(hdev, "can't alloc keyboard descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, tpcsc);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid_parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hid_hw_start failed\n");
+		return ret;
+	}
+
+	/*
+	 * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
+	 * regular keys
+	 */
+	ret = tpcompactkbd_send_cmd(hdev, 0x01, 0x03);
+	if (ret)
+		hid_warn(hdev, "Failed to switch F7/9/11 into regular keys\n");
+
+	/* Toggle once to init the state of fn-lock */
+	tpcsc->fn_lock = 0;
+	tpcompactkbd_toggle_fnlock(hdev);
+
+	return 0;
+}
+
+static const struct hid_device_id tpcompactkbd_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, tpcompactkbd_devices);
+
+static struct hid_driver tpcompactkbd_driver = {
+	.name = "lenovo_tpcompactkbd",
+	.id_table = tpcompactkbd_devices,
+	.input_mapping = tpcompactkbd_input_mapping,
+	.probe = tpcompactkbd_probe,
+	.event = tpcompactkbd_event,
+};
+module_hid_driver(tpcompactkbd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>");
+MODULE_DESCRIPTION("ThinkPad Compact Keyboard with TrackPoint input driver");
-- 
1.8.5.2


^ permalink raw reply related

* [PATCH v3] synaptics: Add min/max quirk for ThinkPad T431s, L440, L540, S1 Yoga and X1
From: Hans de Goede @ 2014-04-17 11:41 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Benjamin Tissoires, Peter Hutterer, linux-input, Hans de Goede,
	stable, Benjamin Tissoires
In-Reply-To: <1397734903-26088-1-git-send-email-hdegoede@redhat.com>

We expect that all the Haswell series will need such quirks, sigh.

The T431s seems to be T430 hardware in a T440s case, using the T440s touchpad,
with the same min/max issue.

The X1 Carbon 3rd generation name says 2nd while it is a 3rd generation.

The X1 and T431s share a PnPID with the T540p, but the reported ranges are
closer to those of the T440s.

HdG: Squashed 5 quirk patches into one. T431s + L440 + L540 are written by me,
S1 Yoga and X1 are written by Benjamin Tissoires.

Hdg: Standardized S1 Yoga and X1 values, Yoga uses the same touchpad as the
X240, X1 uses the same touchpad as the T440.

Cc: stable@vger.kernel.org
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/input/mouse/synaptics.c | 42 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index a8b57d7..7c9f509 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -1566,6 +1566,14 @@ static const struct dmi_system_id min_max_dmi_table[] __initconst = {
 		.driver_data = (int []){1232, 5710, 1156, 4696},
 	},
 	{
+		/* Lenovo ThinkPad T431s */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T431"),
+		},
+		.driver_data = (int []){1024, 5112, 2024, 4832},
+	},
+	{
 		/* Lenovo ThinkPad T440s */
 		.matches = {
 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@@ -1574,6 +1582,14 @@ static const struct dmi_system_id min_max_dmi_table[] __initconst = {
 		.driver_data = (int []){1024, 5112, 2024, 4832},
 	},
 	{
+		/* Lenovo ThinkPad L440 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad L440"),
+		},
+		.driver_data = (int []){1024, 5112, 2024, 4832},
+	},
+	{
 		/* Lenovo ThinkPad T540p */
 		.matches = {
 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@@ -1581,6 +1597,32 @@ static const struct dmi_system_id min_max_dmi_table[] __initconst = {
 		},
 		.driver_data = (int []){1024, 5056, 2058, 4832},
 	},
+	{
+		/* Lenovo ThinkPad L540 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad L540"),
+		},
+		.driver_data = (int []){1024, 5112, 2024, 4832},
+	},
+	{
+		/* Lenovo Yoga S1 */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_VERSION,
+					"ThinkPad S1 Yoga"),
+		},
+		.driver_data = (int []){1232, 5710, 1156, 4696},
+	},
+	{
+		/* Lenovo ThinkPad X1 Carbon Haswell (3rd generation) */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION,
+					"ThinkPad X1 Carbon 2nd"),
+		},
+		.driver_data = (int []){1024, 5112, 2024, 4832},
+	},
 #endif
 	{ }
 };
-- 
1.9.0

^ permalink raw reply related

* [PATCH v3 0/1] synaptics: Add min/max quirk for ThinkPad T431s, L440, L540, Yoga and X1
From: Hans de Goede @ 2014-04-17 11:41 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: Benjamin Tissoires, Peter Hutterer, linux-input

Hi All,

And the quirks keep coming in <sigh> so here is a v3. Changelog:

v2:
-Add Cc: stable@vger.kernel.org

v3:
-Add a quirk for the L540
-Standardized S1 Yoga and X1 values, Yoga uses the same touchpad as the
 X240, X1 uses the same touchpad as the T440

Regards,

Hans

^ permalink raw reply

* Re: [RESEND PATCH] hid: Add custom driver for Lenovo ThinkPad Compact Bluetooth Keyboard
From: Jiri Kosina @ 2014-04-17 11:49 UTC (permalink / raw)
  To: Jamie Lentin; +Cc: linux-input, linux-kernel
In-Reply-To: <1397732098-7853-1-git-send-email-jm@lentin.co.uk>

On Thu, 17 Apr 2014, Jamie Lentin wrote:

> This keyboard requires some custom mappings for all keys to be
> available, and the Fn-lock toggle needs to be controlled in software.
> 
> Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
> ---
> Apologies for nagging, but anyone got any feedback on this? I don't
> think there's anything massively contentious, but hasn't been picked up
> by anyone yet.

I've been buried in a lot of other things (travel, merge window, etc.), so 
this is still sitting in my queue that has to be processed (and hasn't 
been forgotten).

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [linux-sunxi] Re: [PATCH v4 4/9] input: misc: Add driver for AXP20x Power Enable Key
From: Carlo Caione @ 2014-04-17 12:07 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: linux-arm-kernel, maxime.ripard, hdegoede, emilio, wens, sameo,
	linux-input, linux-doc, lgirdwood, broonie, lee.jones,
	boris.brezillon, linux-sunxi
In-Reply-To: <20140413081701.GB7267@core.coreip.homeip.net>

On Sun, Apr 13, 2014 at 10:17 AM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:

<cut>

>> +static ssize_t axp20x_show_ext_attr(struct device *dev, struct device_attribute *attr,
>> +                                 char *buf)
>> +{
>> +     struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
>> +     struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr);
>> +     unsigned int val;
>> +     int ret, i;
>> +
>> +     ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val);
>> +     if (ret != 0)
>> +             return ret;
>> +
>> +     val &= axp20x_ea->mask;
>> +     val >>= ffs(axp20x_ea->mask) - 1;
>> +
>> +     for (i = 0; i < 4; i++)
>> +             if (val == axp20x_ea->p_time[i].idx)
>> +                     val = axp20x_ea->p_time[i].time;
>> +
>> +     return sprintf(buf, "%ums\n", val);
>
> Please do not print ums and instead document the units in sysfs ABI
> docs.

Ok, I'll fix it.

<cut>

>> +     if (device_create_file(&pdev->dev, &axp20x_dev_attr_startup.attr) ||
>> +         device_create_file(&pdev->dev, &axp20x_dev_attr_shutdown.attr))
>> +             return -ENODEV;
>
> I think you still want to use attribute group here. You also need to
> clean up the attributes when unbinding device. Also, why returning
> -ENODEV instead of the proper error code?

I'll fix the error code and I'll add the clean up code.
I'm still a bit puzzled about the attribute group for the platform
devices. IIRC for the platform devices the default attribute group is
still a bit an issue [1]

[1] http://www.spinics.net/lists/hotplug/msg05775.html

Thanks for the review,

-- 
Carlo Caione

^ permalink raw reply

* Touchscreen monitor for embedded system
From: Cliff Brake @ 2014-04-17 14:27 UTC (permalink / raw)
  To: linux-input

I'm looking for:

1024x768 res monitor
touch screen
~15" preferred, but not absolute requirement
USB interface for touch
DVI for video

The system is an embedded ARM-linux system (very similar to
beagleboard).  Do you have any products that could be made to work
with this?  I'm mainly concerned about the touchscreen drivers.

It seems that the hid-multitouch compatible devices might be an option.

Appreciate and pointers as to products or companies that might offer something.

Thanks,
Cliff


-- 
=================
http://bec-systems.com

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox