Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v16 05/12] input: cyapa: add sysfs interfaces support in the cyapa
From: Dudley Du @ 2014-12-17  9:57 UTC (permalink / raw)
  To: dmitry.torokhov, jmmahler, rydberg; +Cc: bleung, linux-input, linux-kernel

Add device's basic control and features supported in cyapa driver through
sysfs file system interfaces. These interfaces are commonly used in
pre- and after production, for trackpad device state checking, managing
and firmware image updating.
These interfaces including mode, firmware_version and product_id interfaces
for reading firmware version and trackpad device product id values,
and including update_fw interface to command firmware image update
process. Also including baseline and calibrate interfaces for
reading and checking trackpad device's sensors states.
TEST=test on Chromebooks.

Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
 drivers/input/mouse/cyapa.c | 327 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 327 insertions(+)

diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 3bcfce3..406f38b 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -32,6 +32,8 @@
 #define CYAPA_ADAPTER_FUNC_SMBUS  2
 #define CYAPA_ADAPTER_FUNC_BOTH   3
 
+#define CYAPA_FW_NAME		"cyapa.bin"
+
 const char product_id[] = "CYTRA";
 
 static int cyapa_reinitialize(struct cyapa *cyapa);
@@ -442,6 +444,29 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
 	return 0;
 }
 
+static void cyapa_enable_irq_for_cmd(struct cyapa *cyapa)
+{
+	struct input_dev *input = cyapa->input;
+
+	if (!input || !input->users) {
+		if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
+			cyapa->ops->set_power_mode(cyapa,
+				PWR_MODE_FULL_ACTIVE, 0);
+		enable_irq(cyapa->client->irq);
+	}
+}
+
+static void cyapa_disable_irq_for_cmd(struct cyapa *cyapa)
+{
+	struct input_dev *input = cyapa->input;
+
+	if (!input || !input->users) {
+		disable_irq(cyapa->client->irq);
+		if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
+			cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+	}
+}
+
 /*
  * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
  *
@@ -783,6 +808,295 @@ static int 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)
+{
+	int error;
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+	error = scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+			 cyapa->fw_min_ver);
+	mutex_unlock(&cyapa->state_sync_lock);
+	return error;
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+	int size;
+	int error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+	size = scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+	mutex_unlock(&cyapa->state_sync_lock);
+	return size;
+}
+
+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+	struct device *dev = &cyapa->client->dev;
+	const struct firmware *fw;
+	int error;
+
+	error = request_firmware(&fw, fw_name, dev);
+	if (error) {
+		dev_err(dev, "Could not load firmware from %s: %d\n",
+			fw_name, error);
+		return error;
+	}
+
+	if (cyapa->ops->check_fw) {
+		error = cyapa->ops->check_fw(cyapa, fw);
+		if (error) {
+			dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+					fw_name);
+			goto done;
+		}
+	} else {
+		dev_err(dev, "No valid device ops->check_fw handler set.\n");
+		error = -ENODEV;
+		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);
+
+	/* Require IRQ support for firmware update commands. */
+	cyapa_enable_irq_for_cmd(cyapa);
+
+	if (cyapa->ops->bl_enter) {
+		error = cyapa->ops->bl_enter(cyapa);
+		if (error) {
+			dev_err(dev, "bl_enter failed, %d\n", error);
+			goto err_detect;
+		}
+	}
+
+	if (cyapa->ops->bl_activate) {
+		error = cyapa->ops->bl_activate(cyapa);
+		if (error) {
+			dev_err(dev, "bl_activate failed, %d\n", error);
+			goto err_detect;
+		}
+	}
+
+	if (cyapa->ops->bl_initiate) {
+		error = cyapa->ops->bl_initiate(cyapa, fw);
+		if (error) {
+			dev_err(dev, "bl_initiate failed, %d\n", error);
+			goto err_detect;
+		}
+	}
+
+	if (cyapa->ops->update_fw) {
+		error = cyapa->ops->update_fw(cyapa, fw);
+		if (error) {
+			dev_err(dev, "update_fw failed, %d\n", error);
+			goto err_detect;
+		}
+	}
+
+	if (cyapa->ops->bl_verify_app_integrity) {
+		error = cyapa->ops->bl_verify_app_integrity(cyapa);
+		if (error) {
+			dev_err(dev, "bl_verify_app_integrity failed, %d\n",
+					error);
+			goto err_detect;
+		}
+	}
+
+err_detect:
+	cyapa_disable_irq_for_cmd(cyapa);
+	pm_runtime_put_noidle(dev);
+
+done:
+	release_firmware(fw);
+	return error;
+}
+
+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);
+	char fw_name[NAME_MAX];
+	int ret, error;
+
+	if (count > NAME_MAX) {
+		dev_err(dev, "File name too long\n");
+		return -EINVAL;
+	}
+
+	memcpy(fw_name, buf, count);
+	if (fw_name[count - 1] == '\n')
+		fw_name[count - 1] = '\0';
+	else
+		fw_name[count] = '\0';
+
+	if (cyapa->input) {
+		/*
+		 * Force the input device to be registered after the firmware
+		 * image is updated, so if the corresponding parameters updated
+		 * in the new firmware image can taken effect immediately.
+		 */
+		input_unregister_device(cyapa->input);
+		cyapa->operational = false;
+		cyapa->input = NULL;
+	}
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error) {
+		/*
+		 * Whatever, do reinitialize to try to recover TP state to
+		 * previous state just as it entered fw update entrance.
+		 */
+		cyapa_reinitialize(cyapa);
+		return error;
+	}
+
+	error = cyapa_firmware(cyapa, fw_name);
+	if (error)
+		dev_err(dev, "firmware update failed: %d\n", error);
+	else
+		dev_dbg(dev, "firmware update successfully done.\n");
+
+	/*
+	 * Redetect trackpad device states because firmware update process
+	 * will reset trackpad device into bootloader mode.
+	 */
+	ret = cyapa_reinitialize(cyapa);
+	if (ret) {
+		dev_err(dev, "failed to redetect after updated: %d\n", ret);
+		error = error ? error : ret;
+	}
+
+	mutex_unlock(&cyapa->state_sync_lock);
+
+	return error ? error : 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 error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+
+	if (!cyapa->ops->calibrate_store) {
+		dev_err(dev, "Calibrate operation not supported.\n");
+		error = -ENOTSUPP;
+	} else {
+		cyapa_enable_irq_for_cmd(cyapa);
+		error = cyapa->ops->calibrate_store(dev, attr, buf, count);
+		cyapa_disable_irq_for_cmd(cyapa);
+	}
+
+	mutex_unlock(&cyapa->state_sync_lock);
+	return error < 0 ? error : count;
+}
+
+static ssize_t cyapa_show_baseline(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+	ssize_t error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+
+	if (!cyapa->ops->show_baseline) {
+		dev_err(dev, "Calibrate operation not supported.\n");
+		error = -ENOTSUPP;
+	} else {
+		cyapa_enable_irq_for_cmd(cyapa);
+		error = cyapa->ops->show_baseline(dev, attr, buf);
+		cyapa_disable_irq_for_cmd(cyapa);
+	}
+
+	mutex_unlock(&cyapa->state_sync_lock);
+	return error;
+}
+
+static char *cyapa_state_to_string(struct cyapa *cyapa)
+{
+	switch (cyapa->state) {
+	case CYAPA_STATE_BL_BUSY:
+		return "bootloader busy";
+	case CYAPA_STATE_BL_IDLE:
+		return "bootloader idle";
+	case CYAPA_STATE_BL_ACTIVE:
+		return "bootloader active";
+	case CYAPA_STATE_GEN5_BL:
+		return "bootloader";
+	case CYAPA_STATE_OP:
+	case CYAPA_STATE_GEN5_APP:
+		return "operational";  /* Normal valid state. */
+	default:
+		return "invalid mode";
+	}
+}
+
+static ssize_t cyapa_show_mode(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+	int size;
+	int error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+
+	size = scnprintf(buf, PAGE_SIZE, "gen%d %s\n",
+			cyapa->gen, cyapa_state_to_string(cyapa));
+
+	mutex_unlock(&cyapa->state_sync_lock);
+	return size;
+}
+
+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 DEVICE_ATTR(mode, S_IRUGO, cyapa_show_mode, NULL);
+
+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,
+	&dev_attr_mode.attr,
+	NULL,
+};
+
+static const struct attribute_group cyapa_sysfs_group = {
+	.attrs = cyapa_sysfs_entries,
+};
+
+static void cyapa_remove_sysfs_group(void *data)
+{
+	struct cyapa *cyapa = data;
+
+	sysfs_remove_group(&cyapa->client->dev.kobj, &cyapa_sysfs_group);
+}
+
 static int cyapa_probe(struct i2c_client *client,
 		       const struct i2c_device_id *dev_id)
 {
@@ -822,6 +1136,19 @@ static int cyapa_probe(struct i2c_client *client,
 		return error;
 	}
 
+	error = sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group);
+	if (error) {
+		dev_err(dev, "failed to create sysfs entries: %d\n", error);
+		return error;
+	}
+
+	error = devm_add_action(dev, cyapa_remove_sysfs_group, cyapa);
+	if (error) {
+		cyapa_remove_sysfs_group(cyapa);
+		dev_err(dev, "failed to add sysfs cleanup action: %d\n", error);
+		return error;
+	}
+
 #ifdef CONFIG_PM_SLEEP
 	if (device_can_wakeup(dev)) {
 		error = sysfs_merge_group(&client->dev.kobj,
-- 
1.9.1

^ permalink raw reply related

* [PATCH v16 04/12] input: cyapa: add runtime power management interfaces
From: Dudley Du @ 2014-12-17  9:57 UTC (permalink / raw)
  To: dmitry.torokhov, jmmahler, rydberg; +Cc: bleung, linux-input, linux-kernel

Add runtime_suspend_scanrate_ms power management interfaces in device's
power group, so users or applications can control the runtime power
management strategy of trackpad device as their requirements.
TEST=test on Chromebooks.

Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
 drivers/input/mouse/cyapa.c | 171 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/input/mouse/cyapa.h |   4 ++
 2 files changed, 174 insertions(+), 1 deletion(-)

diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 73f6817..3bcfce3 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -23,6 +23,7 @@
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
+#include <linux/pm_runtime.h>
 #include "cyapa.h"
 
 
@@ -327,6 +328,8 @@ static int cyapa_open(struct input_dev *input)
 	}
 
 	enable_irq(client->irq);
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
 out:
 	mutex_unlock(&cyapa->state_sync_lock);
 	return error;
@@ -340,8 +343,10 @@ static void cyapa_close(struct input_dev *input)
 	mutex_lock(&cyapa->state_sync_lock);
 
 	disable_irq(client->irq);
+	pm_runtime_disable(&client->dev);
 	if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
 		cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+	pm_runtime_set_suspended(&client->dev);
 
 	mutex_unlock(&cyapa->state_sync_lock);
 }
@@ -542,6 +547,7 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
 	struct device *dev = &cyapa->client->dev;
 	bool cont;
 
+	pm_runtime_get_sync(dev);
 	if (device_may_wakeup(dev))
 		pm_wakeup_event(dev, 0);
 
@@ -572,6 +578,8 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
 	}
 
 out:
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_sync_autosuspend(dev);
 	return IRQ_HANDLED;
 }
 
@@ -665,6 +673,116 @@ static void cyapa_remove_power_wakeup_group(void *data)
 }
 #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;
