summaryrefslogtreecommitdiff
path: root/drivers/input/input-mt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/input-mt.c')
-rw-r--r--drivers/input/input-mt.c222
1 files changed, 162 insertions, 60 deletions
diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index d398f1321f14..09f518897d4a 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -1,16 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Input Multitouch Library
*
* Copyright (c) 2008-2010 Henrik Rydberg
- *
- * 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/mt.h>
#include <linux/export.h>
#include <linux/slab.h>
+#include "input-core-private.h"
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
@@ -19,7 +17,7 @@ static void copy_abs(struct input_dev *dev, unsigned int dst, unsigned int src)
if (dev->absinfo && test_bit(src, dev->absbit)) {
dev->absinfo[dst] = dev->absinfo[src];
dev->absinfo[dst].fuzz = 0;
- dev->absbit[BIT_WORD(dst)] |= BIT_MASK(dst);
+ __set_bit(dst, dev->absbit);
}
}
@@ -41,17 +39,20 @@ static void copy_abs(struct input_dev *dev, unsigned int dst, unsigned int src)
int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,
unsigned int flags)
{
- struct input_mt *mt = dev->mt;
- int i;
-
if (!num_slots)
return 0;
- if (mt)
- return mt->num_slots != num_slots ? -EINVAL : 0;
- mt = kzalloc(sizeof(*mt) + num_slots * sizeof(*mt->slots), GFP_KERNEL);
+ if (dev->mt)
+ return dev->mt->num_slots != num_slots ? -EINVAL : 0;
+
+ /* Arbitrary limit for avoiding too large memory allocation. */
+ if (num_slots > 1024)
+ return -EINVAL;
+
+ struct input_mt *mt __free(kfree) =
+ kzalloc(struct_size(mt, slots, num_slots), GFP_KERNEL);
if (!mt)
- goto err_mem;
+ return -ENOMEM;
mt->num_slots = num_slots;
mt->flags = flags;
@@ -85,18 +86,18 @@ int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,
unsigned int n2 = num_slots * num_slots;
mt->red = kcalloc(n2, sizeof(*mt->red), GFP_KERNEL);
if (!mt->red)
- goto err_mem;
+ return -ENOMEM;
}
- /* Mark slots as 'unused' */
- for (i = 0; i < num_slots; i++)
+ /* Mark slots as 'inactive' */
+ for (unsigned int i = 0; i < num_slots; i++)
input_mt_set_value(&mt->slots[i], ABS_MT_TRACKING_ID, -1);
- dev->mt = mt;
+ /* Mark slots as 'unused' */
+ mt->frame = 1;
+
+ dev->mt = no_free_ptr(mt);
return 0;
-err_mem:
- kfree(mt);
- return -ENOMEM;
}
EXPORT_SYMBOL(input_mt_init_slots);
@@ -128,8 +129,10 @@ EXPORT_SYMBOL(input_mt_destroy_slots);
* inactive, or if the tool type is changed, a new tracking id is
* assigned to the slot. The tool type is only reported if the
* corresponding absbit field is set.
+ *
+ * Returns true if contact is active.
*/
-void input_mt_report_slot_state(struct input_dev *dev,
+bool input_mt_report_slot_state(struct input_dev *dev,
unsigned int tool_type, bool active)
{
struct input_mt *mt = dev->mt;
@@ -137,22 +140,24 @@ void input_mt_report_slot_state(struct input_dev *dev,
int id;
if (!mt)
- return;
+ return false;
slot = &mt->slots[mt->slot];
slot->frame = mt->frame;
if (!active) {
input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
- return;
+ return false;
}
id = input_mt_get_value(slot, ABS_MT_TRACKING_ID);
- if (id < 0 || input_mt_get_value(slot, ABS_MT_TOOL_TYPE) != tool_type)
+ if (id < 0)
id = input_mt_new_trkid(mt);
input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, id);
input_event(dev, EV_ABS, ABS_MT_TOOL_TYPE, tool_type);
+
+ return true;
}
EXPORT_SYMBOL(input_mt_report_slot_state);
@@ -193,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
struct input_mt *mt = dev->mt;
struct input_mt_slot *oldest;
int oldid, count, i;
+ int p, reported_p = 0;
if (!mt)
return;
@@ -211,12 +217,34 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
oldest = ps;
oldid = id;
}
+ if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
+ p = input_mt_get_value(ps, ABS_MT_PRESSURE);
+ if (mt->flags & INPUT_MT_TOTAL_FORCE)
+ reported_p += p;
+ else if (oldid == id)
+ reported_p = p;
+ }
count++;
}
input_event(dev, EV_KEY, BTN_TOUCH, count > 0);
- if (use_count)
+
+ if (use_count) {
+ if (count == 0 &&
+ !test_bit(ABS_MT_DISTANCE, dev->absbit) &&
+ test_bit(ABS_DISTANCE, dev->absbit) &&
+ input_abs_get_val(dev, ABS_DISTANCE) != 0) {
+ /*
+ * Force reporting BTN_TOOL_FINGER for devices that
+ * only report general hover (and not per-contact
+ * distance) when contact is in proximity but not
+ * on the surface.
+ */
+ count = 1;
+ }
+
input_mt_report_finger_count(dev, count);
+ }
if (oldest) {
int x = input_mt_get_value(oldest, ABS_MT_POSITION_X);
@@ -225,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
input_event(dev, EV_ABS, ABS_X, x);
input_event(dev, EV_ABS, ABS_Y, y);
- if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
- int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
- input_event(dev, EV_ABS, ABS_PRESSURE, p);
- }
+ if (test_bit(ABS_MT_PRESSURE, dev->absbit))
+ input_event(dev, EV_ABS, ABS_PRESSURE, reported_p);
} else {
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
input_event(dev, EV_ABS, ABS_PRESSURE, 0);
@@ -236,6 +262,65 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
}
EXPORT_SYMBOL(input_mt_report_pointer_emulation);
+static void __input_mt_drop_unused(struct input_dev *dev, struct input_mt *mt)
+{
+ int i;
+
+ lockdep_assert_held(&dev->event_lock);
+
+ for (i = 0; i < mt->num_slots; i++) {
+ if (input_mt_is_active(&mt->slots[i]) &&
+ !input_mt_is_used(mt, &mt->slots[i])) {
+ input_handle_event(dev, EV_ABS, ABS_MT_SLOT, i);
+ input_handle_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ }
+ }
+}
+
+/**
+ * input_mt_drop_unused() - Inactivate slots not seen in this frame
+ * @dev: input device with allocated MT slots
+ *
+ * Lift all slots not seen since the last call to this function.
+ */
+void input_mt_drop_unused(struct input_dev *dev)
+{
+ struct input_mt *mt = dev->mt;
+
+ if (mt) {
+ guard(spinlock_irqsave)(&dev->event_lock);
+
+ __input_mt_drop_unused(dev, mt);
+ mt->frame++;
+ }
+}
+EXPORT_SYMBOL(input_mt_drop_unused);
+
+/**
+ * input_mt_release_slots() - Deactivate all slots
+ * @dev: input device with allocated MT slots
+ *
+ * Lift all active slots.
+ */
+void input_mt_release_slots(struct input_dev *dev)
+{
+ struct input_mt *mt = dev->mt;
+
+ lockdep_assert_held(&dev->event_lock);
+
+ if (mt) {
+ /* This will effectively mark all slots unused. */
+ mt->frame++;
+
+ __input_mt_drop_unused(dev, mt);
+
+ if (test_bit(ABS_PRESSURE, dev->absbit))
+ input_handle_event(dev, EV_ABS, ABS_PRESSURE, 0);
+
+ mt->frame++;
+ }
+}
+
/**
* input_mt_sync_frame() - synchronize mt frame
* @dev: input device with allocated MT slots
@@ -247,19 +332,14 @@ EXPORT_SYMBOL(input_mt_report_pointer_emulation);
void input_mt_sync_frame(struct input_dev *dev)
{
struct input_mt *mt = dev->mt;
- struct input_mt_slot *s;
bool use_count = false;
if (!mt)
return;
if (mt->flags & INPUT_MT_DROP_UNUSED) {
- for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
- if (input_mt_is_used(mt, s))
- continue;
- input_mt_slot(dev, s - mt->slots);
- input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
- }
+ guard(spinlock_irqsave)(&dev->event_lock);
+ __input_mt_drop_unused(dev, mt);
}
if ((mt->flags & INPUT_MT_POINTER) && !(mt->flags & INPUT_MT_SEMI_MT))
@@ -271,7 +351,7 @@ void input_mt_sync_frame(struct input_dev *dev)
}
EXPORT_SYMBOL(input_mt_sync_frame);
-static int adjust_dual(int *begin, int step, int *end, int eq)
+static int adjust_dual(int *begin, int step, int *end, int eq, int mu)
{
int f, *p, s, c;
@@ -282,16 +362,20 @@ static int adjust_dual(int *begin, int step, int *end, int eq)
p = begin + step;
s = p == end ? f + 1 : *p;
- for (; p != end; p += step)
- if (*p < f)
- s = f, f = *p;
- else if (*p < s)
+ for (; p != end; p += step) {
+ if (*p < f) {
+ s = f;
+ f = *p;
+ } else if (*p < s) {
s = *p;
+ }
+ }
c = (f + s + 1) / 2;
- if (c == 0 || (c > 0 && !eq))
+ if (c == 0 || (c > mu && (!eq || mu > 0)))
return 0;
- if (s < 0)
+ /* Improve convergence for positive matrices by penalizing overcovers */
+ if (s < 0 && mu <= 0)
c *= 2;
for (p = begin; p != end; p += step)
@@ -300,23 +384,24 @@ static int adjust_dual(int *begin, int step, int *end, int eq)
return (c < s && s <= 0) || (f >= 0 && f < c);
}
-static void find_reduced_matrix(int *w, int nr, int nc, int nrc)
+static void find_reduced_matrix(int *w, int nr, int nc, int nrc, int mu)
{
int i, k, sum;
for (k = 0; k < nrc; k++) {
for (i = 0; i < nr; i++)
- adjust_dual(w + i, nr, w + i + nrc, nr <= nc);
+ adjust_dual(w + i, nr, w + i + nrc, nr <= nc, mu);
sum = 0;
for (i = 0; i < nrc; i += nr)
- sum += adjust_dual(w + i, 1, w + i + nr, nc <= nr);
+ sum += adjust_dual(w + i, 1, w + i + nr, nc <= nr, mu);
if (!sum)
break;
}
}
static int input_mt_set_matrix(struct input_mt *mt,
- const struct input_mt_pos *pos, int num_pos)
+ const struct input_mt_pos *pos, int num_pos,
+ int mu)
{
const struct input_mt_pos *p;
struct input_mt_slot *s;
@@ -330,7 +415,7 @@ static int input_mt_set_matrix(struct input_mt *mt,
y = input_mt_get_value(s, ABS_MT_POSITION_Y);
for (p = pos; p != pos + num_pos; p++) {
int dx = x - p->x, dy = y - p->y;
- *w++ = dx * dx + dy * dy;
+ *w++ = dx * dx + dy * dy - mu;
}
}
@@ -341,27 +426,35 @@ static void input_mt_set_slots(struct input_mt *mt,
int *slots, int num_pos)
{
struct input_mt_slot *s;
- int *w = mt->red, *p;
+ int *w = mt->red, j;
- for (p = slots; p != slots + num_pos; p++)
- *p = -1;
+ for (j = 0; j != num_pos; j++)
+ slots[j] = -1;
for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
if (!input_mt_is_active(s))
continue;
- for (p = slots; p != slots + num_pos; p++)
- if (*w++ < 0)
- *p = s - mt->slots;
+
+ for (j = 0; j != num_pos; j++) {
+ if (w[j] < 0) {
+ slots[j] = s - mt->slots;
+ break;
+ }
+ }
+
+ w += num_pos;
}
for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
if (input_mt_is_active(s))
continue;
- for (p = slots; p != slots + num_pos; p++)
- if (*p < 0) {
- *p = s - mt->slots;
+
+ for (j = 0; j != num_pos; j++) {
+ if (slots[j] < 0) {
+ slots[j] = s - mt->slots;
break;
}
+ }
}
}
@@ -371,17 +464,24 @@ static void input_mt_set_slots(struct input_mt *mt,
* @slots: the slot assignment to be filled
* @pos: the position array to match
* @num_pos: number of positions
+ * @dmax: maximum ABS_MT_POSITION displacement (zero for infinite)
*
* Performs a best match against the current contacts and returns
* the slot assignment list. New contacts are assigned to unused
* slots.
*
+ * The assignments are balanced so that all coordinate displacements are
+ * below the euclidian distance dmax. If no such assignment can be found,
+ * some contacts are assigned to unused slots.
+ *
* Returns zero on success, or negative error in case of failure.
*/
int input_mt_assign_slots(struct input_dev *dev, int *slots,
- const struct input_mt_pos *pos, int num_pos)
+ const struct input_mt_pos *pos, int num_pos,
+ int dmax)
{
struct input_mt *mt = dev->mt;
+ int mu = 2 * dmax * dmax;
int nrc;
if (!mt || !mt->red)
@@ -391,8 +491,8 @@ int input_mt_assign_slots(struct input_dev *dev, int *slots,
if (num_pos < 1)
return 0;
- nrc = input_mt_set_matrix(mt, pos, num_pos);
- find_reduced_matrix(mt->red, num_pos, nrc / num_pos, nrc);
+ nrc = input_mt_set_matrix(mt, pos, num_pos, mu);
+ find_reduced_matrix(mt->red, num_pos, nrc / num_pos, nrc, mu);
input_mt_set_slots(mt, slots, num_pos);
return 0;
@@ -408,6 +508,8 @@ EXPORT_SYMBOL(input_mt_assign_slots);
* set the key on the first unused slot and return.
*
* If no available slot can be found, -1 is returned.
+ * Note that for this function to work properly, input_mt_sync_frame() has
+ * to be called at each frame.
*/
int input_mt_get_slot_by_key(struct input_dev *dev, int key)
{
@@ -422,7 +524,7 @@ int input_mt_get_slot_by_key(struct input_dev *dev, int key)
return s - mt->slots;
for (s = mt->slots; s != mt->slots + mt->num_slots; s++)
- if (!input_mt_is_active(s)) {
+ if (!input_mt_is_active(s) && !input_mt_is_used(mt, s)) {
s->key = key;
return s - mt->slots;
}