freebsd-dev/sys/dev/sab/sab.c
Marius Strobl 26280d88d7 - Introduce an ofw_bus kobj-interface for retrieving the OFW node and a
subset ("compatible", "device_type", "model" and "name") of the standard
  properties in drivers for devices on Open Firmware supported busses. The
  standard properties "reg", "interrupts" und "address" are not covered by
  this interface because they are only of interest in the respective bridge
  code. There's a remaining standard property "status" which is unclear how
  to support properly but which also isn't used in FreeBSD at present.
  This ofw_bus kobj-interface allows to replace the various (ebus_get_node(),
  ofw_pci_get_node(), etc.) and partially inconsistent (central_get_type()
  vs. sbus_get_device_type(), etc.) existing IVAR ones with a common one.
  This in turn allows to simplify and remove code-duplication in drivers for
  devices that can hang off of more than one OFW supported bus.
- Convert the sparc64 Central, EBus, FHC, PCI and SBus bus drivers and the
  drivers for their children to use the ofw_bus kobj-interface. The IVAR-
  interfaces of the Central, EBus and FHC are entirely replaced by this. The
  PCI bus driver used its own kobj-interface and now also uses the ofw_bus
  one. The IVARs special to the SBus, e.g. for retrieving the burst size,
  remain.
  Beware: this causes an ABI-breakage for modules of drivers which used the
  IVAR-interfaces, i.e. esp(4), hme(4), isp(4) and uart(4), which need to be
  recompiled.
  The style-inconsistencies introduced in some of the bus drivers will be
  fixed by tmm@ in a generic clean-up of the respective drivers later (he
  requested to add the changes in the "new" style).
- Convert the powerpc MacIO bus driver and the drivers for its children to
  use the ofw_bus kobj-interface. This invloves removing the IVARs related
  to the "reg" property which were unused and a leftover from the NetBSD
  origini of the code. There's no ABI-breakage caused by this because none
  of these driver are currently built as modules.
  There are other powerpc bus drivers which can be converted to the ofw_bus
  kobj-interface, e.g. the PCI bus driver, which should be done together
  with converting powerpc to use the OFW PCI code from sparc64.
- Make the SBus and FHC front-end of zs(4) and the sparc64 eeprom(4) take
  advantage of the ofw_bus kobj-interface and simplify them a bit.

Reviewed by:	grehan, tmm
Approved by:	re (scottl)
Discussed with:	tmm
Tested with:	Sun AX1105, AXe, Ultra 2, Ultra 60; PPC cross-build on i386
2004-08-12 17:41:33 +00:00

1229 lines
27 KiB
C