+	u16 sleep_time;
+	int error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+	pwr_cmd = cyapa->runtime_suspend_power_mode;
+	sleep_time = cyapa->runtime_suspend_sleep_time;
+	mutex_unlock(&cyapa->state_sync_lock);
+
+	if (cyapa->gen == CYAPA_GEN3)
+		return scnprintf(buf, PAGE_SIZE, "%u\n",
+			cyapa_pwr_cmd_to_sleep_time(pwr_cmd));
+	return scnprintf(buf, PAGE_SIZE, "%u\n", 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;
+	int error;
+
+	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);
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+	cyapa->runtime_suspend_sleep_time = max_t(u16, time, 1000);
+	cyapa->runtime_suspend_power_mode =
+		cyapa_sleep_time_to_pwr_cmd(cyapa->runtime_suspend_sleep_time);
+	mutex_unlock(&cyapa->state_sync_lock);
+	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_remove_power_runtime_group(void *data)
+{
+	struct cyapa *cyapa = data;
+
+	sysfs_unmerge_group(&cyapa->client->dev.kobj,
+				&cyapa_power_runtime_group);
+}
+
+static int cyapa_start_runtime(struct cyapa *cyapa)
+{
+	struct device *dev = &cyapa->client->dev;
+	int error;
+
+	cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+	cyapa->runtime_suspend_sleep_time =
+		cyapa_pwr_cmd_to_sleep_time(cyapa->runtime_suspend_power_mode);
+
+	error = sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group);
+	if (error) {
+		dev_err(dev,
+			"failed to create power runtime group: %d\n", error);
+		return error;
+	}
+
+	error = devm_add_action(dev, cyapa_remove_power_runtime_group, cyapa);
+	if (error) {
+		cyapa_remove_power_runtime_group(cyapa);
+		dev_err(dev,
+			"failed to add power runtime cleanup action: %d\n",
+			error);
+		return error;
+	}
+
+	/* runtime is enabled until device is operational and opened. */
+	pm_runtime_set_suspended(dev);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+
+	return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
 static int cyapa_probe(struct i2c_client *client,
 		       const struct i2c_device_id *dev_id)
 {
@@ -725,6 +843,14 @@ static int cyapa_probe(struct i2c_client *client,
 	}
 #endif /* CONFIG_PM_SLEEP */
 
+#ifdef CONFIG_PM_RUNTIME
+	error = cyapa_start_runtime(cyapa);
+	if (error) {
+		dev_err(dev, "failed to start pm_runtime: %d\n", error);
+		return error;
+	}
+#endif /* CONFIG_PM_RUNTIME */
+
 	error = devm_request_threaded_irq(dev, client->irq,
 					  NULL, cyapa_irq,
 					  IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
@@ -765,6 +891,7 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
 	if (error)
 		return error;
 
+	pm_runtime_disable(dev);
 	disable_irq(client->irq);
 
 	/*
@@ -781,6 +908,8 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
 					error);
 	}
 
+	pm_runtime_set_suspended(dev);
+
 	if (device_may_wakeup(dev))
 		cyapa->irq_wake = (enable_irq_wake(client->irq) == 0);
 
@@ -801,17 +930,57 @@ static int __maybe_unused cyapa_resume(struct device *dev)
 		cyapa->irq_wake = false;
 	}
 
+	pm_runtime_set_active(dev);
+
 	error = cyapa_reinitialize(cyapa);
 	if (error)
 		dev_warn(dev, "failed to reinitialize TP device: %d\n", error);
 
 	enable_irq(client->irq);
 
+	pm_runtime_enable(dev);
+
 	mutex_unlock(&cyapa->state_sync_lock);
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+#ifdef CONFIG_PM_RUNTIME
+static int cyapa_runtime_suspend(struct device *dev)
+{
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+	int error;
+
+	if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode) {
+		error = cyapa->ops->set_power_mode(cyapa,
+				cyapa->runtime_suspend_power_mode,
+				cyapa->runtime_suspend_sleep_time);
+		if (error)
+			dev_warn(dev, "runtime suspend failed: %d\n", error);
+	}
+
+	return 0;
+}
+
+static int cyapa_runtime_resume(struct device *dev)
+{
+	struct cyapa *cyapa = dev_get_drvdata(dev);
+	int error;
+
+	if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode) {
+		error = cyapa->ops->set_power_mode(cyapa,
+				PWR_MODE_FULL_ACTIVE, 0);
+		if (error)
+			dev_warn(dev, "runtime resume failed: %d\n", error);
+	}
+
+	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 },
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index 4c09be4..922a473 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -253,6 +253,10 @@ struct cyapa {
 	/* power mode settings */
 	u8 suspend_power_mode;
 	u16 suspend_sleep_time;
+#ifdef CONFIG_PM_RUNTIME
+	u8 runtime_suspend_power_mode;
+	u16 runtime_suspend_sleep_time;
+#endif /* CONFIG_PM_RUNTIME */
 	u8 dev_pwr_mode;
 	u16 dev_sleep_time;
 
-- 
1.9.1

^ permalink raw reply related

* [PATCH v16 03/12] input: cyapa: add power management interfaces support
From: Dudley Du @ 2014-12-17  9:56 UTC (permalink / raw)
  To: dmitry.torokhov, jmmahler, rydberg; +Cc: bleung, linux-input, linux-kernel

Add suspend_scanrate_ms power management interfaces in device's
power group, so users or applications can control the power management
strategy of trackpad device as their requirements.
TEST=test on Chromebooks.

Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
 drivers/input/mouse/cyapa.c | 111 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 111 insertions(+)

diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index d4560a3..73f6817 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -575,6 +575,96 @@ out:
 	return IRQ_HANDLED;
 }
 
+/*
+ **************************************************************
+ * 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;
+	int error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+	pwr_cmd = cyapa->suspend_power_mode;
+	sleep_time = cyapa->suspend_sleep_time;
+	mutex_unlock(&cyapa->state_sync_lock);
+
+	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);
+		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;
+	int error;
+
+	error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+	if (error)
+		return error;
+
+	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_sleep_time = max_t(u16, sleep_time, 1000);
+		cyapa->suspend_power_mode =
+			cyapa_sleep_time_to_pwr_cmd(cyapa->suspend_sleep_time);
+	} else {
+		count = 0;
+	}
+
+	mutex_unlock(&cyapa->state_sync_lock);
+
+	if (!count)
+		dev_err(dev, "invalid suspend scanrate ms parameters\n");
+	return count ? count : -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,
+};
+
+static void cyapa_remove_power_wakeup_group(void *data)
+{
+	struct cyapa *cyapa = data;
+
+	sysfs_unmerge_group(&cyapa->client->dev.kobj,
+				&cyapa_power_wakeup_group);
+}
+#endif /* CONFIG_PM_SLEEP */
+
 static int cyapa_probe(struct i2c_client *client,
 		       const struct i2c_device_id *dev_id)
 {
@@ -614,6 +704,27 @@ static int cyapa_probe(struct i2c_client *client,
 		return error;
 	}
 
+#ifdef CONFIG_PM_SLEEP
+	if (device_can_wakeup(dev)) {
+		error = sysfs_merge_group(&client->dev.kobj,
+					&cyapa_power_wakeup_group);
+		if (error) {
+			dev_err(dev, "failed to add power wakeup group: %d\n",
+				error);
+			return error;
+		}
+
+		error = devm_add_action(dev,
+				cyapa_remove_power_wakeup_group, cyapa);
+		if (error) {
+			cyapa_remove_power_wakeup_group(cyapa);
+			dev_err(dev, "failed to add power cleanup action: %d\n",
+				error);
+			return error;
+		}
+	}
+#endif /* CONFIG_PM_SLEEP */
+
 	error = devm_request_threaded_irq(dev, client->irq,
 					  NULL, cyapa_irq,
 					  IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-- 
1.9.1

^ permalink raw reply related

* [PATCH v16 02/12] input: cyapa: add gen5 trackpad device basic functions
From: Dudley Du @ 2014-12-17  9:56 UTC (permalink / raw)
  To: dmitry.torokhov, jmmahler, rydberg; +Cc: bleung, linux-input, linux-kernel

Based on the cyapa core, add the gen5 trackpad device's basic functions
supported, so gen5 trackpad device can work with kernel input system.
And also based on the state parse interface, the cyapa driver can
automatically determine the attached is gen3 or gen5 protocol trackpad
device, then set the correct protocol to work with the attached
trackpad device.
TEST=test on Chromebooks.

Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
 drivers/input/mouse/Makefile     |    2 +-
 drivers/input/mouse/cyapa.c      |   13 +
 drivers/input/mouse/cyapa.h      |    1 +
 drivers/input/mouse/cyapa_gen5.c | 1677 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 1692 insertions(+), 1 deletion(-)
 create mode 100644 drivers/input/mouse/cyapa_gen5.c

diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 8bd950d..8a9c98e 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -24,7 +24,7 @@ obj-$(CONFIG_MOUSE_SYNAPTICS_I2C)	+= synaptics_i2c.o
 obj-$(CONFIG_MOUSE_SYNAPTICS_USB)	+= synaptics_usb.o
 obj-$(CONFIG_MOUSE_VSXXXAA)		+= vsxxxaa.o
 
-cyapatp-objs := cyapa.o cyapa_gen3.o
+cyapatp-objs := cyapa.o cyapa_gen3.o cyapa_gen5.o
 psmouse-objs := psmouse-base.o synaptics.o focaltech.o
 
 psmouse-$(CONFIG_MOUSE_PS2_ALPS)	+= alps.o
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index ae1df15..d4560a3 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -157,6 +157,14 @@ static int cyapa_get_state(struct cyapa *cyapa)
 			if (!error)
 				goto out_detected;
 		}
+		if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
+				cyapa->gen == CYAPA_GEN5) &&
+			!smbus && even_addr) {
+			error = cyapa_gen5_ops.state_parse(cyapa,
+					status, BL_STATUS_SIZE);
+			if (!error)
+				goto out_detected;
+		}
 
 		/*
 		 * Write 0x00 0x00 to trackpad device to force update its
@@ -240,6 +248,9 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
 		return error;
 
 	switch (cyapa->gen) {
+	case CYAPA_GEN5:
+		cyapa->ops = &cyapa_gen5_ops;
+		break;
 	case CYAPA_GEN3:
 		cyapa->ops = &cyapa_gen3_ops;
 		break;
@@ -476,6 +487,8 @@ static int cyapa_initialize(struct cyapa *cyapa)
 
 	if (cyapa_gen3_ops.initialize)
 		error = cyapa_gen3_ops.initialize(cyapa);
+	if (!error && cyapa_gen5_ops.initialize)
+		error = cyapa_gen5_ops.initialize(cyapa);
 	if (error)
 		return error;
 
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index 8a75c52..4c09be4 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -303,5 +303,6 @@ u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode);
 
 extern const char product_id[];
 extern const struct cyapa_dev_ops cyapa_gen3_ops;
+extern const struct cyapa_dev_ops cyapa_gen5_ops;
 
 #endif
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
new file mode 100644
index 0000000..2ce0149
--- /dev/null
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -0,0 +1,1677 @@
+/*
+ * Cypress APA trackpad with I2C interface
+ *
+ * Author: Dudley Du <dudl@cypress.com>
+ *
+ * Copyright (C) 2014 Cypress Semiconductor, Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/unaligned/access_ok.h>
+#include "cyapa.h"
+
+
+/* Macro 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
+
+#define CYAPA_TSG_MAX_CMD_SIZE              256
+
+#define GEN5_BL_CMD_VERIFY_APP_INTEGRITY    0x31
+#define GEN5_BL_CMD_GET_BL_INFO		    0x38
+#define GEN5_BL_CMD_PROGRAM_VERIFY_ROW      0x39
+#define GEN5_BL_CMD_LAUNCH_APP		    0x3b
+#define GEN5_BL_CMD_INITIATE_BL		    0x48
+
+#define GEN5_HID_DESCRIPTOR_ADDR	0x0001
+#define GEN5_REPORT_DESCRIPTOR_ADDR	0x0002
+#define GEN5_INPUT_REPORT_ADDR		0x0003
+#define GEN5_OUTPUT_REPORT_ADDR		0x0004
+#define GEN5_CMD_DATA_ADDR		0x0006
+
+#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_CMD_GET_PARAMETER		     0x05
+#define GEN5_CMD_SET_PARAMETER		     0x06
+#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 VALID_CMD_RESP_HEADER(resp, cmd)				    \
+	(((resp)[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID) && \
+	((resp)[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) &&	    \
+	(GET_GEN5_CMD_CODE((resp)[GEN5_RESP_APP_CMD_OFFSET]) == (cmd)))
+
+#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_OFFSET  0x00
+#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
+
+/* The offset only valid for reterive PWC and panel scan commands */
+#define GEN5_RESP_DATA_STRUCTURE_OFFSET      10
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK      0x07
+
+#define	GEN5_NUMBER_OF_TOUCH_OFFSET  5
+#define GEN5_NUMBER_OF_TOUCH_MASK    0x1f
+#define GEN5_BUTTONS_OFFSET          5
+#define GEN5_BUTTONS_MASK            0x0f
+#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_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 allow tracking a touch as it moves around the panel.
+	 */
+	u8 touch_tip_event_id;
+
+	/* Bit 7 - 0 of X-axis coordinate of the touch in pixel. */
+	u8 x_lo;
+
+	/* Bit 15 - 8 of X-axis coordinate of the touch in pixel. */
+	u8 x_hi;
+
+	/* Bit 7 - 0 of Y-axis coordinate of the touch in pixel. */
+	u8 y_lo;
+
+	/* Bit 15 - 8 of Y-axis coordinate 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 gen5_app_cmd_head {
+	__le16 addr;   /* Output report register address, must be 0004h */
+	/* Size of packet not including output report register address */
+	__le16 length;
+	u8 report_id;  /* Application output report id, must be 2Fh */
+	u8 rsvd;  /* Reserved, must be 0 */
+	/*
+	 * Bit 7: reserved, must be 0.
+	 * Bit 6-0: command code.
+	 */
+	u8 cmd_code;
+	u8 parameter_data[0];  /* Parameter data variable based on cmd_code */
+} __packed;
+
+/* Applicaton get/set parameter command data structure */
+struct gen5_app_set_parameter_data {
+	u8 parameter_id;
+	u8 parameter_size;
+	__le32 value;
+} __packed;
+
+struct gen5_app_get_parameter_data {
+	u8 parameter_id;
+} __packed;
+
+/* Variables to record latest gen5 trackpad power states. */
+#define GEN5_DEV_SET_PWR_STATE(cyapa, s)	((cyapa)->dev_pwr_mode = (s))
+#define GEN5_DEV_GET_PWR_STATE(cyapa)		((cyapa)->dev_pwr_mode)
+#define GEN5_DEV_SET_SLEEP_TIME(cyapa, t)	((cyapa)->dev_sleep_time = (t))
+#define GEN5_DEV_GET_SLEEP_TIME(cyapa)		((cyapa)->dev_sleep_time)
+#define GEN5_DEV_UNINIT_SLEEP_TIME(cyapa)	\
+		(((cyapa)->dev_sleep_time) == UNINIT_SLEEP_TIME)
+
+static int cyapa_gen5_initialize(struct cyapa *cyapa)
+{
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	init_completion(&gen5_pip->cmd_ready);
+	atomic_set(&gen5_pip->cmd_issued, 0);
+	mutex_init(&gen5_pip->cmd_lock);
+
+	gen5_pip->resp_sort_func = NULL;
+	gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+	gen5_pip->resp_data = NULL;
+	gen5_pip->resp_len = NULL;
+
+	cyapa->dev_pwr_mode = UNINIT_PWR_MODE;
+	cyapa->dev_sleep_time = UNINIT_SLEEP_TIME;
+
+	return 0;
+}
+
+/* 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;
+}
+
+/**
+ * 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, cb_sort func)
+{
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+	int length;
+	int report_count;
+	int empty_count;
+	int buf_len;
+	int error;
+
+	buf_len = 0;
+	if (len) {
+		buf_len = (*len < CYAPA_REG_MAP_SIZE) ?
+				*len : CYAPA_REG_MAP_SIZE;
+		*len = 0;
+	}
+
+	report_count = 8;  /* max 7 pending data before command response data */
+	empty_count = 0;
+	do {
+		/*
+		 * Depending on testing in cyapa driver, there are max 5 "02 00"
+		 * packets between two valid buffered 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;
+
+		error = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf,
+				GEN5_RESP_LENGTH_SIZE);
+		if (error < 0)
+			return error;
+
+		length = get_unaligned_le16(gen5_pip->empty_buf);
+		if (length == GEN5_RESP_LENGTH_SIZE) {
+			empty_count++;
+			continue;
+		} else if (length > CYAPA_REG_MAP_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, gen5_pip->empty_buf, length)) {
+				length = min(buf_len, length);
+				memcpy(buf, gen5_pip->empty_buf, length);
+				*len = length;
+				/* Response found, success. */
+				return 0;
+			}
+			continue;
+		}
+
+		error = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+		if (error < 0)
+			return error;
+
+		report_count--;
+		empty_count = 0;
+		length = get_unaligned_le16(gen5_pip->empty_buf);
+		if (length <= GEN5_RESP_LENGTH_SIZE) {
+			empty_count++;
+		} else if (buf && buf_len && func &&
+			func(cyapa, gen5_pip->empty_buf, length)) {
+			length = min(buf_len, length);
+			memcpy(buf, gen5_pip->empty_buf, length);
+			*len = length;
+			/* Response found, success. */
+			return 0;
+		}
+
+		error = -EINVAL;
+	} while (report_count);
+
+	return error;
+}
+
+static int cyapa_do_i2c_pip_cmd_irq_sync(
+		struct cyapa *cyapa,
+		u8 *cmd, size_t cmd_len,
+		unsigned long timeout)
+{
+	int error;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	/* Wait for interrupt to set ready completion */
+	init_completion(&gen5_pip->cmd_ready);
+
+	atomic_inc(&gen5_pip->cmd_issued);
+	error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+	if (error) {
+		atomic_dec(&gen5_pip->cmd_issued);
+		return (error < 0) ? error : -EIO;
+	}
+
+	/* Wait for interrupt to indicate command is completed. */
+	timeout = wait_for_completion_timeout(&gen5_pip->cmd_ready,
+				msecs_to_jiffies(timeout));
+	if (timeout == 0) {
+		atomic_dec(&gen5_pip->cmd_issued);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int cyapa_do_i2c_pip_cmd_polling(
+		struct cyapa *cyapa,
+		u8 *cmd, size_t cmd_len,
+		u8 *resp_data, int *resp_len,
+		unsigned long timeout,
+		cb_sort func)
+{
+	int error;
+	int tries;
+	int length;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	atomic_inc(&gen5_pip->cmd_issued);
+	error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+	if (error) {
+		atomic_dec(&gen5_pip->cmd_issued);
+		return error < 0 ? error : -EIO;
+	}
+
+	length = resp_len ? *resp_len : 0;
+	if (resp_data && resp_len && length != 0 && func) {
+		tries = timeout / 5;
+		do {
+			usleep_range(3000, 5000);
+			*resp_len = length;
+			error = cyapa_empty_pip_output_data(cyapa,
+					resp_data, resp_len, func);
+			if (error || *resp_len == 0)
+				continue;
+			else
+				break;
+		} while (--tries > 0);
+		if ((error || *resp_len == 0) || tries <= 0)
+			error = error ? error : -ETIMEDOUT;
+	}
+
+	atomic_dec(&gen5_pip->cmd_issued);
+	return error;
+}
+
+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,
+		cb_sort func,
+		bool irq_mode)
+{
+	int error;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	if (!cmd || !cmd_len)
+		return -EINVAL;
+
+	/* Commands must be serialized. */
+	error = mutex_lock_interruptible(&gen5_pip->cmd_lock);
+	if (error)
+		return error;
+
+	gen5_pip->resp_sort_func = func;
+	gen5_pip->resp_data = resp_data;
+	gen5_pip->resp_len = resp_len;
+
+	if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+			cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+		/* Application command */
+		gen5_pip->in_progress_cmd = cmd[6] & 0x7f;
+	} else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+			cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+		/* Bootloader command */
+		gen5_pip->in_progress_cmd = cmd[7];
+	}
+
+	/* Send command data, wait and read output response data's length. */
+	if (irq_mode) {
+		gen5_pip->is_irq_mode = true;
+		error = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+							timeout);
+		if (error == -ETIMEDOUT && resp_data &&
+				resp_len && *resp_len != 0 && func) {
+			/*
+			 * For some old version, there was no interrupt for
+			 * the command response data, so need to poll here
+			 * to try to get the response data.
+			 */
+			error = cyapa_empty_pip_output_data(cyapa,
+					resp_data, resp_len, func);
+			if (error || *resp_len == 0)
+				error = error ? error : -ETIMEDOUT;
+		}
+	} else {
+		gen5_pip->is_irq_mode = false;
+		error = cyapa_do_i2c_pip_cmd_polling(cyapa, cmd, cmd_len,
+				resp_data, resp_len, timeout, func);
+	}
+
+	gen5_pip->resp_sort_func = NULL;
+	gen5_pip->resp_data = NULL;
+	gen5_pip->resp_len = NULL;
+	gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+
+	mutex_unlock(&gen5_pip->cmd_lock);
+	return error;
+}
+
+static 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;
+}
+
+static bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+		u8 *data, int len)
+{
+	int resp_len;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	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[GEN5_RESP_LENGTH_OFFSET]);
+		if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+			resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+			data[5] == gen5_pip->in_progress_cmd) {
+			/* Unsupported command code */
+			return false;
+		} else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+				gen5_pip->in_progress_cmd) {
+			/* Correct command response received */
+			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[GEN5_RESP_LENGTH_OFFSET]);
+	max_output_len = get_unaligned_le16(&buf[16]);
+	if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+		if (buf[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_HID_REPORT_ID &&
+				max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+			/* BL mode HID Descriptor */
+			return true;
+		} else if ((buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+				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[GEN5_RESP_REPORT_ID_OFFSET] ==
+			GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+		(buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+			GEN5_DEEP_SLEEP_OPCODE)
+		return true;
+	return false;
+}
+
+static int gen5_idle_state_parse(struct cyapa *cyapa)
+{
+	int ret;
+	int error;
+	int length;
+	u8 cmd[2];
+	u8 resp_data[GEN5_HID_DESCRIPTOR_SIZE];
+	int max_output_len;
+
+	/*
+	 * Dump all buffered data firstly for the situation
+	 * when the 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 ret < 0 ? ret : -EIO;
+
+	length = get_unaligned_le16(&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+	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 description from trackpad device */
+		cmd[0] = 0x01;
+		cmd[1] = 0x00;
+		length = GEN5_HID_DESCRIPTOR_SIZE;
+		error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+				cmd, GEN5_RESP_LENGTH_SIZE,
+				resp_data, &length,
+				300,
+				cyapa_gen5_sort_hid_descriptor_data,
+				false);
+		if (error)
+			return error;
+
+		length = get_unaligned_le16(
+				&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+		max_output_len = get_unaligned_le16(&resp_data[16]);
+		if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+				length == GEN5_RESP_LENGTH_SIZE) &&
+			(resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_BL_HID_REPORT_ID) &&
+			max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+			/* BL mode HID Description read */
+			cyapa->state = CYAPA_STATE_GEN5_BL;
+		} else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+				length == GEN5_RESP_LENGTH_SIZE) &&
+			(resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_APP_HID_REPORT_ID) &&
+			max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+			/* APP mode HID Description read */
+			cyapa->state = CYAPA_STATE_GEN5_APP;
+		} else {
+			/* Should not happen!!! */
+			cyapa->state = CYAPA_STATE_NO_DEVICE;
+		}
+	}
+
+	return 0;
+}
+
+static int gen5_hid_description_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+	int ret;
+	int length;
+	u8 resp_data[32];
+	int max_output_len;
+
+	/* 0x20 0x00 0xF7 is Gen5 Application HID Description Header;
+	 * 0x20 0x00 0xFF is Gen5 Booloader HID Description Header.
+	 *
+	 * Must read HID Description content through out,
+	 * otherwise Gen5 trackpad cannot response 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 ret < 0 ? ret : -EIO;
+	length = get_unaligned_le16(&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+	max_output_len = get_unaligned_le16(&resp_data[16]);
+	if (length == GEN5_RESP_LENGTH_SIZE) {
+		if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_BL_HID_REPORT_ID) {
+			/*
+			 * BL mode HID Description has been previously
+			 * read out.
+			 */
+			cyapa->gen = CYAPA_GEN5;
+			cyapa->state = CYAPA_STATE_GEN5_BL;
+		} else {
+			/*
+			 * APP mode HID Description has been previously
+			 * read out.
+			 */
+			cyapa->gen = CYAPA_GEN5;
+			cyapa->state = CYAPA_STATE_GEN5_APP;
+		}
+	} 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;
+	} else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+			(resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				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;
+	} else {
+		/* Should not happen!!! */
+		cyapa->state = CYAPA_STATE_NO_DEVICE;
+	}
+
+	return 0;
+}
+
+static int gen5_report_data_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+	int length;
+
+	length = get_unaligned_le16(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+	switch (reg_data[GEN5_RESP_REPORT_ID_OFFSET]) {
+	case GEN5_TOUCH_REPORT_ID:
+		if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+			length > GEN5_TOUCH_REPORT_MAX_SIZE)
+			return -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)
+			return -EINVAL;
+		break;
+	case GEN5_WAKEUP_EVENT_REPORT_ID:
+		if (length != GEN5_WAKEUP_EVENT_SIZE)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cyapa->gen = CYAPA_GEN5;
+	cyapa->state = CYAPA_STATE_GEN5_APP;
+	return 0;
+}
+
+static int gen5_cmd_resp_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+	int ret;
+	int length;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	/*
+	 * Must read report data through out,
+	 * otherwise Gen5 trackpad cannot response next command
+	 * or report any touch or button data.
+	 */
+	length = get_unaligned_le16(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+	ret = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+	if (ret != length)
+		return ret < 0 ? ret : -EIO;
+
+	if (length == GEN5_RESP_LENGTH_SIZE) {
+		/* Previous command has read the data through out. */
+		if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				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 ((gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_BL_RESP_REPORT_ID) &&
+			(gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+				GEN5_RESP_RSVD_KEY) &&
+			(gen5_pip->empty_buf[GEN5_RESP_BL_SOP_OFFSET] ==
+				GEN5_SOP_KEY) &&
+			(gen5_pip->empty_buf[length - 1] ==
+				GEN5_EOP_KEY)) {
+		/* Gen5 BL command response data detected */
+		cyapa->gen = CYAPA_GEN5;
+		cyapa->state = CYAPA_STATE_GEN5_BL;
+	} else if (gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_APP_RESP_REPORT_ID &&
+			gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+				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;
+	}
+
+	return 0;
+}
+
+static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+	int length;
+
+	if (!reg_data || len < 3)
+		return -EINVAL;
+
+	cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+	/* Parse based on Gen5 characteristic registers and bits */
+	length = get_unaligned_le16(&reg_data[GEN5_RESP_LENGTH_OFFSET]);
+	if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+		gen5_idle_state_parse(cyapa);
+	} else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+			(reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+				reg_data[2] == GEN5_APP_HID_REPORT_ID)) {
+		gen5_hid_description_header_parse(cyapa, reg_data);
+	} 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_GEN5_APP;
+	} 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_GEN5_BL;
+	} 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) {
+		gen5_report_data_header_parse(cyapa, reg_data);
+	} else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+			reg_data[2] == GEN5_APP_RESP_REPORT_ID) {
+		gen5_cmd_resp_header_parse(cyapa, reg_data);
+	}
+
+	if (cyapa->gen == CYAPA_GEN5) {
+		/*
+		 * Must read the content (e.g.: report description and so on)
+		 * from trackpad device throughout. Otherwise,
+		 * Gen5 trackpad cannot response to next command or
+		 * report any touch or button data later.
+		 */
+		cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+		if (cyapa->state == CYAPA_STATE_GEN5_APP ||
+			cyapa->state == CYAPA_STATE_GEN5_BL)
+			return 0;
+	}
+
+	return -EAGAIN;
+}
+
+static 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[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_BL_RESP_REPORT_ID &&
+			buf[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+			buf[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY &&
+			buf[10] == GEN5_EOP_KEY)
+		return true;
+
+	return false;
+}
+
+static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
+{
+	int error;
+	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);
+	error = 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, false);
+	if (error)
+		return error;
+
+	if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+			resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+				GEN5_BL_RESP_REPORT_ID)
+		return -EAGAIN;
+
+	if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+		return 0;
+
+	return -ENODEV;
+}
+
+static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
+{
+	u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
+	u8 resp_data[6];
+	int resp_len;
+	int error;
+
+	cmd[7] = power_state;
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+	if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x08) ||
+			!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+		return error < 0 ? error : -EINVAL;
+
+	return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+		u8 parameter_id, u16 interval_time)
+{
+	struct gen5_app_cmd_head *app_cmd_head;
+	struct gen5_app_set_parameter_data *parameter_data;
+	u8 cmd[CYAPA_TSG_MAX_CMD_SIZE];
+	int cmd_len;
+	u8 resp_data[7];
+	int resp_len;
+	u8 parameter_size;
+	int error;
+
+	memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE);
+	app_cmd_head = (struct gen5_app_cmd_head *)cmd;
+	parameter_data = (struct gen5_app_set_parameter_data *)
+			 app_cmd_head->parameter_data;
+	cmd_len = sizeof(struct gen5_app_cmd_head) +
+		  sizeof(struct gen5_app_set_parameter_data);
+
+	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;
+	}
+
+	put_unaligned_le16(GEN5_OUTPUT_REPORT_ADDR, &app_cmd_head->addr);
+	/*
+	 * Don't include unused parameter value bytes and
+	 * 2 bytes register address.
+	 */
+	put_unaligned_le16(cmd_len - (4 - parameter_size) - 2,
+			   &app_cmd_head->length);
+	app_cmd_head->report_id = GEN5_APP_CMD_REPORT_ID;
+	app_cmd_head->cmd_code = GEN5_CMD_SET_PARAMETER;
+	parameter_data->parameter_id = parameter_id;
+	parameter_data->parameter_size = parameter_size;
+	put_unaligned_le32((u32)interval_time, &parameter_data->value);
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+	if (error || resp_data[5] != parameter_id ||
+		resp_data[6] != parameter_size ||
+		!VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_SET_PARAMETER))
+		return error < 0 ? error : -EINVAL;
+
+	return 0;
+}
+
+static int cyapa_gen5_get_interval_time(struct cyapa *cyapa,
+		u8 parameter_id, u16 *interval_time)
+{
+	struct gen5_app_cmd_head *app_cmd_head;
+	struct gen5_app_get_parameter_data *parameter_data;
+	u8 cmd[CYAPA_TSG_MAX_CMD_SIZE];
+	int cmd_len;
+	u8 resp_data[11];
+	int resp_len;
+	u8 parameter_size;
+	u16 mask, i;
+	int error;
+
+	memset(cmd, 0, CYAPA_TSG_MAX_CMD_SIZE);
+	app_cmd_head = (struct gen5_app_cmd_head *)cmd;
+	parameter_data = (struct gen5_app_get_parameter_data *)
+			 app_cmd_head->parameter_data;
+	cmd_len = sizeof(struct gen5_app_cmd_head) +
+		  sizeof(struct gen5_app_get_parameter_data);
+
+	*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;
+	}
+
+	put_unaligned_le16(GEN5_HID_DESCRIPTOR_ADDR, &app_cmd_head->addr);
+	/* Don't include 2 bytes register address */
+	put_unaligned_le16(cmd_len - 2, &app_cmd_head->length);
+	app_cmd_head->report_id = GEN5_APP_CMD_REPORT_ID;
+	app_cmd_head->cmd_code = GEN5_CMD_GET_PARAMETER;
+	parameter_data->parameter_id = parameter_id;
+
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+	if (error || resp_data[5] != parameter_id || resp_data[6] == 0 ||
+		!VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_GET_PARAMETER))
+		return error < 0 ? error : -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)
+{
+	struct gen5_app_cmd_head *app_cmd_head;
+	u8 cmd[10];
+	u8 resp_data[7];
+	int resp_len;
+	int error;
+
+	memset(cmd, 0, sizeof(cmd));
+	app_cmd_head = (struct gen5_app_cmd_head *)cmd;
+
+	put_unaligned_le16(GEN5_HID_DESCRIPTOR_ADDR, &app_cmd_head->addr);
+	put_unaligned_le16(sizeof(cmd) - 2, &app_cmd_head->length);
+	app_cmd_head->report_id = GEN5_APP_CMD_REPORT_ID;
+	app_cmd_head->cmd_code = GEN5_CMD_SET_PARAMETER;
+	app_cmd_head->parameter_data[0] = GEN5_PARAMETER_DISABLE_PIP_REPORT;
+	app_cmd_head->parameter_data[1] = 0x01;
+	app_cmd_head->parameter_data[2] = 0x01;
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+	if (error || resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT ||
+		!VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_SET_PARAMETER) ||
+		resp_data[6] != 0x01)
+		return error < 0 ? error : -EINVAL;
+
+	return 0;
+}
+
+static int cyapa_gen5_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+	u8 cmd[] = { 0x05, 0x00, 0x00, 0x08};
+	u8 resp_data[5];
+	int resp_len;
+	int error;
+
+	cmd[2] = state & GEN5_DEEP_SLEEP_STATE_MASK;
+	resp_len = sizeof(resp_data);
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_deep_sleep_data, false);
+	if (error || ((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;
+	u8 power_state;
+	int error;
+
+	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);
+
+	if (GEN5_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) {
+		/*
+		 * Assume TP in deep sleep mode when driver is loaded,
+		 * avoid driver unload and reload command IO issue caused by TP
+		 * has been set into deep sleep mode when unloading.
+		 */
+		GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+	}
+
+	if (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) &&
+			GEN5_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF)
+		if (cyapa_gen5_get_interval_time(cyapa,
+				GEN5_PARAMETER_LP_INTRVL_ID,
+				&cyapa->dev_sleep_time) != 0)
+			GEN5_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME);
+
+	if (GEN5_DEV_GET_PWR_STATE(cyapa) == power_mode) {
+		if (power_mode == PWR_MODE_OFF ||
+			power_mode == PWR_MODE_FULL_ACTIVE ||
+			power_mode == PWR_MODE_BTN_ONLY ||
+			GEN5_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) {
+			/* Has in correct power mode state, early return. */
+			return 0;
+		}
+	}
+
+	if (power_mode == PWR_MODE_OFF) {
+		error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_OFF);
+		if (error) {
+			dev_err(dev, "enter deep sleep fail: %d\n", error);
+			return error;
+		}
+
+		GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+		return 0;
+	}
+
+	/*
+	 * When trackpad in power off mode, it cannot change to other power
+	 * state directly, must be wake up from sleep firstly, then
+	 * continue to do next power sate change.
+	 */
+	if (GEN5_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) {
+		error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+		if (error) {
+			dev_err(dev, "deep sleep wake fail: %d\n", error);
+			return error;
+		}
+	}
+
+	if (power_mode == PWR_MODE_FULL_ACTIVE) {
+		error = cyapa_gen5_change_power_state(cyapa,
+				GEN5_POWER_STATE_ACTIVE);
+		if (error) {
+			dev_err(dev, "change to active fail: %d\n", error);
+			return error;
+		}
+
+		GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE);
+	} else if (power_mode == PWR_MODE_BTN_ONLY) {
+		error = cyapa_gen5_change_power_state(cyapa,
+				GEN5_POWER_STATE_BTN_ONLY);
+		if (error) {
+			dev_err(dev, "fail to button only mode: %d\n", error);
+			return error;
+		}
+
+		GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY);
+	} else {
+		/*
+		 * Continue to change power mode even failed to set
+		 * interval time, it won't affect the power mode change.
+		 * except the sleep interval time is not correct.
+		 */
+		if (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) ||
+				sleep_time != GEN5_DEV_GET_SLEEP_TIME(cyapa))
+			if (cyapa_gen5_set_interval_time(cyapa,
+					GEN5_PARAMETER_LP_INTRVL_ID,
+					sleep_time) == 0)
+				GEN5_DEV_SET_SLEEP_TIME(cyapa, sleep_time);
+
+		if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
+			power_state = GEN5_POWER_STATE_READY;
+		else
+			power_state = GEN5_POWER_STATE_IDLE;
+		error = cyapa_gen5_change_power_state(cyapa, power_state);
+		if (error) {
+			dev_err(dev, "set power state to 0x%02x failed: %d\n",
+				power_state, error);
+			return error;
+		}
+
+		/*
+		 * 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_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+		cyapa_gen5_disable_pip_report(cyapa);
+
+		GEN5_DEV_SET_PWR_STATE(cyapa,
+			cyapa_sleep_time_to_pwr_cmd(sleep_time));
+	}
+
+	return 0;
+}
+
+static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
+		u8 *buf, int len)
+{
+	/* Check the report id and command code */
+	if (VALID_CMD_RESP_HEADER(buf, 0x02))
+		return true;
+
+	return false;
+}
+
+static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
+{
+	u8 bl_query_data_cmd[] = { 0x04, 0x00, 0x0b, 0x00, 0x40, 0x00,
+		0x01, 0x3c, 0x00, 0x00, 0xb0, 0x42, 0x17
+	};
+	u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+	int resp_len;
+	int error;
+
+	resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+	error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+			bl_query_data_cmd, sizeof(bl_query_data_cmd),
+			resp_data, &resp_len,
+			500, cyapa_gen5_sort_tsg_pip_bl_resp_data, false);
+	if (error || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+		!GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+		return error ? error : -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)
+{
+	u8 resp_data[71];
+	int resp_len;
+	u8 get_system_information[] = {
+		0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+	};
+	u16 product_family;
+	int error;
+
+	resp_len = sizeof(resp_data);
+	error = 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, false);
+	if (error || resp_len < sizeof(resp_data))
+		return error ? error : -EIO;
+
+	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 error;
+
+	if (cyapa->gen != CYAPA_GEN5)
+		return -ENODEV;
+
+	switch (cyapa->state) {
+	case CYAPA_STATE_GEN5_BL:
+		error = cyapa_gen5_bl_exit(cyapa);
+		if (error) {
+			/* Rry to update trackpad product information. */
+			cyapa_gen5_bl_query_data(cyapa);
+			goto out;
+		}
+
+		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 try to reset trackpad device to full active when
+		 * the device state is requeried.
+		 */
+		error = cyapa_gen5_set_power_mode(cyapa,
+				PWR_MODE_FULL_ACTIVE, 0);
+		if (error)
+			dev_warn(dev, "%s: failed to set power active mode.\n",
+				__func__);
+
+		/* Get trackpad product information. */
+		error = cyapa_gen5_get_query_data(cyapa);
+		if (error)
+			goto out;
+		/* Only support product ID starting with CYTRA */
+		if (memcmp(cyapa->product_id, product_id,
+				strlen(product_id)) != 0) {
+			dev_err(dev, "%s: unknown product ID (%s)\n",
+				__func__, cyapa->product_id);
+			error = -EINVAL;
+		}
+		break;
+	default:
+		error = -EINVAL;
+	}
+
+out:
+	return error;
+}
+
+/*
+ * Return false, do not continue process
+ * Return true, continue process.
+ */
+static bool cyapa_gen5_irq_cmd_handler(struct cyapa *cyapa)
+{
+	int length;
+	struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+	if (atomic_read(&gen5_pip->cmd_issued)) {
+		/* Polling command response data. */
+		if (gen5_pip->is_irq_mode == false)
+			return false;
+
+		/*
+		 * 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, gen5_pip->irq_cmd_buf,
+			GEN5_RESP_LENGTH_SIZE);
+		length = get_unaligned_le16(gen5_pip->irq_cmd_buf);
+		length = (length <= GEN5_RESP_LENGTH_SIZE) ?
+				GEN5_RESP_LENGTH_SIZE : length;
+		if (length > GEN5_RESP_LENGTH_SIZE)
+			cyapa_i2c_pip_read(cyapa,
+				gen5_pip->irq_cmd_buf, length);
+
+		if (!(gen5_pip->resp_sort_func &&
+			gen5_pip->resp_sort_func(cyapa,
+				gen5_pip->irq_cmd_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 = *gen5_pip->resp_len;
+			cyapa_empty_pip_output_data(cyapa,
+					gen5_pip->resp_data,
+					&length,
+					gen5_pip->resp_sort_func);
+			if (gen5_pip->resp_len && length != 0) {
+				*gen5_pip->resp_len = length;
+				atomic_dec(&gen5_pip->cmd_issued);
+				complete(&gen5_pip->cmd_ready);
+			}
+			return false;
+		}
+
+		if (gen5_pip->resp_data && gen5_pip->resp_len) {
+			*gen5_pip->resp_len = (*gen5_pip->resp_len < length) ?
+				*gen5_pip->resp_len : length;
+			memcpy(gen5_pip->resp_data, gen5_pip->irq_cmd_buf,
+				*gen5_pip->resp_len);
+		}
+		atomic_dec(&gen5_pip->cmd_issued);
+		complete(&gen5_pip->cmd_ready);
+		return false;
+	}
+
+	return true;
+}
+
+static void cyapa_gen5_report_buttons(struct cyapa *cyapa,
+		const struct cyapa_gen5_report_data *report_data)
+{
+	struct input_dev *input = cyapa->input;
+	u8 buttons = report_data->report_head[GEN5_BUTTONS_OFFSET];
+
+	buttons = (buttons << CAPABILITY_BTN_SHIFT) & 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);
+}
+
+static void cyapa_gen5_report_slot_data(struct cyapa *cyapa,
+		const struct cyapa_gen5_touch_record *touch)
+{
+	struct input_dev *input = cyapa->input;
+	u8 event_id = GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+	int slot = GEN5_GET_TOUCH_ID(touch->touch_tip_event_id);
+	int x, y;
+
+	if (event_id == RECORD_EVENT_LIFTOFF)
+		return;
+
+	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);
+}
+
+static void cyapa_gen5_report_touches(struct cyapa *cyapa,
+		const struct cyapa_gen5_report_data *report_data)
+{
+	struct input_dev *input = cyapa->input;
+	unsigned int touch_num;
+	int i;
+
+	touch_num = report_data->report_head[GEN5_NUMBER_OF_TOUCH_OFFSET] &
+			GEN5_NUMBER_OF_TOUCH_MASK;
+
+	for (i = 0; i < touch_num; i++)
+		cyapa_gen5_report_slot_data(cyapa,
+			&report_data->touch_records[i]);
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+	struct device *dev = &cyapa->client->dev;
+	struct cyapa_gen5_report_data report_data;
+	int ret;
+	u8 report_id;
+	unsigned int report_len;
+
+	if (cyapa->gen != CYAPA_GEN5 ||
+		cyapa->state != CYAPA_STATE_GEN5_APP) {
+		dev_err(dev, "invalid device state, gen=%d, state=0x%02x\n",
+			cyapa->gen, cyapa->state);
+		return -EINVAL;
+	}
+
+	ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data,
+			GEN5_RESP_LENGTH_SIZE);
+	if (ret != GEN5_RESP_LENGTH_SIZE) {
+		dev_err(dev, "failed to read length bytes, (%d)\n", ret);
+		return -EINVAL;
+	}
+
+	report_len = get_unaligned_le16(
+			&report_data.report_head[GEN5_RESP_LENGTH_OFFSET]);
+	if (report_len < GEN5_RESP_LENGTH_SIZE) {
+		/* Invliad length or internal reset happened. */
+		dev_err(dev, "invalid report_len=%d. bytes: %02x %02x\n",
+			report_len, report_data.report_head[0],
+			report_data.report_head[1]);
+		return -EINVAL;
+	}
+
+	/* Idle, no data for report. */
+	if (report_len == GEN5_RESP_LENGTH_SIZE)
+		return 0;
+
+	ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len);
+	if (ret != report_len) {
+		dev_err(dev, "failed to read %d bytes report data, (%d)\n",
+			report_len, ret);
+		return -EINVAL;
+	}
+
+	report_id = report_data.report_head[GEN5_RESP_REPORT_ID_OFFSET];
+	if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+			report_len == GEN5_WAKEUP_EVENT_SIZE) {
+		/*
+		 * Device wake event from deep sleep mode for touch.
+		 * This interrupt event is used to wake system up.
+		 */
+		return 0;
+	} 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. */
+		dev_err(dev, "invalid report_id=0x%02x\n", report_id);
+		return -EINVAL;
+	}
+
+	if (report_id == GEN5_TOUCH_REPORT_ID &&
+		(report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+			report_len > GEN5_TOUCH_REPORT_MAX_SIZE)) {
+		/* Invalid report data length for finger packet. */
+		dev_err(dev, "invalid touch packet length=%d\n", report_len);
+		return 0;
+	}
+
+	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)) {
+		/* Invalid report data length of button packet. */
+		dev_err(dev, "invalid button packet length=%d\n", report_len);
+		return 0;
+	}
+
+	if (report_id == GEN5_TOUCH_REPORT_ID)
+		cyapa_gen5_report_touches(cyapa, &report_data);
+	else
+		cyapa_gen5_report_buttons(cyapa, &report_data);
+
+	return 0;
+}
+
+const struct cyapa_dev_ops cyapa_gen5_ops = {
+	.initialize = cyapa_gen5_initialize,
+
+	.state_parse = cyapa_gen5_state_parse,
+	.operational_check = cyapa_gen5_do_operational_check,
+
+	.irq_handler = cyapa_gen5_irq_handler,
+	.irq_cmd_handler = cyapa_gen5_irq_cmd_handler,
+	.sort_empty_output_data = cyapa_empty_pip_output_data,
+	.set_power_mode = cyapa_gen5_set_power_mode,
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH v16 00/12] input: cyapa: instruction of cyapa patches
From: Dudley Du @ 2014-12-17  9:55 UTC (permalink / raw)
  To: dmitry.torokhov, jmmahler, rydberg
  Cc: bleung, David Solda, linux-input, linux-kernel

