Skip to content
Permalink
Browse files

linux: Fix Synaptics touchpad on Lenovo s21e

Pick Andrew Duggan's patches to fix Synaptics touchpad on Lenovo s21e

[PATCH 1/3] HID: rmi: Disable scanning if the device is not a wake
[PATCH 2/3] HID: rmi: Write updated F11 control registers after reset
[PATCH 3/3] HID: rmi: Set F01 interrupt enable register when not set

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=91102
Upstream: Picked from https://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid.git/refs/heads/for-4.3/rmi
  • Loading branch information...
harisokanovic committed Sep 13, 2015
1 parent 31f0055 commit f4550c211ca7809ecf926f8074c7b7250a74bd92
@@ -0,0 +1,118 @@
From b61df239885feca89db21b5326e00780e947faed Mon Sep 17 00:00:00 2001
From: Andrew Duggan <aduggan@synaptics.com>
Date: Mon, 6 Jul 2015 16:48:31 -0700
Subject: [PATCH 1/3] HID: rmi: Disable scanning if the device is not a wake
source

Some touchpads are configured with firmware which continues to scan for
fingers at a minimal scan rate even after receiving the HID power sleep
command. This allows a finger touching the touchpad to genrate a wake
event. This patch ensures that scanning is disabled if the touchpad is
not a wake source and ensures scanning is enabled on resume.

Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-rmi.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 368ffdf2c0a3af086d9242f00476f598a3fffdf9..c307dda4737d7f3516eaa7a4a1eb52ae88aace7b 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -33,6 +33,9 @@
#define RMI_READ_DATA_PENDING BIT(1)
#define RMI_STARTED BIT(2)

+#define RMI_SLEEP_NORMAL 0x0
+#define RMI_SLEEP_DEEP_SLEEP 0x1
+
/* device flags */
#define RMI_DEVICE BIT(0)
#define RMI_DEVICE_HAS_PHYS_BUTTONS BIT(1)
@@ -126,6 +129,8 @@ struct rmi_data {

unsigned long device_flags;
unsigned long firmware_id;
+
+ u8 f01_ctrl0;
};

#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
@@ -532,9 +537,51 @@ static int rmi_event(struct hid_device *hdev, struct hid_field *field,
}

#ifdef CONFIG_PM
+static int rmi_set_sleep_mode(struct hid_device *hdev, int sleep_mode)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+ u8 f01_ctrl0;
+
+ f01_ctrl0 = (data->f01_ctrl0 & ~0x3) | sleep_mode;
+
+ ret = rmi_write(hdev, data->f01.control_base_addr,
+ &f01_ctrl0);
+ if (ret) {
+ hid_err(hdev, "can not write sleep mode\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rmi_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ if (!device_may_wakeup(hdev->dev.parent))
+ return rmi_set_sleep_mode(hdev, RMI_SLEEP_DEEP_SLEEP);
+
+ return 0;
+}
+
static int rmi_post_reset(struct hid_device *hdev)
{
- return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ int ret;
+
+ ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ if (ret) {
+ hid_err(hdev, "can not set rmi mode\n");
+ return ret;
+ }
+
+ if (!device_may_wakeup(hdev->dev.parent)) {
+ ret = rmi_set_sleep_mode(hdev, RMI_SLEEP_NORMAL);
+ if (ret) {
+ hid_err(hdev, "can not write sleep mode\n");
+ return ret;
+ }
+ }
+
+ return ret;
}

static int rmi_post_resume(struct hid_device *hdev)
@@ -732,6 +779,12 @@ static int rmi_populate_f01(struct hid_device *hdev)
data->firmware_id += info[2] * 65536;
}

+ ret = rmi_read(hdev, data->f01.control_base_addr, &data->f01_ctrl0);
+
+ if (ret) {
+ hid_err(hdev, "can not read f01 ctrl0\n");
+ return ret;
+ }
return 0;
}

