summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/arch/amd64/conf/GENERIC6
-rw-r--r--sys/dev/hid/files.hid6
-rw-r--r--sys/dev/hid/hid.h17
-rw-r--r--sys/dev/hid/hidmt.c421
-rw-r--r--sys/dev/hid/hidmtvar.h78
-rw-r--r--sys/dev/i2c/files.i2c7
-rw-r--r--sys/dev/i2c/imt.c247
7 files changed, 776 insertions, 6 deletions
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index ba6dd199f92..fee3c2ab7bf 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.413 2016/01/14 21:04:20 kettenis Exp $
+# $OpenBSD: GENERIC,v 1.414 2016/01/20 01:26:00 jcs Exp $
#
# For further information on compiling OpenBSD kernels, see the config(8)
# man page.
@@ -151,10 +151,12 @@ sdtemp* at iic? # SO-DIMM (JC-42.4) temperature
wbng* at iic? # Winbond W83793G
nvt* at iic? # Novoton W83795G
ihidev* at iic? # HID-over-i2c
-ims* at ihidev? # i2c mouse/trackpad
+ims* at ihidev? # HID-over-i2c mouse/trackpad
wsmouse* at ims? mux 0
ikbd* at ihidev? # i2c keyboard
wskbd* at ikbd? mux 1
+imt* at ihidev? # HID-over-i2c multitouch trackpad
+wsmouse* at imt? mux 0
skgpio0 at isa? port 0x680 # Soekris net6501 GPIO and LEDs
gpio* at skgpio?
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);
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index 2b2fb296422..1248bd2b59d 100644
--- a/sys/dev/i2c/files.i2c
+++ b/sys/dev/i2c/files.i2c
@@ -1,4 +1,4 @@
-# $OpenBSD: files.i2c,v 1.53 2016/01/14 21:01:49 kettenis Exp $
+# $OpenBSD: files.i2c,v 1.54 2016/01/20 01:26:00 jcs Exp $
# $NetBSD: files.i2c,v 1.3 2003/10/20 16:24:10 briggs Exp $
define i2c {[addr = -1], [size = -1]}
@@ -189,3 +189,8 @@ file dev/i2c/ikbd.c ikbd
device ims: hid, hidms, wsmousedev
attach ims at ihidbus
file dev/i2c/ims.c ims
+
+# HID Multitouch Trackpad
+device imt: hid, hidmt, wsmousedev
+attach imt at ihidbus
+file dev/i2c/imt.c imt
diff --git a/sys/dev/i2c/imt.c b/sys/dev/i2c/imt.c
new file mode 100644
index 00000000000..bb41444fccc
--- /dev/null
+++ b/sys/dev/i2c/imt.c
@@ -0,0 +1,247 @@
+/* $OpenBSD: imt.c,v 1.1 2016/01/20 01:26:00 jcs Exp $ */
+/*
+ * HID-over-i2c multitouch trackpad 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 <dev/i2c/i2cvar.h>
+#include <dev/i2c/ihidev.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsmousevar.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidmtvar.h>
+
+struct imt_softc {
+ struct ihidev sc_hdev;
+ struct hidmt sc_mt;
+
+ int sc_rep_input;
+ int sc_rep_config;
+ int sc_rep_cap;
+};
+
+int imt_enable(void *);
+int imt_open(struct ihidev *);
+void imt_intr(struct ihidev *, void *, u_int);
+void imt_disable(void *);
+int imt_ioctl(void *, u_long, caddr_t, int, struct proc *);
+
+const struct wsmouse_accessops imt_accessops = {
+ imt_enable,
+ imt_ioctl,
+ imt_disable,
+};
+
+int imt_match(struct device *, void *, void *);
+int imt_find_winptp_reports(struct ihidev_softc *, void *, int,
+ struct imt_softc *);
+void imt_attach(struct device *, struct device *, void *);
+int ims_hidev_get_report(struct device *, int, int, void *, int);
+int ims_hidev_set_report(struct device *, int, int, void *, int);
+int imt_detach(struct device *, int);
+
+struct cfdriver imt_cd = {
+ NULL, "imt", DV_DULL
+};
+
+const struct cfattach imt_ca = {
+ sizeof(struct imt_softc),
+ imt_match,
+ imt_attach,
+ imt_detach
+};
+
+int
+imt_match(struct device *parent, void *match, void *aux)
+{
+ struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
+ int size;
+ void *desc;
+
+ if (iha->reportid == IHIDEV_CLAIM_ALLREPORTID) {
+ ihidev_get_report_desc(iha->parent, &desc, &size);
+ if (imt_find_winptp_reports(iha->parent, desc, size, NULL))
+ return (IMATCH_DEVCLASS_DEVSUBCLASS);
+ }
+
+ return (IMATCH_NONE);
+}
+
+int
+imt_find_winptp_reports(struct ihidev_softc *parent, void *desc, int size,
+ struct imt_softc *sc)
+{
+ int repid;
+ int input = 0, conf = 0, cap = 0;
+
+ if (sc != NULL) {
+ sc->sc_rep_input = -1;
+ sc->sc_rep_config = -1;
+ sc->sc_rep_cap = -1;
+ }
+
+ for (repid = 0; repid < parent->sc_nrepid; repid++) {
+ if (hid_report_size(desc, size, hid_input, repid) == 0 &&
+ hid_report_size(desc, size, hid_output, repid) == 0 &&
+ hid_report_size(desc, size, hid_feature, repid) == 0)
+ continue;
+
+ if (hid_is_collection(desc, size, repid,
+ HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD))) {
+ input = 1;
+ if (sc != NULL && sc->sc_rep_input == -1)
+ sc->sc_rep_input = repid;
+ } else if (hid_is_collection(desc, size, repid,
+ HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG))) {
+ conf = 1;
+ if (sc != NULL && sc->sc_rep_config == -1)
+ sc->sc_rep_config = repid;
+ }
+
+ /* capabilities report could be anywhere */
+ if (hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS,
+ HUD_CONTACT_MAX), repid, hid_feature, NULL, NULL)) {
+ cap = 1;
+ if (sc != NULL && sc->sc_rep_cap == -1)
+ sc->sc_rep_cap = repid;
+ }
+ }
+
+ return (conf && input && cap);
+}
+
+void
+imt_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct imt_softc *sc = (struct imt_softc *)self;
+ struct hidmt *mt = &sc->sc_mt;
+ struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
+ int size;
+ void *desc;
+
+ sc->sc_hdev.sc_intr = imt_intr;
+ sc->sc_hdev.sc_parent = iha->parent;
+
+ ihidev_get_report_desc(iha->parent, &desc, &size);
+ imt_find_winptp_reports(iha->parent, desc, size, sc);
+
+ memset(mt, 0, sizeof(sc->sc_mt));
+
+ /* assume everything has "natural scrolling" where Y axis is reversed */
+ mt->sc_flags = HIDMT_REVY;
+
+ mt->hidev_get_report = ims_hidev_get_report;
+ mt->hidev_set_report = ims_hidev_set_report;
+ mt->sc_rep_input = sc->sc_rep_input;
+ mt->sc_rep_config = sc->sc_rep_config;
+ mt->sc_rep_cap = sc->sc_rep_cap;
+
+ if (hidmt_setup(self, mt, desc, size) != 0)
+ return;
+
+ hidmt_attach(mt, &imt_accessops);
+}
+
+int
+ims_hidev_get_report(struct device *self, int type, int id, void *data, int len)
+{
+ struct imt_softc *sc = (struct imt_softc *)self;
+
+ return ihidev_get_report((struct device *)sc->sc_hdev.sc_parent, type,
+ id, data, len);
+}
+
+int
+ims_hidev_set_report(struct device *self, int type, int id, void *data, int len)
+{
+ struct imt_softc *sc = (struct imt_softc *)self;
+
+ return ihidev_set_report((struct device *)sc->sc_hdev.sc_parent, type,
+ id, data, len);
+}
+
+int
+imt_detach(struct device *self, int flags)
+{
+ struct imt_softc *sc = (struct imt_softc *)self;
+ struct hidmt *mt = &sc->sc_mt;
+
+ return hidmt_detach(mt, flags);
+}
+
+void
+imt_intr(struct ihidev *dev, void *buf, u_int len)
+{
+ struct imt_softc *sc = (struct imt_softc *)dev;
+ struct hidmt *mt = &sc->sc_mt;
+
+ if (!mt->sc_enabled)
+ return;
+
+ hidmt_input(mt, (uint8_t *)buf, len);
+}
+
+int
+imt_enable(void *v)
+{
+ struct imt_softc *sc = v;
+ struct hidmt *mt = &sc->sc_mt;
+ int rv;
+
+ if ((rv = hidmt_enable(mt)) != 0)
+ return rv;
+
+ rv = ihidev_open(&sc->sc_hdev);
+
+ hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT);
+
+ return rv;
+}
+
+void
+imt_disable(void *v)
+{
+ struct imt_softc *sc = v;
+ struct hidmt *mt = &sc->sc_mt;
+
+ hidmt_disable(mt);
+ ihidev_close(&sc->sc_hdev);
+}
+
+int
+imt_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct imt_softc *sc = v;
+ struct hidmt *mt = &sc->sc_mt;
+ int rc;
+
+ rc = ihidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
+ if (rc != -1)
+ return rc;
+
+ return hidmt_ioctl(mt, cmd, data, flag, p);
+}