V16 patches have below updates, details of other updates see history list:
1) Fix all miss-spelling and space issue.
2) Rename variables and functions with much more clearer names.
3) Initialize and document tries near where it will be used.
4) Modify cmd buffer to struct for more descriptive way.


This patch series is aimed to re-design the cyapa driver to support
old gen3 trackpad devices and new gen5 trackpad devices in one
cyapa driver, it's for easily productions support based on
customers' requirements. And add sysfs functions and interfaces
supported that required by users and customers.

Since the earlier gen3 and the latest gen5 trackpad devices using
two different chipsets, and have different protocols and interfaces,
so if supported these two type trackpad devices in two different drivers,
then it will be difficult to manage productions and later firmware updates.
e.g.: It will cause customer don't know which one trackpad device firmware
image to use and update when it has been used and integrated
in same one productions, so here we support these two trackpad
devices in same on driver.

The new design cyapa driver contains:
cyapa.c - the core of the re-design, supply interfaces and
functions to system and read trackpad devices.
cyapa.h - header file including macros and data structure definitions.
cyapa_gen3.c - functions support for gen3 trackpad devices,
cyapa_gen5.c - functions support for gen5 trackpad devices.

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

patch 1/12: re-design cyapa driver with core functions and interface
to support multi-type trackpad devices.

