/* $OpenBSD: comkbd_ebus.c,v 1.18 2005/11/11 16:44:51 miod Exp $ */
/*
* Copyright (c) 2002 Jason L. Wright (jason@thought.net)
* 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.
*
* Effort sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F30602-01-2-0537.
*
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <machine/bus.h>
#include <machine/autoconf.h>
#include <machine/openfirm.h>
#include <sparc64/dev/ebusreg.h>
#include <sparc64/dev/ebusvar.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/sun/sunkbdreg.h>
#include <dev/sun/sunkbdvar.h>
#include <dev/ic/comreg.h>
#include <dev/ic/comvar.h>
#include <dev/ic/ns16550reg.h>
#include <dev/cons.h>
#define COMK_RX_RING 64
#define COMK_TX_RING 64
struct comkbd_softc {
struct sunkbd_softc sc_base;
bus_space_tag_t sc_iot; /* bus tag */
bus_space_handle_t sc_ioh; /* bus handle */
void *sc_ih, *sc_si; /* interrupt vectors */
u_int sc_rxcnt;
u_int8_t sc_rxbuf[COMK_RX_RING];
u_int8_t *sc_rxbeg, *sc_rxend, *sc_rxget, *sc_rxput;
u_int sc_txcnt;
u_int8_t sc_txbuf[COMK_TX_RING];
u_int8_t *sc_txbeg, *sc_txend, *sc_txget, *sc_txput;
u_int8_t sc_ier;
};
#define COM_WRITE(sc,r,v) \
bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
#define COM_READ(sc,r) \
bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r))
int comkbd_match(struct device *, void *, void *);
void comkbd_attach(struct device *, struct device *, void *);
int comkbd_iskbd(int);
/* wskbd glue */
void comkbd_cnpollc(void *, int);
void comkbd_cngetc(void *, u_int *, int *);
/* internals */
int comkbd_enqueue(void *, u_int8_t *, u_int);
int comkbd_init(struct comkbd_softc *);
void comkbd_putc(struct comkbd_softc *, u_int8_t);
int comkbd_intr(void *);
void comkbd_soft(void *);
struct cfattach comkbd_ca = {
sizeof(struct comkbd_softc), comkbd_match, comkbd_attach
};
struct cfdriver comkbd_cd = {
NULL, "comkbd", DV_DULL
};
const char *comkbd_names[] = {
"su",
"su_pnp",
NULL
};
struct wskbd_consops comkbd_consops = {
comkbd_cngetc,
comkbd_cnpollc
};
int
comkbd_iskbd(node)
int node;
{
if (OF_getproplen(node, "keyboard") == 0)
return (10);
return (0);
}
int
comkbd_match(parent, match, aux)
struct device *parent;
void *match;
void *aux;
{
struct ebus_attach_args *ea = aux;
int i;
for (i = 0; comkbd_names[i]; i++)
if (strcmp(ea->ea_name, comkbd_names[i]) == 0)
return (comkbd_iskbd(ea->ea_node));
if (strcmp(ea->ea_name, "serial") == 0) {
char compat[80];
if ((i = OF_getproplen(ea->ea_node, "compatible")) &&
OF_getprop(ea->ea_node, "compatible", compat,
sizeof(compat)) == i) {
if (strcmp(compat, "su16550") == 0 ||
strcmp(compat, "su") == 0)
return (comkbd_iskbd(ea->ea_node));
}
}
return (0);
}
void
comkbd_attach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct comkbd_softc *sc = (void *)self;
struct sunkbd_softc *ss = (void *)sc;
struct ebus_attach_args *ea = aux;
struct wskbddev_attach_args a;
int console;
ss->sc_sendcmd = comkbd_enqueue;
timeout_set(&ss->sc_bellto, sunkbd_bellstop, sc);
sc->sc_iot = ea->ea_memtag;
sc->sc_rxget = sc->sc_rxput = sc->sc_rxbeg = sc->sc_rxbuf;
sc->sc_rxend = sc->sc_rxbuf + COMK_RX_RING;
sc->sc_rxcnt = 0;
sc->sc_txget = sc->sc_txput = sc->sc_txbeg = sc->sc_txbuf;
sc->sc_txend = sc->sc_txbuf + COMK_TX_RING;
sc->sc_txcnt = 0;
console = (ea->ea_node == OF_instance_to_package(OF_stdin()));
sc->sc_si = softintr_establish(IPL_TTY, comkbd_soft, sc);
if (sc->sc_si == NULL) {
printf(": can't get soft intr\n");
return;
}
/* Use prom address if available, otherwise map it. */
if (ea->ea_nvaddrs && bus_space_map(ea->ea_memtag, ea->ea_vaddrs[0], 0,
BUS_SPACE_MAP_PROMADDRESS, &sc->sc_ioh) == 0) {
sc->sc_iot = ea->ea_memtag;
} else if (ebus_bus_map(ea->ea_memtag, 0,
EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
ea->ea_regs[0].size, 0, 0, &sc->sc_ioh) == 0) {
sc->sc_iot = ea->ea_memtag;
} else if (ebus_bus_map(ea->ea_iotag, 0,
EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
ea->ea_regs[0].size, 0, 0, &sc->sc_ioh) == 0) {
sc->sc_iot = ea->ea_iotag;
} else {
printf(": can't map register space\n");
return;
}
sc->sc_ih = bus_intr_establish(sc->sc_iot,
ea->ea_intrs[0], IPL_TTY, 0, comkbd_intr, sc, self->dv_xname);
if (sc->sc_ih == NULL) {
printf(": can't get hard intr\n");
return;
}
if (comkbd_init(sc) == 0) {
return;
}
ss->sc_click =
strcmp(getpropstring(optionsnode, "keyboard-click?"), "true") == 0;
sunkbd_setclick(ss, ss->sc_click);
a.console = console;
if (ISTYPE5(ss->sc_layout)) {
a.keymap = &sunkbd5_keymapdata;
#ifndef SUNKBD5_LAYOUT
if (ss->sc_layout < MAXSUNLAYOUT &&
sunkbd_layouts[ss->sc_layout] != -1)
sunkbd5_keymapdata.layout =
sunkbd_layouts[ss->sc_layout];
#endif
} else {
a.keymap = &sunkbd_keymapdata;
#ifndef SUNKBD_LAYOUT
if (ss->sc_layout < MAXSUNLAYOUT &&
sunkbd_layouts[ss->sc_layout] != -1)
sunkbd_keymapdata.layout =
sunkbd_layouts[ss->sc_layout];
#endif
}
a.accessops = &sunkbd_accessops;
a.accesscookie = sc;
if (console) {
cn_tab->cn_dev = makedev(77, ss->sc_dev.dv_unit); /* XXX */
cn_tab->cn_pollc = wskbd_cnpollc;
cn_tab->cn_getc = wskbd_cngetc;
wskbd_cnattach(&comkbd_consops, sc, a.keymap);
sc->sc_ier = IER_ETXRDY | IER_ERXRDY;
COM_WRITE(sc, com_ier, sc->sc_ier);
COM_READ(sc, com_iir);
COM_WRITE(sc, com_mcr, MCR_IENABLE | MCR_DTR | MCR_RTS);
}
ss->sc_wskbddev = config_found(self, &a, wskbddevprint);
}
void
comkbd_cnpollc(vsc, on)
void *vsc;
int on;
{
}
void
comkbd_cngetc(v, type, data)
void *v;
u_int *type;
int *data;
{
struct comkbd_softc *sc = v;
int s;
u_int8_t c;
s = splhigh();
while (1) {
if (COM_READ(sc, com_lsr) & LSR_RXRDY)
break;
}
c = COM_READ(sc, com_data);
COM_READ(sc, com_iir);
splx(s);
sunkbd_decode(c, type, data);
}
void
comkbd_putc(sc, c)
struct comkbd_softc *sc;
u_int8_t c;
{
int s, timo;
s = splhigh();
timo = 150000;
while (--timo) {
if (COM_READ(sc, com_lsr) & LSR_TXRDY)
break;
}
COM_WRITE(sc, com_data, c);
bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, COM_NPORTS,
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
timo = 150000;
while (--timo) {
if (COM_READ(sc, com_lsr) & LSR_TXRDY)
break;
}
splx(s);
}
int
comkbd_enqueue(v, buf, buflen)
void *v;
u_int8_t *buf;
u_int buflen;
{
struct comkbd_softc *sc = v;
int s;
u_int i;
s = spltty();
/* See if there is room... */
if ((sc->sc_txcnt + buflen) > COMK_TX_RING) {
splx(s);
return (-1);
}
for (i = 0; i < buflen; i++) {
*sc->sc_txget = *buf;
buf++;
sc->sc_txcnt++;
sc->sc_txget++;
if (sc->sc_txget == sc->sc_txend)
sc->sc_txget = sc->sc_txbeg;
}
comkbd_soft(sc);
splx(s);
return (0);
}
void
comkbd_soft(vsc)
void *vsc;
{
struct comkbd_softc *sc = vsc;
struct sunkbd_softc *ss = (void *)sc;
u_int type;
int value;
u_int8_t c;
while (sc->sc_rxcnt) {
c = *sc->sc_rxget;
if (++sc->sc_rxget == sc->sc_rxend)
sc->sc_rxget = sc->sc_rxbeg;
sc->sc_rxcnt--;
sunkbd_decode(c, &type, &value);
wskbd_input(ss->sc_wskbddev, type, value);
}
if (sc->sc_txcnt) {
c = sc->sc_ier | IER_ETXRDY;
if (c != sc->sc_ier) {
COM_WRITE(sc, com_ier, c);
sc->sc_ier = c;
}
if (COM_READ(sc, com_lsr) & LSR_TXRDY) {
sc->sc_txcnt--;
COM_WRITE(sc, com_data, *sc->sc_txput);
if (++sc->sc_txput == sc->sc_txend)
sc->sc_txput = sc->sc_txbeg;
}
}
}
int
comkbd_intr(vsc)
void *vsc;
{
struct comkbd_softc *sc = vsc;
u_int8_t iir, lsr, data;
int needsoft = 0;
/* Nothing to do */
iir = COM_READ(sc, com_iir);
if (iir & IIR_NOPEND)
return (0);
for (;;) {
lsr = COM_READ(sc, com_lsr);
if (lsr & LSR_RXRDY) {
needsoft = 1;
do {
data = COM_READ(sc, com_data);
if (sc->sc_rxcnt != COMK_RX_RING) {
*sc->sc_rxput = data;
if (++sc->sc_rxput == sc->sc_rxend)
sc->sc_rxput = sc->sc_rxbeg;
sc->sc_rxcnt++;
}
lsr = COM_READ(sc, com_lsr);
} while (lsr & LSR_RXRDY);
}
if (lsr & LSR_TXRDY) {
if (sc->sc_txcnt == 0) {
/* Nothing further to send */
sc->sc_ier &= ~IER_ETXRDY;
COM_WRITE(sc, com_ier, sc->sc_ier);
} else
needsoft = 1;
}
iir = COM_READ(sc, com_iir);
if (iir & IIR_NOPEND)
break;
}
if (needsoft)
softintr_schedule(sc->sc_si);
return (1);
}
int
comkbd_init(sc)
struct comkbd_softc *sc;
{
struct sunkbd_softc *ss = (void *)sc;
u_int8_t stat, c;
int tries;
for (tries = 5; tries != 0; tries--) {
int ltries;
ss->sc_leds = 0;
ss->sc_layout = -1;
/* Send reset request */
comkbd_putc(sc, SKBD_CMD_RESET);
ltries = 1000;
while (--ltries > 0) {
stat = COM_READ(sc,com_lsr);
if (stat & LSR_RXRDY) {
c = COM_READ(sc, com_data);
sunkbd_raw(ss, c);
if (ss->sc_kbdstate == SKBD_STATE_RESET)
break;
}
DELAY(1000);
}
if (ltries == 0)
continue;
/* Wait for reset to finish. */
ltries = 1000;
while (--ltries > 0) {
stat = COM_READ(sc, com_lsr);
if (stat & LSR_RXRDY) {
c = COM_READ(sc, com_data);
sunkbd_raw(ss, c);
if (ss->sc_kbdstate == SKBD_STATE_GETKEY)
break;
}
DELAY(1000);
}
if (ltries == 0)
continue;
/* Send layout request */
comkbd_putc(sc, SKBD_CMD_LAYOUT);
ltries = 1000;
while (--ltries > 0) {
stat = COM_READ(sc, com_lsr);
if (stat & LSR_RXRDY) {
c = COM_READ(sc, com_data);
sunkbd_raw(ss, c);
if (ss->sc_layout != -1)
break;
}
DELAY(1000);
}
if (ltries != 0)
break;
}
if (tries == 0)
printf(": no keyboard\n");
else
printf(": layout %d\n", ss->sc_layout);
return tries;
}