diff options
author | Joshua Stein <jcs@cvs.openbsd.org> | 2016-01-20 01:26:01 +0000 |
---|---|---|
committer | Joshua Stein <jcs@cvs.openbsd.org> | 2016-01-20 01:26:01 +0000 |
commit | 3733a3817db89ab1ff676c67fcfdb946b4820d78 (patch) | |
tree | d346a13ebb9644c11c0e4c48fd888f2a55a94eac /sys/dev/hid | |
parent | 931b99bb793b391a061ef120612a93eed0984b70 (diff) |
add hidmt, a HID-layer driver for multitouch touchpads that conform
to the "Windows Precision Touchpad" standard. when a compatible
device is found, hidmt claims all report ids and switches the device
into multitouch packet mode.
add imt, an i2c-HID driver that sits between ihidev and hidmt
Diffstat (limited to 'sys/dev/hid')
-rw-r--r-- | sys/dev/hid/files.hid | 6 | ||||
-rw-r--r-- | sys/dev/hid/hid.h | 17 | ||||
-rw-r--r-- | sys/dev/hid/hidmt.c | 421 | ||||
-rw-r--r-- | sys/dev/hid/hidmtvar.h | 78 |
4 files changed, 519 insertions, 3 deletions
diff --git a/sys/dev/hid/files.hid b/sys/dev/hid/files.hid index c2bd1d2ce80..438f076ce5c 100644 --- a/sys/dev/hid/files.hid +++ b/sys/dev/hid/files.hid @@ -1,4 +1,4 @@ -# $OpenBSD: files.hid,v 1.1 2016/01/08 15:54:13 jcs Exp $ +# $OpenBSD: files.hid,v 1.2 2016/01/20 01:26:00 jcs Exp $ # Human Interface Devices @@ -16,3 +16,7 @@ file dev/hid/hidkbd.c hidkbd needs-flag # Mice define hidms file dev/hid/hidms.c hidms + +# Multitouch +define hidmt +file dev/hid/hidmt.c hidmt diff --git a/sys/dev/hid/hid.h b/sys/dev/hid/hid.h index 6840a4a197d..3297e5cec37 100644 --- a/sys/dev/hid/hid.h +++ b/sys/dev/hid/hid.h @@ -1,4 +1,4 @@ -/* $OpenBSD: hid.h,v 1.3 2016/01/20 01:11:50 jcs Exp $ */ +/* $OpenBSD: hid.h,v 1.4 2016/01/20 01:26:00 jcs Exp $ */ /* $NetBSD: hid.h,v 1.8 2002/07/11 21:14:25 augustss Exp $ */ /* $FreeBSD: src/sys/dev/usb/hid.h,v 1.7 1999/11/17 22:33:40 n_hibma Exp $ */ @@ -123,8 +123,10 @@ int hid_is_collection(const void *, int, uint8_t, int32_t); #define HUP_SCALE 0x008c #define HUP_CAMERA_CONTROL 0x0090 #define HUP_ARCADE 0x0091 -#define HUP_APPLE 0x00ff +#define HUP_VENDOR 0x00ff #define HUP_MICROSOFT 0xff00 +/* XXX compat */ +#define HUP_APPLE 0x00ff /* Usages, Power Device */ #define HUP_INAME 0x0001 @@ -348,6 +350,7 @@ int hid_is_collection(const void *, int, uint8_t, int32_t); #define HUD_PEN 0x0002 #define HUD_TOUCHSCREEN 0x0004 #define HUD_TOUCHPAD 0x0005 +#define HUD_CONFIG 0x000e #define HUD_FINGER 0x0022 #define HUD_TIP_PRESSURE 0x0030 #define HUD_BARREL_PRESSURE 0x0031 @@ -372,6 +375,16 @@ int hid_is_collection(const void *, int, uint8_t, int32_t); #define HUD_BARREL_SWITCH 0x0044 #define HUD_ERASER 0x0045 #define HUD_TABLET_PICK 0x0046 +#define HUD_CONFIDENCE 0x0047 +#define HUD_WIDTH 0x0048 +#define HUD_HEIGHT 0x0049 +#define HUD_CONTACTID 0x0051 +#define HUD_INPUT_MODE 0x0052 +#define HUD_DEVICE_INDEX 0x0053 +#define HUD_CONTACTCOUNT 0x0054 +#define HUD_CONTACT_MAX 0x0055 +#define HUD_SCAN_TIME 0x0056 +#define HUD_BUTTON_TYPE 0x0059 /* Usages, LED */ #define HUL_NUM_LOCK 0x0001 diff --git a/sys/dev/hid/hidmt.c b/sys/dev/hid/hidmt.c new file mode 100644 index 00000000000..db74eef90c0 --- /dev/null +++ b/sys/dev/hid/hidmt.c @@ -0,0 +1,421 @@ +/* $OpenBSD: hidmt.c,v 1.1 2016/01/20 01:26:00 jcs Exp $ */ +/* + * HID multitouch driver for devices conforming to Windows Precision Touchpad + * standard + * + * https://msdn.microsoft.com/en-us/library/windows/hardware/dn467314%28v=vs.85%29.aspx + * + * Copyright (c) 2016 joshua stein <jcs@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> + +#include <dev/wscons/wsconsio.h> +#include <dev/wscons/wsmousevar.h> + +#include <dev/hid/hid.h> +#include <dev/hid/hidmtvar.h> + +/* #define HIDMT_DEBUG */ + +#ifdef HIDMT_DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +int +hidmt_setup(struct device *self, struct hidmt *mt, void *desc, int dlen) +{ + struct hid_location cap; + int32_t d; + uint8_t *rep; + int capsize; + + struct hid_data *hd; + struct hid_item h; + + mt->sc_device = self; + mt->sc_rep_input_size = hid_report_size(desc, dlen, hid_input, + mt->sc_rep_input); + + mt->sc_minx = mt->sc_miny = mt->sc_maxx = mt->sc_maxy = 0; + + capsize = hid_report_size(desc, dlen, hid_feature, mt->sc_rep_cap); + rep = malloc(capsize, M_DEVBUF, M_NOWAIT | M_ZERO); + + if (mt->hidev_get_report(mt->sc_device, hid_feature, mt->sc_rep_cap, + rep, capsize)) { + printf("\n%s: failed getting capability report\n", + self->dv_xname); + return 1; + } + + /* find maximum number of contacts being reported per input report */ + if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), + mt->sc_rep_cap, hid_feature, &cap, NULL)) { + printf("\n%s: can't find maximum contacts\n", self->dv_xname); + return 1; + } + + d = hid_get_udata(rep, capsize, &cap); + if (d > HIDMT_MAX_CONTACTS) { + printf("\n%s: contacts %d > max %d\n", self->dv_xname, d, + HIDMT_MAX_CONTACTS); + return 1; + } + else + mt->sc_num_contacts = d; + + /* find whether this is a clickpad or not */ + if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE), + mt->sc_rep_cap, hid_feature, &cap, NULL)) { + printf("\n%s: can't find button type\n", self->dv_xname); + return 1; + } + + d = hid_get_udata(rep, capsize, &cap); + mt->sc_clickpad = (d == 0); + + /* + * Walk HID descriptor and store usages we care about to know what to + * pluck out of input reports. + */ + + SIMPLEQ_INIT(&mt->sc_inputs); + + hd = hid_start_parse(desc, dlen, hid_input); + while (hid_get_item(hd, &h)) { + struct hidmt_input *input; + + if (h.report_ID != mt->sc_rep_input) + continue; + + switch (h.usage) { + /* contact level usages */ + case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): + if (h.physical_minimum < mt->sc_minx) + mt->sc_minx = h.physical_minimum; + if (h.physical_maximum > mt->sc_maxx) + mt->sc_maxx = h.physical_maximum; + + break; + case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): + if (h.physical_minimum < mt->sc_miny) + mt->sc_miny = h.physical_minimum; + if (h.physical_maximum > mt->sc_maxy) + mt->sc_maxy = h.physical_maximum; + + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): + case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): + case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): + + /* report level usages */ + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): + case HID_USAGE2(HUP_BUTTON, 0x01): + break; + default: + continue; + } + + input = malloc(sizeof(*input), M_DEVBUF, M_NOWAIT | M_ZERO); + memcpy(&input->loc, &h.loc, sizeof(struct hid_location)); + input->usage = h.usage; + + SIMPLEQ_INSERT_TAIL(&mt->sc_inputs, input, entry); + } + hid_end_parse(hd); + + if (mt->sc_maxx <= 0 || mt->sc_maxy <= 0) { + printf("\n%s: invalid max X/Y %d/%d\n", self->dv_xname, + mt->sc_maxx, mt->sc_maxy); + return 1; + } + + if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT)) { + printf("\n%s: switch to multitouch mode failed\n", + self->dv_xname); + return 1; + } + + return 0; +} + +void +hidmt_attach(struct hidmt *mt, const struct wsmouse_accessops *ops) +{ + struct wsmousedev_attach_args a; + + printf(": %spad, %d contact%s\n", + (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, + (mt->sc_num_contacts == 1 ? "" : "s")); + + a.accessops = ops; + a.accesscookie = mt->sc_device; + mt->sc_wsmousedev = config_found(mt->sc_device, &a, wsmousedevprint); +} + +int +hidmt_detach(struct hidmt *mt, int flags) +{ + int rv = 0; + + if (mt->sc_wsmousedev != NULL) + rv = config_detach(mt->sc_wsmousedev, flags); + + return (rv); +} + +int +hidmt_set_input_mode(struct hidmt *mt, int mode) +{ + return mt->hidev_set_report(mt->sc_device, hid_feature, + mt->sc_rep_config, &mode, 1); +} + +void +hidmt_input(struct hidmt *mt, uint8_t *data, u_int len) +{ + struct hidmt_input *hi; + struct hidmt_contact hc; + int32_t d, firstu = 0; + int contactcount = 0, seencontacts = 0, tips = 0, i, s; + + if (len != mt->sc_rep_input_size) { + DPRINTF(("%s: %s: length %d not %d, ignoring\n", + mt->sc_device->dv_xname, __func__, len, + mt->sc_rep_input_size)); + return; + } + + /* + * "In Parallel mode, devices report all contact information in a + * single packet. Each physical contact is represented by a logical + * collection that is embedded in the top-level collection." + * + * Since additional contacts that were not present will still be in the + * report with contactid=0 but contactids are zero-based, find + * contactcount first. + */ + SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { + if (hi->usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) + contactcount = hid_get_udata(data, len, + &hi->loc); + } + + if (!contactcount) { + DPRINTF(("%s: %s: no contactcount in report\n", + mt->sc_device->dv_xname, __func__)); + return; + } + + /* + * Walk through each input we know about and fetch its data from the + * report, storing it in a temporary contact. Once we see our first + * usage again, we'll know we saw all usages being presented for that + * contact. + */ + bzero(&hc, sizeof(struct hidmt_contact)); + SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { + d = hid_get_udata(data, len, &hi->loc); + + if (firstu && hi->usage == firstu) { + if (seencontacts < contactcount && + hc.contactid < HIDMT_MAX_CONTACTS) { + hc.seen = 1; + memcpy(&mt->sc_contacts[hc.contactid], &hc, + sizeof(struct hidmt_contact)); + seencontacts++; + } + + bzero(&hc, sizeof(struct hidmt_contact)); + } + else if (!firstu) + firstu = hi->usage; + + switch (hi->usage) { + case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): + hc.x = d; + break; + case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): + if (mt->sc_flags & HIDMT_REVY) + hc.y = mt->sc_maxy - d; + else + hc.y = d; + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): + hc.tip = d; + if (d) + tips++; + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): + hc.confidence = d; + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): + hc.width = d; + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): + hc.height = d; + break; + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): + hc.contactid = d; + break; + + /* these will only appear once per report */ + case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): + contactcount = d; + break; + case HID_USAGE2(HUP_BUTTON, 0x01): + mt->sc_button = (d != 0); + break; + } + } + if (seencontacts < contactcount && hc.contactid < HIDMT_MAX_CONTACTS) { + hc.seen = 1; + memcpy(&mt->sc_contacts[hc.contactid], &hc, + sizeof(struct hidmt_contact)); + seencontacts++; + } + + s = spltty(); + for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { + if (!mt->sc_contacts[i].seen) + continue; + + mt->sc_contacts[i].seen = 0; + + DPRINTF(("%s: %s: contact %d of %d: id %d, x %d, y %d, " + "touch %d, confidence %d, width %d, height %d " + "(button %d)\n", + mt->sc_device->dv_xname, __func__, + i + 1, contactcount, + mt->sc_contacts[i].contactid, + mt->sc_contacts[i].x, + mt->sc_contacts[i].y, + mt->sc_contacts[i].tip, + mt->sc_contacts[i].confidence, + mt->sc_contacts[i].width, + mt->sc_contacts[i].height, + mt->sc_button)); + + if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) + continue; + + if (mt->sc_wsmode == WSMOUSE_NATIVE) { + int width = 0; + if (mt->sc_contacts[i].tip) { + width = mt->sc_contacts[i].width; + if (width < 50) + width = 50; + } + + wsmouse_input(mt->sc_wsmousedev, mt->sc_button, + (mt->last_x = mt->sc_contacts[i].x), + (mt->last_y = mt->sc_contacts[i].y), + width, tips, + WSMOUSE_INPUT_ABSOLUTE_X | + WSMOUSE_INPUT_ABSOLUTE_Y | + WSMOUSE_INPUT_ABSOLUTE_Z | + WSMOUSE_INPUT_ABSOLUTE_W); + } else { + wsmouse_input(mt->sc_wsmousedev, mt->sc_button, + (mt->last_x - mt->sc_contacts[i].x), + (mt->last_y - mt->sc_contacts[i].y), + 0, 0, + WSMOUSE_INPUT_DELTA); + mt->last_x = mt->sc_contacts[i].x; + mt->last_y = mt->sc_contacts[i].y; + } + + /* + * XXX: wscons can only handle one finger of data + */ + break; + } + + splx(s); +} + +int +hidmt_enable(struct hidmt *mt) +{ + if (mt->sc_enabled) + return EBUSY; + + mt->sc_enabled = 1; + + return 0; +} + +int +hidmt_ioctl(struct hidmt *mt, u_long cmd, caddr_t data, int flag, + struct proc *p) +{ + struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; + int wsmode; + + switch (cmd) { + case WSMOUSEIO_GTYPE: + /* + * So we can specify our own finger/w values to the + * xf86-input-synaptics driver like pms(4) + */ + *(u_int *)data = WSMOUSE_TYPE_ELANTECH; + break; + + case WSMOUSEIO_GCALIBCOORDS: + wsmc->minx = mt->sc_minx; + wsmc->maxx = mt->sc_maxx; + wsmc->miny = mt->sc_miny; + wsmc->maxy = mt->sc_maxy; + wsmc->swapxy = 0; + wsmc->resx = 0; + wsmc->resy = 0; + break; + + case WSMOUSEIO_SETMODE: + wsmode = *(u_int *)data; + if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) { + printf("%s: invalid mode %d\n", + mt->sc_device->dv_xname, wsmode); + return (EINVAL); + } + + DPRINTF(("%s: changing mode to %s\n", mt->sc_device->dv_xname, + (wsmode == WSMOUSE_COMPAT ? "compat" : "native"))); + + mt->sc_wsmode = wsmode; + break; + + default: + return -1; + } + + return 0; +} + +void +hidmt_disable(struct hidmt *mt) +{ + mt->sc_enabled = 0; +} diff --git a/sys/dev/hid/hidmtvar.h b/sys/dev/hid/hidmtvar.h new file mode 100644 index 00000000000..5cf1329d541 --- /dev/null +++ b/sys/dev/hid/hidmtvar.h @@ -0,0 +1,78 @@ +/* $OpenBSD: hidmtvar.h,v 1.1 2016/01/20 01:26:00 jcs Exp $ */ +/* + * Copyright (c) 2016 joshua stein <jcs@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct hidmt_input { + int32_t usage; + struct hid_location loc; + SIMPLEQ_ENTRY(hidmt_input) entry; +}; + +struct hidmt_contact { + int x; + int y; + int width; + int height; + int tip; + int confidence; + int contactid; + + int seen; +}; + +struct hidmt { + int sc_enabled; + uint32_t sc_flags; +#define HIDMT_REVY 0x0001 /* Y-axis is reversed ("natural" scrolling) */ + + struct device *sc_device; + int (*hidev_get_report)(struct device *, int, int, void *, + int); + int (*hidev_set_report)(struct device *, int, int, void *, + int); + + int sc_rep_input; + int sc_rep_input_size; + int sc_rep_config; + int sc_rep_cap; + + SIMPLEQ_HEAD(, hidmt_input) sc_inputs; + + struct device *sc_wsmousedev; + int sc_wsmode; + + int sc_clickpad; + int sc_num_contacts; +#define HIDMT_MAX_CONTACTS 5 + int sc_minx, sc_maxx; + int sc_miny, sc_maxy; + + struct hidmt_contact sc_contacts[HIDMT_MAX_CONTACTS]; + int sc_button; + + int last_x, last_y; +}; + +int hidmt_set_input_mode(struct hidmt *, int); +#define HIDMT_INPUT_MODE_MT 0x3 + +void hidmt_attach(struct hidmt *, const struct wsmouse_accessops *); +int hidmt_detach(struct hidmt *, int); +void hidmt_disable(struct hidmt *); +int hidmt_enable(struct hidmt *); +void hidmt_input(struct hidmt *, uint8_t *, u_int); +int hidmt_ioctl(struct hidmt *, u_long, caddr_t, int, struct proc *); +int hidmt_setup(struct device *, struct hidmt *, void *, int); |