/* $NetBSD: hilkbd.c,v 1.7 2021/09/19 04:55:58 tsutsui Exp $ */
/* $OpenBSD: hilkbd.c,v 1.14 2009/01/21 21:53:59 grange Exp $ */
/*
* Copyright (c) 2003, Miodrag Vallat.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "opt_wsdisplay_compat.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/callout.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <machine/autoconf.h>
#include <dev/hil/hilreg.h>
#include <dev/hil/hilvar.h>
#include <dev/hil/hildevs.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>
#include <dev/hil/hilkbdmap.h>
struct hilkbd_softc {
struct hildev_softc sc_hildev;
int sc_numleds;
int sc_ledstate;
int sc_enabled;
int sc_console;
int sc_lastarrow;
device_t sc_wskbddev;
#ifdef WSDISPLAY_COMPAT_RAWKBD
int sc_rawkbd;
#endif
};
static int hilkbdprobe(device_t, cfdata_t, void *);
static void hilkbdattach(device_t, device_t, void *);
static int hilkbddetach(device_t, int);
CFATTACH_DECL_NEW(hilkbd, sizeof(struct hilkbd_softc),
hilkbdprobe, hilkbdattach, hilkbddetach, NULL);
static int hilkbd_enable(void *, int);
static void hilkbd_set_leds(void *, int);
static int hilkbd_ioctl(void *, u_long, void *, int, struct lwp *);
static const struct wskbd_accessops hilkbd_accessops = {
.enable = hilkbd_enable,
.set_leds = hilkbd_set_leds,
.ioctl = hilkbd_ioctl,
};
static void hilkbd_cngetc(void *, u_int *, int *);
static void hilkbd_cnpollc(void *, int);
static void hilkbd_cnbell(void *, u_int, u_int, u_int);
static const struct wskbd_consops hilkbd_consops = {
.getc = hilkbd_cngetc,
.pollc = hilkbd_cnpollc,
.bell = hilkbd_cnbell,
};
static struct wskbd_mapdata hilkbd_keymapdata = {
.keydesc = hilkbd_keydesctab,
.layout =
#ifdef HILKBD_LAYOUT
HILKBD_LAYOUT,
#else
KB_US,
#endif
};
static struct wskbd_mapdata hilkbd_keymapdata_ps2 = {
.keydesc = hilkbd_keydesctab_ps2,
.layout =
#ifdef HILKBD_LAYOUT
HILKBD_LAYOUT,
#else
KB_US,
#endif
};
static void hilkbd_bell(struct hil_softc *, u_int, u_int, u_int);
static void hilkbd_callback(struct hildev_softc *, u_int, uint8_t *);
static void hilkbd_decode(struct hilkbd_softc *, uint8_t, u_int *, int *,
int);
static int hilkbd_is_console(int);
static int seen_hilkbd_console;
int
hilkbdprobe(device_t parent, cfdata_t cf, void *aux)
{
struct hil_attach_args *ha = aux;
if (ha->ha_type != HIL_DEVICE_KEYBOARD &&
ha->ha_type != HIL_DEVICE_BUTTONBOX)
return 0;
return 1;
}
void
hilkbdattach(device_t parent, device_t self, void *aux)
{
struct hilkbd_softc *sc = device_private(self);
struct hil_attach_args *ha = aux;
struct wskbddev_attach_args a;
uint8_t layoutcode;
int ps2;
sc->sc_hildev.sc_dev = self;
sc->hd_code = ha->ha_code;
sc->hd_type = ha->ha_type;
sc->hd_infolen = ha->ha_infolen;
memcpy(sc->hd_info, ha->ha_info, ha->ha_infolen);
sc->hd_fn = hilkbd_callback;
if (ha->ha_type == HIL_DEVICE_KEYBOARD) {
/*
* Determine the keyboard language configuration, but don't
* override a user-specified setting.
*/
layoutcode = ha->ha_id & (MAXHILKBDLAYOUT - 1);
#ifndef HILKBD_LAYOUT
if (layoutcode < MAXHILKBDLAYOUT &&
hilkbd_layouts[layoutcode] != -1)
hilkbd_keymapdata.layout =
hilkbd_keymapdata_ps2.layout =
hilkbd_layouts[layoutcode];
#endif
aprint_normal(", layout %x", layoutcode);
}
/*
* Interpret the identification bytes, if any
*/
if (ha->ha_infolen > 2 && (ha->ha_info[1] & HIL_IOB) != 0) {
/* HILIOB_PROMPT is not always reported... */
sc->sc_numleds = (ha->ha_info[2] & HILIOB_PMASK) >> 4;
if (sc->sc_numleds != 0)
aprint_normal(", %d leds", sc->sc_numleds);
}
aprint_normal("\n");
/*
* Red lettered keyboards come in two flavours, the old one
* with only one control key, to the left of the escape key,
* and the modern one which has a PS/2 like layout, and leds.
*
* Unfortunately for us, they use the same device ID range.
* We'll differentiate them by looking at the leds property.
*/
ps2 = (sc->sc_numleds != 0);
/* Do not consider button boxes as console devices. */
if (ha->ha_type == HIL_DEVICE_BUTTONBOX)
a.console = 0;
else
a.console = hilkbd_is_console(ha->ha_console);
a.keymap = ps2 ? &hilkbd_keymapdata_ps2 : &hilkbd_keymapdata;
a.accessops = &hilkbd_accessops;
a.accesscookie = sc;
if (a.console) {
sc->sc_console = sc->sc_enabled = 1;
wskbd_cnattach(&hilkbd_consops, sc, a.keymap);
} else {
sc->sc_console = sc->sc_enabled = 0;
}
sc->sc_wskbddev = config_found(self, &a, wskbddevprint, CFARGS_NONE);
/*
* If this is an old keyboard with a numeric pad but no ``num lock''
* key, simulate it being pressed so that the keyboard runs in
* numeric mode.
*/
if (!ps2 && sc->sc_wskbddev != NULL) {
wskbd_input(sc->sc_wskbddev, WSCONS_EVENT_KEY_DOWN, 80);
wskbd_input(sc->sc_wskbddev, WSCONS_EVENT_KEY_UP, 80);
}
}
int
hilkbddetach(device_t self, int flags)
{
struct hilkbd_softc *sc = device_private(self);
/*
* Handle console keyboard for the best. It should have been set
* as the first device in the loop anyways.
*/
if (sc->sc_console) {
wskbd_cndetach();
seen_hilkbd_console = 0;
}
if (sc->sc_wskbddev != NULL)
return config_detach(sc->sc_wskbddev, flags);
return 0;
}
int
hilkbd_enable(void *v, int on)
{
struct hilkbd_softc *sc = v;
if (on) {
if (sc->sc_enabled)
return EBUSY;
} else {
if (sc->sc_console)
return EBUSY;
}
sc->sc_enabled = on;
return 0;
}
void
hilkbd_set_leds(void *v, int leds)
{
struct hilkbd_softc *sc = v;
struct hildev_softc *hdsc = &sc->sc_hildev;
int changemask;
if (sc->sc_numleds == 0)
return;
changemask = leds ^ sc->sc_ledstate;
if (changemask == 0)
return;
/* We do not handle more than 3 leds here */
if (changemask & WSKBD_LED_SCROLL)
send_hildev_cmd(hdsc,
(leds & WSKBD_LED_SCROLL) ? HIL_PROMPT1 : HIL_ACK1,
NULL, NULL);
if (changemask & WSKBD_LED_NUM)
send_hildev_cmd(hdsc,
(leds & WSKBD_LED_NUM) ? HIL_PROMPT2 : HIL_ACK2,
NULL, NULL);
if (changemask & WSKBD_LED_CAPS)
send_hildev_cmd(hdsc,
(leds & WSKBD_LED_CAPS) ? HIL_PROMPT3 : HIL_ACK3,
NULL, NULL);
sc->sc_ledstate = leds;
}
int
hilkbd_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
{
struct hilkbd_softc *sc = v;
switch (cmd) {
case WSKBDIO_GTYPE:
*(int *)data = WSKBD_TYPE_HIL;
return 0;
case WSKBDIO_SETLEDS:
hilkbd_set_leds(v, *(int *)data);
return 0;
case WSKBDIO_GETLEDS:
*(int *)data = sc->sc_ledstate;
return 0;
#ifdef WSDISPLAY_COMPAT_RAWKBD
case WSKBDIO_SETMODE:
sc->sc_rawkbd = *(int *)data == WSKBD_RAW;
return 0;
#endif
case WSKBDIO_COMPLEXBELL:
#define d ((struct wskbd_bell_data *)data)
hilkbd_bell(device_private(device_parent(sc->sc_hildev.sc_dev)),
d->pitch, d->period, d->volume);
#undef d
return 0;
}
return EPASSTHROUGH;
}
void
hilkbd_cngetc(void *v, u_int *type, int *data)
{
struct hilkbd_softc *sc = v;
struct hildev_softc *hdsc = &sc->sc_hildev;
uint8_t c, stat;
for (;;) {
while (hil_poll_data(hdsc, &stat, &c) != 0)
;
/*
* Disregard keyboard data packet header.
* Note that no key generates it, so we're safe.
*/
if (c != HIL_KBDBUTTON)
break;
}
hilkbd_decode(sc, c, type, data, HIL_KBDBUTTON);
}
void
hilkbd_cnpollc(void *v, int on)
{
struct hilkbd_softc *sc = v;
hil_set_poll(device_private(device_parent(sc->sc_hildev.sc_dev)), on);
}
void
hilkbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
{
struct hilkbd_softc *sc = v;
hilkbd_bell(device_private(device_parent(sc->sc_hildev.sc_dev)),
pitch, period, volume);
}
void
hilkbd_bell(struct hil_softc *sc, u_int pitch, u_int period, u_int volume)
{
uint8_t buf[2];
/* XXX there could be at least a pitch -> HIL pitch conversion here */
#define BELLDUR 80 /* tone duration in msec (10-2560) */
#define BELLFREQ 8 /* tone frequency (0-63) */
buf[0] = ar_format(period - 10);
buf[1] = BELLFREQ;
send_hil_cmd(sc, HIL_SETTONE, buf, 2, NULL);
}
void
hilkbd_callback(struct hildev_softc *hdsc, u_int buflen, uint8_t *buf)
{
struct hilkbd_softc *sc = device_private(hdsc->sc_dev);
u_int type;
int kbdtype, key;
int i, s;
/*
* Ignore packet if we don't need it
*/
if (sc->sc_enabled == 0)
return;
if (buflen == 0)
return;
switch ((kbdtype = *buf & HIL_KBDDATA)) {
case HIL_BUTTONBOX:
case HIL_KBDBUTTON:
break;
default:
return;
}
#ifdef WSDISPLAY_COMPAT_RAWKBD
if (sc->sc_rawkbd) {
uint8_t cbuf[HILBUFSIZE * 2];
int c, j;
j = 0;
for (i = 1, buf++; i < buflen; i++) {
hilkbd_decode(sc, *buf++, &type, &key, kbdtype);
c = hilkbd_raw[key];
if (c == 0)
continue;
/* fake extended scancode if necessary */
if (c & 0x80)
cbuf[j++] = 0xe0;
cbuf[j] = c & 0x7f;
if (type == WSCONS_EVENT_KEY_UP)
cbuf[j] |= 0x80;
j++;
}
s = spltty();
wskbd_rawinput(sc->sc_wskbddev, cbuf, j);
splx(s);
} else
#endif
{
s = spltty();
for (i = 1, buf++; i < buflen; i++) {
hilkbd_decode(sc, *buf++, &type, &key, kbdtype);
if (sc->sc_wskbddev != NULL)
wskbd_input(sc->sc_wskbddev, type, key);
}
splx(s);
}
}
void
hilkbd_decode(struct hilkbd_softc *sc, uint8_t data, u_int *type, int *key,
int kbdtype)
{
if (kbdtype == HIL_BUTTONBOX) {
if (data == 0x02) /* repeat arrow */
data = sc->sc_lastarrow;
else if (data >= 0xf8)
sc->sc_lastarrow = data;
}
*type = (data & 1) ? WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN;
*key = data >> 1;
}
int
hilkbd_is_console(int hil_is_console)
{
/* if not first hil keyboard, then not the console */
if (seen_hilkbd_console)
return 0;
/* if PDC console does not match hil bus path, then not the console */
if (hil_is_console == 0)
return 0;
seen_hilkbd_console = 1;
return 1;
}