patch 2/12: add gen5 trackpad device basic functions support in the
re-design cyapa driver.

patch 3/12: add power management interfaces support for the device.

patch 4/12: add runtime power management interfaces support for the device.

patch 5/12: add sysfs interfaces supported in the cyapa driver.
Including read firmware version, get production ID, read baseline,
re-calibrate trackpad baselines and do trackpad firmware update.

patch 6/12: add gen3 trackpad device's firmware update function support.

patch 7/12: add gen3 trackpad device's read baseline function support.

patch 8/12: add gen3 trackpad device's force re-calibrate function support.

patch 9/12: add gen5 trackpad device's firmware update function support.

patch 10/12: add gen5 trackpad device's read baseline function support.

patch 11/12: add gen5 trackpad device's force re-calibrate function.

patch 12/12: add acpi device id support.


History patch series modifications list:
V15 patches have below main updates compared with v14 patches:
1) Fix all warning errors of sparse tool when running with "make C=1".
2) Change variable name "unique_str" to "product_id" for clearer meanings.
3) Update cyapa_i2c_write function to return error directly when length > 31.

V14 patches have below main updates compared with v13 patches:
1) Correct 9 miss spelling issues of "bufferred" to "buffered".
2) Fix the upgrade issue of removing MOUSE_CYAPA config when make oldconfig
   by replase "depends on I2C && CRC_ITU_T" with
	"depends on I2C"
	"select CRC_ITU_T"
   in patch 9.