/* $OpenBSD: sab.c,v 1.7 2002/04/08 17:49:42 jason Exp $ */
/*
* Copyright (c) 2001 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Jason L. Wright
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* SAB82532 Dual UART driver
*/
#include "opt_comconsole.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/fcntl.h>
#include <sys/interrupt.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/openfirm.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <sys/serial.h>
#include <sys/syslog.h>
#include <sys/tty.h>
#include <dev/sab/sab82532reg.h>
#define SAB_READ(sc, r) \
bus_space_read_1((sc)->sc_bt, (sc)->sc_bh, (r))
#define SAB_WRITE(sc, r, v) \
bus_space_write_1((sc)->sc_bt, (sc)->sc_bh, (r), (v))
#define SABTTY_LOCK(sc) mtx_lock_spin(&sc->sc_mtx)
#define SABTTY_UNLOCK(sc) mtx_unlock_spin(&sc->sc_mtx)
struct sabtty_softc {
device_t sc_dev;
struct sab_softc *sc_parent;
bus_space_tag_t sc_bt;
bus_space_handle_t sc_bh;
struct cdev *sc_si;
struct tty *sc_tty;
int sc_channel;
int sc_icnt;
uint8_t *sc_iput;
uint8_t *sc_iget;
int sc_ocnt;
uint8_t *sc_oget;
int sc_alt_break_state;
struct mtx sc_mtx;
uint8_t sc_console;
uint8_t sc_tx_done;
uint8_t sc_pvr_dtr;
uint8_t sc_pvr_dsr;
uint8_t sc_imr0;
uint8_t sc_imr1;
uint8_t sc_ibuf[CBLOCK];
uint8_t sc_obuf[CBLOCK];
};
struct sab_softc {
device_t sc_dev;
bus_space_tag_t sc_bt;
bus_space_handle_t sc_bh;
struct sabtty_softc *sc_child[SAB_NCHAN];
void *sc_ih;
void *sc_softih;
struct resource *sc_irqres;
int sc_irqrid;
struct resource *sc_iores;
int sc_iorid;
uint8_t sc_ipc;
};
static int sab_probe(device_t dev);
static int sab_attach(device_t dev);
static int sab_detach(device_t dev);
static void sab_intr(void *vsc);
static void sab_softintr(void *vsc);
static void sab_shutdown(void *vsc);
static int sabtty_probe(device_t dev);
static int sabtty_attach(device_t dev);
static int sabtty_detach(device_t dev);
static int sabtty_intr(struct sabtty_softc *sc);
static void sabtty_softintr(struct sabtty_softc *sc);
static void sabttybreak(struct tty *tp, int brk);
static int sabttymodem(struct tty *tp, int biton, int bitoff);
static int sabtty_param(struct sabtty_softc *sc, struct tty *tp,
struct termios *t);
static void sabtty_cec_wait(struct sabtty_softc *sc);
static void sabtty_tec_wait(struct sabtty_softc *sc);
static void sabtty_reset(struct sabtty_softc *sc);
static void sabtty_flush(struct sabtty_softc *sc);
static int sabtty_speed(int);
static int sabtty_console(device_t dev, char *mode, int len);
static cn_probe_t sab_cnprobe;
static cn_init_t sab_cninit;
static cn_term_t sab_cnterm;
static cn_getc_t sab_cngetc;
static cn_checkc_t sab_cncheckc;
static cn_putc_t sab_cnputc;
static cn_dbctl_t sab_cndbctl;
static int sabtty_cngetc(struct sabtty_softc *sc);
static int sabtty_cncheckc(struct sabtty_softc *sc);
static void sabtty_cnputc(struct sabtty_softc *sc, int c);
static d_open_t sabttyopen;
static d_close_t sabttyclose;
static void sabttystart(struct tty *tp);
static void sabttystop(struct tty *tp, int rw);
static int sabttyparam(struct tty *tp, struct termios *t);
static struct cdevsw sabtty_cdevsw = {
.d_version = D_VERSION,
.d_open = sabttyopen,
.d_close = sabttyclose,
.d_name = "sabtty",
.d_flags = D_TTY | D_NEEDGIANT,
};
static device_method_t sab_methods[] = {
DEVMETHOD(device_probe, sab_probe),
DEVMETHOD(device_attach, sab_attach),
DEVMETHOD(device_detach, sab_detach),
DEVMETHOD(bus_print_child, bus_generic_print_child),
{ 0, 0 }
};
static device_method_t sabtty_methods[] = {
DEVMETHOD(device_probe, sabtty_probe),
DEVMETHOD(device_attach, sabtty_attach),
DEVMETHOD(device_detach, sabtty_detach),
{ 0, 0 }
};
static driver_t sab_driver = {
"sab",
sab_methods,
sizeof(struct sab_softc),
};
static driver_t sabtty_driver = {
"sabtty",
sabtty_methods,
sizeof(struct sabtty_softc),
};
static devclass_t sab_devclass;
static devclass_t sabtty_devclass;
static struct sabtty_softc *sabtty_cons;
DRIVER_MODULE(sab, ebus, sab_driver, sab_devclass, 0, 0);
DRIVER_MODULE(sabtty, sab, sabtty_driver, sabtty_devclass, 0, 0);
CONS_DRIVER(sab, sab_cnprobe, sab_cninit, sab_cnterm, sab_cngetc,
sab_cncheckc, sab_cnputc, sab_cndbctl);
struct sabtty_rate {
int baud;
int n, m;
};
struct sabtty_rate sabtty_baudtable[] = {
{ 50, 35, 10 },
{ 75, 47, 9 },
{ 110, 32, 9 },
{ 134, 53, 8 },
{ 150, 47, 8 },
{ 200, 35, 8 },
{ 300, 47, 7 },
{ 600, 47, 6 },
{ 1200, 47, 5 },
{ 1800, 31, 5 },
{ 2400, 47, 4 },
{ 4800, 47, 3 },
{ 9600, 47, 2 },
{ 19200, 47, 1 },
{ 38400, 23, 1 },
{ 57600, 15, 1 },
{ 115200, 7, 1 },
{ 230400, 3, 1 },
{ 460800, 1, 1 },
{ 76800, 11, 1 },
{ 153600, 5, 1 },
{ 307200, 3, 1 },
{ 614400, 3, 0 },
{ 921600, 0, 1 },
};
static int
sab_probe(device_t dev)
{
struct resource *res;
bus_space_handle_t handle;
bus_space_tag_t tag;
uint8_t r;
int rid;
const char *name;
name = ofw_bus_get_name(dev);
if (strcmp(name, "se") != 0 && strcmp(name, "serial") != 0)
return (ENXIO);
rid = 0;
res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid,
RF_ACTIVE);
if (res == NULL)
return (ENXIO);
tag = rman_get_bustag(res);
handle = rman_get_bushandle(res);
r = bus_space_read_1(tag, handle, SAB_VSTR) & SAB_VSTR_VMASK;
switch (r) {
case SAB_VSTR_V_1:
device_set_desc(dev, "Siemens SAB 82532 v1");
break;
case SAB_VSTR_V_2:
device_set_desc(dev, "Siemens SAB 82532 v2");
break;
case SAB_VSTR_V_32:
device_set_desc(dev, "Siemens SAB 82532 v3.2");
break;
default:
device_set_desc(dev, "Siemens SAB 82532 ???");
break;
}
bus_release_resource(dev, SYS_RES_IOPORT, rid, res);
return (0);
}
static int
sab_attach(device_t dev)
{
struct device *child[SAB_NCHAN];
struct resource *irqres;
struct resource *iores;
struct sab_softc *sc;
int irqrid;
int iorid;
int i;
iorid = 0;
irqrid = 0;
irqres = NULL;
sc = device_get_softc(dev);
iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &iorid,
RF_ACTIVE);
if (iores == NULL)
goto error;
irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irqrid,
RF_ACTIVE);
if (irqres == NULL)
goto error;
if (bus_setup_intr(dev, irqres, INTR_TYPE_TTY | INTR_FAST, sab_intr,
sc, &sc->sc_ih) != 0)
goto error;
sc->sc_dev = dev;
sc->sc_irqres = irqres;
sc->sc_iores = iores;
sc->sc_iorid = iorid;
sc->sc_irqres = irqres;
sc->sc_irqrid = irqrid;
sc->sc_bt = rman_get_bustag(iores);
sc->sc_bh = rman_get_bushandle(iores);
/* Set all pins, except DTR pins to be inputs */
SAB_WRITE(sc, SAB_PCR, ~(SAB_PVR_DTR_A | SAB_PVR_DTR_B));
/* Disable port interrupts */
SAB_WRITE(sc, SAB_PIM, 0xff);
SAB_WRITE(sc, SAB_PVR, SAB_PVR_DTR_A | SAB_PVR_DTR_B | SAB_PVR_MAGIC);
sc->sc_ipc = SAB_IPC_ICPL | SAB_IPC_VIS;
SAB_WRITE(sc, SAB_IPC, sc->sc_ipc);
for (i = 0; i < SAB_NCHAN; i++)
child[i] = device_add_child(dev, "sabtty", -1);
bus_generic_attach(dev);
for (i = 0; i < SAB_NCHAN; i++)
sc->sc_child[i] = device_get_softc(child[i]);
swi_add(&tty_ithd, "tty:sab", sab_softintr, sc, SWI_TTY,
INTR_TYPE_TTY, &sc->sc_softih);
if (sabtty_cons != NULL) {
DELAY(50000);
cninit();
}
EVENTHANDLER_REGISTER(shutdown_final, sab_shutdown, sc,
SHUTDOWN_PRI_DEFAULT);
return (0);
error:
if (irqres != NULL)
bus_release_resource(dev, SYS_RES_IRQ, irqrid, irqres);
if (iores != NULL)
bus_release_resource(dev, SYS_RES_IRQ, iorid, iores);
return (ENXIO);
}
static int
sab_detach(device_t dev)
{
struct sab_softc *sc;
sc = device_get_softc(dev);
bus_generic_detach(dev);
if (sc->sc_iores != NULL)
bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_iorid,
sc->sc_iores);
return (0);
}
static void
sab_intr(void *v)
{
struct sab_softc *sc = v;
int needsoft = 0;
uint8_t gis;
gis = SAB_READ(sc, SAB_GIS);
/* channel A */
if ((gis & (SAB_GIS_ISA1 | SAB_GIS_ISA0)) != 0)
needsoft |= sabtty_intr(sc->sc_child[0]);
/* channel B */
if ((gis & (SAB_GIS_ISB1 | SAB_GIS_ISB0)) != 0)
needsoft |= sabtty_intr(sc->sc_child[1]);
if (needsoft)
swi_sched(sc->sc_softih, 0);
}
static void
sab_softintr(void *v)
{
struct sab_softc *sc = v;
sabtty_softintr(sc->sc_child[0]);
sabtty_softintr(sc->sc_child[1]);
}
static void
sab_shutdown(void *v)
{
struct sab_softc *sc = v;
SAB_WRITE(sc, SAB_IPC, SAB_IPC_ICPL | SAB_IPC_VIS);
SAB_WRITE(sc, SAB_RFC, SAB_READ(sc, SAB_RFC) & ~SAB_RFC_RFDF);
}
static int
sabtty_probe(device_t dev)
{
char desc[32];
snprintf(desc, sizeof(desc), "tty%c", device_get_unit(dev) + 'a');
device_set_desc_copy(dev, desc);
return (0);
}
static int
sabtty_attach(device_t dev)
{
struct sabtty_softc *sc;
struct tty *tp;
char mode[32];
int baud;
int clen;
char parity;
int stop;
char c;
sc = device_get_softc(dev);
mtx_init(&sc->sc_mtx, "sabtty", NULL, MTX_SPIN);
sc->sc_dev = dev;
sc->sc_parent = device_get_softc(device_get_parent(dev));
sc->sc_bt = sc->sc_parent->sc_bt;
sc->sc_channel = device_get_unit(dev) & 1;
sc->sc_iput = sc->sc_iget = sc->sc_ibuf;
sc->sc_oget = sc->sc_obuf;
switch (sc->sc_channel) {
case 0: /* port A */
sc->sc_pvr_dtr = SAB_PVR_DTR_A;
sc->sc_pvr_dsr = SAB_PVR_DSR_A;
bus_space_subregion(sc->sc_bt, sc->sc_parent->sc_bh,
SAB_CHAN_A, SAB_CHANLEN, &sc->sc_bh);
break;
case 1: /* port B */
sc->sc_pvr_dtr = SAB_PVR_DTR_B;
sc->sc_pvr_dsr = SAB_PVR_DSR_B;
bus_space_subregion(sc->sc_bt, sc->sc_parent->sc_bh,
SAB_CHAN_B, SAB_CHANLEN, &sc->sc_bh);
break;
default:
return (ENXIO);
}
tp = ttymalloc(NULL);
sc->sc_si = make_dev(&sabtty_cdevsw, device_get_unit(dev),
UID_ROOT, GID_WHEEL, 0600, "%s", device_get_desc(dev));
sc->sc_si->si_drv1 = sc;
sc->sc_si->si_tty = tp;
tp->t_dev = sc->sc_si;
sc->sc_tty = tp;
tp->t_oproc = sabttystart;
tp->t_param = sabttyparam;
tp->t_modem = sabttymodem;
tp->t_break = sabttybreak;
tp->t_stop = sabttystop;
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
tp->t_cflag = CREAD | CLOCAL | CS8;
tp->t_ospeed = TTYDEF_SPEED;
tp->t_ispeed = TTYDEF_SPEED;
if (sabtty_console(dev, mode, sizeof(mode))) {
ttychars(tp);
if (sscanf(mode, "%d,%d,%c,%d,%c", &baud, &clen, &parity,
&stop, &c) == 5) {
tp->t_ospeed = baud;
tp->t_ispeed = baud;
tp->t_cflag = CREAD | CLOCAL;
switch (clen) {
case 5:
tp->t_cflag |= CS5;
break;
case 6:
tp->t_cflag |= CS6;
break;
case 7:
tp->t_cflag |= CS7;
break;
case 8:
default:
tp->t_cflag |= CS8;
break;
}
if (parity == 'e')
tp->t_cflag |= PARENB;
else if (parity == 'o')
tp->t_cflag |= PARENB | PARODD;
if (stop == 2)
tp->t_cflag |= CSTOPB;
}
device_printf(dev, "console %s\n", mode);
sc->sc_console = 1;
sabtty_cons = sc;
}
return (0);
}
static int
sabtty_detach(device_t dev)
{
return (bus_generic_detach(dev));
}
static int
sabtty_intr(struct sabtty_softc *sc)
{
int clearfifo;
int needsoft;
uint8_t isr0;
uint8_t isr1;
uint8_t c;
int brk;
int len;
int i;
brk = 0;
len = 0;
clearfifo = 0;
needsoft = 0;
SABTTY_LOCK(sc);
isr0 = SAB_READ(sc, SAB_ISR0);
isr1 = SAB_READ(sc, SAB_ISR1);
if (isr0 & SAB_ISR0_RPF) {
len = 32;
clearfifo = 1;
}
if (isr0 & SAB_ISR0_TCD) {
len = (32 - 1) & SAB_READ(sc, SAB_RBCL);
clearfifo = 1;
}
if (isr0 & SAB_ISR0_TIME) {
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RFRD);
}
if (isr0 & SAB_ISR0_RFO)
clearfifo = 1;
if (len != 0) {
for (i = 0; i < len; i++) {
c = SAB_READ(sc, SAB_RFIFO);
#if defined(KDB) && defined(ALT_BREAK_TO_DEBUGGER)
if (sc->sc_console != 0 && (i & 1) == 0)
brk = kdb_alt_break(c,
&sc->sc_alt_break_state);
#endif
*sc->sc_iput++ = c;
if (sc->sc_iput == sc->sc_ibuf + sizeof(sc->sc_ibuf))
sc->sc_iput = sc->sc_ibuf;
}
needsoft = 1;
}
if (clearfifo) {
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RMC);
}
if (isr1 & SAB_ISR1_ALLS) {
sc->sc_tx_done = 1;
sc->sc_imr1 |= SAB_IMR1_ALLS;
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
needsoft = 1;
}
#if defined(DDB) && defined(BREAK_TO_DEBUGGER)
if (sc->sc_console != 0 && (isr1 & SAB_ISR1_BRK) != 0) {
brk = 1;
}
#endif
if (isr1 & SAB_ISR1_XPR) {
if (sc->sc_ocnt > 0) {
len = min(sc->sc_ocnt, 32);
for (i = 0; i < len; i++)
SAB_WRITE(sc, SAB_XFIFO + i, *sc->sc_oget++);
sc->sc_ocnt -= len;
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_XF);
}
if (sc->sc_ocnt == 0) {
sc->sc_imr1 |= SAB_IMR1_XPR;
sc->sc_imr1 &= ~SAB_IMR1_ALLS;
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
}
}
SABTTY_UNLOCK(sc);
if (brk != 0)
breakpoint();
return (needsoft);
}
static void
sabtty_softintr(struct sabtty_softc *sc)
{
struct tty *tp = sc->sc_tty;
int data;
int stat;
if ((tp->t_state & TS_ISOPEN) == 0)
return;
while (sc->sc_iget != sc->sc_iput) {
data = *sc->sc_iget++;
stat = *sc->sc_iget++;
if (stat & SAB_RSTAT_PE)
data |= TTY_PE;
if (stat & SAB_RSTAT_FE)
data |= TTY_FE;
if (sc->sc_iget == sc->sc_ibuf + sizeof(sc->sc_ibuf))
sc->sc_iget = sc->sc_ibuf;
ttyld_rint(tp, data);
}
if (sc->sc_tx_done != 0) {
sc->sc_tx_done = 0;
tp->t_state &= ~TS_BUSY;
ttyld_start(tp);
}
}
static int
sabttyopen(struct cdev *dev, int flags, int mode, struct thread *td)
{
struct sabtty_softc *sc;
struct tty *tp;
int error;
sc = dev->si_drv1;
tp = dev->si_tty;
if ((tp->t_state & TS_ISOPEN) != 0 &&
(tp->t_state & TS_XCLUDE) != 0 &&
suser(td) != 0)
return (EBUSY);
if ((tp->t_state & TS_ISOPEN) == 0) {
struct termios t;
sc->sc_iput = sc->sc_iget = sc->sc_ibuf;
/*
* Initialize the termios status to the defaults. Add in the
* sticky bits from TIOCSFLAGS.
*/
t.c_ispeed = 0;
t.c_ospeed = TTYDEF_SPEED;
t.c_cflag = TTYDEF_CFLAG;
/* Make sure zstty_param() will do something. */
tp->t_ospeed = 0;
(void)sabtty_param(sc, tp, &t);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
ttychars(tp);
ttsetwater(tp);
SABTTY_LOCK(sc);
sabtty_reset(sc);
sc->sc_parent->sc_ipc = SAB_IPC_ICPL;
SAB_WRITE(sc->sc_parent, SAB_IPC, sc->sc_parent->sc_ipc);
sc->sc_imr0 = SAB_IMR0_PERR | SAB_IMR0_FERR | SAB_IMR0_PLLA;
SAB_WRITE(sc, SAB_IMR0, sc->sc_imr0);
#if defined(DDB) && defined(BREAK_TO_DEBUGGER)
sc->sc_imr1 = SAB_IMR1_ALLS | SAB_IMR1_XDU |
SAB_IMR1_TIN | SAB_IMR1_CSC | SAB_IMR1_XMR | SAB_IMR1_XPR;
#else
sc->sc_imr1 = SAB_IMR1_BRK | SAB_IMR1_ALLS | SAB_IMR1_XDU |
SAB_IMR1_TIN | SAB_IMR1_CSC | SAB_IMR1_XMR | SAB_IMR1_XPR;
#endif
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
SAB_WRITE(sc, SAB_CCR0, SAB_READ(sc, SAB_CCR0) | SAB_CCR0_PU);
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_XRES);
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RRES);
sabtty_cec_wait(sc);
sabtty_flush(sc);
SABTTY_UNLOCK(sc);
/* XXX turn on DTR */
/* XXX handle initial DCD */
}
error = tty_open(dev, tp);
if (error != 0)
return (error);
error = ttyld_open(tp, dev);
if (error != 0)
return (error);
return (0);
}
static int
sabttyclose(struct cdev *dev, int flags, int mode, struct thread *td)
{
struct tty *tp;
tp = dev->si_tty;
if ((tp->t_state & TS_ISOPEN) == 0)
return (0);
ttyld_close(tp, flags);
tty_close(tp);
return (0);
}
static void
sabttybreak(struct tty *tp, int brk)
{
struct sabtty_softc *sc;
sc = tp->t_dev->si_drv1;
if (brk)
SAB_WRITE(sc, SAB_DAFO,
SAB_READ(sc, SAB_DAFO) | SAB_DAFO_XBRK);
else
SAB_WRITE(sc, SAB_DAFO,
SAB_READ(sc, SAB_DAFO) & ~SAB_DAFO_XBRK);
}
static void
sabttystart(struct tty *tp)
{
struct sabtty_softc *sc;
sc = tp->t_dev->si_drv1;
if ((tp->t_state & TS_TBLOCK) != 0)
/* XXX clear RTS */;
else
/* XXX set RTS */;
if ((tp->t_state & (TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) != 0) {
ttwwakeup(tp);
return;
}
if (tp->t_outq.c_cc <= tp->t_olowat) {
if ((tp->t_state & TS_SO_OLOWAT) != 0) {
tp->t_state &= ~TS_SO_OLOWAT;
wakeup(TSA_OLOWAT(tp));
}
selwakeuppri(&tp->t_wsel, TTOPRI);
if (tp->t_outq.c_cc == 0) {
if ((tp->t_state & (TS_BUSY | TS_SO_OCOMPLETE)) ==
TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) {
tp->t_state &= ~TS_SO_OCOMPLETE;
wakeup(TSA_OCOMPLETE(tp));
}
return;
}
}
sc->sc_ocnt = q_to_b(&tp->t_outq, sc->sc_obuf, sizeof(sc->sc_obuf));
if (sc->sc_ocnt == 0)
return;
sc->sc_oget = sc->sc_obuf;
tp->t_state |= TS_BUSY;
sc->sc_imr1 &= ~SAB_IMR1_XPR;
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
ttwwakeup(tp);
}
static void
sabttystop(struct tty *tp, int flag)
{
struct sabtty_softc *sc;
sc = tp->t_dev->si_drv1;
if ((flag & FREAD) != 0) {
/* XXX stop reading, anything to do? */;
}
if ((flag & FWRITE) != 0) {
if ((tp->t_state & TS_BUSY) != 0) {
/* XXX do what? */
if ((tp->t_state & TS_TTSTOP) == 0)
tp->t_state |= TS_FLUSH;
}
}
}
static int
sabttyparam(struct tty *tp, struct termios *t)
{
struct sabtty_softc *sc;
sc = tp->t_dev->si_drv1;
return (sabtty_param(sc, tp, t));
}
int
sabttymodem(struct tty *tp, int biton, int bitoff)
{
struct sabtty_softc *sc;
u_int8_t r;
sc = tp->t_dev->si_drv1;
if (biton == 0 && bitoff == 0) {
if (SAB_READ(sc, SAB_STAR) & SAB_STAR_CTS)
biton |= TIOCM_CTS;
if ((SAB_READ(sc, SAB_VSTR) & SAB_VSTR_CD) == 0)
biton |= TIOCM_CD;
r = SAB_READ(sc, SAB_PVR);
if ((r & sc->sc_pvr_dtr) == 0)
biton |= TIOCM_DTR;
if ((r & sc->sc_pvr_dsr) == 0)
biton |= TIOCM_DSR;
r = SAB_READ(sc, SAB_MODE);
if ((r & (SAB_MODE_RTS|SAB_MODE_FRTS)) == SAB_MODE_RTS)
biton |= TIOCM_RTS;
return (biton);
}
bitoff |= biton;
if (bitoff & SER_RTS) {
r = SAB_READ(sc, SAB_MODE);
r &= ~SAB_MODE_FRTS;
if (biton & SER_RTS)
r |= SAB_MODE_RTS;
SAB_WRITE(sc, SAB_MODE, r);
}
if (bitoff & SER_DTR) {
r = SAB_READ(sc, SAB_PVR);
r &= ~sc->sc_pvr_dtr;
if (biton & SER_DTR)
r |= sc->sc_pvr_dtr;
SAB_WRITE(sc, SAB_PVR, r);
}
return (0);
}
int
sabtty_param(struct sabtty_softc *sc, struct tty *tp, struct termios *t)
{
int ospeed;
tcflag_t cflag;
u_int8_t dafo, r;
ospeed = sabtty_speed(t->c_ospeed);
if (ospeed < 0 || (t->c_ispeed && t->c_ispeed != t->c_ospeed))
return (EINVAL);
/*
* If there were no changes, don't do anything. This avoids dropping
* input and improves performance when all we did was frob things like
* VMIN and VTIME.
*/
if (tp->t_ospeed == t->c_ospeed &&
tp->t_cflag == t->c_cflag)
return (0);
/* hang up line if ospeed is zero, otherwise raise dtr */
if (t->c_ospeed != 0)
sabttymodem(tp, SER_DTR, 0);
else
sabttymodem(tp, 0, SER_DTR);
dafo = SAB_READ(sc, SAB_DAFO);
cflag = t->c_cflag;
if (sc->sc_console != 0) {
cflag |= CLOCAL;
cflag &= ~HUPCL;
}
if (cflag & CSTOPB)
dafo |= SAB_DAFO_STOP;
else
dafo &= ~SAB_DAFO_STOP;
dafo &= ~SAB_DAFO_CHL_CSIZE;
switch (cflag & CSIZE) {
case CS5:
dafo |= SAB_DAFO_CHL_CS5;
break;
case CS6:
dafo |= SAB_DAFO_CHL_CS6;
break;
case CS7:
dafo |= SAB_DAFO_CHL_CS7;
break;
default:
dafo |= SAB_DAFO_CHL_CS8;
break;
}
dafo &= ~SAB_DAFO_PARMASK;
if (cflag & PARENB) {
if (cflag & PARODD)
dafo |= SAB_DAFO_PAR_ODD;
else
dafo |= SAB_DAFO_PAR_EVEN;
} else
dafo |= SAB_DAFO_PAR_NONE;
tp->t_ispeed = 0;
tp->t_ospeed = t->c_ospeed;
tp->t_cflag = cflag;
ttsetwater(tp);
SABTTY_LOCK(sc);
if (ospeed != 0) {
SAB_WRITE(sc, SAB_BGR, ospeed & 0xff);
r = SAB_READ(sc, SAB_CCR2);
r &= ~(SAB_CCR2_BR9 | SAB_CCR2_BR8);
r |= (ospeed >> 2) & (SAB_CCR2_BR9 | SAB_CCR2_BR8);
SAB_WRITE(sc, SAB_CCR2, r);
}
r = SAB_READ(sc, SAB_MODE);
r |= SAB_MODE_RAC;
if (cflag & CRTSCTS) {
r &= ~(SAB_MODE_RTS | SAB_MODE_FCTS);
r |= SAB_MODE_FRTS;
sc->sc_imr1 &= ~SAB_IMR1_CSC;
} else {
r |= SAB_MODE_RTS | SAB_MODE_FCTS;
r &= ~SAB_MODE_FRTS;
sc->sc_imr1 |= SAB_IMR1_CSC;
}
SAB_WRITE(sc, SAB_MODE, r);
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
SABTTY_UNLOCK(sc);
return (0);
}
static void
sabtty_cec_wait(struct sabtty_softc *sc)
{
int i = 50000;
for (;;) {
if ((SAB_READ(sc, SAB_STAR) & SAB_STAR_CEC) == 0)
return;
if (--i == 0)
break;
DELAY(1);
}
}
static void
sabtty_tec_wait(struct sabtty_softc *sc)
{
int i = 200000;
for (;;) {
if ((SAB_READ(sc, SAB_STAR) & SAB_STAR_TEC) == 0)
return;
if (--i == 0)
break;
DELAY(1);
}
}
static void
sabtty_reset(struct sabtty_softc *sc)
{
/* power down */
SAB_WRITE(sc, SAB_CCR0, 0);
/* set basic configuration */
SAB_WRITE(sc, SAB_CCR0,
SAB_CCR0_MCE | SAB_CCR0_SC_NRZ | SAB_CCR0_SM_ASYNC);
SAB_WRITE(sc, SAB_CCR1, SAB_CCR1_ODS | SAB_CCR1_BCR | SAB_CCR1_CM_7);
SAB_WRITE(sc, SAB_CCR2, SAB_CCR2_BDF | SAB_CCR2_SSEL | SAB_CCR2_TOE);
SAB_WRITE(sc, SAB_CCR3, 0);
SAB_WRITE(sc, SAB_CCR4, SAB_CCR4_MCK4 | SAB_CCR4_EBRG);
SAB_WRITE(sc, SAB_MODE, SAB_MODE_RTS | SAB_MODE_FCTS | SAB_MODE_RAC);
SAB_WRITE(sc, SAB_RFC,
SAB_RFC_DPS | SAB_RFC_RFDF | SAB_RFC_RFTH_32CHAR);
/* clear interrupts */
sc->sc_imr0 = sc->sc_imr1 = 0xff;
SAB_WRITE(sc, SAB_IMR0, sc->sc_imr0);
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
SAB_READ(sc, SAB_ISR0);
SAB_READ(sc, SAB_ISR1);
}
static void
sabtty_flush(struct sabtty_softc *sc)
{
/* clear rx fifo */
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RRES);
/* clear tx fifo */
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_XRES);
}
static int
sabtty_speed(int rate)
{
int i, len, r;
if (rate == 0)
return (0);
len = sizeof(sabtty_baudtable)/sizeof(sabtty_baudtable[0]);
for (i = 0; i < len; i++) {
if (rate == sabtty_baudtable[i].baud) {
r = sabtty_baudtable[i].n |
(sabtty_baudtable[i].m << 6);
return (r);
}
}
return (-1);
}
static void
sab_cnprobe(struct consdev *cn)
{
struct sabtty_softc *sc = sabtty_cons;
if (sc == NULL)
cn->cn_pri = CN_DEAD;
else {
cn->cn_pri = CN_REMOTE;
strcpy(cn->cn_name, devtoname(sc->sc_si));
cn->cn_tp = sc->sc_tty;
}
}
static void
sab_cninit(struct consdev *cn)
{
}
static void
sab_cnterm(struct consdev *cn)
{
}
static int
sab_cngetc(struct consdev *cn)
{
struct sabtty_softc *sc = sabtty_cons;
if (sc == NULL)
return (-1);
return (sabtty_cngetc(sc));
}
static int
sab_cncheckc(struct consdev *cn)
{
struct sabtty_softc *sc = sabtty_cons;
if (sc == NULL)
return (-1);
return (sabtty_cncheckc(sc));
}
static void
sab_cnputc(struct consdev *cn, int c)
{
struct sabtty_softc *sc = sabtty_cons;
if (sc == NULL)
return;
sabtty_cnputc(sc, c);
}
static void
sab_cndbctl(struct consdev *cn, int c)
{
}
static void
sabtty_cnopen(struct sabtty_softc *sc)
{
SAB_WRITE(sc, SAB_IMR0, 0xff);
SAB_WRITE(sc, SAB_IMR1, 0xff);
SAB_WRITE(sc->sc_parent, SAB_IPC, sc->sc_parent->sc_ipc | SAB_IPC_VIS);
}
static void
sabtty_cnclose(struct sabtty_softc *sc)
{
SAB_WRITE(sc, SAB_IMR0, sc->sc_imr0);
SAB_WRITE(sc, SAB_IMR1, sc->sc_imr1);
SAB_WRITE(sc->sc_parent, SAB_IPC, sc->sc_parent->sc_ipc);
}
static int
sabtty_cngetc(struct sabtty_softc *sc)
{
uint8_t len;
uint8_t r;
sabtty_cnopen(sc);
again:
do {
r = SAB_READ(sc, SAB_STAR);
} while ((r & SAB_STAR_RFNE) == 0);
/*
* Ok, at least one byte in RFIFO, ask for permission to access RFIFO
* (I hate this chip... hate hate hate).
*/
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RFRD);
/* Wait for RFIFO to come ready */
do {
r = SAB_READ(sc, SAB_ISR0);
} while ((r & SAB_ISR0_TCD) == 0);
len = SAB_READ(sc, SAB_RBCL) & (32 - 1);
if (len == 0)
goto again; /* Shouldn't happen... */
r = SAB_READ(sc, SAB_RFIFO);
/*
* Blow away everything left in the FIFO...
*/
sabtty_cec_wait(sc);
SAB_WRITE(sc, SAB_CMDR, SAB_CMDR_RMC);
sabtty_cnclose(sc);
return (r);
}
static int
sabtty_cncheckc(struct sabtty_softc *sc)
{
int8_t r;
r = -1;
sabtty_cnopen(sc);
if ((SAB_READ(sc, SAB_STAR) & SAB_STAR_RFNE) != 0)
r = sabtty_cngetc(sc);
sabtty_cnclose(sc);
return (r);
}
static void
sabtty_cnputc(struct sabtty_softc *sc, int c)
{
sabtty_cnopen(sc);
sabtty_tec_wait(sc);
SAB_WRITE(sc, SAB_TIC, c);
sabtty_tec_wait(sc);
sabtty_cnclose(sc);
}
static int
sabtty_console(device_t dev, char *mode, int len)
{
phandle_t chosen;
phandle_t options;
phandle_t parent;
ihandle_t stdin;
ihandle_t stdout;
char output[32];
char input[32];
char name[32];
chosen = OF_finddevice("/chosen");
options = OF_finddevice("/options");
parent = ofw_bus_get_node(device_get_parent(dev));
if (OF_getprop(chosen, "stdin", &stdin, sizeof(stdin)) == -1 ||
OF_getprop(chosen, "stdout", &stdout, sizeof(stdout)) == -1 ||
OF_getprop(options, "input-device", input, sizeof(input)) == -1 ||
OF_getprop(options, "output-device", output, sizeof(output)) == -1)
return (0);
if (parent != OF_instance_to_package(stdin) ||
parent != OF_instance_to_package(stdout))
return (0);
if ((strcmp(input, device_get_desc(dev)) == 0 &&
strcmp(output, device_get_desc(dev)) == 0) ||
(strcmp(input, "keyboard") == 0 && strcmp(output, "screen") == 0 &&
(device_get_unit(dev) & 1) == 0)) {
if (mode != NULL) {
sprintf(name, "%s-mode", device_get_desc(dev));
return (OF_getprop(options, name, mode, len) != -1);
} else
return (1);
}
return (0);
}