xref: /openbsd-src/sys/dev/hid/hidmt.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /* $OpenBSD: hidmt.c,v 1.2 2016/03/30 23:34:12 bru Exp $ */
2 /*
3  * HID multitouch driver for devices conforming to Windows Precision Touchpad
4  * standard
5  *
6  * https://msdn.microsoft.com/en-us/library/windows/hardware/dn467314%28v=vs.85%29.aspx
7  *
8  * Copyright (c) 2016 joshua stein <jcs@openbsd.org>
9  *
10  * Permission to use, copy, modify, and distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 
23 #include <sys/param.h>
24 #include <sys/systm.h>
25 #include <sys/kernel.h>
26 #include <sys/device.h>
27 #include <sys/ioctl.h>
28 #include <sys/malloc.h>
29 
30 #include <dev/wscons/wsconsio.h>
31 #include <dev/wscons/wsmousevar.h>
32 
33 #include <dev/hid/hid.h>
34 #include <dev/hid/hidmtvar.h>
35 
36 /* #define HIDMT_DEBUG */
37 
38 #ifdef HIDMT_DEBUG
39 #define DPRINTF(x) printf x
40 #else
41 #define DPRINTF(x)
42 #endif
43 
44 int
45 hidmt_setup(struct device *self, struct hidmt *mt, void *desc, int dlen)
46 {
47 	struct hid_location cap;
48 	int32_t d;
49 	uint8_t *rep;
50 	int capsize;
51 
52 	struct hid_data *hd;
53 	struct hid_item h;
54 
55 	mt->sc_device = self;
56 	mt->sc_rep_input_size = hid_report_size(desc, dlen, hid_input,
57 	    mt->sc_rep_input);
58 
59 	mt->sc_minx = mt->sc_miny = mt->sc_maxx = mt->sc_maxy = 0;
60 
61 	capsize = hid_report_size(desc, dlen, hid_feature, mt->sc_rep_cap);
62 	rep = malloc(capsize, M_DEVBUF, M_NOWAIT | M_ZERO);
63 
64 	if (mt->hidev_get_report(mt->sc_device, hid_feature, mt->sc_rep_cap,
65 	    rep, capsize)) {
66 		printf("\n%s: failed getting capability report\n",
67 		    self->dv_xname);
68 		return 1;
69 	}
70 
71 	/* find maximum number of contacts being reported per input report */
72 	if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX),
73 	    mt->sc_rep_cap, hid_feature, &cap, NULL)) {
74 		printf("\n%s: can't find maximum contacts\n", self->dv_xname);
75 		return 1;
76 	}
77 
78 	d = hid_get_udata(rep, capsize, &cap);
79 	if (d > HIDMT_MAX_CONTACTS) {
80 		printf("\n%s: contacts %d > max %d\n", self->dv_xname, d,
81 		    HIDMT_MAX_CONTACTS);
82 		return 1;
83 	}
84 	else
85 		mt->sc_num_contacts = d;
86 
87 	/* find whether this is a clickpad or not */
88 	if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE),
89 	    mt->sc_rep_cap, hid_feature, &cap, NULL)) {
90 		printf("\n%s: can't find button type\n", self->dv_xname);
91 		return 1;
92 	}
93 
94 	d = hid_get_udata(rep, capsize, &cap);
95 	mt->sc_clickpad = (d == 0);
96 
97 	/*
98 	 * Walk HID descriptor and store usages we care about to know what to
99 	 * pluck out of input reports.
100 	 */
101 
102 	SIMPLEQ_INIT(&mt->sc_inputs);
103 
104 	hd = hid_start_parse(desc, dlen, hid_input);
105 	while (hid_get_item(hd, &h)) {
106 		struct hidmt_input *input;
107 
108 		if (h.report_ID != mt->sc_rep_input)
109 			continue;
110 
111 		switch (h.usage) {
112 		/* contact level usages */
113 		case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X):
114 			if (h.physical_minimum < mt->sc_minx)
115 				mt->sc_minx = h.physical_minimum;
116 			if (h.physical_maximum > mt->sc_maxx)
117 				mt->sc_maxx = h.physical_maximum;
118 
119 			break;
120 		case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y):
121 			if (h.physical_minimum < mt->sc_miny)
122 				mt->sc_miny = h.physical_minimum;
123 			if (h.physical_maximum > mt->sc_maxy)
124 				mt->sc_maxy = h.physical_maximum;
125 
126 			break;
127 		case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH):
128 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE):
129 		case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH):
130 		case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT):
131 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID):
132 
133 		/* report level usages */
134 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT):
135 		case HID_USAGE2(HUP_BUTTON, 0x01):
136 			break;
137 		default:
138 			continue;
139 		}
140 
141 		input = malloc(sizeof(*input), M_DEVBUF, M_NOWAIT | M_ZERO);
142 		memcpy(&input->loc, &h.loc, sizeof(struct hid_location));
143 		input->usage = h.usage;
144 
145 		SIMPLEQ_INSERT_TAIL(&mt->sc_inputs, input, entry);
146 	}
147 	hid_end_parse(hd);
148 
149 	if (mt->sc_maxx <= 0 || mt->sc_maxy <= 0) {
150 		printf("\n%s: invalid max X/Y %d/%d\n", self->dv_xname,
151 		    mt->sc_maxx, mt->sc_maxy);
152 		return 1;
153 	}
154 
155 	if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT)) {
156 		printf("\n%s: switch to multitouch mode failed\n",
157 		    self->dv_xname);
158 		return 1;
159 	}
160 
161 	return 0;
162 }
163 
164 void
165 hidmt_attach(struct hidmt *mt, const struct wsmouse_accessops *ops)
166 {
167 	struct wsmousedev_attach_args a;
168 
169 	printf(": %spad, %d contact%s\n",
170 	    (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts,
171 	    (mt->sc_num_contacts == 1 ? "" : "s"));
172 
173 	a.accessops = ops;
174 	a.accesscookie = mt->sc_device;
175 	mt->sc_wsmousedev = config_found(mt->sc_device, &a, wsmousedevprint);
176 }
177 
178 int
179 hidmt_detach(struct hidmt *mt, int flags)
180 {
181 	int rv = 0;
182 
183 	if (mt->sc_wsmousedev != NULL)
184 		rv = config_detach(mt->sc_wsmousedev, flags);
185 
186 	return (rv);
187 }
188 
189 int
190 hidmt_set_input_mode(struct hidmt *mt, int mode)
191 {
192 	return mt->hidev_set_report(mt->sc_device, hid_feature,
193 	    mt->sc_rep_config, &mode, 1);
194 }
195 
196 void
197 hidmt_input(struct hidmt *mt, uint8_t *data, u_int len)
198 {
199 	struct hidmt_input *hi;
200 	struct hidmt_contact hc;
201 	int32_t d, firstu = 0;
202 	int contactcount = 0, seencontacts = 0, tips = 0, i, s;
203 
204 	if (len != mt->sc_rep_input_size) {
205 		DPRINTF(("%s: %s: length %d not %d, ignoring\n",
206 		    mt->sc_device->dv_xname, __func__, len,
207 		    mt->sc_rep_input_size));
208 		return;
209 	}
210 
211 	/*
212 	 * "In Parallel mode, devices report all contact information in a
213 	 * single packet. Each physical contact is represented by a logical
214 	 * collection that is embedded in the top-level collection."
215 	 *
216 	 * Since additional contacts that were not present will still be in the
217 	 * report with contactid=0 but contactids are zero-based, find
218 	 * contactcount first.
219 	 */
220 	SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) {
221 		if (hi->usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT))
222 			contactcount = hid_get_udata(data, len,
223 			    &hi->loc);
224 	}
225 
226 	if (!contactcount) {
227 		DPRINTF(("%s: %s: no contactcount in report\n",
228 		    mt->sc_device->dv_xname, __func__));
229 		return;
230 	}
231 
232 	/*
233 	 * Walk through each input we know about and fetch its data from the
234 	 * report, storing it in a temporary contact.  Once we see our first
235 	 * usage again, we'll know we saw all usages being presented for that
236 	 * contact.
237 	 */
238 	bzero(&hc, sizeof(struct hidmt_contact));
239 	SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) {
240 		d = hid_get_udata(data, len, &hi->loc);
241 
242 		if (firstu && hi->usage == firstu) {
243 			if (seencontacts < contactcount &&
244 			    hc.contactid < HIDMT_MAX_CONTACTS) {
245 				hc.seen = 1;
246 				memcpy(&mt->sc_contacts[hc.contactid], &hc,
247 				    sizeof(struct hidmt_contact));
248 				seencontacts++;
249 			}
250 
251 			bzero(&hc, sizeof(struct hidmt_contact));
252 		}
253 		else if (!firstu)
254 			firstu = hi->usage;
255 
256 		switch (hi->usage) {
257 		case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X):
258 			hc.x = d;
259 			break;
260 		case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y):
261 			if (mt->sc_flags & HIDMT_REVY)
262 				hc.y = mt->sc_maxy - d;
263 			else
264 				hc.y = d;
265 			break;
266 		case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH):
267 			hc.tip = d;
268 			if (d)
269 				tips++;
270 			break;
271 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE):
272 			hc.confidence = d;
273 			break;
274 		case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH):
275 			hc.width = d;
276 			break;
277 		case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT):
278 			hc.height = d;
279 			break;
280 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID):
281 			hc.contactid = d;
282 			break;
283 
284 		/* these will only appear once per report */
285 		case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT):
286 			contactcount = d;
287 			break;
288 		case HID_USAGE2(HUP_BUTTON, 0x01):
289 			mt->sc_button = (d != 0);
290 			break;
291 		}
292 	}
293 	if (seencontacts < contactcount && hc.contactid < HIDMT_MAX_CONTACTS) {
294 		hc.seen = 1;
295 		memcpy(&mt->sc_contacts[hc.contactid], &hc,
296 		    sizeof(struct hidmt_contact));
297 		seencontacts++;
298 	}
299 
300 	s = spltty();
301 	for (i = 0; i < HIDMT_MAX_CONTACTS; i++) {
302 		if (!mt->sc_contacts[i].seen)
303 			continue;
304 
305 		mt->sc_contacts[i].seen = 0;
306 
307 		DPRINTF(("%s: %s: contact %d of %d: id %d, x %d, y %d, "
308 		    "touch %d, confidence %d, width %d, height %d "
309 		    "(button %d)\n",
310 		    mt->sc_device->dv_xname, __func__,
311 		    i + 1, contactcount,
312 		    mt->sc_contacts[i].contactid,
313 		    mt->sc_contacts[i].x,
314 		    mt->sc_contacts[i].y,
315 		    mt->sc_contacts[i].tip,
316 		    mt->sc_contacts[i].confidence,
317 		    mt->sc_contacts[i].width,
318 		    mt->sc_contacts[i].height,
319 		    mt->sc_button));
320 
321 		if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence)
322 			continue;
323 
324 		if (mt->sc_wsmode == WSMOUSE_NATIVE) {
325 			int width = 0;
326 			if (mt->sc_contacts[i].tip) {
327 				width = mt->sc_contacts[i].width;
328 				if (width < 50)
329 					width = 50;
330 			}
331 
332 			WSMOUSE_TOUCH(mt->sc_wsmousedev, mt->sc_button,
333 			    (mt->last_x = mt->sc_contacts[i].x),
334 			    (mt->last_y = mt->sc_contacts[i].y),
335 			    width, tips);
336 		} else {
337 			WSMOUSE_INPUT(mt->sc_wsmousedev, mt->sc_button,
338 			    (mt->last_x - mt->sc_contacts[i].x),
339 			    (mt->last_y - mt->sc_contacts[i].y),
340 			    0, 0);
341 			mt->last_x = mt->sc_contacts[i].x;
342 			mt->last_y = mt->sc_contacts[i].y;
343 		}
344 
345 		/*
346 		 * XXX: wscons can only handle one finger of data
347 		 */
348 		break;
349 	}
350 
351 	splx(s);
352 }
353 
354 int
355 hidmt_enable(struct hidmt *mt)
356 {
357 	if (mt->sc_enabled)
358 		return EBUSY;
359 
360 	mt->sc_enabled = 1;
361 
362 	return 0;
363 }
364 
365 int
366 hidmt_ioctl(struct hidmt *mt, u_long cmd, caddr_t data, int flag,
367     struct proc *p)
368 {
369 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
370 	int wsmode;
371 
372 	switch (cmd) {
373 	case WSMOUSEIO_GTYPE:
374 		/*
375 		 * So we can specify our own finger/w values to the
376 		 * xf86-input-synaptics driver like pms(4)
377 		 */
378 		*(u_int *)data = WSMOUSE_TYPE_ELANTECH;
379 		break;
380 
381 	case WSMOUSEIO_GCALIBCOORDS:
382 		wsmc->minx = mt->sc_minx;
383 		wsmc->maxx = mt->sc_maxx;
384 		wsmc->miny = mt->sc_miny;
385 		wsmc->maxy = mt->sc_maxy;
386 		wsmc->swapxy = 0;
387 		wsmc->resx = 0;
388 		wsmc->resy = 0;
389 		break;
390 
391 	case WSMOUSEIO_SETMODE:
392 		wsmode = *(u_int *)data;
393 		if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) {
394 			printf("%s: invalid mode %d\n",
395 			    mt->sc_device->dv_xname, wsmode);
396 			return (EINVAL);
397 		}
398 
399 		DPRINTF(("%s: changing mode to %s\n", mt->sc_device->dv_xname,
400 		    (wsmode == WSMOUSE_COMPAT ? "compat" : "native")));
401 
402 		mt->sc_wsmode = wsmode;
403 		break;
404 
405 	default:
406 		return -1;
407 	}
408 
409 	return 0;
410 }
411 
412 void
413 hidmt_disable(struct hidmt *mt)
414 {
415 	mt->sc_enabled = 0;
416 }
417