V13 patches have below main updates compared with v12 patches:
1) Remove all debugfs interface, including read_fw and raw_data interfaces.
2) This patches are made based linux next-20141208.

V12 patches have below main updates compared with v11 patches:
1) Add check that when TP is detected but not operational, do not exit driver
   immediately, but wait and export the update_fw interface for recovering.
2) Re-arrange the function codes, remove unnesseary protype definitions in
   the header file.

V11 patches have below main updates compared with v10 patches:
1) Add add acpi device id supported for old gen3 and new gen5 trackpad devices.
2) Fix the unable to update firmware issue when cyapa_open is not called
   which means the irq for firwmare update process is not enabled. This fix
   by checking if the irq is enabled, if not then enable irq before start to
   do firmware update.

V10 patches have below main updates compared with v9 patches:
1) Modify code to following kernel code style.
   e.g.: correct to use error as return name when there is only error path,
   and fix the checkpatch.sh wanting in the driver.
2) Remove cyapa_remove method and use input open and close interface to
   following device resouse management infrastructure.
3) Modify cyapa_detect method to return tristate issue to make the return value
   much more consistent and clear.
4) Use platform supplied functions as possible instead of driver
   specific rewritten version.

V9 patches have below updates compared with v8 patches:
1) Removed all async thread stuff from the driver.
2) Split driver into 18 patches for each function change one patch.

V8 patches have below updates compared with v7 patches:
1) [PATCH v8 01/13] - Remove the async thread for device detect in
   probe routine, now the device detect process is completely done within
   the device probe routine.
2) [PATCH v8 01/13] - Split the irq cmd hander function to separated
   function cyapa_default_irq_cmd_handler() and set it to interface
   cyapa_default_ops.irq_cmd_handler.
3) [PATCH v8 06/13] - Add cyapa->gen check in cyapa_gen3_irq_cmd_handler()
   to avoid miss-enter when device protocol is still in detecting.

V7 patches have below updates compared with v6 patches:
1) [PATCH v7 01/13] - Split the irq cmd hander function to separated
   function cyapa_default_irq_cmd_handler() and set it to interface
   cyapa_default_ops.irq_cmd_handler.
2) [PATCH v7 06/13] - Add cyapa->gen check in cyapa_gen3_irq_cmd_handler()
   to avoid miss-enter when device protocol is still in detecting.


V6 patches have below updates compared with v5 patches:
1) Remove patch 14 of the lid filtering from the cyapa driver.

V5 patches have below updates compared with v4 patches:
1) Uses get_device()/put_device() instead of kobject_get()/kobject_put();
2) Fix memories freed before debugfs entries issue;
3) Make cyapa_debugs_root valid in driver module level
   in module_init()/moudle_exit() ;
4) Fix i2c_transfer() may return partial transfer issues.
5) Add cyapa->removed flag to avoid detecting thread may still running
   when driver module is removed.
6) Fix the meanings of some comments and return error code not clear issue.

^ permalink raw reply

* Re: [PATCH v2] HID: logitech-hidpp: prefix the name with Logitech
From: Jiri Kosina @ 2014-12-17  9:38 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Peter Wu, Nestor Lopez Casado, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <1418337599-10239-1-git-send-email-benjamin.tissoires@redhat.com>

On Thu, 11 Dec 2014, Benjamin Tissoires wrote:

> Current names are reported as "K750", "M705", and it can be misleading
> for the users when they look at their input device list.
> 
> Prefixing the names with "Logitech " makes things better.
> 
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
> 
> Changes in v2:
> - renamed PREFIX_SIZE into PREFIX_LENGTH
> - changed "name_length + PREFIX_LENGTH;" into "PREFIX_LENGTH + name_length;"
> - rebased on Peter's last patch series

Ok, looks reasonable. I was waiting whether someone from Logitech would 
comment on the prefix check that could be used other than "Logitech".

But we can add that later.

Applied to for-3.20/logitech, thanks.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [RFC PATCH] fixp-arith: replace sin/cos table by a better precision one
From: Prashant Laddha (prladdha) @ 2014-12-17  8:17 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Linux Media Mailing List
  Cc: Mauro Carvalho Chehab, Dmitry Torokhov, Hans de Goede,
	linux-input@vger.kernel.org
In-Reply-To: <f437103f284c4ed964bd6577ff0c7793e8299d52.1418743646.git.mchehab@osg.samsung.com>

Thanks for the patch, Mauro.  Just a correction below.

> 
>+/* cos(x) = sin(x + pi radians) */
>+
This should pi / 2. Correcting for the same below.
>+#define fixp_cos32_rad(rad, twopi)	\
>+	fixp_sin32_rad(rad + twopi/2, twopi)
          fixp_sin32_rad(rad + twopi/4, twopi)


>+

I think this patch will serve the need. I will test it for vivid sir tone
generation. I will rework my patches to use sin/cos functions from
fixp-arith.h.
>


^ permalink raw reply

* Re: [PATCH] HID: i2c-hid: Do not free buffers in i2c_hid_stop()
From: Jiri Kosina @ 2014-12-17  8:15 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-input, linux-kernel, Gabriele Mazzotta, Benjamin Tissoires,
	aduggan
In-Reply-To: <1418385709-223977-1-git-send-email-mika.westerberg@linux.intel.com>

On Fri, 12 Dec 2014, Mika Westerberg wrote:

> When a hid driver that uses i2c-hid as transport is unloaded, the hid core
> will call i2c_hid_stop() which releases all the buffers associated with the
> device. This includes also the command buffer.
> 
> Now, when the i2c-hid driver itself is unloaded it tries to power down the
> device by sending it PWR_SLEEP command. Since the command buffer is already
> released we get following crash:
> 
>  [   79.691459] BUG: unable to handle kernel NULL pointer dereference at           (null)
>  [   79.691532] IP: [<ffffffffa05bc049>] __i2c_hid_command+0x49/0x310 [i2c_hid]
>  ...
>  [   79.693467] Call Trace:
>  [   79.693494]  [<ffffffff810424e1>] ? __unmask_ioapic+0x21/0x30
>  [   79.693537]  [<ffffffff81042855>] ? unmask_ioapic+0x25/0x40
>  [   79.693581]  [<ffffffffa05bc35b>] ? i2c_hid_set_power+0x4b/0xa0 [i2c_hid]
>  [   79.693632]  [<ffffffffa05bc3cf>] ? i2c_hid_runtime_resume+0x1f/0x30 [i2c_hid]
>  [   79.693689]  [<ffffffff814c08fb>] ? __rpm_callback+0x2b/0x70
>  [   79.693733]  [<ffffffff814c0961>] ? rpm_callback+0x21/0x90
>  [   79.693776]  [<ffffffff814c0dec>] ? rpm_resume+0x41c/0x600
>  [   79.693820]  [<ffffffff814c1e1c>] ? __pm_runtime_resume+0x4c/0x80
>  [   79.693868]  [<ffffffff814b8588>] ? __device_release_driver+0x28/0x100
>  [   79.693917]  [<ffffffff814b8d90>] ? driver_detach+0xa0/0xb0
>  [   79.693959]  [<ffffffff814b82cc>] ? bus_remove_driver+0x4c/0xb0
>  [   79.694006]  [<ffffffff810d1cfd>] ? SyS_delete_module+0x11d/0x1d0
>  [   79.694054]  [<ffffffff8165f107>] ? int_signal+0x12/0x17
>  [   79.694095]  [<ffffffff8165ee69>] ? system_call_fastpath+0x12/0x17
> 
> Fix this so that we only free buffers when the i2c-hid driver itself is
> removed.

Applied to for-3.19/upstream-fixes, thanks for fixing it.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH v3] HID: rmi: Scan the report descriptor to determine if the device is suitable for the hid-rmi driver
From: Jiri Kosina @ 2014-12-17  8:14 UTC (permalink / raw)
  To: Andrew Duggan; +Cc: linux-input, linux-kernel, Benjamin Tissoires
In-Reply-To: <1418408246-5360-1-git-send-email-aduggan@synaptics.com>

On Fri, 12 Dec 2014, Andrew Duggan wrote:

