1 | /* $NetBSD: btms.c,v 1.12 2014/12/13 19:28:55 nonaka Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 1998 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Lennart Augustsson (lennart@augustsson.net) at |
9 | * Carlstedt Research & Technology. |
10 | * |
11 | * Redistribution and use in source and binary forms, with or without |
12 | * modification, are permitted provided that the following conditions |
13 | * are met: |
14 | * 1. Redistributions of source code must retain the above copyright |
15 | * notice, this list of conditions and the following disclaimer. |
16 | * 2. Redistributions in binary form must reproduce the above copyright |
17 | * notice, this list of conditions and the following disclaimer in the |
18 | * documentation and/or other materials provided with the distribution. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
21 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
24 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 | * POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | /*- |
34 | * Copyright (c) 2006 Itronix Inc. |
35 | * All rights reserved. |
36 | * |
37 | * Written by Iain Hibbert for Itronix Inc. |
38 | * |
39 | * Redistribution and use in source and binary forms, with or without |
40 | * modification, are permitted provided that the following conditions |
41 | * are met: |
42 | * 1. Redistributions of source code must retain the above copyright |
43 | * notice, this list of conditions and the following disclaimer. |
44 | * 2. Redistributions in binary form must reproduce the above copyright |
45 | * notice, this list of conditions and the following disclaimer in the |
46 | * documentation and/or other materials provided with the distribution. |
47 | * 3. The name of Itronix Inc. may not be used to endorse |
48 | * or promote products derived from this software without specific |
49 | * prior written permission. |
50 | * |
51 | * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND |
52 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
53 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
54 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY |
55 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
56 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
57 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
58 | * ON ANY THEORY OF LIABILITY, WHETHER IN |
59 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
60 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
61 | * POSSIBILITY OF SUCH DAMAGE. |
62 | */ |
63 | |
64 | /* |
65 | * based on dev/usb/ums.c |
66 | */ |
67 | |
68 | #include <sys/cdefs.h> |
69 | __KERNEL_RCSID(0, "$NetBSD: btms.c,v 1.12 2014/12/13 19:28:55 nonaka Exp $" ); |
70 | |
71 | #include <sys/param.h> |
72 | #include <sys/conf.h> |
73 | #include <sys/device.h> |
74 | #include <sys/proc.h> |
75 | #include <sys/systm.h> |
76 | |
77 | #include <netbt/bluetooth.h> |
78 | |
79 | #include <dev/bluetooth/bthid.h> |
80 | #include <dev/bluetooth/bthidev.h> |
81 | |
82 | #include <dev/usb/hid.h> |
83 | #include <dev/usb/usb.h> |
84 | #include <dev/usb/usbhid.h> |
85 | |
86 | #include <dev/wscons/wsconsio.h> |
87 | #include <dev/wscons/wsmousevar.h> |
88 | |
89 | #ifdef BTMS_DEBUG |
90 | int btms_debug = 0; |
91 | #define BTMSDBG(s) if (btms_debug) printf s |
92 | #define BTMSDBGN(n,s) if (btms_debug > (n)) printf s |
93 | #else |
94 | #define BTMSDBG(s) |
95 | #define BTMSDBGN(n,s) |
96 | #endif |
97 | |
98 | #define MAX_BUTTONS 31 |
99 | #define BUTTON(n) (1 << (((n) == 1 || (n) == 2) ? 3 - (n) : (n))) |
100 | #define NOTMOUSE(f) (((f) & (HIO_CONST | HIO_RELATIVE)) != HIO_RELATIVE) |
101 | |
102 | struct btms_softc { |
103 | struct bthidev sc_hidev; /* device+ */ |
104 | |
105 | device_t sc_wsmouse; /* child */ |
106 | int sc_enabled; |
107 | uint16_t sc_flags; |
108 | |
109 | /* locators */ |
110 | struct hid_location sc_loc_x; |
111 | struct hid_location sc_loc_y; |
112 | struct hid_location sc_loc_z; |
113 | struct hid_location sc_loc_w; |
114 | struct hid_location sc_loc_button[MAX_BUTTONS]; |
115 | |
116 | int sc_num_buttons; |
117 | uint32_t sc_buttons; |
118 | }; |
119 | |
120 | /* sc_flags */ |
121 | #define BTMS_REVZ (1 << 0) /* reverse Z direction */ |
122 | #define BTMS_HASZ (1 << 1) /* has Z direction */ |
123 | #define BTMS_HASW (1 << 2) /* has W direction */ |
124 | |
125 | /* autoconf(9) methods */ |
126 | static int btms_match(device_t, cfdata_t, void *); |
127 | static void btms_attach(device_t, device_t, void *); |
128 | static int btms_detach(device_t, int); |
129 | |
130 | CFATTACH_DECL_NEW(btms, sizeof(struct btms_softc), |
131 | btms_match, btms_attach, btms_detach, NULL); |
132 | |
133 | /* wsmouse(4) accessops */ |
134 | static int btms_wsmouse_enable(void *); |
135 | static int btms_wsmouse_ioctl(void *, unsigned long, void *, int, struct lwp *); |
136 | static void btms_wsmouse_disable(void *); |
137 | |
138 | static const struct wsmouse_accessops btms_wsmouse_accessops = { |
139 | btms_wsmouse_enable, |
140 | btms_wsmouse_ioctl, |
141 | btms_wsmouse_disable, |
142 | }; |
143 | |
144 | /* bthid methods */ |
145 | static void btms_input(struct bthidev *, uint8_t *, int); |
146 | |
147 | #ifdef BTMS_DEBUG |
148 | static void btms_print_device(struct btms_softc *); |
149 | #endif |
150 | |
151 | /* |
152 | * quirks |
153 | */ |
154 | static const struct btms_quirk { |
155 | int vendor; |
156 | int product; |
157 | |
158 | uint32_t flags; |
159 | #define BTMS_QUIRK_ELECOM __BIT(0) |
160 | } btms_quirk_table[] = { |
161 | /* ELECOM M-XG2BB */ |
162 | { 0x056e, 0x00d2, BTMS_QUIRK_ELECOM }, |
163 | }; |
164 | |
165 | static uint32_t |
166 | btms_lookup_quirk_flags(int vendor, int product) |
167 | { |
168 | const struct btms_quirk *q; |
169 | int i; |
170 | |
171 | for (i = 0; i < __arraycount(btms_quirk_table); ++i) { |
172 | q = &btms_quirk_table[i]; |
173 | if (vendor == q->vendor && product == q->product) |
174 | return q->flags; |
175 | } |
176 | return 0; |
177 | } |
178 | |
179 | static void btms_fixup_elecom(struct bthidev_attach_args *,struct btms_softc *); |
180 | |
181 | /***************************************************************************** |
182 | * |
183 | * btms autoconf(9) routines |
184 | */ |
185 | |
186 | static int |
187 | btms_match(device_t parent, cfdata_t match, void *aux) |
188 | { |
189 | struct bthidev_attach_args *ba = aux; |
190 | |
191 | if (hid_is_collection(ba->ba_desc, ba->ba_dlen, ba->ba_id, |
192 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) |
193 | return 1; |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static void |
199 | btms_attach(device_t parent, device_t self, void *aux) |
200 | { |
201 | struct btms_softc *sc = device_private(self); |
202 | struct bthidev_attach_args *ba = aux; |
203 | struct wsmousedev_attach_args wsma; |
204 | struct hid_location *zloc; |
205 | uint32_t flags, quirks; |
206 | int i, hl; |
207 | |
208 | ba->ba_input = btms_input; |
209 | |
210 | quirks = btms_lookup_quirk_flags(ba->ba_vendor, ba->ba_product); |
211 | |
212 | /* control the horizontal */ |
213 | hl = hid_locate(ba->ba_desc, |
214 | ba->ba_dlen, |
215 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), |
216 | ba->ba_id, |
217 | hid_input, |
218 | &sc->sc_loc_x, |
219 | &flags); |
220 | |
221 | if (hl == 0 || NOTMOUSE(flags)) { |
222 | aprint_error("X report 0x%04x not supported\n" , flags); |
223 | return; |
224 | } |
225 | |
226 | /* control the vertical */ |
227 | hl = hid_locate(ba->ba_desc, |
228 | ba->ba_dlen, |
229 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), |
230 | ba->ba_id, |
231 | hid_input, |
232 | &sc->sc_loc_y, |
233 | &flags); |
234 | |
235 | if (hl == 0 || NOTMOUSE(flags)) { |
236 | aprint_error("Y report 0x%04x not supported\n" , flags); |
237 | return; |
238 | } |
239 | |
240 | /* Try the wheel first as the Z activator since it's tradition. */ |
241 | hl = hid_locate(ba->ba_desc, |
242 | ba->ba_dlen, |
243 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), |
244 | ba->ba_id, |
245 | hid_input, |
246 | &sc->sc_loc_z, |
247 | &flags); |
248 | |
249 | zloc = &sc->sc_loc_z; |
250 | if (hl) { |
251 | if (NOTMOUSE(flags)) { |
252 | aprint_error("Wheel report 0x%04x ignored\n" , flags); |
253 | |
254 | /* ignore Bad Z coord */ |
255 | sc->sc_loc_z.size = 0; |
256 | } else { |
257 | sc->sc_flags |= BTMS_HASZ; |
258 | /* Wheels need the Z axis reversed. */ |
259 | sc->sc_flags ^= BTMS_REVZ; |
260 | /* Put Z on the W coordinate */ |
261 | zloc = &sc->sc_loc_w; |
262 | } |
263 | } |
264 | |
265 | hl = hid_locate(ba->ba_desc, |
266 | ba->ba_dlen, |
267 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), |
268 | ba->ba_id, |
269 | hid_input, |
270 | zloc, |
271 | &flags); |
272 | |
273 | /* |
274 | * The horizontal component of the scrollball can also be given by |
275 | * Application Control Pan in the Consumer page, so if we didnt see |
276 | * any Z then check that. |
277 | */ |
278 | if (!hl) { |
279 | hl = hid_locate(ba->ba_desc, |
280 | ba->ba_dlen, |
281 | HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN), |
282 | ba->ba_id, |
283 | hid_input, |
284 | zloc, |
285 | &flags); |
286 | } |
287 | |
288 | if (hl) { |
289 | if (NOTMOUSE(flags)) |
290 | zloc->size = 0; /* ignore Z */ |
291 | else { |
292 | if (sc->sc_flags & BTMS_HASZ) |
293 | sc->sc_flags |= BTMS_HASW; |
294 | else |
295 | sc->sc_flags |= BTMS_HASZ; |
296 | } |
297 | } |
298 | |
299 | for (i = 1 ; i <= MAX_BUTTONS ; i++) { |
300 | hl = hid_locate(ba->ba_desc, |
301 | ba->ba_dlen, |
302 | HID_USAGE2(HUP_BUTTON, i), |
303 | ba->ba_id, |
304 | hid_input, |
305 | &sc->sc_loc_button[i - 1], |
306 | NULL); |
307 | |
308 | if (hl == 0) |
309 | break; |
310 | } |
311 | sc->sc_num_buttons = i - 1; |
312 | |
313 | if (ISSET(quirks, BTMS_QUIRK_ELECOM)) |
314 | btms_fixup_elecom(ba, sc); |
315 | |
316 | aprint_normal(": %d button%s%s%s%s.\n" , |
317 | sc->sc_num_buttons, |
318 | sc->sc_num_buttons == 1 ? "" : "s" , |
319 | sc->sc_flags & BTMS_HASW ? ", W" : "" , |
320 | sc->sc_flags & BTMS_HASZ ? " and Z dir" : "" , |
321 | sc->sc_flags & BTMS_HASW ? "s" : "" ); |
322 | #ifdef BTMS_DEBUG |
323 | if (btms_debug) |
324 | btms_print_device(sc); |
325 | #endif |
326 | |
327 | wsma.accessops = &btms_wsmouse_accessops; |
328 | wsma.accesscookie = sc; |
329 | |
330 | sc->sc_wsmouse = config_found(self, &wsma, wsmousedevprint); |
331 | |
332 | pmf_device_register(self, NULL, NULL); |
333 | } |
334 | |
335 | static int |
336 | btms_detach(device_t self, int flags) |
337 | { |
338 | struct btms_softc *sc = device_private(self); |
339 | int err = 0; |
340 | |
341 | pmf_device_deregister(self); |
342 | |
343 | if (sc->sc_wsmouse != NULL) { |
344 | err = config_detach(sc->sc_wsmouse, flags); |
345 | sc->sc_wsmouse = NULL; |
346 | } |
347 | |
348 | return err; |
349 | } |
350 | |
351 | /***************************************************************************** |
352 | * |
353 | * wsmouse(4) accessops |
354 | */ |
355 | |
356 | static int |
357 | btms_wsmouse_enable(void *cookie) |
358 | { |
359 | struct btms_softc *sc = cookie; |
360 | |
361 | if (sc->sc_enabled) |
362 | return EBUSY; |
363 | |
364 | sc->sc_enabled = 1; |
365 | return 0; |
366 | } |
367 | |
368 | static int |
369 | btms_wsmouse_ioctl(void *cookie, unsigned long cmd, void *data, |
370 | int flag, struct lwp *l) |
371 | { |
372 | |
373 | switch (cmd) { |
374 | case WSMOUSEIO_GTYPE: |
375 | *(uint *)data = WSMOUSE_TYPE_BLUETOOTH; |
376 | break; |
377 | |
378 | default: |
379 | return EPASSTHROUGH; |
380 | } |
381 | |
382 | return 0; |
383 | } |
384 | |
385 | static void |
386 | btms_wsmouse_disable(void *cookie) |
387 | { |
388 | struct btms_softc *sc = cookie; |
389 | |
390 | sc->sc_enabled = 0; |
391 | } |
392 | |
393 | /***************************************************************************** |
394 | * |
395 | * btms input routine, called from our parent |
396 | */ |
397 | |
398 | static void |
399 | btms_input(struct bthidev *hidev, uint8_t *data, int len) |
400 | { |
401 | struct btms_softc *sc = (struct btms_softc *)hidev; |
402 | int dx, dy, dz, dw; |
403 | uint32_t buttons; |
404 | int i, s; |
405 | |
406 | if (sc->sc_wsmouse == NULL || sc->sc_enabled == 0) |
407 | return; |
408 | |
409 | #ifdef BTMS_DEBUG |
410 | if (btms_debug > 9) { |
411 | printf("%s: data: " , __func__); |
412 | for (i = 0; i < len; ++i) { |
413 | printf("%02x" , data[i]); |
414 | } |
415 | printf("\n" ); |
416 | } |
417 | #endif |
418 | |
419 | dx = hid_get_data(data, &sc->sc_loc_x); |
420 | dy = -hid_get_data(data, &sc->sc_loc_y); |
421 | dz = hid_get_data(data, &sc->sc_loc_z); |
422 | dw = hid_get_data(data, &sc->sc_loc_w); |
423 | |
424 | if (sc->sc_flags & BTMS_REVZ) |
425 | dz = -dz; |
426 | |
427 | buttons = 0; |
428 | for (i = 0 ; i < sc->sc_num_buttons ; i++) |
429 | if (hid_get_data(data, &sc->sc_loc_button[i])) |
430 | buttons |= BUTTON(i); |
431 | |
432 | BTMSDBGN(9,("%s: dx=%d, dy=%d, dz=%d, dw=%d, buttons=0x%08x\n" , |
433 | __func__, dx, dy, dz, dw, buttons)); |
434 | if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || buttons != sc->sc_buttons) { |
435 | sc->sc_buttons = buttons; |
436 | |
437 | s = spltty(); |
438 | wsmouse_input(sc->sc_wsmouse, |
439 | buttons, |
440 | dx, dy, dz, dw, |
441 | WSMOUSE_INPUT_DELTA); |
442 | splx(s); |
443 | } |
444 | } |
445 | |
446 | #ifdef BTMS_DEBUG |
447 | static void |
448 | btms_print_device(struct btms_softc *sc) |
449 | { |
450 | int i; |
451 | |
452 | printf("btms: X: pos=%d, size=%d\n" , |
453 | sc->sc_loc_x.pos, sc->sc_loc_x.size); |
454 | printf("btms: Y: pos=%d, size=%d\n" , |
455 | sc->sc_loc_y.pos, sc->sc_loc_y.size); |
456 | if (sc->sc_flags & BTMS_HASZ) { |
457 | printf("btms: Z: pos=%d, size=%d%s\n" , |
458 | sc->sc_loc_z.pos, sc->sc_loc_z.size, |
459 | ((sc->sc_flags & BTMS_REVZ) ? ", REVZ" : "" )); |
460 | } |
461 | if (sc->sc_flags & BTMS_HASW) { |
462 | printf("btms: W: pos=%d, size=%d\n" , |
463 | sc->sc_loc_w.pos, sc->sc_loc_w.size); |
464 | } |
465 | |
466 | for (i = 0; i < sc->sc_num_buttons; ++i) { |
467 | printf("btms: button%d: pos=%d, size=%d\n" , i, |
468 | sc->sc_loc_button[i].pos, sc->sc_loc_button[i].size); |
469 | } |
470 | } |
471 | #endif |
472 | |
473 | /***************************************************************************** |
474 | * |
475 | * fixup routines |
476 | */ |
477 | static void |
478 | btms_fixup_elecom(struct bthidev_attach_args *ba, struct btms_softc *sc) |
479 | { |
480 | |
481 | switch (ba->ba_product) { |
482 | case 0x00d2: /* M-XG2BB */ |
483 | /* invalid Wheel and AC_Pan */ |
484 | BTMSDBG(("%s: fixup ELECOM M-XG2BB\n" , __func__)); |
485 | sc->sc_loc_z.pos = 40; |
486 | sc->sc_loc_z.size = 8; |
487 | sc->sc_loc_w.pos = 0; |
488 | sc->sc_loc_w.size = 0; |
489 | sc->sc_flags = BTMS_HASZ | BTMS_REVZ; |
490 | break; |
491 | } |
492 | } |
493 | |