1/* $NetBSD: uts.c,v 1.5 2016/04/27 19:35:17 jakllsch 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.5 2016/04/27 19:35:17 jakllsch 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/usb/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
67int utsdebug = 0;
68#else
69#define DPRINTF(x)
70#define DPRINTFN(n,x)
71#endif
72
73
74struct uts_softc {
75 struct uhidev sc_hdev;
76
77 struct hid_location sc_loc_x, sc_loc_y, sc_loc_z;
78 struct hid_location sc_loc_btn;
79
80 int sc_enabled;
81
82 int flags; /* device configuration */
83#define UTS_ABS 0x1 /* absolute position */
84
85 uint32_t sc_buttons; /* touchscreen button status */
86 device_t sc_wsmousedev;
87 struct tpcalib_softc sc_tpcalib; /* calibration */
88 struct wsmouse_calibcoords sc_calibcoords;
89
90 char sc_dying;
91};
92
93#define TSCREEN_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
94
95Static void uts_intr(struct uhidev *, void *, u_int);
96
97Static int uts_enable(void *);
98Static void uts_disable(void *);
99Static int uts_ioctl(void *, u_long, void *, int, struct lwp *);
100
101Static const struct wsmouse_accessops uts_accessops = {
102 uts_enable,
103 uts_ioctl,
104 uts_disable,
105};
106
107Static int uts_match(device_t, cfdata_t, void *);
108Static void uts_attach(device_t, device_t, void *);
109Static void uts_childdet(device_t, device_t);
110Static int uts_detach(device_t, int);
111Static int uts_activate(device_t, enum devact);
112
113extern struct cfdriver uts_cd;
114
115CFATTACH_DECL2_NEW(uts, sizeof(struct uts_softc), uts_match, uts_attach,
116 uts_detach, uts_activate, NULL, uts_childdet);
117
118Static int
119uts_match(device_t parent, cfdata_t match, void *aux)
120{
121 struct uhidev_attach_arg *uha = aux;
122 int size;
123 void *desc;
124
125 uhidev_get_report_desc(uha->parent, &desc, &size);
126 if (!hid_is_collection(desc, size, uha->reportid,
127 HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCH_SCREEN)) &&
128 !hid_is_collection(desc, size, uha->reportid,
129 HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER)))
130 return UMATCH_NONE;
131
132 return UMATCH_IFACECLASS;
133}
134
135Static void
136uts_attach(device_t parent, device_t self, void *aux)
137{
138 struct uts_softc *sc = device_private(self);
139 struct uhidev_attach_arg *uha = aux;
140 struct wsmousedev_attach_args a;
141 int size;
142 void *desc;
143 uint32_t flags;
144 struct hid_data * d;
145 struct hid_item item;
146
147 aprint_naive("\n");
148
149 sc->sc_hdev.sc_dev = self;
150 sc->sc_hdev.sc_intr = uts_intr;
151 sc->sc_hdev.sc_parent = uha->parent;
152 sc->sc_hdev.sc_report_id = uha->reportid;
153
154 uhidev_get_report_desc(uha->parent, &desc, &size);
155
156 if (!pmf_device_register(self, NULL, NULL))
157 aprint_error_dev(self, "couldn't establish power handler\n");
158
159 /* requires HID usage Generic_Desktop:X */
160 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
161 uha->reportid, hid_input, &sc->sc_loc_x, &flags)) {
162 aprint_error_dev(sc->sc_hdev.sc_dev,
163 "touchscreen has no X report\n");
164 return;
165 }
166 switch (flags & TSCREEN_FLAGS_MASK) {
167 case 0:
168 sc->flags |= UTS_ABS;
169 break;
170 case HIO_RELATIVE:
171 break;
172 default:
173 aprint_error_dev(sc->sc_hdev.sc_dev,
174 "X report 0x%04x not supported\n", flags);
175 return;
176 }
177
178 /* requires HID usage Generic_Desktop:Y */
179 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
180 uha->reportid, hid_input, &sc->sc_loc_y, &flags)) {
181 aprint_error_dev(sc->sc_hdev.sc_dev,
182 "touchscreen has no Y report\n");
183 return;
184 }
185 switch (flags & TSCREEN_FLAGS_MASK) {
186 case 0:
187 sc->flags |= UTS_ABS;
188 break;
189 case HIO_RELATIVE:
190 break;
191 default:
192 aprint_error_dev(sc->sc_hdev.sc_dev,
193 "Y report 0x%04x not supported\n", flags);
194 return;
195 }
196
197 /* requires HID usage Digitizer:Tip_Switch */
198 if (!hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH),
199 uha->reportid, hid_input, &sc->sc_loc_btn, 0)) {
200 aprint_error_dev(sc->sc_hdev.sc_dev,
201 "touchscreen has no tip switch report\n");
202 return;
203 }
204
205 /* requires HID usage Digitizer:In_Range */
206 if (!hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE),
207 uha->reportid, hid_input, &sc->sc_loc_z, &flags)) {
208 aprint_error_dev(sc->sc_hdev.sc_dev,
209 "touchscreen has no range report\n");
210 return;
211 }
212
213 /* multi-touch support would need HUD_CONTACTID and HUD_CONTACTMAX */
214
215#ifdef UTS_DEBUG
216 DPRINTF(("uts_attach: sc=%p\n", sc));
217 DPRINTF(("uts_attach: X\t%d/%d\n",
218 sc->sc_loc_x.pos, sc->sc_loc_x.size));
219 DPRINTF(("uts_attach: Y\t%d/%d\n",
220 sc->sc_loc_y.pos, sc->sc_loc_y.size));
221 DPRINTF(("uts_attach: Z\t%d/%d\n",
222 sc->sc_loc_z.pos, sc->sc_loc_z.size));
223#endif
224
225 a.accessops = &uts_accessops;
226 a.accesscookie = sc;
227
228 sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
229
230 /* calibrate the touchscreen */
231 memset(&sc->sc_calibcoords, 0, sizeof(sc->sc_calibcoords));
232 sc->sc_calibcoords.maxx = 4095;
233 sc->sc_calibcoords.maxy = 4095;
234 sc->sc_calibcoords.samplelen = WSMOUSE_CALIBCOORDS_RESET;
235 d = hid_start_parse(desc, size, hid_input);
236 while (hid_get_item(d, &item)) {
237 if (item.kind != hid_input
238 || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP
239 || item.report_ID != sc->sc_hdev.sc_report_id)
240 continue;
241 if (HID_GET_USAGE(item.usage) == HUG_X) {
242 sc->sc_calibcoords.minx = item.logical_minimum;
243 sc->sc_calibcoords.maxx = item.logical_maximum;
244 }
245 if (HID_GET_USAGE(item.usage) == HUG_Y) {
246 sc->sc_calibcoords.miny = item.logical_minimum;
247 sc->sc_calibcoords.maxy = item.logical_maximum;
248 }
249 }
250 hid_end_parse(d);
251
252 tpcalib_init(&sc->sc_tpcalib);
253 tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
254 (void *)&sc->sc_calibcoords, 0, 0);
255
256 return;
257}
258
259Static int
260uts_detach(device_t self, int flags)
261{
262 struct uts_softc *sc = device_private(self);
263 int rv = 0;
264
265 DPRINTF(("uts_detach: sc=%p flags=%d\n", sc, flags));
266
267 if (sc->sc_wsmousedev != NULL)
268 rv = config_detach(sc->sc_wsmousedev, flags);
269
270 pmf_device_deregister(self);
271
272 return rv;
273}
274
275Static void
276uts_childdet(device_t self, device_t child)
277{
278 struct uts_softc *sc = device_private(self);
279
280 KASSERT(sc->sc_wsmousedev == child);
281 sc->sc_wsmousedev = NULL;
282}
283
284Static int
285uts_activate(device_t self, enum devact act)
286{
287 struct uts_softc *sc = device_private(self);
288
289 switch (act) {
290 case DVACT_DEACTIVATE:
291 sc->sc_dying = 1;
292 return 0;
293 default:
294 return EOPNOTSUPP;
295 }
296}
297
298Static int
299uts_enable(void *v)
300{
301 struct uts_softc *sc = v;
302
303 DPRINTFN(1,("uts_enable: sc=%p\n", sc));
304
305 if (sc->sc_dying)
306 return EIO;
307
308 if (sc->sc_enabled)
309 return EBUSY;
310
311 sc->sc_enabled = 1;
312 sc->sc_buttons = 0;
313
314 return uhidev_open(&sc->sc_hdev);
315}
316
317Static void
318uts_disable(void *v)
319{
320 struct uts_softc *sc = v;
321
322 DPRINTFN(1,("uts_disable: sc=%p\n", sc));
323#ifdef DIAGNOSTIC
324 if (!sc->sc_enabled) {
325 printf("uts_disable: not enabled\n");
326 return;
327 }
328#endif
329
330 sc->sc_enabled = 0;
331 uhidev_close(&sc->sc_hdev);
332}
333
334Static int
335uts_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
336{
337 struct uts_softc *sc = v;
338
339 switch (cmd) {
340 case WSMOUSEIO_GTYPE:
341 if (sc->flags & UTS_ABS)
342 *(u_int *)data = WSMOUSE_TYPE_TPANEL;
343 else
344 *(u_int *)data = WSMOUSE_TYPE_USB;
345 return 0;
346 case WSMOUSEIO_SCALIBCOORDS:
347 case WSMOUSEIO_GCALIBCOORDS:
348 return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
349 }
350
351 return EPASSTHROUGH;
352}
353
354Static void
355uts_intr(struct uhidev *addr, void *ibuf, u_int len)
356{
357 struct uts_softc *sc = (struct uts_softc *)addr;
358 int dx, dy, dz;
359 uint32_t buttons = 0;
360 int flags, s;
361
362 DPRINTFN(5,("uts_intr: len=%d\n", len));
363
364 flags = WSMOUSE_INPUT_DELTA | WSMOUSE_INPUT_ABSOLUTE_Z;
365
366 dx = hid_get_data(ibuf, &sc->sc_loc_x);
367 if (sc->flags & UTS_ABS) {
368 flags |= (WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
369 dy = hid_get_data(ibuf, &sc->sc_loc_y);
370 tpcalib_trans(&sc->sc_tpcalib, dx, dy, &dx, &dy);
371 } else
372 dy = -hid_get_data(ibuf, &sc->sc_loc_y);
373
374 dz = hid_get_data(ibuf, &sc->sc_loc_z);
375
376 if (hid_get_data(ibuf, &sc->sc_loc_btn))
377 buttons |= 1;
378
379 if (dx != 0 || dy != 0 || dz != 0 || buttons != sc->sc_buttons) {
380 DPRINTFN(10,("uts_intr: x:%d y:%d z:%d buttons:0x%x\n",
381 dx, dy, dz, buttons));
382 sc->sc_buttons = buttons;
383 if (sc->sc_wsmousedev != NULL) {
384 s = spltty();
385 wsmouse_input(sc->sc_wsmousedev, buttons, dx, dy, dz, 0,
386 flags);
387 splx(s);
388 }
389 }
390}
391