> On composite HID devices there may be multiple HID devices on separate
> interfaces, but hid-rmi should only bind to the touchpad. The previous version
> simply checked that the interface protocol was set to mouse. Unfortuately, it
> is not always the case that the touchpad has the mouse interface protocol set.
> This patch takes a different approach and scans the report descriptor looking
> for the Generic Desktop Pointer usage and the Vendor Specific Top Level
> Collection needed by the hid-rmi driver to interface with the device.
> 
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
> Here is the v3 version with Benjamin's comments addressed. I also updated it to
> apply to the version of hid-core.c in for-3.19/hid-rmi.

Applied to for-3.20/rmi. Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH 1/2] HID: logitech-hidpp: bail out if wtp_connect fails
From: Jiri Kosina @ 2014-12-17  8:09 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Peter Wu, Nestor Lopez Casado, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <1418767562-4136-1-git-send-email-benjamin.tissoires@redhat.com>

On Tue, 16 Dec 2014, Benjamin Tissoires wrote:

> If wtp_connect() fails, that means most of the time that the device has
> been disconnected. Subsequent attempts to contact the device will fail
> too, so it's simpler to bail out earlier.
> 
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>

I have applied this one to for-3.20/logitech. I am postponing 2/2, 
expecting v2 with an updated comment.

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH] hid: Add battery quirk for USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO keyboard
From: Jiri Kosina @ 2014-12-17  8:03 UTC (permalink / raw)
  To: Karl Relton; +Cc: linux-input
In-Reply-To: <1418744242.3923.1.camel@ntlworld.com>

On Tue, 16 Dec 2014, Karl Relton wrote:

> Apple bluetooth wireless keyboard (sold in UK) has always reported zero
> for battery strength no matter what condition the batteries are actually
> in. With this patch applied (applying same quirk as other Apple
> keyboards), the battery strength is now correctly reported.
> 
> Signed-off-by: Karl Relton <karllinuxtest.relton@ntlworld.com>

Applied to for-3.19/upstream-fixes.

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH 0/2] HID: lenovo: Extra features for compact keyboards
From: Jiri Kosina @ 2014-12-17  8:01 UTC (permalink / raw)
  To: Jamie Lentin; +Cc: linux-input, linux-kernel
In-Reply-To: <1418765206-30885-1-git-send-email-jm@lentin.co.uk>

On Tue, 16 Dec 2014, Jamie Lentin wrote:

> Another set of patches for the Lenovo Compact Keyboards, enabling
> sensitivity control and improving scroll-wheel emulation.
> 
> This applies to 3.18, once previous fixups[0] have been applied. Tested
> with USB and Bluetooth versions of the keyboard.

Applied to for-3.20/lenovo.

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH 3/3] HID: logitech-hidpp: separate HID++ from WTP processing
From: Jiri Kosina @ 2014-12-17  7:55 UTC (permalink / raw)
  To: Peter Wu; +Cc: Benjamin Tissoires, Nestor Lopez Casado, linux-input,
	linux-kernel
In-Reply-To: <1418772231-31330-1-git-send-email-peter@lekensteyn.nl>

On Wed, 17 Dec 2014, Peter Wu wrote:

> Previously wtp_raw_event would be called through
> hidpp_raw_hidpp_event (for the touchpad report) and hidpp_raw_event
> (for the mouse report).
> 
> This patch removes one calling surface, making a clearer distinction
> between "generic HID++ processing" (matching internal reports) and
> device-specific event processing.
> 
> Suggested-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> Signed-off-by: Peter Wu <peter@lekensteyn.nl>
> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
>  v1: patch 2/3 HID: logitech-{dj,hidpp}: check report length
>  v2: splitted original report length check patch. Restructured code.
>  v3: renamed var r to ret for consistency, added R-b of Benjamin

Applied to for-3.20/logitech.

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH v2 2/3] HID: logitech-hidpp: check WTP report length
From: Jiri Kosina @ 2014-12-17  7:53 UTC (permalink / raw)
  To: Peter Wu; +Cc: Benjamin Tissoires, Nestor Lopez Casado, linux-input,
	linux-kernel
In-Reply-To: <1418745323-17133-2-git-send-email-peter@lekensteyn.nl>

On Tue, 16 Dec 2014, Peter Wu wrote:

> Malicious USB devices can send bogus reports smaller than the expected
> buffer size. Ensure that the length for WTP reports is valid to avoid
> reading out of bounds.
> 
> Signed-off-by: Peter Wu <peter@lekensteyn.nl>
> ---
>  v1: patch 2/3 HID: logitech-{dj,hidpp}: check report length
>  v2: splitted original report length check patch

Applied to for-3.19/upstream-fixes.

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH v2 1/3] HID: logitech-dj: check report length
From: Jiri Kosina @ 2014-12-17  7:53 UTC (permalink / raw)
  To: Peter Wu; +Cc: Benjamin Tissoires, Nestor Lopez Casado, linux-input,
	linux-kernel
In-Reply-To: <1418745323-17133-1-git-send-email-peter@lekensteyn.nl>

On Tue, 16 Dec 2014, Peter Wu wrote:

> Malicious USB devices can send bogus reports smaller than the expected
> buffer size. Ensure that the length is valid to avoid reading out of
> bounds.
> 
> Signed-off-by: Peter Wu <peter@lekensteyn.nl>
> ---
>  v1: patch 2/3 HID: logitech-{dj,hidpp}: check report length
>  v2: splitted original report length check patch

Applied to for-3.19/upstream-fixes.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH v15 09/12] input: cyapa: add gen5 trackpad device firmware update function support
From: Jeremiah Mahler @ 2014-12-17  6:48 UTC (permalink / raw)
  To: Benson Leung
  Cc: Dudley Du, Dmitry Torokhov, Henrik Rydberg,
	linux-input@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CANLzEks+hyNGy4_c+pHPW203oZUY=ms-cF3CttkW2NyRmNt8Zw@mail.gmail.com>

all,

On Tue, Dec 16, 2014 at 12:24:37PM -0800, Benson Leung wrote:
> On Tue, Dec 16, 2014 at 5:56 AM, Jeremiah Mahler <jmmahler@gmail.com> wrote:
> > On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
> >> Add firmware image update function supported for gen5 trackpad device,
> >> it can be used through sysfs update_fw interface.
> >> TEST=test on Chromebooks.
> >>
> >> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
> >> ---
> >>  drivers/input/mouse/Kconfig      |   1 +
> >>  drivers/input/mouse/cyapa_gen5.c | 292 ++++++++++++++++++++++++++++++++++++++-
> >>  2 files changed, 292 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> >> index d8b46b0..728490e 100644
> >> --- a/drivers/input/mouse/Kconfig
> >> +++ b/drivers/input/mouse/Kconfig
> >> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
> >>  config MOUSE_CYAPA
> >>       tristate "Cypress APA I2C Trackpad support"
> >>       depends on I2C
> >> +     select CRC_ITU_T
> >>       help
> >
> > Just found out that if I2C_DESIGNWARE_PCI isn't enabled the touchpad
> > won't work.  Verify this on your machines.  Then perhaps add a depends
> > for I2C_DESIGNWARE_PCI instead of I2C since it would include the former.
> 
> This isn't strictly true on all devices, though. This is true on
> DESIGNWARE_PCI based devices like the Acer C720 and the HP Chromebook
> 14, but on other platforms that use Cypress trackpads, such as ARM
> platforms like the Samsung Chromebook Series 3 DESIGNWARE_PCI is not
> required, and will just result in a driver that's never used being
> built.
> 
> The specific I2C bus that's being used here shouldn't matter here...
> that's more of a platform issue. In the case with Chromebooks, it
> might make sense to change drivers/platform/chrome/Kconfig so that
> CHROMEOS_LAPTOP depends on I2C_DESIGNWARE_PCI, maybe.
> 

So DESIGNWARE_PCI isn't required for all devices.  But if MOUSE_CYAPA is
enabled some devices won't work unless I2C_DESIGNWARE_PCI is enabled.  I
wonder if MOUSE_CYAPA should be broken up in to separate working
configurations?  MOUSE_CYAPA_GEN5, MOUSE_CYAPA_GEN3, ?

> -- 
> Benson Leung
> Software Engineer, Chrome OS
> Google Inc.
> bleung@google.com

-- 
- Jeremiah Mahler

^ permalink raw reply

* Re: [PATCH 1/2] HID: logitech-hidpp: bail out if wtp_connect fails
From: Benjamin Tissoires @ 2014-12-17  4:23 UTC (permalink / raw)
  To: Peter Wu
  Cc: Jiri Kosina, Nestor Lopez Casado, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <2965951.04q6IShhJ2@al>

On Dec 17 2014 or thereabouts, Peter Wu wrote:
> Sorry for the rapid mail, I forgot to mention something.
> 
> wtp_connect won't work on non-HID++ devices. What about moving it down,
> between the generic routines (reading protocol and name) and
> hidpp_allocate_input? Then the connected parameter can also be dropped.

No, this will not work. wtp_connect sets the device in the raw report
mode. If we call it after hidpp_allocate_input, this will work on the
first connect. Then, if you switch off/on the device, the connect_event
will be called, but the device will not be set in the raw mode.

We really need to unconditionally call wtp_connect at each
connect_event.

> 
> Kind regards,
> Peter
> 
> On Wednesday 17 December 2014 00:33:55 Peter Wu wrote:
> > On Tuesday 16 December 2014 17:06:01 Benjamin Tissoires wrote:
> > > If wtp_connect() fails, that means most of the time that the device has
> > > been disconnected. Subsequent attempts to contact the device will fail
> > > too, so it's simpler to bail out earlier.
> > > 
> > > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > > ---
> > >  drivers/hid/hid-logitech-hidpp.c | 15 +++++++++------
> > >  1 file changed, 9 insertions(+), 6 deletions(-)
> > > 
> > > diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> > > index d008d71..c0fb5fe 100644
> > > --- a/drivers/hid/hid-logitech-hidpp.c
> > > +++ b/drivers/hid/hid-logitech-hidpp.c
> > > @@ -914,24 +914,24 @@ static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
> > >  	return 0;
> > >  };
> > >  
> > > -static void wtp_connect(struct hid_device *hdev, bool connected)
> > > +static int wtp_connect(struct hid_device *hdev, bool connected)
> > >  {
> > >  	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
> > >  	struct wtp_data *wd = hidpp->private_data;
> > >  	int ret;
> > >  
> > >  	if (!connected)
> > > -		return;
> > > +		return 0;
> > 
> > "0" is success, what about -1 or -ENODEV here to signal an error? The
> > following line (in hidpp_connect_event) returns on !connected, but that
> > is no reason to return 0 here.

0 is fine here. Maybe I over thought the API, but the connect_event is
sent whenever the device is connected or disconnected.
This allows a subdriver to do things on connect and on disconnect. For
instance, you could delete the input node on disconnect. This is not
something we want though, so the disconnect event is just discarded.

But this disconnect event is not an error, it is just a discarded event,
so returning success is fine.

> > 
> > ("No connection" sounds like an error condition to me as "[wtp_]connect"
> > cannot do something meaningful.)
> > 
> > Whether or not you change the return value is up to you. This patch is
> > Reviewed-by: Peter Wu <peter@lekensteyn.nl>

Thanks for the review!

Cheers,
Benjamin

> > 
> > Kind regards,
> > Peter
> > 
> > >  	if (!wd->x_size) {
> > >  		ret = wtp_get_config(hidpp);
> > >  		if (ret) {
> > >  			hid_err(hdev, "Can not get wtp config: %d\n", ret);
> > > -			return;
> > > +			return ret;
> > >  		}
> > >  	}
> > >  
> > > -	hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
> > > +	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
> > >  			true, true);
> > >  }
> > >  
> > > @@ -1115,8 +1115,11 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
> > >  	struct input_dev *input;
> > >  	char *name, *devm_name;
> > >  
> > > -	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
> > > -		wtp_connect(hdev, connected);
> > > +	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
> > > +		ret = wtp_connect(hdev, connected);
> > > +		if (ret)
> > > +			return;
> > > +	}
> > >  
> > >  	if (!connected || hidpp->delayed_input)
> > >  		return;
> > > 
> 

^ permalink raw reply

* [PATCH v7 3/3] ARM: dts: exynos3250-monk: Add regulator-haptic node for haptics
From: Jaewon Kim @ 2014-12-17  3:35 UTC (permalink / raw)
  To: Dmitry Torokhov, Kukjin Kim
  Cc: linux-kernel, linux-samsung-soc, linux-input, Chanwoo Choi,
	Jaewon Kim
In-Reply-To: <1418787308-29019-1-git-send-email-jaewon02.kim@samsung.com>

This patch adds regulator-haptic device node controlled by regulator.

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
---
 arch/arm/boot/dts/exynos3250-monk.dts |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm/boot/dts/exynos3250-monk.dts b/arch/arm/boot/dts/exynos3250-monk.dts
index 24822aa..7102d88 100644
--- a/arch/arm/boot/dts/exynos3250-monk.dts
+++ b/arch/arm/boot/dts/exynos3250-monk.dts
@@ -109,6 +109,13 @@
 			};
 		};
 	};
+
+	haptics {
+		compatible = "regulator-haptic";
+		haptic-supply = <&motor_reg>;
+		min-microvolt = <1100000>;
+		max-microvolt = <2700000>;
+	};
 };
 
 &adc {
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v7 2/3] ARM: dts: exynos3250-rinato: Add regulator-haptic node for haptics
From: Jaewon Kim @ 2014-12-17  3:35 UTC (permalink / raw)
  To: Dmitry Torokhov, Kukjin Kim
  Cc: linux-kernel, linux-samsung-soc, linux-input, Chanwoo Choi,
	Jaewon Kim
In-Reply-To: <1418787308-29019-1-git-send-email-jaewon02.kim@samsung.com>

