1 /* $NetBSD: uts.c,v 1.16 2023/05/10 00:12:44 riastradh Exp $ */
2
3 /*
4 * Copyright (c) 2012 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Pierre Pronchery (khorben@defora.org).
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * USB generic Touch Screen driver.
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: uts.c,v 1.16 2023/05/10 00:12:44 riastradh Exp $");
38
39 #ifdef _KERNEL_OPT
40 #include "opt_usb.h"
41 #endif
42
43 #include <sys/param.h>
44 #include <sys/systm.h>
45 #include <sys/kernel.h>
46 #include <sys/device.h>
47 #include <sys/ioctl.h>
48 #include <sys/vnode.h>
49
50 #include <dev/usb/usb.h>
51 #include <dev/usb/usbhid.h>
52
53 #include <dev/usb/usbdi.h>
54 #include <dev/usb/usbdi_util.h>
55 #include <dev/usb/usbdevs.h>
56 #include <dev/usb/usb_quirks.h>
57 #include <dev/usb/uhidev.h>
58 #include <dev/hid/hid.h>
59
60 #include <dev/wscons/wsconsio.h>
61 #include <dev/wscons/wsmousevar.h>
62 #include <dev/wscons/tpcalibvar.h>
63
64 #ifdef UTS_DEBUG
65 #define DPRINTF(x) if (utsdebug) printf x
66 #define DPRINTFN(n,x) if (utsdebug>(n)) printf x
67 int utsdebug = 0;
68 #else
69 #define DPRINTF(x)
70 #define DPRINTFN(n,x)
71 #endif
72
73
74 struct uts_softc {
75 device_t sc_dev;
76 struct uhidev *sc_hdev;
77
78 struct hid_location sc_loc_x, sc_loc_y, sc_loc_z;
79 struct hid_location sc_loc_btn;
80
81 int sc_enabled;
82
83 int flags; /* device configuration */
84 #define UTS_ABS 0x1 /* absolute position */
85
86 uint32_t sc_buttons; /* touchscreen button status */
87 device_t sc_wsmousedev;
88 struct tpcalib_softc sc_tpcalib; /* calibration */
89 struct wsmouse_calibcoords sc_calibcoords;
90
91 char sc_dying;
92 };
93
94 #define TSCREEN_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
95
96 Static void uts_intr(void *, void *, u_int);
97
98 Static int uts_enable(void *);
99 Static void uts_disable(void *);
100 Static int uts_ioctl(void *, u_long, void *, int, struct lwp *);
101
102 Static const struct wsmouse_accessops uts_accessops = {
103 uts_enable,
104 uts_ioctl,
105 uts_disable,
106 };
107
108 Static int uts_match(device_t, cfdata_t, void *);
109 Static void uts_attach(device_t, device_t, void *);
110 Static void uts_childdet(device_t, device_t);
111 Static int uts_detach(device_t, int);
112 Static int uts_activate(device_t, enum devact);
113
114
115
116 CFATTACH_DECL2_NEW(uts, sizeof(struct uts_softc), uts_match, uts_attach,
117 uts_detach, uts_activate, NULL, uts_childdet);
118
119 Static int
uts_match(device_t parent,cfdata_t match,void * aux)120 uts_match(device_t parent, cfdata_t match, void *aux)
121 {
122 struct uhidev_attach_arg *uha = aux;
123 int size;
124 void *desc;
125
126 uhidev_get_report_desc(uha->parent, &desc, &size);
127 if (!hid_is_collection(desc, size, uha->reportid,
128 HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCH_SCREEN)) &&
129 !hid_is_collection(desc, size, uha->reportid,
130 HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER)))
131 return UMATCH_NONE;
132
133 return UMATCH_IFACECLASS;
134 }
135
136 Static void
uts_attach(device_t parent,device_t self,void * aux)137 uts_attach(device_t parent, device_t self, void *aux)
138 {
139 struct uts_softc *sc = device_private(self);
140 struct uhidev_attach_arg *uha = aux;
141 struct wsmousedev_attach_args a;
142 int size;
143 void *desc;
144 uint32_t flags;
145 struct hid_data * d;
146 struct hid_item item;
147
148 aprint_normal("\n");
149
150 sc->sc_dev = self;
151 sc->sc_hdev = uha->parent;
152
153 uhidev_get_report_desc(uha->parent, &desc, &size);
154
155 if (!pmf_device_register(self, NULL, NULL))
156 aprint_error_dev(self, "couldn't establish power handler\n");
157
158 /* requires HID usage Generic_Desktop:X */
159 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
160 uha->reportid, hid_input, &sc->sc_loc_x, &flags)) {
161 aprint_error_dev(sc->sc_dev,
162 "touchscreen has no X report\n");
163 return;
164 }
165 switch (flags & TSCREEN_FLAGS_MASK) {
166 case 0:
167 sc->flags |= UTS_ABS;
168 break;
169 case HIO_RELATIVE:
170 break;
171 default:
172 aprint_error_dev(sc->sc_dev,
173 "X report 0x%04x not supported\n", flags);
174 return;
175 }
176
177 /* requires HID usage Generic_Desktop:Y */
178 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
179 uha->reportid, hid_input, &sc->sc_loc_y, &flags)) {
180 aprint_error_dev(sc->sc_dev,
181 "touchscreen has no Y report\n");
182 return;
183 }
184 switch (flags & TSCREEN_FLAGS_MASK) {
185 case 0:
186 sc->flags |= UTS_ABS;
187 break;
188 case HIO_RELATIVE:
189 break;
190 default:
191 aprint_error_dev(sc->sc_dev,
192 "Y report 0x%04x not supported\n", flags);
193 return;
194 }
195
196 /* requires HID usage Digitizer:Tip_Switch */
197 if (!hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH),
198 uha->reportid, hid_input, &sc->sc_loc_btn, 0)) {
199 aprint_error_dev(sc->sc_dev,
200 "touchscreen has no tip switch report\n");
201 return;
202 }
203
204 /* requires HID usage Digitizer:In_Range */
205 if (!hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE),
206 uha->reportid, hid_input, &sc->sc_loc_z, &flags)) {
207 if (uha->uiaa->uiaa_vendor == USB_VENDOR_ELAN) {
208 /*
209 * XXX
210 * ELAN touchscreens error out here but still return
211 * valid data
212 */
213 aprint_debug_dev(sc->sc_dev,
214 "ELAN touchscreen found, working around bug.\n");
215 } else {
216 aprint_error_dev(sc->sc_dev,
217 "touchscreen has no range report\n");
218 return;
219 }
220 }
221
222 /* multi-touch support would need HUD_CONTACTID and HUD_CONTACTMAX */
223
224 #ifdef UTS_DEBUG
225 DPRINTF(("uts_attach: sc=%p\n", sc));
226 DPRINTF(("uts_attach: X\t%d/%d\n",
227 sc->sc_loc_x.pos, sc->sc_loc_x.size));
228 DPRINTF(("uts_attach: Y\t%d/%d\n",
229 sc->sc_loc_y.pos, sc->sc_loc_y.size));
230 DPRINTF(("uts_attach: Z\t%d/%d\n",
231 sc->sc_loc_z.pos, sc->sc_loc_z.size));
232 #endif
233
234 a.accessops = &uts_accessops;
235 a.accesscookie = sc;
236
237 sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint, CFARGS_NONE);
238
239 /* calibrate the touchscreen */
240 memset(&sc->sc_calibcoords, 0, sizeof(sc->sc_calibcoords));
241 sc->sc_calibcoords.maxx = 4095;
242 sc->sc_calibcoords.maxy = 4095;
243 sc->sc_calibcoords.samplelen = WSMOUSE_CALIBCOORDS_RESET;
244 d = hid_start_parse(desc, size, hid_input);
245 if (d != NULL) {
246 while (hid_get_item(d, &item)) {
247 if (item.kind != hid_input
248 || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP
249 || item.report_ID != uha->reportid)
250 continue;
251 if (HID_GET_USAGE(item.usage) == HUG_X) {
252 sc->sc_calibcoords.minx = item.logical_minimum;
253 sc->sc_calibcoords.maxx = item.logical_maximum;
254 }
255 if (HID_GET_USAGE(item.usage) == HUG_Y) {
256 sc->sc_calibcoords.miny = item.logical_minimum;
257 sc->sc_calibcoords.maxy = item.logical_maximum;
258 }
259 }
260 hid_end_parse(d);
261 }
262 tpcalib_init(&sc->sc_tpcalib);
263 tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
264 (void *)&sc->sc_calibcoords, 0, 0);
265
266 return;
267 }
268
269 Static int
uts_detach(device_t self,int flags)270 uts_detach(device_t self, int flags)
271 {
272 struct uts_softc *sc = device_private(self);
273 int error;
274
275 __USE(sc);
276 DPRINTF(("uts_detach: sc=%p flags=%d\n", sc, flags));
277
278 error = config_detach_children(self, flags);
279 if (error)
280 return error;
281
282 pmf_device_deregister(self);
283 return 0;
284 }
285
286 Static void
uts_childdet(device_t self,device_t child)287 uts_childdet(device_t self, device_t child)
288 {
289 struct uts_softc *sc = device_private(self);
290
291 KASSERT(sc->sc_wsmousedev == child);
292 sc->sc_wsmousedev = NULL;
293 }
294
295 Static int
uts_activate(device_t self,enum devact act)296 uts_activate(device_t self, enum devact act)
297 {
298 struct uts_softc *sc = device_private(self);
299
300 switch (act) {
301 case DVACT_DEACTIVATE:
302 sc->sc_dying = 1;
303 return 0;
304 default:
305 return EOPNOTSUPP;
306 }
307 }
308
309 Static int
uts_enable(void * v)310 uts_enable(void *v)
311 {
312 struct uts_softc *sc = v;
313
314 DPRINTFN(1,("uts_enable: sc=%p\n", sc));
315
316 if (sc->sc_dying)
317 return EIO;
318
319 if (sc->sc_enabled)
320 return EBUSY;
321
322 sc->sc_enabled = 1;
323 sc->sc_buttons = 0;
324
325 return uhidev_open(sc->sc_hdev, &uts_intr, sc);
326 }
327
328 Static void
uts_disable(void * v)329 uts_disable(void *v)
330 {
331 struct uts_softc *sc = v;
332
333 DPRINTFN(1,("uts_disable: sc=%p\n", sc));
334 #ifdef DIAGNOSTIC
335 if (!sc->sc_enabled) {
336 printf("uts_disable: not enabled\n");
337 return;
338 }
339 #endif
340
341 sc->sc_enabled = 0;
342 uhidev_close(sc->sc_hdev);
343 }
344
345 Static int
uts_ioctl(void * v,u_long cmd,void * data,int flag,struct lwp * l)346 uts_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
347 {
348 struct uts_softc *sc = v;
349
350 switch (cmd) {
351 case WSMOUSEIO_GTYPE:
352 if (sc->flags & UTS_ABS)
353 *(u_int *)data = WSMOUSE_TYPE_TPANEL;
354 else
355 *(u_int *)data = WSMOUSE_TYPE_USB;
356 return 0;
357 case WSMOUSEIO_SCALIBCOORDS:
358 case WSMOUSEIO_GCALIBCOORDS:
359 return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
360 }
361
362 return EPASSTHROUGH;
363 }
364
365 Static void
uts_intr(void * cookie,void * ibuf,u_int len)366 uts_intr(void *cookie, void *ibuf, u_int len)
367 {
368 struct uts_softc *sc = cookie;
369 int dx, dy, dz;
370 uint32_t buttons = 0;
371 int flags, s;
372
373 DPRINTFN(5,("uts_intr: len=%d\n", len));
374
375 flags = WSMOUSE_INPUT_DELTA | WSMOUSE_INPUT_ABSOLUTE_Z;
376
377 dx = hid_get_data(ibuf, &sc->sc_loc_x);
378 if (sc->flags & UTS_ABS) {
379 flags |= (WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
380 dy = hid_get_data(ibuf, &sc->sc_loc_y);
381 tpcalib_trans(&sc->sc_tpcalib, dx, dy, &dx, &dy);
382 } else
383 dy = -hid_get_data(ibuf, &sc->sc_loc_y);
384
385 dz = hid_get_data(ibuf, &sc->sc_loc_z);
386
387 if (hid_get_data(ibuf, &sc->sc_loc_btn))
388 buttons |= 1;
389
390 if (dx != 0 || dy != 0 || dz != 0 || buttons != sc->sc_buttons) {
391 DPRINTFN(10,("uts_intr: x:%d y:%d z:%d buttons:%#x\n",
392 dx, dy, dz, buttons));
393 sc->sc_buttons = buttons;
394 if (sc->sc_wsmousedev != NULL) {
395 s = spltty();
396 wsmouse_input(sc->sc_wsmousedev, buttons, dx, dy, dz, 0,
397 flags);
398 splx(s);
399 }
400 }
401 }
402