summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/hid/hid-multitouch.c102
1 files changed, 79 insertions, 23 deletions
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index ba0675cf21a0..25641b2f90ab 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -44,6 +44,7 @@
#include <linux/slab.h>
#include <linux/input/mt.h>
#include <linux/string.h>
+#include <linux/timer.h>
MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
@@ -70,12 +71,15 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_FORCE_GET_FEATURE BIT(13)
#define MT_QUIRK_FIX_CONST_CONTACT_ID BIT(14)
#define MT_QUIRK_TOUCH_SIZE_SCALING BIT(15)
+#define MT_QUIRK_STICKY_FINGERS BIT(16)
#define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03
#define MT_BUTTONTYPE_CLICKPAD 0
+#define MT_IO_FLAGS_RUNNING 0
+
struct mt_slot {
__s32 x, y, cx, cy, p, w, h;
__s32 contactid; /* the device ContactID assigned to this slot */
@@ -104,8 +108,10 @@ struct mt_fields {
struct mt_device {
struct mt_slot curdata; /* placeholder of incoming data */
struct mt_class mtclass; /* our mt device class */
+ struct timer_list release_timer; /* to release sticky fingers */
struct mt_fields *fields; /* temporary placeholder for storing the
multitouch fields */
+ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */
int cc_index; /* contact count field index in the report */
int cc_value_index; /* contact count value index in the field */
unsigned last_slot_field; /* the last field of a slot */
@@ -214,7 +220,8 @@ static struct mt_class mt_classes[] = {
.quirks = MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_IGNORE_DUPLICATES |
MT_QUIRK_HOVERING |
- MT_QUIRK_CONTACT_CNT_ACCURATE },
+ MT_QUIRK_CONTACT_CNT_ACCURATE |
+ MT_QUIRK_STICKY_FINGERS },
{ .name = MT_CLS_EXPORT_ALL_INPUTS,
.quirks = MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_CONTACT_CNT_ACCURATE,
@@ -804,6 +811,10 @@ static void mt_touch_report(struct hid_device *hid, struct hid_report *report)
unsigned count;
int r, n;
+ /* sticky fingers release in progress, abort */
+ if (test_and_set_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+ return;
+
/*
* Includes multi-packet support where subsequent
* packets are sent with zero contactcount.
@@ -829,6 +840,29 @@ static void mt_touch_report(struct hid_device *hid, struct hid_report *report)
if (td->num_received >= td->num_expected)
mt_sync_frame(td, report->field[0]->hidinput->input);
+
+ /*
+ * Windows 8 specs says 2 things:
+ * - once a contact has been reported, it has to be reported in each
+ * subsequent report
+ * - the report rate when fingers are present has to be at least
+ * the refresh rate of the screen, 60 or 120 Hz
+ *
+ * I interprete this that the specification forces a report rate of
+ * at least 60 Hz for a touchscreen to be certified.
+ * Which means that if we do not get a report whithin 16 ms, either
+ * something wrong happens, either the touchscreen forgets to send
+ * a release. Taking a reasonable margin allows to remove issues
+ * with USB communication or the load of the machine.
+ *
+ * Given that Win 8 devices are forced to send a release, this will
+ * only affect laggish machines and the ones that have a firmware
+ * defect.
+ */
+ if (td->mtclass.quirks & MT_QUIRK_STICKY_FINGERS)
+ mod_timer(&td->release_timer, jiffies + msecs_to_jiffies(100));
+
+ clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
}
static int mt_touch_input_configured(struct hid_device *hdev,
@@ -1140,6 +1174,46 @@ static void mt_fix_const_fields(struct hid_device *hdev, unsigned int usage)
}
}
+static void mt_release_contacts(struct hid_device *hid)
+{
+ struct hid_input *hidinput;
+ struct mt_device *td = hid_get_drvdata(hid);
+
+ list_for_each_entry(hidinput, &hid->inputs, list) {
+ struct input_dev *input_dev = hidinput->input;
+ struct input_mt *mt = input_dev->mt;
+ int i;
+
+ if (mt) {
+ for (i = 0; i < mt->num_slots; i++) {
+ input_mt_slot(input_dev, i);
+ input_mt_report_slot_state(input_dev,
+ MT_TOOL_FINGER,
+ false);
+ }
+ input_mt_sync_frame(input_dev);
+ input_sync(input_dev);
+ }
+ }
+
+ td->num_received = 0;
+}
+
+static void mt_expired_timeout(unsigned long arg)
+{
+ struct hid_device *hdev = (void *)arg;
+ struct mt_device *td = hid_get_drvdata(hdev);
+
+ /*
+ * An input report came in just before we release the sticky fingers,
+ * it will take care of the sticky fingers.
+ */
+ if (test_and_set_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+ return;
+ mt_release_contacts(hdev);
+ clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+}
+
static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret, i;
@@ -1209,6 +1283,8 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
*/
hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+ setup_timer(&td->release_timer, mt_expired_timeout, (long)hdev);
+
ret = hid_parse(hdev);
if (ret != 0)
return ret;
@@ -1236,28 +1312,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
#ifdef CONFIG_PM
-static void mt_release_contacts(struct hid_device *hid)
-{
- struct hid_input *hidinput;
-
- list_for_each_entry(hidinput, &hid->inputs, list) {
- struct input_dev *input_dev = hidinput->input;
- struct input_mt *mt = input_dev->mt;
- int i;
-
- if (mt) {
- for (i = 0; i < mt->num_slots; i++) {
- input_mt_slot(input_dev, i);
- input_mt_report_slot_state(input_dev,
- MT_TOOL_FINGER,
- false);
- }
- input_mt_sync_frame(input_dev);
- input_sync(input_dev);
- }
- }
-}
-
static int mt_reset_resume(struct hid_device *hdev)
{
mt_release_contacts(hdev);
@@ -1282,6 +1336,8 @@ static void mt_remove(struct hid_device *hdev)
{
struct mt_device *td = hid_get_drvdata(hdev);
+ del_timer_sync(&td->release_timer);
+
sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group);
hid_hw_stop(hdev);
hdev->quirks = td->initial_quirks;