adb_kbd.c revision 1.3
1/*	$NetBSD: adb_kbd.c,v 1.3 2007/02/20 01:27:25 macallan Exp $	*/
2
3/*
4 * Copyright (C) 1998	Colin Wood
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by Colin Wood.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__KERNEL_RCSID(0, "$NetBSD: adb_kbd.c,v 1.3 2007/02/20 01:27:25 macallan Exp $");
35
36#include <sys/param.h>
37#include <sys/device.h>
38#include <sys/fcntl.h>
39#include <sys/poll.h>
40#include <sys/select.h>
41#include <sys/proc.h>
42#include <sys/systm.h>
43#include <sys/kernel.h>
44#include <sys/sysctl.h>
45
46#include <dev/wscons/wsconsio.h>
47#include <dev/wscons/wskbdvar.h>
48#include <dev/wscons/wsksymdef.h>
49#include <dev/wscons/wsksymvar.h>
50#include <dev/wscons/wsmousevar.h>
51
52#include <dev/sysmon/sysmonvar.h>
53#include <dev/sysmon/sysmon_taskq.h>
54
55#include <machine/autoconf.h>
56#include <machine/keyboard.h>
57#include <machine/adbsys.h>
58
59#include <dev/adb/adbvar.h>
60#include <dev/adb/adb_keymap.h>
61
62#include "adbdebug.h"
63
64struct adbkbd_softc {
65	struct device sc_dev;
66	struct adb_device *sc_adbdev;
67	struct adb_bus_accessops *sc_ops;
68	struct device *sc_wskbddev;
69	struct device *sc_wsmousedev;
70	struct sysmon_pswitch sc_sm_pbutton;
71	int sc_leds;
72	int sc_have_led_control;
73	int sc_msg_len;
74	int sc_event;
75	int sc_poll;
76	int sc_polled_chars;
77	int sc_trans[3];
78	int sc_capslock;
79#ifdef WSDISPLAY_COMPAT_RAWKBD
80	int sc_rawkbd;
81#endif
82	uint8_t sc_buffer[16];
83	uint8_t sc_pollbuf[16];
84	uint8_t sc_us;
85};
86
87/*
88 * Function declarations.
89 */
90static int	adbkbd_match(struct device *, struct cfdata *, void *);
91static void	adbkbd_attach(struct device *, struct device *, void *);
92
93static void	adbkbd_initleds(struct adbkbd_softc *);
94static void	adbkbd_keys(struct adbkbd_softc *, uint8_t, uint8_t);
95static inline void adbkbd_key(struct adbkbd_softc *, uint8_t);
96static int	adbkbd_wait(struct adbkbd_softc *, int);
97
98/* Driver definition. */
99CFATTACH_DECL(adbkbd, sizeof(struct adbkbd_softc),
100    adbkbd_match, adbkbd_attach, NULL, NULL);
101
102extern struct cfdriver akbd_cd;
103
104static int adbkbd_enable(void *, int);
105static int adbkbd_ioctl(void *, u_long, caddr_t, int, struct lwp *);
106static void adbkbd_set_leds(void *, int);
107static void adbkbd_handler(void *, int, uint8_t *);
108
109struct wskbd_accessops adbkbd_accessops = {
110	adbkbd_enable,
111	adbkbd_set_leds,
112	adbkbd_ioctl,
113};
114
115static void adbkbd_cngetc(void *, u_int *, int *);
116static void adbkbd_cnpollc(void *, int);
117
118struct wskbd_consops adbkbd_consops = {
119	adbkbd_cngetc,
120	adbkbd_cnpollc,
121};
122
123struct wskbd_mapdata adbkbd_keymapdata = {
124	akbd_keydesctab,
125#ifdef AKBD_LAYOUT
126	AKBD_LAYOUT,
127#else
128	KB_US,
129#endif
130};
131
132static int adbkms_enable(void *);
133static int adbkms_ioctl(void *, u_long, caddr_t, int, struct lwp *);
134static void adbkms_disable(void *);
135
136const struct wsmouse_accessops adbkms_accessops = {
137	adbkms_enable,
138	adbkms_ioctl,
139	adbkms_disable,
140};
141
142static int  adbkbd_sysctl_button(SYSCTLFN_ARGS);
143static void adbkbd_setup_sysctl(struct adbkbd_softc *);
144
145#ifdef ADBKBD_DEBUG
146#define DPRINTF printf
147#else
148#define DPRINTF while (0) printf
149#endif
150
151static int adbkbd_is_console = 0;
152static int adbkbd_console_attached = 0;
153
154static int
155adbkbd_match(parent, cf, aux)
156	struct device *parent;
157	struct cfdata *cf;
158	void   *aux;
159{
160	struct adb_attach_args *aaa = aux;
161
162	if (aaa->dev->original_addr == ADBADDR_KBD)
163		return 1;
164	else
165		return 0;
166}
167
168static void
169adbkbd_attach(struct device *parent, struct device *self, void *aux)
170{
171	struct adbkbd_softc *sc = (struct adbkbd_softc *)self;
172	struct adb_attach_args *aaa = aux;
173	short cmd;
174	struct wskbddev_attach_args a;
175	struct wsmousedev_attach_args am;
176
177	sc->sc_ops = aaa->ops;
178	sc->sc_adbdev = aaa->dev;
179	sc->sc_adbdev->cookie = sc;
180	sc->sc_adbdev->handler = adbkbd_handler;
181	sc->sc_us = ADBTALK(sc->sc_adbdev->current_addr, 0);
182
183	sc->sc_leds = 0;	/* initially off */
184	sc->sc_have_led_control = 0;
185	sc->sc_msg_len = 0;
186	sc->sc_poll = 0;
187	sc->sc_capslock = 0;
188	sc->sc_trans[1] = 103;	/* F11 */
189	sc->sc_trans[2] = 111;	/* F12 */
190
191	printf(" addr %d ", sc->sc_adbdev->current_addr);
192
193	switch (sc->sc_adbdev->handler_id) {
194	case ADB_STDKBD:
195		printf("standard keyboard\n");
196		break;
197	case ADB_ISOKBD:
198		printf("standard keyboard (ISO layout)\n");
199		break;
200	case ADB_EXTKBD:
201		cmd = ADBTALK(sc->sc_adbdev->current_addr, 1);
202		sc->sc_msg_len = 0;
203		sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
204		adbkbd_wait(sc, 10);
205
206		/* Ignore Logitech MouseMan/Trackman pseudo keyboard */
207		/* XXX needs testing */
208		if (sc->sc_buffer[2] == 0x9a && sc->sc_buffer[3] == 0x20) {
209			printf("Mouseman (non-EMP) pseudo keyboard\n");
210			return;
211		} else if (sc->sc_buffer[2] == 0x9a &&
212		    sc->sc_buffer[3] == 0x21) {
213			printf("Trackman (non-EMP) pseudo keyboard\n");
214			return;
215		} else {
216			printf("extended keyboard\n");
217			adbkbd_initleds(sc);
218		}
219		break;
220	case ADB_EXTISOKBD:
221		printf("extended keyboard (ISO layout)\n");
222		adbkbd_initleds(sc);
223		break;
224	case ADB_KBDII:
225		printf("keyboard II\n");
226		break;
227	case ADB_ISOKBDII:
228		printf("keyboard II (ISO layout)\n");
229		break;
230	case ADB_PBKBD:
231		printf("PowerBook keyboard\n");
232		break;
233	case ADB_PBISOKBD:
234		printf("PowerBook keyboard (ISO layout)\n");
235		break;
236	case ADB_ADJKPD:
237		printf("adjustable keypad\n");
238		break;
239	case ADB_ADJKBD:
240		printf("adjustable keyboard\n");
241		break;
242	case ADB_ADJISOKBD:
243		printf("adjustable keyboard (ISO layout)\n");
244		break;
245	case ADB_ADJJAPKBD:
246		printf("adjustable keyboard (Japanese layout)\n");
247		break;
248	case ADB_PBEXTISOKBD:
249		printf("PowerBook extended keyboard (ISO layout)\n");
250		break;
251	case ADB_PBEXTJAPKBD:
252		printf("PowerBook extended keyboard (Japanese layout)\n");
253		break;
254	case ADB_JPKBDII:
255		printf("keyboard II (Japanese layout)\n");
256		break;
257	case ADB_PBEXTKBD:
258		printf("PowerBook extended keyboard\n");
259		break;
260	case ADB_DESIGNKBD:
261		printf("extended keyboard\n");
262		adbkbd_initleds(sc);
263		break;
264	case ADB_PBJPKBD:
265		printf("PowerBook keyboard (Japanese layout)\n");
266		break;
267	case ADB_PBG3KBD:
268		printf("PowerBook G3 keyboard\n");
269		break;
270	case ADB_PBG3JPKBD:
271		printf("PowerBook G3 keyboard (Japanese layout)\n");
272		break;
273	case ADB_IBOOKKBD:
274		printf("iBook keyboard\n");
275		break;
276	default:
277		printf("mapped device (%d)\n", sc->sc_adbdev->handler_id);
278		break;
279	}
280
281	if (adbkbd_is_console && (adbkbd_console_attached == 0)) {
282		wskbd_cnattach(&adbkbd_consops, sc, &adbkbd_keymapdata);
283		adbkbd_console_attached = 1;
284		a.console = 1;
285	} else {
286		a.console = 0;
287	}
288	a.keymap = &adbkbd_keymapdata;
289	a.accessops = &adbkbd_accessops;
290	a.accesscookie = sc;
291
292	sc->sc_wskbddev = config_found_ia(self, "wskbddev", &a, wskbddevprint);
293
294	/* attach the mouse device */
295	am.accessops = &adbkms_accessops;
296	am.accesscookie = sc;
297	sc->sc_wsmousedev = config_found_ia(self, "wsmousedev", &am, wsmousedevprint);
298
299	if (sc->sc_wsmousedev != NULL)
300		adbkbd_setup_sysctl(sc);
301
302	/* finally register the power button */
303	sysmon_task_queue_init();
304	memset(&sc->sc_sm_pbutton, 0, sizeof(struct sysmon_pswitch));
305	sc->sc_sm_pbutton.smpsw_name = sc->sc_dev.dv_xname;
306	sc->sc_sm_pbutton.smpsw_type = PSWITCH_TYPE_POWER;
307	if (sysmon_pswitch_register(&sc->sc_sm_pbutton) != 0)
308		printf("%s: unable to register power button with sysmon\n",
309		    sc->sc_dev.dv_xname);
310}
311
312static void
313adbkbd_handler(void *cookie, int len, uint8_t *data)
314{
315	struct adbkbd_softc *sc = cookie;
316
317#ifdef ADBKBD_DEBUG
318	int i;
319	printf("%s: %02x - ", sc->sc_dev.dv_xname, sc->sc_us);
320	for (i = 0; i < len; i++) {
321		printf(" %02x", data[i]);
322	}
323	printf("\n");
324#endif
325	if (len >= 2) {
326		if (data[1] == sc->sc_us) {
327			adbkbd_keys(sc, data[2], data[3]);
328			return;
329		} else {
330			memcpy(sc->sc_buffer, data, len);
331		}
332		sc->sc_msg_len = len;
333		wakeup(&sc->sc_event);
334	} else {
335		DPRINTF("bogus message\n");
336	}
337}
338
339static int
340adbkbd_wait(struct adbkbd_softc *sc, int timeout)
341{
342	int cnt = 0;
343
344	if (sc->sc_poll) {
345		while (sc->sc_msg_len == 0) {
346			sc->sc_ops->poll(sc->sc_ops->cookie);
347		}
348	} else {
349		while ((sc->sc_msg_len == 0) && (cnt < timeout)) {
350			tsleep(&sc->sc_event, 0, "adbkbdio", hz);
351			cnt++;
352		}
353	}
354	return (sc->sc_msg_len > 0);
355}
356
357static void
358adbkbd_keys(struct adbkbd_softc *sc, uint8_t k1, uint8_t k2)
359{
360
361	/* keyboard event processing */
362
363	DPRINTF("[%02x %02x]", k1, k2);
364
365	if (((k1 == k2) && (k1 == 0x7f)) || (k1 == 0x7e)) {
366
367		/* power button, report to sysmon */
368		sysmon_pswitch_event(&sc->sc_sm_pbutton,
369		    ADBK_PRESS(k1) ? PSWITCH_EVENT_PRESSED :
370		    PSWITCH_EVENT_RELEASED);
371	} else {
372
373		adbkbd_key(sc, k1);
374		if (k2 != 0xff)
375			adbkbd_key(sc, k2);
376	}
377}
378
379static inline void
380adbkbd_key(struct adbkbd_softc *sc, uint8_t k)
381{
382
383	if (sc->sc_poll) {
384		if (sc->sc_polled_chars >= 16) {
385			printf("%s: polling buffer is full\n",
386			    sc->sc_dev.dv_xname);
387		}
388		sc->sc_pollbuf[sc->sc_polled_chars] = k;
389		sc->sc_polled_chars++;
390		return;
391	}
392
393	/* translate some keys to mouse events */
394	if (sc->sc_wsmousedev != NULL) {
395		if (ADBK_KEYVAL(k) == sc->sc_trans[1]) {
396			wsmouse_input(sc->sc_wsmousedev, ADBK_PRESS(k) ? 2 : 0,
397			      0, 0, 0, 0,
398			      WSMOUSE_INPUT_DELTA);
399			return;
400		}
401		if (ADBK_KEYVAL(k) == sc->sc_trans[2]) {
402			wsmouse_input(sc->sc_wsmousedev, ADBK_PRESS(k) ? 4 : 0,
403			      0, 0, 0, 0,
404			      WSMOUSE_INPUT_DELTA);
405			return;
406		}
407	}
408#ifdef WSDISPLAY_COMPAT_RAWKBD
409	if (sc->sc_rawkbd) {
410		char cbuf[2];
411		int s;
412		int j = 0;
413		int c = keyboard[ADBK_KEYVAL(k)][3]
414
415		if (k & 0x80)
416			cbuf[j++] = 0xe0;
417
418		cbuf[j++] = (c & 0x7f) | (ADBK_PRESS(k)? 0 : 0x80);
419
420		s = spltty();
421		wskbd_rawinput(sc->sc_wskbddev, cbuf, j);
422		splx(s);
423	} else {
424#endif
425
426	if (ADBK_KEYVAL(k) == 0x39) {
427		/* caps lock - send up and down */
428		if (ADBK_PRESS(k) != sc->sc_capslock) {
429			sc->sc_capslock = ADBK_PRESS(k);
430			wskbd_input(sc->sc_wskbddev,
431			    WSCONS_EVENT_KEY_DOWN, 0x39);
432			wskbd_input(sc->sc_wskbddev,
433			    WSCONS_EVENT_KEY_UP, 0x39);
434		}
435	} else {
436		/* normal event */
437		int type;
438
439		type = ADBK_PRESS(k) ?
440		    WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP;
441		wskbd_input(sc->sc_wskbddev, type, ADBK_KEYVAL(k));
442	}
443#ifdef WSDISPLAY_COMPAT_RAWKBD
444	}
445#endif
446}
447
448/*
449 * Set the keyboard LED's.
450 *
451 * Automatically translates from ioctl/softc format to the
452 * actual keyboard register format
453 */
454static void
455adbkbd_set_leds(void *cookie, int leds)
456{
457	struct adbkbd_softc *sc = cookie;
458	int aleds;
459	short cmd;
460	uint8_t buffer[2];
461
462	DPRINTF("adbkbd_set_leds: %02x\n", leds);
463	if ((leds & 0x07) == (sc->sc_leds & 0x07))
464		return;
465
466 	if (sc->sc_have_led_control) {
467
468		aleds = (~leds & 0x04) | 3;
469		if (leds & 1)
470			aleds &= ~2;
471		if (leds & 2)
472			aleds &= ~1;
473
474		buffer[0] = 0xff;
475		buffer[1] = aleds | 0xf8;
476
477		cmd = ADBLISTEN(sc->sc_adbdev->current_addr, 2);
478		sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 2, buffer);
479	}
480
481	sc->sc_leds = leds & 7;
482}
483
484static void
485adbkbd_initleds(struct adbkbd_softc *sc)
486{
487	short cmd;
488
489	/* talk R2 */
490	cmd = ADBTALK(sc->sc_adbdev->current_addr, 2);
491	sc->sc_msg_len = 0;
492	sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
493	if (!adbkbd_wait(sc, 10)) {
494		printf("unable to read LED state\n");
495		return;
496	}
497	sc->sc_have_led_control = 1;
498	DPRINTF("have LED control\n");
499	return;
500}
501
502static int
503adbkbd_enable(void *v, int on)
504{
505	return 0;
506}
507
508static int
509adbkbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct lwp *l)
510{
511	struct adbkbd_softc *sc = (struct adbkbd_softc *) v;
512
513	switch (cmd) {
514
515	case WSKBDIO_GTYPE:
516		*(int *)data = WSKBD_TYPE_ADB;
517		return 0;
518	case WSKBDIO_SETLEDS:
519		adbkbd_set_leds(sc, *(int *)data);
520		return 0;
521	case WSKBDIO_GETLEDS:
522		*(int *)data = sc->sc_leds;
523		return 0;
524#ifdef WSDISPLAY_COMPAT_RAWKBD
525	case WSKBDIO_SETMODE:
526		sc->sc_rawkbd = *(int *)data == WSKBD_RAW;
527		return 0;
528#endif
529	}
530
531	return EPASSTHROUGH;
532}
533
534int
535adbkbd_cnattach()
536{
537
538	adbkbd_is_console = 1;
539	return 0;
540}
541
542static void
543adbkbd_cngetc(void *v, u_int *type, int *data)
544{
545	struct adbkbd_softc *sc = v;
546	int key, press, val;
547	int s;
548
549	s = splhigh();
550
551	KASSERT(sc->sc_poll);
552
553	DPRINTF("polling...");
554	while (sc->sc_polled_chars == 0) {
555		sc->sc_ops->poll(sc->sc_ops->cookie);
556	}
557	DPRINTF(" got one\n");
558	splx(s);
559
560	key = sc->sc_pollbuf[0];
561	sc->sc_polled_chars--;
562	memmove(sc->sc_pollbuf, sc->sc_pollbuf + 1,
563		sc->sc_polled_chars);
564
565	press = ADBK_PRESS(key);
566	val = ADBK_KEYVAL(key);
567
568	*data = val;
569	*type = press ? WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP;
570}
571
572static void
573adbkbd_cnpollc(void *v, int on)
574{
575	struct adbkbd_softc *sc = v;
576
577	sc->sc_poll = on;
578	if (!on) {
579		int i;
580
581		/* feed the poll buffer's content to wskbd */
582		for (i = 0; i < sc->sc_polled_chars; i++) {
583			adbkbd_key(sc, sc->sc_pollbuf[i]);
584		}
585		sc->sc_polled_chars = 0;
586	}
587}
588
589/* stuff for the pseudo mouse */
590static int
591adbkms_enable(void *v)
592{
593	return 0;
594}
595
596static int
597adbkms_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct lwp *l)
598{
599
600	switch (cmd) {
601	case WSMOUSEIO_GTYPE:
602		*(u_int *)data = WSMOUSE_TYPE_PSEUDO;
603		break;
604
605	default:
606		return (EPASSTHROUGH);
607	}
608	return (0);
609}
610
611static void
612adbkms_disable(void *v)
613{
614}
615
616static void
617adbkbd_setup_sysctl(struct adbkbd_softc *sc)
618{
619	struct sysctlnode *node, *me;
620	int ret;
621
622	DPRINTF("%s: sysctl setup\n", sc->sc_dev.dv_xname);
623	ret = sysctl_createv(NULL, 0, NULL, (const struct sysctlnode **)&me,
624	       CTLFLAG_READWRITE,
625	       CTLTYPE_NODE, sc->sc_dev.dv_xname, NULL,
626	       NULL, 0, NULL, 0,
627	       CTL_MACHDEP, CTL_CREATE, CTL_EOL);
628
629	ret = sysctl_createv(NULL, 0, NULL,
630	    (const struct sysctlnode **)&node,
631	    CTLFLAG_READWRITE | CTLFLAG_OWNDESC | CTLFLAG_IMMEDIATE,
632	    CTLTYPE_INT, "middle", "middle mouse button", adbkbd_sysctl_button,
633		    1, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);
634	node->sysctl_data = sc;
635
636	ret = sysctl_createv(NULL, 0, NULL,
637	    (const struct sysctlnode **)&node,
638	    CTLFLAG_READWRITE | CTLFLAG_OWNDESC | CTLFLAG_IMMEDIATE,
639	    CTLTYPE_INT, "right", "right mouse button", adbkbd_sysctl_button,
640		    2, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);
641	node->sysctl_data = sc;
642}
643
644static int
645adbkbd_sysctl_button(SYSCTLFN_ARGS)
646{
647	struct sysctlnode node = *rnode;
648	struct adbkbd_softc *sc=(struct adbkbd_softc *)node.sysctl_data;
649	const int *np = newp;
650	int btn = node.sysctl_idata, reg;
651
652	DPRINTF("adbkbd_sysctl_button %d\n", btn);
653	node.sysctl_idata = sc->sc_trans[btn];
654	reg = sc->sc_trans[btn];
655	if (np) {
656		/* we're asked to write */
657		node.sysctl_data = &reg;
658		if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
659
660			sc->sc_trans[btn] = node.sysctl_idata;
661			return 0;
662		}
663		return EINVAL;
664	} else {
665		node.sysctl_size = 4;
666		return (sysctl_lookup(SYSCTLFN_CALL(&node)));
667	}
668}
669
670SYSCTL_SETUP(sysctl_adbkbdtrans_setup, "adbkbd translator setup")
671{
672
673	sysctl_createv(NULL, 0, NULL, NULL,
674		       CTLFLAG_PERMANENT,
675		       CTLTYPE_NODE, "machdep", NULL,
676		       NULL, 0, NULL, 0,
677		       CTL_MACHDEP, CTL_EOL);
678}
679