1 /*        $NetBSD: uts.c,v 1.18 2025/04/07 11:13:15 hans 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.18 2025/04/07 11:13:15 hans 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           int error;
314 
315           DPRINTFN(1,("uts_enable: sc=%p\n", sc));
316 
317           if (sc->sc_dying)
318                     return EIO;
319 
320           if (sc->sc_enabled)
321                     return EBUSY;
322 
323           error = uhidev_open(sc->sc_hdev, &uts_intr, sc);
324 
325           if (error != 0)
326                     return error;
327 
328           sc->sc_enabled = 1;
329           sc->sc_buttons = 0;
330 
331           return 0;
332 }
333 
334 Static void
uts_disable(void * v)335 uts_disable(void *v)
336 {
337           struct uts_softc *sc = v;
338 
339           DPRINTFN(1,("uts_disable: sc=%p\n", sc));
340 
341           if (!sc->sc_enabled)
342                     return;
343 
344           sc->sc_enabled = 0;
345           uhidev_close(sc->sc_hdev);
346 }
347 
348 Static int
uts_ioctl(void * v,u_long cmd,void * data,int flag,struct lwp * l)349 uts_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
350 {
351           struct uts_softc *sc = v;
352 
353           switch (cmd) {
354           case WSMOUSEIO_GTYPE:
355                     if (sc->flags & UTS_ABS)
356                               *(u_int *)data = WSMOUSE_TYPE_TPANEL;
357                     else
358                               *(u_int *)data = WSMOUSE_TYPE_USB;
359                     return 0;
360           case WSMOUSEIO_SCALIBCOORDS:
361           case WSMOUSEIO_GCALIBCOORDS:
362                     return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
363           }
364 
365           return EPASSTHROUGH;
366 }
367 
368 Static void
uts_intr(void * cookie,void * ibuf,u_int len)369 uts_intr(void *cookie, void *ibuf, u_int len)
370 {
371           struct uts_softc *sc = cookie;
372           int dx, dy, dz;
373           uint32_t buttons = 0;
374           int flags, s;
375 
376           DPRINTFN(5,("uts_intr: len=%d\n", len));
377 
378           flags = WSMOUSE_INPUT_DELTA | WSMOUSE_INPUT_ABSOLUTE_Z;
379 
380           dx = hid_get_data(ibuf, &sc->sc_loc_x);
381           if (sc->flags & UTS_ABS) {
382                     flags |= (WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
383                     dy = hid_get_data(ibuf, &sc->sc_loc_y);
384                     tpcalib_trans(&sc->sc_tpcalib, dx, dy, &dx, &dy);
385           } else
386                     dy = -hid_get_data(ibuf, &sc->sc_loc_y);
387 
388           dz = hid_get_data(ibuf, &sc->sc_loc_z);
389 
390           if (hid_get_data(ibuf, &sc->sc_loc_btn))
391                     buttons |= 1;
392 
393           if (dx != 0 || dy != 0 || dz != 0 || buttons != sc->sc_buttons) {
394                     DPRINTFN(10,("uts_intr: x:%d y:%d z:%d buttons:%#x\n",
395                         dx, dy, dz, buttons));
396                     sc->sc_buttons = buttons;
397                     if (sc->sc_wsmousedev != NULL) {
398                               s = spltty();
399                               wsmouse_input(sc->sc_wsmousedev, buttons, dx, dy, dz, 0,
400                                   flags);
401                               splx(s);
402                     }
403           }
404 }
405