Commit | Line | Data |
---|---|---|
12bd3c8b SW |
1 | /*- |
2 | * Copyright 2010, Gleb Smirnoff <glebius@FreeBSD.org> | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * | |
14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
24 | * SUCH DAMAGE. | |
25 | * | |
26 | * $FreeBSD$ | |
27 | */ | |
28 | ||
29 | /* | |
30 | * http://home.eeti.com.tw/web20/drivers/Software%20Programming%20Guide_v2.0.pdf | |
31 | */ | |
32 | ||
33 | #include <sys/param.h> | |
34 | #include <sys/bus.h> | |
35 | #include <sys/callout.h> | |
36 | #include <sys/conf.h> | |
37 | #include <sys/kernel.h> | |
38 | #include <sys/lock.h> | |
39 | #include <sys/module.h> | |
12bd3c8b SW |
40 | #include <sys/sysctl.h> |
41 | #include <sys/systm.h> | |
42 | ||
8d7664cb SW |
43 | #include <bus/u4b/usb.h> |
44 | #include <bus/u4b/usbdi.h> | |
45 | #include <bus/u4b/usbdi_util.h> | |
46 | #include <bus/u4b/usbhid.h> | |
ef4aa9ff | 47 | #include "usbdevs.h" |
12bd3c8b | 48 | |
12bd3c8b SW |
49 | #include <sys/fcntl.h> |
50 | #include <sys/tty.h> | |
51 | ||
52 | #define USB_DEBUG_VAR uep_debug | |
8d7664cb | 53 | #include <bus/u4b/usb_debug.h> |
12bd3c8b SW |
54 | |
55 | #ifdef USB_DEBUG | |
56 | static int uep_debug = 0; | |
57 | ||
58 | static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW, 0, "USB uep"); | |
a9b765b7 | 59 | SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RW, |
12bd3c8b SW |
60 | &uep_debug, 0, "Debug level"); |
61 | #endif | |
62 | ||
63 | #define UEP_MAX_X 2047 | |
64 | #define UEP_MAX_Y 2047 | |
65 | ||
66 | #define UEP_DOWN 0x01 | |
67 | #define UEP_PACKET_LEN_MAX 16 | |
68 | #define UEP_PACKET_LEN_REPORT 5 | |
69 | #define UEP_PACKET_LEN_REPORT2 6 | |
70 | #define UEP_PACKET_DIAG 0x0a | |
71 | #define UEP_PACKET_REPORT_MASK 0xe0 | |
72 | #define UEP_PACKET_REPORT 0x80 | |
73 | #define UEP_PACKET_REPORT_PRESSURE 0xc0 | |
74 | #define UEP_PACKET_REPORT_PLAYER 0xa0 | |
3b964699 | 75 | #define UEP_PACKET_LEN_MASK |
12bd3c8b SW |
76 | |
77 | #define UEP_FIFO_BUF_SIZE 8 /* bytes */ | |
78 | #define UEP_FIFO_QUEUE_MAXLEN 50 /* units */ | |
79 | ||
80 | enum { | |
81 | UEP_INTR_DT, | |
82 | UEP_N_TRANSFER, | |
83 | }; | |
84 | ||
85 | struct uep_softc { | |
8d7664cb | 86 | struct lock lock; |
12bd3c8b SW |
87 | |
88 | struct usb_xfer *xfer[UEP_N_TRANSFER]; | |
89 | struct usb_fifo_sc fifo; | |
90 | ||
91 | u_int pollrate; | |
92 | u_int state; | |
93 | #define UEP_ENABLED 0x01 | |
94 | ||
95 | /* Reassembling buffer. */ | |
96 | u_char buf[UEP_PACKET_LEN_MAX]; | |
97 | uint8_t buf_len; | |
98 | }; | |
99 | ||
100 | static usb_callback_t uep_intr_callback; | |
101 | ||
102 | static device_probe_t uep_probe; | |
103 | static device_attach_t uep_attach; | |
104 | static device_detach_t uep_detach; | |
105 | ||
106 | static usb_fifo_cmd_t uep_start_read; | |
107 | static usb_fifo_cmd_t uep_stop_read; | |
108 | static usb_fifo_open_t uep_open; | |
109 | static usb_fifo_close_t uep_close; | |
110 | ||
111 | static void uep_put_queue(struct uep_softc *, u_char *); | |
112 | ||
113 | static struct usb_fifo_methods uep_fifo_methods = { | |
114 | .f_open = &uep_open, | |
115 | .f_close = &uep_close, | |
116 | .f_start_read = &uep_start_read, | |
117 | .f_stop_read = &uep_stop_read, | |
118 | .basename[0] = "uep", | |
119 | }; | |
120 | ||
121 | static int | |
122 | get_pkt_len(u_char *buf) | |
123 | { | |
124 | if (buf[0] == UEP_PACKET_DIAG) { | |
125 | int len; | |
126 | ||
127 | len = buf[1] + 2; | |
128 | if (len > UEP_PACKET_LEN_MAX) { | |
129 | DPRINTF("bad packet len %u\n", len); | |
130 | return (UEP_PACKET_LEN_MAX); | |
131 | } | |
132 | ||
133 | return (len); | |
134 | } | |
135 | ||
136 | switch (buf[0] & UEP_PACKET_REPORT_MASK) { | |
137 | case UEP_PACKET_REPORT: | |
138 | return (UEP_PACKET_LEN_REPORT); | |
139 | case UEP_PACKET_REPORT_PRESSURE: | |
140 | case UEP_PACKET_REPORT_PLAYER: | |
141 | case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER: | |
142 | return (UEP_PACKET_LEN_REPORT2); | |
143 | default: | |
144 | DPRINTF("bad packet len 0\n"); | |
145 | return (0); | |
146 | } | |
147 | } | |
148 | ||
149 | static void | |
150 | uep_process_pkt(struct uep_softc *sc, u_char *buf) | |
151 | { | |
152 | int32_t x, y; | |
153 | ||
154 | if ((buf[0] & 0xFE) != 0x80) { | |
155 | DPRINTF("bad input packet format 0x%.2x\n", buf[0]); | |
156 | return; | |
157 | } | |
158 | ||
159 | /* | |
160 | * Packet format is 5 bytes: | |
161 | * | |
162 | * 1000000T | |
163 | * 0000AAAA | |
164 | * 0AAAAAAA | |
165 | * 0000BBBB | |
166 | * 0BBBBBBB | |
167 | * | |
168 | * T: 1=touched 0=not touched | |
169 | * A: bits of axis A position, MSB to LSB | |
170 | * B: bits of axis B position, MSB to LSB | |
171 | * | |
172 | * For the unit I have, which is CTF1020-S from CarTFT.com, | |
173 | * A = X and B = Y. But in NetBSD uep(4) it is other way round :) | |
174 | * | |
175 | * The controller sends a stream of T=1 events while the | |
176 | * panel is touched, followed by a single T=0 event. | |
177 | * | |
178 | */ | |
179 | ||
180 | x = (buf[1] << 7) | buf[2]; | |
181 | y = (buf[3] << 7) | buf[4]; | |
182 | ||
183 | DPRINTFN(2, "x %u y %u\n", x, y); | |
184 | ||
185 | uep_put_queue(sc, buf); | |
186 | } | |
187 | ||
188 | static void | |
189 | uep_intr_callback(struct usb_xfer *xfer, usb_error_t error) | |
190 | { | |
191 | struct uep_softc *sc = usbd_xfer_softc(xfer); | |
192 | int len; | |
193 | ||
194 | usbd_xfer_status(xfer, &len, NULL, NULL, NULL); | |
195 | ||
196 | switch (USB_GET_STATE(xfer)) { | |
197 | case USB_ST_TRANSFERRED: | |
198 | { | |
199 | struct usb_page_cache *pc; | |
200 | u_char buf[17], *p; | |
201 | int pkt_len; | |
202 | ||
203 | if (len > sizeof(buf)) { | |
204 | DPRINTF("bad input length %d\n", len); | |
205 | goto tr_setup; | |
206 | } | |
207 | ||
208 | pc = usbd_xfer_get_frame(xfer, 0); | |
209 | usbd_copy_out(pc, 0, buf, len); | |
210 | ||
211 | /* | |
212 | * The below code mimics Linux a lot. I don't know | |
213 | * why NetBSD reads complete packets, but we need | |
214 | * to reassamble 'em like Linux does (tries?). | |
215 | */ | |
216 | if (sc->buf_len > 0) { | |
217 | int res; | |
218 | ||
219 | if (sc->buf_len == 1) | |
220 | sc->buf[1] = buf[0]; | |
221 | ||
222 | if ((pkt_len = get_pkt_len(sc->buf)) == 0) | |
223 | goto tr_setup; | |
224 | ||
225 | res = pkt_len - sc->buf_len; | |
226 | memcpy(sc->buf + sc->buf_len, buf, res); | |
227 | uep_process_pkt(sc, sc->buf); | |
228 | sc->buf_len = 0; | |
229 | ||
230 | p = buf + res; | |
231 | len -= res; | |
232 | } else | |
233 | p = buf; | |
234 | ||
235 | if (len == 1) { | |
236 | sc->buf[0] = buf[0]; | |
237 | sc->buf_len = 1; | |
238 | ||
239 | goto tr_setup; | |
240 | } | |
241 | ||
242 | while (len > 0) { | |
243 | if ((pkt_len = get_pkt_len(p)) == 0) | |
244 | goto tr_setup; | |
245 | ||
246 | /* full packet: process */ | |
247 | if (pkt_len <= len) { | |
248 | uep_process_pkt(sc, p); | |
249 | } else { | |
250 | /* incomplete packet: save in buffer */ | |
251 | memcpy(sc->buf, p, len); | |
252 | sc->buf_len = len; | |
253 | } | |
254 | p += pkt_len; | |
255 | len -= pkt_len; | |
256 | } | |
257 | } | |
258 | case USB_ST_SETUP: | |
259 | tr_setup: | |
260 | /* check if we can put more data into the FIFO */ | |
261 | if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) != 0) { | |
262 | usbd_xfer_set_frame_len(xfer, 0, | |
263 | usbd_xfer_max_len(xfer)); | |
264 | usbd_transfer_submit(xfer); | |
265 | } | |
266 | break; | |
267 | ||
268 | default: | |
269 | if (error != USB_ERR_CANCELLED) { | |
270 | /* try clear stall first */ | |
271 | usbd_xfer_set_stall(xfer); | |
272 | goto tr_setup; | |
273 | } | |
274 | break; | |
275 | } | |
276 | } | |
277 | ||
278 | static const struct usb_config uep_config[UEP_N_TRANSFER] = { | |
279 | [UEP_INTR_DT] = { | |
280 | .type = UE_INTERRUPT, | |
281 | .endpoint = UE_ADDR_ANY, | |
282 | .direction = UE_DIR_IN, | |
283 | .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, | |
284 | .bufsize = 0, /* use wMaxPacketSize */ | |
285 | .callback = &uep_intr_callback, | |
286 | }, | |
287 | }; | |
288 | ||
289 | static const STRUCT_USB_HOST_ID uep_devs[] = { | |
290 | {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)}, | |
291 | {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)}, | |
292 | {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)}, | |
293 | }; | |
294 | ||
295 | static int | |
296 | uep_probe(device_t dev) | |
297 | { | |
298 | struct usb_attach_arg *uaa = device_get_ivars(dev); | |
299 | ||
300 | if (uaa->usb_mode != USB_MODE_HOST) | |
301 | return (ENXIO); | |
302 | if (uaa->info.bConfigIndex != 0) | |
303 | return (ENXIO); | |
304 | if (uaa->info.bIfaceIndex != 0) | |
305 | return (ENXIO); | |
306 | ||
307 | return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa)); | |
308 | } | |
309 | ||
310 | static int | |
311 | uep_attach(device_t dev) | |
312 | { | |
313 | struct usb_attach_arg *uaa = device_get_ivars(dev); | |
314 | struct uep_softc *sc = device_get_softc(dev); | |
315 | int error; | |
316 | ||
317 | device_set_usb_desc(dev); | |
318 | ||
8d7664cb | 319 | lockinit(&sc->lock, "uep lock", 0, 0); |
12bd3c8b SW |
320 | |
321 | error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, | |
8d7664cb | 322 | sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->lock); |
12bd3c8b SW |
323 | |
324 | if (error) { | |
325 | DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); | |
326 | goto detach; | |
327 | } | |
328 | ||
8d7664cb | 329 | error = usb_fifo_attach(uaa->device, sc, &sc->lock, &uep_fifo_methods, |
12bd3c8b SW |
330 | &sc->fifo, device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, |
331 | UID_ROOT, GID_OPERATOR, 0644); | |
332 | ||
333 | if (error) { | |
334 | DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error)); | |
335 | goto detach; | |
336 | } | |
337 | ||
338 | sc->buf_len = 0; | |
339 | ||
3b964699 | 340 | return (0); |
12bd3c8b SW |
341 | |
342 | detach: | |
343 | uep_detach(dev); | |
344 | ||
345 | return (ENOMEM); /* XXX */ | |
346 | } | |
347 | ||
348 | static int | |
349 | uep_detach(device_t dev) | |
350 | { | |
351 | struct uep_softc *sc = device_get_softc(dev); | |
352 | ||
353 | usb_fifo_detach(&sc->fifo); | |
354 | ||
355 | usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER); | |
356 | ||
8d7664cb | 357 | lockuninit(&sc->lock); |
12bd3c8b SW |
358 | |
359 | return (0); | |
360 | } | |
361 | ||
362 | static void | |
363 | uep_start_read(struct usb_fifo *fifo) | |
364 | { | |
365 | struct uep_softc *sc = usb_fifo_softc(fifo); | |
366 | u_int rate; | |
367 | ||
368 | if ((rate = sc->pollrate) > 1000) | |
369 | rate = 1000; | |
370 | ||
371 | if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) { | |
372 | usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); | |
373 | usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate); | |
374 | sc->pollrate = 0; | |
375 | } | |
376 | ||
377 | usbd_transfer_start(sc->xfer[UEP_INTR_DT]); | |
378 | } | |
379 | ||
380 | static void | |
381 | uep_stop_read(struct usb_fifo *fifo) | |
382 | { | |
383 | struct uep_softc *sc = usb_fifo_softc(fifo); | |
384 | ||
385 | usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); | |
386 | } | |
387 | ||
388 | static void | |
389 | uep_put_queue(struct uep_softc *sc, u_char *buf) | |
390 | { | |
391 | usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf, | |
392 | UEP_PACKET_LEN_REPORT, 1); | |
393 | } | |
394 | ||
395 | static int | |
396 | uep_open(struct usb_fifo *fifo, int fflags) | |
397 | { | |
398 | if (fflags & FREAD) { | |
399 | struct uep_softc *sc = usb_fifo_softc(fifo); | |
400 | ||
401 | if (sc->state & UEP_ENABLED) | |
402 | return (EBUSY); | |
403 | if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE, | |
404 | UEP_FIFO_QUEUE_MAXLEN)) | |
405 | return (ENOMEM); | |
406 | ||
407 | sc->state |= UEP_ENABLED; | |
408 | } | |
409 | ||
410 | return (0); | |
411 | } | |
412 | ||
413 | static void | |
414 | uep_close(struct usb_fifo *fifo, int fflags) | |
415 | { | |
416 | if (fflags & FREAD) { | |
417 | struct uep_softc *sc = usb_fifo_softc(fifo); | |
418 | ||
419 | sc->state &= ~(UEP_ENABLED); | |
420 | usb_fifo_free_buffer(fifo); | |
421 | } | |
422 | } | |
423 | ||
424 | static devclass_t uep_devclass; | |
425 | ||
426 | static device_method_t uep_methods[] = { | |
427 | DEVMETHOD(device_probe, uep_probe), | |
428 | DEVMETHOD(device_attach, uep_attach), | |
429 | DEVMETHOD(device_detach, uep_detach), | |
d3c9c58e | 430 | DEVMETHOD_END |
12bd3c8b SW |
431 | }; |
432 | ||
433 | static driver_t uep_driver = { | |
434 | .name = "uep", | |
435 | .methods = uep_methods, | |
436 | .size = sizeof(struct uep_softc), | |
437 | }; | |
438 | ||
439 | DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL); | |
440 | MODULE_DEPEND(uep, usb, 1, 1, 1); | |
441 | MODULE_VERSION(uep, 1); |