@@ -1270,6 +1323,7 @@ static struct hid_driver rmi_driver = {
.input_mapping = rmi_input_mapping,
.input_configured = rmi_input_configured,
#ifdef CONFIG_PM
+ .suspend = rmi_suspend,
.resume = rmi_post_resume,
.reset_resume = rmi_post_reset,
#endif
--
1.9.1

@@ -0,0 +1,136 @@
From 5e3f8fc71de1e12652727e9e68fe4c79662f0fdd Mon Sep 17 00:00:00 2001
From: Andrew Duggan <aduggan@synaptics.com>
Date: Fri, 10 Jul 2015 12:48:21 -0700
Subject: [PATCH 2/3] HID: rmi: Write updated F11 control registers after reset

When a device is reset the values of control registers will be reset to
the defaults. This patch reapplies the control register values set for F11
by the driver.

Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Tested-by: Gabriele Mazzotta <gabriele.mzt@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-rmi.c | 56 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 43 insertions(+), 13 deletions(-)

diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index c307dda4737d7f3516eaa7a4a1eb52ae88aace7b..adbc2aa3467cfc684bdc77444e2edc6d73720b7d 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -40,6 +40,14 @@
#define RMI_DEVICE BIT(0)
#define RMI_DEVICE_HAS_PHYS_BUTTONS BIT(1)

+/*
+ * retrieve the ctrl registers
+ * the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
+ * and there is no way to know if the first 20 bytes are here or not.
+ * We use only the first 12 bytes, so get only them.
+ */
+#define RMI_F11_CTRL_REG_COUNT 12
+
enum rmi_mode_type {
RMI_MODE_OFF = 0,
RMI_MODE_ATTN_REPORTS = 1,
@@ -116,6 +124,8 @@ struct rmi_data {
unsigned int max_y;
unsigned int x_size_mm;
unsigned int y_size_mm;
+ bool read_f11_ctrl_regs;
+ u8 f11_ctrl_regs[RMI_F11_CTRL_REG_COUNT];

unsigned int gpio_led_count;
unsigned int button_count;
@@ -557,6 +567,18 @@ static int rmi_set_sleep_mode(struct hid_device *hdev, int sleep_mode)

static int rmi_suspend(struct hid_device *hdev, pm_message_t message)
{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+ u8 buf[RMI_F11_CTRL_REG_COUNT];
+
+ ret = rmi_read_block(hdev, data->f11.control_base_addr, buf,
+ RMI_F11_CTRL_REG_COUNT);
+ if (ret)
+ hid_warn(hdev, "can not read F11 control registers\n");
+ else
+ memcpy(data->f11_ctrl_regs, buf, RMI_F11_CTRL_REG_COUNT);
+
+
if (!device_may_wakeup(hdev->dev.parent))
return rmi_set_sleep_mode(hdev, RMI_SLEEP_DEEP_SLEEP);

@@ -565,6 +587,7 @@ static int rmi_suspend(struct hid_device *hdev, pm_message_t message)

static int rmi_post_reset(struct hid_device *hdev)
{
+ struct rmi_data *data = hid_get_drvdata(hdev);
int ret;

ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
@@ -573,6 +596,14 @@ static int rmi_post_reset(struct hid_device *hdev)
return ret;
}

+ if (data->read_f11_ctrl_regs) {
+ ret = rmi_write_block(hdev, data->f11.control_base_addr,
+ data->f11_ctrl_regs, RMI_F11_CTRL_REG_COUNT);
+ if (ret)
+ hid_warn(hdev,
+ "can not write F11 control registers after reset\n");
+ }
+
if (!device_may_wakeup(hdev->dev.parent)) {
ret = rmi_set_sleep_mode(hdev, RMI_SLEEP_NORMAL);
if (ret) {
@@ -957,24 +988,23 @@ static int rmi_populate_f11(struct hid_device *hdev)
if (has_data40)
data->f11.report_size += data->max_fingers * 2;

- /*
- * retrieve the ctrl registers
- * the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
- * and there is no way to know if the first 20 bytes are here or not.
- * We use only the first 12 bytes, so get only them.
- */
- ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 12);
+ ret = rmi_read_block(hdev, data->f11.control_base_addr,
+ data->f11_ctrl_regs, RMI_F11_CTRL_REG_COUNT);
if (ret) {
hid_err(hdev, "can not read ctrl block of size 11: %d.\n", ret);
return ret;
}

- data->max_x = buf[6] | (buf[7] << 8);
- data->max_y = buf[8] | (buf[9] << 8);
+ /* data->f11_ctrl_regs now contains valid register data */
+ data->read_f11_ctrl_regs = true;
+
+ data->max_x = data->f11_ctrl_regs[6] | (data->f11_ctrl_regs[7] << 8);
+ data->max_y = data->f11_ctrl_regs[8] | (data->f11_ctrl_regs[9] << 8);

if (has_dribble) {
- buf[0] = buf[0] & ~BIT(6);
- ret = rmi_write(hdev, data->f11.control_base_addr, buf);
+ data->f11_ctrl_regs[0] = data->f11_ctrl_regs[0] & ~BIT(6);
+ ret = rmi_write(hdev, data->f11.control_base_addr,
+ data->f11_ctrl_regs);
if (ret) {
hid_err(hdev, "can not write to control reg 0: %d.\n",
ret);
@@ -983,9 +1013,9 @@ static int rmi_populate_f11(struct hid_device *hdev)
}

if (has_palm_detect) {
- buf[11] = buf[11] & ~BIT(0);
+ data->f11_ctrl_regs[11] = data->f11_ctrl_regs[11] & ~BIT(0);
ret = rmi_write(hdev, data->f11.control_base_addr + 11,
- &buf[11]);
+ &data->f11_ctrl_regs[11]);
if (ret) {
hid_err(hdev, "can not write to control reg 11: %d.\n",
ret);
--
1.9.1

@@ -0,0 +1,135 @@
From 43d790ab374a3cf9ea833d3db77297a32281d8c9 Mon Sep 17 00:00:00 2001
From: Andrew Duggan <aduggan@synaptics.com>
Date: Thu, 16 Jul 2015 17:14:00 -0700
Subject: [PATCH 3/3] HID: rmi: Set F01 interrupt enable register when not set

A firmware bug in some touchpads causes the F01 interrupt enable register
to be cleared on reset. This register controls which RMI functions generate
interrupts and when it is cleared, the touchpad stops reporting all data.
This patch looks for the cleared F01 control register and writes the
correct value based on interrupt mask computed while scanning the PDT.

Fixes:
https://bugs.freedesktop.org/show_bug.cgi?id=91102
Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-rmi.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 52 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index adbc2aa3467cfc684bdc77444e2edc6d73720b7d..870e35c5e454a1ccaac41ea13eedfe2e3aee15be 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -141,6 +141,8 @@ struct rmi_data {
unsigned long firmware_id;

u8 f01_ctrl0;
+ u8 interrupt_enable_mask;
+ bool restore_interrupt_mask;
};

#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
@@ -361,13 +363,34 @@ static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
}
}

+static int rmi_reset_attn_mode(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ if (ret)
+ return ret;
+
+ if (data->restore_interrupt_mask) {
+ ret = rmi_write(hdev, data->f01.control_base_addr + 1,
+ &data->interrupt_enable_mask);
+ if (ret) {
+ hid_err(hdev, "can not write F01 control register\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static void rmi_reset_work(struct work_struct *work)
{
struct rmi_data *hdata = container_of(work, struct rmi_data,
reset_work);

/* switch the device to RMI if we receive a generic mouse report */
- rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
+ rmi_reset_attn_mode(hdata->hdev);
}

static inline int rmi_schedule_reset(struct hid_device *hdev)
@@ -590,7 +613,7 @@ static int rmi_post_reset(struct hid_device *hdev)
struct rmi_data *data = hid_get_drvdata(hdev);
int ret;

- ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ ret = rmi_reset_attn_mode(hdev);
if (ret) {
hid_err(hdev, "can not set rmi mode\n");
return ret;
@@ -617,7 +640,7 @@ static int rmi_post_reset(struct hid_device *hdev)

static int rmi_post_resume(struct hid_device *hdev)
{
- return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ return rmi_reset_attn_mode(hdev);
}
#endif /* CONFIG_PM */

@@ -673,6 +696,7 @@ static void rmi_register_function(struct rmi_data *data,
f->interrupt_count = pdt_entry->interrupt_source_count;
f->irq_mask = rmi_gen_mask(f->interrupt_base,
f->interrupt_count);
+ data->interrupt_enable_mask |= f->irq_mask;
}
}

@@ -810,12 +834,35 @@ static int rmi_populate_f01(struct hid_device *hdev)
data->firmware_id += info[2] * 65536;
}

- ret = rmi_read(hdev, data->f01.control_base_addr, &data->f01_ctrl0);
+ ret = rmi_read_block(hdev, data->f01.control_base_addr, info,
+ 2);

if (ret) {
- hid_err(hdev, "can not read f01 ctrl0\n");
+ hid_err(hdev, "can not read f01 ctrl registers\n");
return ret;
}
+
+ data->f01_ctrl0 = info[0];
+
+ if (!info[1]) {
+ /*
+ * Do to a firmware bug in some touchpads the F01 interrupt
+ * enable control register will be cleared on reset.
+ * This will stop the touchpad from reporting data, so
+ * if F01 CTRL1 is 0 then we need to explicitly enable
+ * interrupts for the functions we want data for.
+ */
+ data->restore_interrupt_mask = true;
+
+ ret = rmi_write(hdev, data->f01.control_base_addr + 1,
+ &data->interrupt_enable_mask);
+ if (ret) {
+ hid_err(hdev, "can not write to control reg 1: %d.\n",
+ ret);
+ return ret;
+ }
+ }
+
return 0;
}

--
1.9.1

Oops, something went wrong.

0 comments on commit f4550c2

Please sign in to comment.
You can’t perform that action at this time.