This patch adds regulator-haptic device node controlled by regulator.

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
---
 arch/arm/boot/dts/exynos3250-rinato.dts |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm/boot/dts/exynos3250-rinato.dts b/arch/arm/boot/dts/exynos3250-rinato.dts
index 80aa8b4..0e3d499 100644
--- a/arch/arm/boot/dts/exynos3250-rinato.dts
+++ b/arch/arm/boot/dts/exynos3250-rinato.dts
@@ -100,6 +100,13 @@
 			};
 		};
 	};
+
+	haptics {
+		compatible = "regulator-haptic";
+		haptic-supply = <&motor_reg>;
+		min-microvolt = <1100000>;
+		max-microvolt = <2700000>;
+	};
 };
 
 &adc {
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v7 1/3] Input: add regulator haptic driver
From: Jaewon Kim @ 2014-12-17  3:35 UTC (permalink / raw)
  To: Dmitry Torokhov, Kukjin Kim
  Cc: linux-kernel, linux-samsung-soc, linux-input, Chanwoo Choi,
	Jaewon Kim, Hyunhee Kim
In-Reply-To: <1418787308-29019-1-git-send-email-jaewon02.kim@samsung.com>

This patch adds support for haptic driver controlled by
voltage of regulator. And this driver support for
Force Feedback interface from input framework

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
Signed-off-by: Hyunhee Kim <hyunhee.kim@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
Tested-by: Chanwoo Choi <cw00.choi@samsung.com>
Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
Reviewed-by: Pankaj Dubey <pankaj.dubey@samsung.com>
---
 .../devicetree/bindings/input/regulator-haptic.txt |   21 ++
 drivers/input/misc/Kconfig                         |   11 +
 drivers/input/misc/Makefile                        |    1 +
 drivers/input/misc/regulator-haptic.c              |  247 ++++++++++++++++++++
 include/linux/platform_data/regulator-haptic.h     |   29 +++
 5 files changed, 309 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
 create mode 100644 drivers/input/misc/regulator-haptic.c
 create mode 100644 include/linux/platform_data/regulator-haptic.h

diff --git a/Documentation/devicetree/bindings/input/regulator-haptic.txt b/Documentation/devicetree/bindings/input/regulator-haptic.txt
new file mode 100644
index 0000000..3ed1c7e
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/regulator-haptic.txt
@@ -0,0 +1,21 @@
+* Regulator Haptic Device Tree Bindings
+
+Required Properties:
+ - compatible : Should be "regulator-haptic"
+ - haptic-supply : Power supply to the haptic motor.
+	[*] refer Documentation/devicetree/bindings/regulator/regulator.txt
+
+ - max-microvolt : The maximum voltage value supplied to the haptic motor.
+		[The unit of the voltage is a micro]
+
+ - min-microvolt : The minimum voltage value supplied to the haptic motor.
+		[The unit of the voltage is a micro]
+
+Example:
+
+	haptics {
+		compatible = "regulator-haptic";
+		haptic-supply = <&motor_regulator>;
+		max-microvolt = <2700000>;
+		min-microvolt = <1100000>;
+	};
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 23297ab..e5e556d 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -394,6 +394,17 @@ config INPUT_CM109
 	  To compile this driver as a module, choose M here: the module will be
 	  called cm109.
 
+config INPUT_REGULATOR_HAPTIC
+	tristate "regulator haptics support"
+	select INPUT_FF_MEMLESS
+	help
+	  This option enables device driver support for the haptic controlled
+	  by regulator. This driver supports ff-memless interface
+	  from input framework.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called regulator-haptic.
+
 config INPUT_RETU_PWRBUTTON
 	tristate "Retu Power button Driver"
 	depends on MFD_RETU
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 19c7603..1f135af 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY)	+= pmic8xxx-pwrkey.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_PWM_BEEPER)		+= pwm-beeper.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
+obj-$(CONFIG_INPUT_REGULATOR_HAPTIC)	+= regulator-haptic.o
 obj-$(CONFIG_INPUT_RETU_PWRBUTTON)	+= retu-pwrbutton.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)	+= rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)		+= sgi_btns.o
diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
new file mode 100644
index 0000000..16f5ec8
--- /dev/null
+++ b/drivers/input/misc/regulator-haptic.c
@@ -0,0 +1,247 @@
+/*
+ * Regulator haptic driver
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jaewon Kim <jaewon02.kim@samsung.com>
+ * Author: Hyunhee Kim <hyunhee.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/regulator-haptic.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define MAX_MAGNITUDE_SHIFT	16
+
+struct regulator_haptic {
+	struct device *dev;
+	struct input_dev *input_dev;
+	struct regulator *regulator;
+
+	struct work_struct work;
+	struct mutex mutex;
+
+	bool enabled;
+	bool suspend_state;
+	unsigned int max_volt;
+	unsigned int min_volt;
+	unsigned int intensity;
+	unsigned int magnitude;
+};
+
+static void regulator_haptic_enable(struct regulator_haptic *haptic, bool state)
+{
+	int error;
+
+	if (haptic->enabled == state)
+		return;
+
+	error = state ? regulator_enable(haptic->regulator) :
+		regulator_disable(haptic->regulator);
+	if (error) {
+		dev_err(haptic->dev, "cannot enable regulator\n");
+		return;
+	}
+
+	haptic->enabled = state;
+}
+
+static void regulator_haptic_work(struct work_struct *work)
+{
+	struct regulator_haptic *haptic = container_of(work,
+					struct regulator_haptic, work);
+	int error;
+
+	mutex_lock(&haptic->mutex);
+
+	if (haptic->suspend_state)
+		goto err;
+
+	error = regulator_set_voltage(haptic->regulator,
+			haptic->intensity + haptic->min_volt, haptic->max_volt);
+	if (error) {
+		dev_err(haptic->dev, "cannot set regulator voltage\n");
+		goto err;
+	}
+
+	if (haptic->magnitude)
+		regulator_haptic_enable(haptic, true);
+	else
+		regulator_haptic_enable(haptic, false);
+
+err:
+	mutex_unlock(&haptic->mutex);
+}
+
+static int regulator_haptic_play_effect(struct input_dev *input, void *data,
+					struct ff_effect *effect)
+{
+	struct regulator_haptic *haptic = input_get_drvdata(input);
+	u64 volt_mag_multi;
+
+	haptic->magnitude = effect->u.rumble.strong_magnitude;
+	if (!haptic->magnitude)
+		haptic->magnitude = effect->u.rumble.weak_magnitude;
+
+
+	volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) *
+					haptic->magnitude;
+	haptic->intensity = (unsigned int)(volt_mag_multi >>
+					MAX_MAGNITUDE_SHIFT);
+
+	schedule_work(&haptic->work);
+
+	return 0;
+}
+
+static void regulator_haptic_close(struct input_dev *input)
+{
+	struct regulator_haptic *haptic = input_get_drvdata(input);
+
+	cancel_work_sync(&haptic->work);
+	regulator_haptic_enable(haptic, false);
+}
+
+static int regulator_haptic_get_data(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct regulator_haptic_data *data = dev_get_platdata(&pdev->dev);
+	struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+	int error;
+
+	if (data) {
+		haptic->max_volt = data->max_volt;
+		haptic->min_volt = data->min_volt;
+	} else if (pdev->dev.of_node) {
+		error = of_property_read_u32(node, "max-microvolt",
+				&haptic->max_volt);
+		if (error) {
+			dev_err(&pdev->dev, "cannot parse max-microvolt\n");
+			return error;
+		}
+
+		error = of_property_read_u32(node, "min-microvolt",
+				&haptic->min_volt);
+		if (error) {
+			dev_err(&pdev->dev, "cannot parse min-microvolt\n");
+			return error;
+		}
+	} else {
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int regulator_haptic_probe(struct platform_device *pdev)
+{
+	struct regulator_haptic *haptic;
+	struct input_dev *input_dev;
+	int error;
+
+	haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
+	if (!haptic)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, haptic);
+	haptic->dev = &pdev->dev;
+	haptic->enabled = false;
+	haptic->suspend_state = false;
+	mutex_init(&haptic->mutex);
+	INIT_WORK(&haptic->work, regulator_haptic_work);
+
+	error = regulator_haptic_get_data(pdev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to get voltage value\n");
+		return error;
+	}
+
+	haptic->regulator = devm_regulator_get_exclusive(&pdev->dev, "haptic");
+	if (IS_ERR(haptic->regulator)) {
+		dev_err(&pdev->dev, "failed to get regulator\n");
+		return PTR_ERR(haptic->regulator);
+	}
+
+	input_dev = devm_input_allocate_device(&pdev->dev);
+	if (!input_dev)
+		return	-ENOMEM;
+
+	haptic->input_dev = input_dev;
+	haptic->input_dev->name = "regulator-haptic";
+	haptic->input_dev->dev.parent = &pdev->dev;
+	haptic->input_dev->close = regulator_haptic_close;
+	input_set_drvdata(haptic->input_dev, haptic);
+	input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
+
+	error = input_ff_create_memless(input_dev, NULL,
+			      regulator_haptic_play_effect);
+	if (error) {
+		dev_err(&pdev->dev, "failed to create force-feedback\n");
+		return error;
+	}
+
+	error = input_register_device(haptic->input_dev);
+	if (error) {
+		dev_err(&pdev->dev, "failed to register input device\n");
+		return error;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused regulator_haptic_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+
+	mutex_lock(&haptic->mutex);
+
+	haptic->suspend_state = true;
+
+	if (haptic->enabled)
+		regulator_haptic_enable(haptic, false);
+
+	mutex_unlock(&haptic->mutex);
+
+	return 0;
+}
+
+static int __maybe_unused regulator_haptic_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+
+	haptic->suspend_state = false;
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(regulator_haptic_pm_ops,
+		regulator_haptic_suspend, regulator_haptic_resume);
+
+static struct of_device_id regulator_haptic_dt_match[] = {
+	{ .compatible = "regulator-haptic" },
+	{ /* sentinel */ },
+};
+
+static struct platform_driver regulator_haptic_driver = {
+	.probe		= regulator_haptic_probe,
+	.driver		= {
+		.name		= "regulator-haptic",
+		.of_match_table = regulator_haptic_dt_match,
+		.pm		= &regulator_haptic_pm_ops,
+	},
+};
+module_platform_driver(regulator_haptic_driver);
+
+MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
+MODULE_AUTHOR("Hyunhee Kim <hyunhee.kim@samsung.com>");
+MODULE_DESCRIPTION("Regulator haptic driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/regulator-haptic.h b/include/linux/platform_data/regulator-haptic.h
new file mode 100644
index 0000000..5658e58
--- /dev/null
+++ b/include/linux/platform_data/regulator-haptic.h
@@ -0,0 +1,29 @@
+/*
+ * Regulator Haptic Platform Data
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jaewon Kim <jaewon02.kim@samsung.com>
+ * Author: Hyunhee Kim <hyunhee.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _REGULATOR_HAPTIC_H
+#define _REGULATOR_HAPTIC_H
+
+/*
+ * struct regulator_haptic_data - Platform device data
+ *
+ * @max_volt: maximum voltage value supplied to the haptic motor.
+ *		<The unit of the voltage is a micro>
+ * @min_volt: minimum voltage value supplied to the haptic motor.
+ *		<The unit of the voltage is a micro>
+ */
+struct regulator_haptic_data {
+	unsigned int max_volt;
+	unsigned int min_volt;
+};
+
+#endif /* _REGULATOR_HAPTIC_H */
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v7 0/3] Add regulator-haptic driver
From: Jaewon Kim @ 2014-12-17  3:35 UTC (permalink / raw)
  To: Dmitry Torokhov, Kukjin Kim
  Cc: linux-kernel, linux-samsung-soc, linux-input, Chanwoo Choi,
	Jaewon Kim

This patch series adds regulator-haptic driver.
The regulator-haptic has haptic motor and it is controlled by
voltage of regulator via force feedback framework.

Changes in v7:
 - move platform_data or of_node check.
 - prevent to start playing effect when kernel entering suspend state.

Changes in v6:
 - prevent racing condition

Changes in v5:
 - give preference to platform data

Changes in v4:
 - _regulator_get() -> _regulator_get_exclusive()

Changes in v3:
 - fix typo in Documentation
 - add define in header file

Changes in v2:
 - remove driver owner
 - merge enable/disable function
 - support platform data
 - fix wrong suspends_state check in regulator_haptic_resume()

Jaewon Kim (3):
  Input: add regulator haptic driver
  ARM: dts: exynos3250-rinato: Add regulator-haptic node for haptics
  ARM: dts: exynos3250-monk: Add regulator-haptic node for haptics

 .../devicetree/bindings/input/regulator-haptic.txt |   21 ++
 arch/arm/boot/dts/exynos3250-monk.dts              |    7 +
 arch/arm/boot/dts/exynos3250-rinato.dts            |    7 +
 drivers/input/misc/Kconfig                         |   11 +
 drivers/input/misc/Makefile                        |    1 +
 drivers/input/misc/regulator-haptic.c              |  247 ++++++++++++++++++++
 include/linux/platform_data/regulator-haptic.h     |   29 +++
 7 files changed, 323 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/regulator-haptic.txt
 create mode 100644 drivers/input/misc/regulator-haptic.c
 create mode 100644 include/linux/platform_data/regulator-haptic.h

-- 
1.7.9.5

^ permalink raw reply

* Re: [PATCH 1/2] HID: logitech-hidpp: bail out if wtp_connect fails
From: Benjamin Tissoires @ 2014-12-17  2:53 UTC (permalink / raw)
  To: Peter Wu
  Cc: Benjamin Tissoires, Jiri Kosina, Nestor Lopez Casado, linux-input,
	linux-kernel@vger.kernel.org
In-Reply-To: <1754756.J50LHtmV20@al>

