g42xxeb_kmkbd.c revision 1.14 1 /* $NetBSD: g42xxeb_kmkbd.c,v 1.14 2012/10/27 17:17:47 chs Exp $ */
2
3 /*-
4 * Copyright (c) 2002, 2003, 2005 Genetec corp.
5 * All rights reserved.
6 *
7 * 4x5 matrix key switch driver for TWINTAIL.
8 * Written by Hiroyuki Bessho for Genetec corp.
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 * 3. Neither the name of The NetBSD Foundation nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 /*
36 * Use on-board matrix switches as wskbd.
37 */
38
39 #include <sys/cdefs.h>
40 __KERNEL_RCSID(0, "$NetBSD: g42xxeb_kmkbd.c,v 1.14 2012/10/27 17:17:47 chs Exp $" );
41
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/device.h>
45 #include <sys/malloc.h>
46 #include <sys/ioctl.h>
47 #include <sys/callout.h>
48 #include <sys/kernel.h> /* for hz */
49
50 #include <sys/bus.h>
51
52 #include <dev/wscons/wsconsio.h>
53 #include <dev/wscons/wskbdvar.h>
54 #include <dev/wscons/wsksymdef.h>
55 #include <dev/wscons/wsksymvar.h>
56
57 #include <arch/evbarm/g42xxeb/g42xxeb_var.h>
58
59 #include "locators.h"
60 #include "opt_wsdisplay_compat.h"
61
62 #define DEBOUNCE_TICKS ((hz<=50)?1:hz/50) /* 20ms */
63 #define RELEASE_WATCH_TICKS (hz/10) /* 100ms */
64
65 #define NUMKEYS (4*5) /* the number of keys */
66
67 struct kmkbd_softc {
68 device_t dev;
69
70 device_t wskbddev;
71 void *ih; /* interrupt handler */
72 struct callout callout;
73
74 uint32_t notified_bits; /* reported state of keys */
75 uint32_t last_bits; /* used for debounce */
76 u_char debounce_counter;
77 #define DEBOUNCE_COUNT 3
78 u_char polling;
79 u_char rawkbd;
80 enum kmkbd_state {
81 ST_INIT,
82 ST_DISABLED,
83 ST_ALL_UP, /* waiting for interrupt */
84 ST_DEBOUNCE, /* doing debounce */
85 ST_KEY_PRESSED /* some keys are pressed */
86 } state;
87 };
88
89
90 int kmkbd_match(device_t, cfdata_t, void *);
91 void kmkbd_attach(device_t, device_t, void *);
92
93 CFATTACH_DECL_NEW(kmkbd, sizeof(struct kmkbd_softc),
94 kmkbd_match, kmkbd_attach, NULL, NULL);
95
96 static int kmkbd_enable(void *, int);
97 static void kmkbd_set_leds(void *, int);
98 static int kmkbd_ioctl(void *, u_long, void *, int, struct lwp *);
99
100 const struct wskbd_accessops kmkbd_accessops = {
101 kmkbd_enable,
102 kmkbd_set_leds,
103 kmkbd_ioctl,
104 };
105
106 #if 0
107 void kmkbd_cngetc(void *, u_int *, int *);
108 void kmkbd_cnpollc(void *, int);
109 void kmkbd_cnbell(void *, u_int, u_int, u_int);
110
111 const struct wskbd_consops kmkbd_consops = {
112 kmkbd_cngetc,
113 kmkbd_cnpollc,
114 kmkbd_cnbell,
115 };
116 #endif
117
118 static const keysym_t kmkbd_keydesc_0[] = {
119 /* pos normal shifted */
120 KS_KEYCODE(0), KS_a, KS_A,
121 KS_KEYCODE(1), KS_b, KS_B,
122 KS_KEYCODE(2), KS_c, KS_C,
123 KS_KEYCODE(3), KS_d, KS_D,
124 KS_KEYCODE(4), KS_e, KS_E,
125 KS_KEYCODE(5), KS_f, KS_F,
126 KS_KEYCODE(6), KS_g, KS_G,
127 KS_KEYCODE(7), KS_h, KS_H,
128 KS_KEYCODE(8), KS_i, KS_I,
129 KS_KEYCODE(9), KS_j, KS_J,
130 KS_KEYCODE(10), KS_k, KS_K,
131 KS_KEYCODE(11), KS_l, KS_L,
132 KS_KEYCODE(12), KS_m, KS_M,
133 KS_KEYCODE(13), KS_n, KS_N,
134 KS_KEYCODE(14), KS_o, KS_O,
135 KS_KEYCODE(15), KS_p, KS_P,
136 KS_KEYCODE(16), KS_q, KS_Q,
137 KS_KEYCODE(17), KS_r, KS_R,
138 KS_KEYCODE(18), '\003', '\003',
139 KS_KEYCODE(19), KS_Return, KS_Linefeed,
140 };
141
142 #define KBD_MAP(name, base, map) \
143 { name, base, sizeof(map)/sizeof(keysym_t), map }
144
145 static const struct wscons_keydesc kmkbd_keydesctab[] = {
146 KBD_MAP(KB_MACHDEP, 0, kmkbd_keydesc_0),
147 {0, 0, 0, 0}
148 };
149
150 const struct wskbd_mapdata kmkbd_keymapdata = {
151 kmkbd_keydesctab,
152 #ifdef KMKBD_LAYOUT
153 KMKBD_LAYOUT,
154 #else
155 KB_MACHDEP,
156 #endif
157 };
158
159 /*
160 * Hackish support for a bell on the PC Keyboard; when a suitable feeper
161 * is found, it attaches itself into the pckbd driver here.
162 */
163 void (*kmkbd_bell_fn)(void *, u_int, u_int, u_int, int);
164 void *kmkbd_bell_fn_arg;
165
166 void kmkbd_bell(u_int, u_int, u_int, int);
167 void kmkbd_hookup_bell(void (* fn)(void *, u_int, u_int, u_int, int), void *arg);
168
169 static int kmkbd_intr(void *);
170 static void kmkbd_new_state(struct kmkbd_softc *, enum kmkbd_state);
171
172 /*struct kmkbd_internal kmkbd_consdata;*/
173
174 static int
175 kmkbd_is_console(void)
176 {
177 #if 0
178 return (kmkbd_consdata.t_isconsole &&
179 (tag == kmkbd_consdata.t_kbctag) &&
180 (slot == kmkbd_consdata.t_kbcslot));
181 #else
182 return 0;
183 #endif
184 }
185
186 int
187 kmkbd_match(device_t parent, cfdata_t cf, void *aux)
188 {
189 return 1;
190 }
191
192 void
193 kmkbd_attach(device_t parent, device_t self, void *aux)
194 {
195 struct kmkbd_softc *sc = device_private(self);
196 /*struct obio_attach_args *oa = aux;*/
197 int state0;
198 struct wskbddev_attach_args a;
199 struct obio_softc *osc = device_private(parent);
200 int s;
201
202 printf("\n");
203
204 sc->dev = self;
205 sc->state = ST_INIT;
206
207 if (kmkbd_is_console()){
208 a.console = 1;
209 state0 = ST_ALL_UP;
210 } else {
211 a.console = 0;
212 state0 = ST_DISABLED;
213 }
214
215 #ifdef WSDISPLAY_COMPAT_RAWKBD
216 sc->rawkbd = 0;
217 #endif
218 callout_init(&sc->callout, 0);
219
220 s = spltty();
221 sc->ih = obio_intr_establish(osc, G42XXEB_INT_KEY, IPL_TTY,
222 IST_EDGE_FALLING, kmkbd_intr, (void *)sc);
223 kmkbd_new_state(sc, state0);
224 splx(s);
225
226 a.keymap = &kmkbd_keymapdata;
227
228 a.accessops = &kmkbd_accessops;
229 a.accesscookie = sc;
230
231
232 /* Attach the wskbd. */
233 sc->wskbddev = config_found(self, &a, wskbddevprint);
234
235 }
236
237 static int
238 kmkbd_enable(void *v, int on)
239 {
240 struct kmkbd_softc *sc = v;
241
242 if (on) {
243 if (sc->state != ST_DISABLED) {
244 #ifdef DIAGNOSTIC
245 printf("kmkbd_enable: bad enable (state=%d)\n", sc->state);
246 #endif
247 return (EBUSY);
248 }
249
250 kmkbd_new_state(sc, ST_ALL_UP);
251 } else {
252 #if 0
253 if (sc->id->t_isconsole)
254 return (EBUSY);
255 #endif
256
257 kmkbd_new_state(sc, ST_DISABLED);
258 }
259
260 return (0);
261 }
262
263
264
265 static void
266 kmkbd_set_leds(void *v, int leds)
267 {
268 }
269
270 static int
271 kmkbd_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
272 {
273 #ifdef WSDISPLAY_COMPAT_RAWKBD
274 struct kmkbd_softc *sc = v;
275 #endif
276
277 switch (cmd) {
278 case WSKBDIO_GTYPE:
279 *(int *)data = WSKBD_TYPE_PC_XT; /* XXX */
280 return 0;
281 case WSKBDIO_COMPLEXBELL:
282 #define d ((struct wskbd_bell_data *)data)
283 /*
284 * Keyboard can't beep directly; we have an
285 * externally-provided global hook to do this.
286 */
287 kmkbd_bell(d->pitch, d->period, d->volume, 0);
288 #undef d
289 return (0);
290 #ifdef WSDISPLAY_COMPAT_RAWKBD
291 case WSKBDIO_SETMODE:
292 sc->rawkbd = (*(int *)data == WSKBD_RAW);
293 return (0);
294 #endif
295
296 #if 0
297 case WSKBDIO_SETLEDS:
298 case WSKBDIO_GETLEDS:
299 /* no LED support */
300 #endif
301 }
302 return EPASSTHROUGH;
303 }
304
305 void
306 kmkbd_bell(u_int pitch, u_int period, u_int volume, int poll)
307 {
308
309 if (kmkbd_bell_fn != NULL)
310 (*kmkbd_bell_fn)(kmkbd_bell_fn_arg, pitch, period,
311 volume, poll);
312 }
313
314 void
315 kmkbd_hookup_bell(void (* fn)(void *, u_int, u_int, u_int, int), void *arg)
316 {
317
318 if (kmkbd_bell_fn == NULL) {
319 kmkbd_bell_fn = fn;
320 kmkbd_bell_fn_arg = arg;
321 }
322 }
323
324 #if 0
325 int
326 kmkbd_cnattach(pckbc_tag_t kbctag, int kbcslot)
327 {
328 int res;
329
330 res = kmkbd_init(&kmkbd_consdata, kbctag, kbcslot, 1);
331
332 wskbd_cnattach(&kmkbd_consops, &kmkbd_consdata, &kmkbd_keymapdata);
333
334 return (0);
335 }
336
337 void
338 kmkbd_cngetc(void *v, u_int type, int *data)
339 {
340 struct kmkbd_internal *t = v;
341 int val;
342
343 for (;;) {
344 val = pckbc_poll_data(t->t_kbctag, t->t_kbcslot);
345 if ((val != -1) && kmkbd_decode(t, val, type, data))
346 return;
347 }
348 }
349
350 void
351 kmkbd_cnpollc(void *v, int on)
352 {
353 struct kmkbd_internal *t = v;
354
355 pckbc_set_poll(t->t_kbctag, t->t_kbcslot, on);
356 }
357
358 void
359 kmkbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
360 {
361
362 kmkbd_bell(pitch, period, volume, 1);
363 }
364 #endif
365
366
367 /*
368 * low level access to key matrix
369 *
370 * returns bitset of keys being pressed.
371 */
372 static u_int
373 kmkbd_read_matrix(struct kmkbd_softc *sc)
374 {
375 int i;
376 u_int ret, data;
377 struct obio_softc *osc = device_private(device_parent(sc->dev));
378 bus_space_tag_t iot = osc->sc_iot;
379 bus_space_handle_t ioh = osc->sc_obioreg_ioh;
380
381 #define KMDELAY() delay(3)
382
383 bus_space_write_2( iot, ioh, G42XXEB_KEYSCAN, 0 );
384 KMDELAY();
385
386 data = KEYSCAN_SENSE_IN &
387 bus_space_read_2(iot, ioh, G42XXEB_KEYSCAN);
388
389 bus_space_write_2(iot, ioh, G42XXEB_KEYSCAN, KEYSCAN_SCAN_OUT);
390
391 if (data == KEYSCAN_SENSE_IN)
392 return 0;
393
394 ret = 0;
395 for( i=0; i<5; ++i ){
396 /* scan one line */
397 bus_space_write_2(iot, ioh, G42XXEB_KEYSCAN, ~(0x0100<<i));
398 KMDELAY();
399 data = bus_space_read_2(iot, ioh, G42XXEB_KEYSCAN );
400
401 data = ~data & KEYSCAN_SENSE_IN;
402 ret |= data << (i*4);
403 }
404
405 bus_space_write_2(iot, ioh, G42XXEB_KEYSCAN, KEYSCAN_SCAN_OUT);
406 return ret;
407
408 #undef KMDELAY
409
410 }
411
412 /*
413 * report key status change to wskbd subsystem.
414 */
415 static void
416 kmkbd_report(struct kmkbd_softc *sc, u_int bitset)
417 {
418 u_int changed;
419 int i;
420
421 if (bitset == sc->notified_bits)
422 return;
423
424 if (sc->notified_bits && bitset == 0){
425 wskbd_input(sc->wskbddev, WSCONS_EVENT_ALL_KEYS_UP, 0);
426 sc->notified_bits = 0;
427 return;
428 }
429
430 changed = bitset ^ sc->notified_bits;
431 for( i=0; changed; ++i){
432 if ((changed & (1<<i)) == 0)
433 continue;
434 changed &= ~(1<<i);
435
436 wskbd_input(sc->wskbddev,
437 (bitset & (1<<i)) ? WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP,
438 i);
439 }
440
441 sc->notified_bits = bitset;
442 }
443
444 #ifdef WSDISPLAY_COMPAT_RAWKBD
445 static void
446 kmkbd_report_raw(struct kmkbd_softc *sc, u_int bitset)
447 {
448 int i, nc;
449 char cbuf[NUMKEYS];
450 u_int changed;
451
452 if (bitset == sc->notified_bits)
453 return;
454
455 nc = 0;
456 changed = bitset ^ sc->notified_bits;
457
458 while (changed) {
459 i = ffs(changed) - 1;
460 if (nc < NUMKEYS) {
461 cbuf[nc] = i + 1;
462 if (0 == (bitset & (1<<i))) {
463 /* the key is released */
464 cbuf[nc] |= 0x80;
465 }
466 ++nc;
467 }
468
469 changed &= ~(1<<i);
470 }
471
472 wskbd_rawinput(sc->wskbddev, cbuf, nc);
473 sc->notified_bits = bitset;
474 }
475 #endif
476
477 static int
478 kmkbd_intr(void *arg)
479 {
480 struct kmkbd_softc *sc = arg;
481 struct obio_softc *osc = device_private(device_parent(sc->dev));
482
483 if ( sc->state != ST_ALL_UP ){
484 printf("Spurious interrupt from key matrix\n");
485 obio_intr_mask(osc, sc->ih);
486 return 1;
487 }
488
489 kmkbd_new_state(sc, ST_DEBOUNCE);
490
491 return 1;
492 }
493
494 static void
495 kmkbd_debounce(void *arg)
496 {
497 struct kmkbd_softc *sc = arg;
498 u_int newbits;
499 enum kmkbd_state new_state = ST_DEBOUNCE;
500 int s = spltty();
501
502 newbits = kmkbd_read_matrix(sc);
503
504 if (newbits != sc->last_bits){
505 sc->last_bits = newbits;
506 sc->debounce_counter = 0;
507 }
508 else if( ++(sc->debounce_counter) >= DEBOUNCE_COUNT ){
509 new_state = newbits == 0 ? ST_ALL_UP : ST_KEY_PRESSED;
510 #ifdef WSDISPLAY_COMPAT_RAWKBD
511 if (sc->rawkbd)
512 kmkbd_report_raw(sc, newbits);
513 else
514 #endif
515 kmkbd_report(sc, newbits);
516 }
517
518 kmkbd_new_state(sc, new_state);
519 splx(s);
520 }
521
522 /* callout routine to watch key release */
523 static void
524 kmkbd_watch(void *arg)
525 {
526 int s = spltty();
527 struct kmkbd_softc *sc = arg;
528 u_int newbits;
529 int new_state = ST_KEY_PRESSED;
530
531 newbits = kmkbd_read_matrix(sc);
532
533 if (newbits != sc->last_bits){
534 /* some keys are released or new keys are pressed.
535 start debounce */
536 new_state = ST_DEBOUNCE;
537 sc->last_bits = newbits;
538 }
539
540 kmkbd_new_state(sc, new_state);
541 splx(s);
542 }
543
544 static void
545 kmkbd_new_state(struct kmkbd_softc *sc, enum kmkbd_state new_state)
546 {
547 struct obio_softc *osc = device_private(device_parent(sc->dev));
548
549 switch(new_state){
550 case ST_DISABLED:
551 if (sc->state != ST_DISABLED){
552 callout_stop(&sc->callout);
553 obio_intr_mask(osc,sc->ih);
554 }
555 break;
556 case ST_DEBOUNCE:
557 if (sc->state == ST_ALL_UP){
558 obio_intr_mask(osc, sc->ih);
559 sc->last_bits = kmkbd_read_matrix(sc);
560 }
561 if (sc->state != ST_DEBOUNCE)
562 sc->debounce_counter = 0;
563
564 /* start debounce timer */
565 callout_reset(&sc->callout, DEBOUNCE_TICKS, kmkbd_debounce, sc);
566 break;
567 case ST_KEY_PRESSED:
568 /* start timer to check key release */
569 callout_reset(&sc->callout, RELEASE_WATCH_TICKS, kmkbd_watch, sc);
570 break;
571 case ST_ALL_UP:
572 if (sc->state != ST_ALL_UP){
573 bus_space_tag_t iot = osc->sc_iot;
574 bus_space_handle_t ioh = osc->sc_obioreg_ioh;
575
576 obio_intr_unmask(osc, sc->ih);
577 bus_space_write_2(iot, ioh, G42XXEB_KEYSCAN, 0);
578 }
579 break;
580 case ST_INIT:
581 ; /* Nothing to do */
582 }
583
584 sc->state = new_state;
585 }
586