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