On Dec 17 2014 or thereabouts, Peter Wu wrote:
> Hi Benjamin,
> 
> On Tuesday 16 December 2014 17:13:05 Benjamin Tissoires wrote:
> > the logitech patches are queuing up really fast.
> > To keep track of them, I made a bundle on patchwork:
> > https://patchwork.kernel.org/bundle/bentiss/hid-logitech-hidpp/
> > (/me discovered a new tool to play with)
> > 
> > Right now, the patch "HID: logitech-hidpp: detect HID++ 2.0 errors
> > too" is waiting for Logitech's approval, and the 2 of this series need
> > review.
> > 
> > Peter, please tell me if I missed one patch.
>  
> Nice, it would be even better if a bundle could be bookmarked, or if a
> group could set review flags in this bundle :-)
> 
> There are no missing patches from my side. All changes (based on
> jikos/hid, branch for-next) are at
> https://git.lekensteyn.nl/peter/linux/log/?h=logitech-hidpp
> and are tested in QEMU with an emulated device and a real device (with
> T400/T650/M525 paired).

Thanks. The only problem with publishing these kind of tree is that at
some point you will want to rebase it, and this will break people who
pulled your tree. I found Jiri's name scheme really good (with a tag for
the current version). This allows to push several branch based on
different revisions without breaking the others.
But I am a little bit digressing here :)

> 
> I noticed that all devices would immediately get an input device (even
> if they were off), except for the T650. This apparently happens because
> the touchpad configuration cannot be retrieved or when the touchpad
> cannot be put in raw reporting mode. I cannot think of something to
> "fix" this though.

That's the design, unfortunately.

Ideally, I would have prefer having a consistant way of setting up
devices: when the receiver is plugged, create the input nodes and done.

Unfortunately, this does not apply to touchpads and mice in raw mode as
we need to query the devices for their capabilities and axis ranges.
We then need to deffer the creation upon the connection.

Unfortunately, we can not do the same for the normal DJ devices. If you
do so, you will lose the very first input reports while the device is
set up, and while the userspace is ready to read from it.
This is *really* problematic for keyboards, especially when you use it
to enter your computer encryption password. You lose the first few
chars, and the password fails, and it's a mess.

So in the end, I came up with this hybrid solution. For a few selected
and tested devices, we deffer the input creation. For the rest of the
world, we try to create them at the earliest in order not losing events.

To sum up, this is really unfortunate :)

Cheers,
Benjamin


> -- 
> Kind regards,
> Peter
> https://lekensteyn.nl
> 

^ permalink raw reply

* Re: [PATCH 2/2] HID: logitech-hidpp: store the name of the device in struct hidpp
From: Benjamin Tissoires @ 2014-12-17  2:43 UTC (permalink / raw)
  To: Peter Wu
  Cc: Jiri Kosina, Nestor Lopez Casado, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <3321911.knGKb1BsCe@al>

On Dec 17 2014 or thereabouts, Peter Wu wrote:
> On Tuesday 16 December 2014 17:06:02 Benjamin Tissoires wrote:
> > If a disconnect occurs while getting the actual name of the device
> > (which can take several HID transactions), the name of the device will
> > be the hid name, provided by the Unifying Receiver.
> > This means that in some cases, the user space will see a different
> > name that what it usually sees when there is no disconnect.
> > 
> > We should store the name of the device in the struct hidpp. That way,
> > if a disconnect occurs while we are accessing the name,
> > hidpp_connect_event() can fail, and the input node is not created.
> > 
> > The input node will be created only if we have a connection which
> > lasts long enough to retrieve all the requested information:
> > name, protocol, and specific configuration.
> > 
> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > ---
> >  drivers/hid/hid-logitech-hidpp.c | 31 ++++++++++++++++++++-----------
> >  1 file changed, 20 insertions(+), 11 deletions(-)
> > 
> > diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> > index c0fb5fe..4bc8714 100644
> > --- a/drivers/hid/hid-logitech-hidpp.c
> > +++ b/drivers/hid/hid-logitech-hidpp.c
> > @@ -89,6 +89,7 @@ struct hidpp_device {
> >  	struct hid_device *hid_dev;
> >  	struct mutex send_mutex;
> >  	void *send_receive_buf;
> > +	char *name;
> >  	wait_queue_head_t wait;
> >  	bool answer_available;
> >  	u8 protocol_major;
> > @@ -1087,6 +1088,7 @@ static void hidpp_input_close(struct input_dev *dev)
> >  static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
> >  {
> >  	struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
> > +	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
> >  
> >  	if (!input_dev)
> >  		return NULL;
> > @@ -1095,7 +1097,7 @@ static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
> >  	input_dev->open = hidpp_input_open;
> >  	input_dev->close = hidpp_input_close;
> >  
> > -	input_dev->name = hdev->name;
> > +	input_dev->name = hidpp->name;
> >  	input_dev->phys = hdev->phys;
> >  	input_dev->uniq = hdev->uniq;
> >  	input_dev->id.bustype = hdev->bus;
> > @@ -1137,22 +1139,28 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
> >  	hid_info(hdev, "HID++ %u.%u device connected.\n",
> >  		 hidpp->protocol_major, hidpp->protocol_minor);
> >  
> > +	if (!hidpp->name || hidpp->name == hdev->name) {
> 
> Hm, is it ever possible that hidpp->name is NULL? probe sets the pointer

No, hidpp->name should never be a NULL reference.
I asked myself about that (i.e. having a NULL reference until the hidpp
calls get_name), but I thought that having a non consistent name would
just confuse other contributors when implementing other devices.
So I choose to have always the current name of the device.

> to an array (hdev->name). Defence in depth I guess, but perhaps a
> comment could clarify this?

That could clarify it, yes. Will send a v2.

> 
> Otherwise the changes look OK. I have tested this situation:
> 
>  1. Insert receiver
>  2. Return a HID++ version for the WTP.
>  3. Return -9 (ResourceError) for the device name feature request (via
>     the QEMU emulation).
>  4. Observe that this fails.

Hehe, I have been testing this by timely putting the device in the on
then off state. About 1 sec of ON is enough to trigger the various
failures :)

> 
> So maybe you could add a comment for the above and add these tags:
> 
> Reviewed-by: Peter Wu <peter@lekensteyn.nl>
> Tested-by: Peter Wu <peter@lekensteyn.nl>

Thanks! (and thanks for the other v3)

Cheers,
Benjamin

> 
> Kind regards,
> Peter
> 
> > +		name = hidpp_get_device_name(hidpp);
> > +		if (!name) {
> > +			hid_err(hdev,
> > +				"unable to retrieve the name of the device");
> > +			return;
> > +		}
> > +
> > +		devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
> > +		kfree(name);
> > +		if (!devm_name)
> > +			return;
> > +
> > +		hidpp->name = devm_name;
> > +	}
> > +
> >  	input = hidpp_allocate_input(hdev);
> >  	if (!input) {
> >  		hid_err(hdev, "cannot allocate new input device: %d\n", ret);
> >  		return;
> >  	}
> >  
> > -	name = hidpp_get_device_name(hidpp);
> > -	if (!name) {
> > -		hid_err(hdev, "unable to retrieve the name of the device");
> > -	} else {
> > -		devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
> > -		if (devm_name)
> > -			input->name = devm_name;
> > -		kfree(name);
> > -	}
> > -
> >  	hidpp_populate_input(hidpp, input, false);
> >  
> >  	ret = input_register_device(input);
> > @@ -1175,6 +1183,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >  		return -ENOMEM;
> >  
> >  	hidpp->hid_dev = hdev;
> > +	hidpp->name = hdev->name;
> >  	hid_set_drvdata(hdev, hidpp);
> >  
> >  	hidpp->quirks = id->driver_data;
> > 
> 

^ permalink raw reply

* RE: [PATCH v15 09/12] input: cyapa: add gen5 trackpad device firmware update function support
From: Dudley Du @ 2014-12-17  1:31 UTC (permalink / raw)
  To: Benson Leung, Jeremiah Mahler, Dudley Du, Dmitry Torokhov,
	Henrik Rydberg, linux-input@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <CANLzEks+hyNGy4_c+pHPW203oZUY=ms-cF3CttkW2NyRmNt8Zw@mail.gmail.com>



> -----Original Message-----
> From: linux-input-owner@vger.kernel.org
> [mailto:linux-input-owner@vger.kernel.org] On Behalf Of Benson Leung
> Sent: 2014年12月17日 4:25
> To: Jeremiah Mahler; Dudley Du; Dmitry Torokhov; Henrik Rydberg; Benson Leung;
> linux-input@vger.kernel.org; linux-kernel@vger.kernel.org
> Subject: Re: [PATCH v15 09/12] input: cyapa: add gen5 trackpad device firmware
> update function support
>
> On Tue, Dec 16, 2014 at 5:56 AM, Jeremiah Mahler <jmmahler@gmail.com> wrote:
> > On Mon, Dec 15, 2014 at 02:23:20PM +0800, Dudley Du wrote:
> >> Add firmware image update function supported for gen5 trackpad device,
> >> it can be used through sysfs update_fw interface.
> >> TEST=test on Chromebooks.
> >>
> >> Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
> >> ---
> >>  drivers/input/mouse/Kconfig      |   1 +
> >>  drivers/input/mouse/cyapa_gen5.c | 292
> ++++++++++++++++++++++++++++++++++++++-
> >>  2 files changed, 292 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> >> index d8b46b0..728490e 100644
> >> --- a/drivers/input/mouse/Kconfig
> >> +++ b/drivers/input/mouse/Kconfig
> >> @@ -206,6 +206,7 @@ config MOUSE_BCM5974
> >>  config MOUSE_CYAPA
> >>       tristate "Cypress APA I2C Trackpad support"
> >>       depends on I2C
> >> +     select CRC_ITU_T
> >>       help
> >
> > Just found out that if I2C_DESIGNWARE_PCI isn't enabled the touchpad
> > won't work.  Verify this on your machines.  Then perhaps add a depends
> > for I2C_DESIGNWARE_PCI instead of I2C since it would include the former.
>
> This isn't strictly true on all devices, though. This is true on
> DESIGNWARE_PCI based devices like the Acer C720 and the HP Chromebook
> 14, but on other platforms that use Cypress trackpads, such as ARM
> platforms like the Samsung Chromebook Series 3 DESIGNWARE_PCI is not
> required, and will just result in a driver that's never used being
> built.
>
> The specific I2C bus that's being used here shouldn't matter here...
> that's more of a platform issue. In the case with Chromebooks, it
> might make sense to change drivers/platform/chrome/Kconfig so that
> CHROMEOS_LAPTOP depends on I2C_DESIGNWARE_PCI, maybe.
>

Thank you all for the information.
Then I won't change this depending on I2C.

> --
> Benson Leung
> Software Engineer, Chrome OS
> Google Inc.
> bleung@google.com
> --
> 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

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.

^ permalink raw reply

* Re: [PATCH 1/2] HID: logitech-hidpp: bail out if wtp_connect fails
From: Peter Wu @ 2014-12-17  1:32 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Nestor Lopez Casado, Peter Hutterer, linux-input,
	linux-kernel
In-Reply-To: <1771836.oYfhfEolNx@al>

Sorry for the rapid mail, I forgot to mention something.

wtp_connect won't work on non-HID++ devices. What about moving it down,
between the generic routines (reading protocol and name) and
hidpp_allocate_input? Then the connected parameter can also be dropped.

Kind regards,
Peter

On Wednesday 17 December 2014 00:33:55 Peter Wu wrote:
> On Tuesday 16 December 2014 17:06:01 Benjamin Tissoires wrote:
> > If wtp_connect() fails, that means most of the time that the device has
> > been disconnected. Subsequent attempts to contact the device will fail
> > too, so it's simpler to bail out earlier.
> > 
> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > ---
> >  drivers/hid/hid-logitech-hidpp.c | 15 +++++++++------
> >  1 file changed, 9 insertions(+), 6 deletions(-)
> > 
> > diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> > index d008d71..c0fb5fe 100644
> > --- a/drivers/hid/hid-logitech-hidpp.c
> > +++ b/drivers/hid/hid-logitech-hidpp.c
> > @@ -914,24 +914,24 @@ static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
> >  	return 0;
> >  };
> >  
> > -static void wtp_connect(struct hid_device *hdev, bool connected)
> > +static int wtp_connect(struct hid_device *hdev, bool connected)
> >  {
> >  	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
> >  	struct wtp_data *wd = hidpp->private_data;
> >  	int ret;
> >  
> >  	if (!connected)
> > -		return;
> > +		return 0;
> 
> "0" is success, what about -1 or -ENODEV here to signal an error? The
> following line (in hidpp_connect_event) returns on !connected, but that
> is no reason to return 0 here.
> 
> ("No connection" sounds like an error condition to me as "[wtp_]connect"
> cannot do something meaningful.)
> 
> Whether or not you change the return value is up to you. This patch is
> Reviewed-by: Peter Wu <peter@lekensteyn.nl>
> 
> Kind regards,
> Peter
> 
> >  	if (!wd->x_size) {
> >  		ret = wtp_get_config(hidpp);
> >  		if (ret) {
> >  			hid_err(hdev, "Can not get wtp config: %d\n", ret);
> > -			return;
> > +			return ret;
> >  		}
> >  	}
> >  
> > -	hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
> > +	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
> >  			true, true);
> >  }
> >  
> > @@ -1115,8 +1115,11 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
> >  	struct input_dev *input;
> >  	char *name, *devm_name;
> >  
> > -	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
> > -		wtp_connect(hdev, connected);
> > +	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
> > +		ret = wtp_connect(hdev, connected);
> > +		if (ret)
> > +			return;
> > +	}
> >  
> >  	if (!connected || hidpp->delayed_input)
> >  		return;
> > 


^ 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