#include <sys/param.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/timeout.h>
#include <sys/tty.h>
#include <sys/systm.h>
#ifdef DDB
#include <ddb/db_var.h>
#endif
#include <machine/autoconf.h>
#include <machine/conf.h>
#include <machine/openfirm.h>
#include <machine/sparc64.h>
#include <dev/cons.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/clock_subr.h>
extern todr_chip_handle_t todr_handle;
#define SBBC_PCI_BAR PCI_MAPREG_START
#define SBBC_REGS_OFFSET 0x800000
#define SBBC_REGS_SIZE 0x6230
#define SBBC_EPLD_OFFSET 0x8e0000
#define SBBC_EPLD_SIZE 0x20
#define SBBC_SRAM_OFFSET 0x900000
#define SBBC_SRAM_SIZE 0x20000
#define SBBC_PCI_INT_STATUS 0x2320
#define SBBC_PCI_INT_ENABLE 0x2330
#define SBBC_PCI_ENABLE_INT_A 0x11
#define SBBC_EPLD_INTERRUPT 0x13
#define SBBC_EPLD_INTERRUPT_ON 0x01
#define SBBC_SRAM_CONS_IN 0x00000001
#define SBBC_SRAM_CONS_OUT 0x00000002
#define SBBC_SRAM_CONS_BRK 0x00000004
#define SBBC_SRAM_CONS_SPACE_IN 0x00000008
#define SBBC_SRAM_CONS_SPACE_OUT 0x00000010
#define SBBC_MAX_TAGS 32
struct sbbc_sram_tag {
char tag_key[8];
uint32_t tag_size;
uint32_t tag_offset;
};
struct sbbc_sram_toc {
char toc_magic[8];
uint8_t toc_reserved;
uint8_t toc_type;
uint16_t toc_version;
uint32_t toc_ntags;
struct sbbc_sram_tag toc_tag[SBBC_MAX_TAGS];
};
struct sbbc_sram_tod {
uint32_t tod_magic;
uint32_t tod_version;
uint64_t tod_time;
uint64_t tod_skew;
uint32_t tod_reserved;
uint32_t tod_heartbeat;
uint32_t tod_timeout;
};
#define SBBC_TOD_MAGIC 0x54443100
#define SBBC_TOD_VERSION 1
struct sbbc_sram_cons {
uint32_t cons_magic;
uint32_t cons_version;
uint32_t cons_size;
uint32_t cons_in_begin;
uint32_t cons_in_end;
uint32_t cons_in_rdptr;
uint32_t cons_in_wrptr;
uint32_t cons_out_begin;
uint32_t cons_out_end;
uint32_t cons_out_rdptr;
uint32_t cons_out_wrptr;
};
#define SBBC_CONS_MAGIC 0x434f4e00
#define SBBC_CONS_VERSION 1
struct sbbc_softc {
struct device sc_dv;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_regs_ioh;
bus_space_handle_t sc_epld_ioh;
bus_space_handle_t sc_sram_ioh;
caddr_t sc_sram;
uint32_t sc_sram_toc;
void * sc_ih;
struct sparc_bus_space_tag sc_bbt;
struct tty *sc_tty;
caddr_t sc_sram_cons;
uint32_t *sc_sram_solscie;
uint32_t *sc_sram_solscir;
uint32_t *sc_sram_scsolie;
uint32_t *sc_sram_scsolir;
void *sc_cons_si;
};
struct sbbc_softc *sbbc_cons_input;
struct sbbc_softc *sbbc_cons_output;
int sbbc_match(struct device *, void *, void *);
void sbbc_attach(struct device *, struct device *, void *);
const struct cfattach sbbc_ca = {
sizeof(struct sbbc_softc), sbbc_match, sbbc_attach
};
struct cfdriver sbbc_cd = {
NULL, "sbbc", DV_DULL
};
int sbbc_intr(void *);
void sbbc_send_intr(struct sbbc_softc *sc);
void sbbc_attach_tod(struct sbbc_softc *, uint32_t);
int sbbc_tod_gettime(todr_chip_handle_t, struct timeval *);
int sbbc_tod_settime(todr_chip_handle_t, struct timeval *);
void sbbc_attach_cons(struct sbbc_softc *, uint32_t);
void sbbc_intr_cons(struct sbbc_softc *, uint32_t);
void sbbc_softintr_cons(void *);
int sbbc_cnlookc(dev_t, int *);
int sbbc_cngetc(dev_t);
void sbbc_cnputc(dev_t, int);
void sbbcstart(struct tty *);
int sbbcparam(struct tty *, struct termios *);
int
sbbc_match(struct device *parent, void *match, void *aux)
{
struct pci_attach_args *pa = aux;
if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_SUN &&
(PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_SUN_SBBC))
return (1);
return (0);
}
void
sbbc_attach(struct device *parent, struct device *self, void *aux)
{
struct sbbc_softc *sc = (void *)self;
struct pci_attach_args *pa = aux;
struct sbbc_sram_toc *toc;
bus_addr_t base;
bus_size_t size;
pci_intr_handle_t ih;
int chosen, iosram;
int i;
sc->sc_bbt = *pa->pa_memt;
sc->sc_bbt.sasi = ASI_PRIMARY;
sc->sc_iot = &sc->sc_bbt;
if (pci_mapreg_info(pa->pa_pc, pa->pa_tag, SBBC_PCI_BAR,
PCI_MAPREG_TYPE_MEM, &base, &size, NULL)) {
printf(": can't find register space\n");
return;
}
if (bus_space_map(sc->sc_iot, base + SBBC_REGS_OFFSET,
SBBC_REGS_SIZE, 0, &sc->sc_regs_ioh)) {
printf(": can't map register space\n");
return;
}
if (bus_space_map(sc->sc_iot, base + SBBC_EPLD_OFFSET,
SBBC_EPLD_SIZE, 0, &sc->sc_epld_ioh)) {
printf(": can't map EPLD registers\n");
goto unmap_regs;
}
if (bus_space_map(sc->sc_iot, base + SBBC_SRAM_OFFSET,
SBBC_SRAM_SIZE, 0, &sc->sc_sram_ioh)) {
printf(": can't map SRAM\n");
goto unmap_epld;
}
if (pci_intr_map(pa, &ih)) {
printf(": unable to map interrupt\n");
goto unmap_sram;
}
printf(": %s\n", pci_intr_string(pa->pa_pc, ih));
sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_TTY,
sbbc_intr, sc, sc->sc_dv.dv_xname);
if (sc->sc_ih == NULL) {
printf("%s: unable to establish interrupt\n", sc->sc_dv.dv_xname);
goto unmap_sram;
}
bus_space_write_4(sc->sc_iot, sc->sc_regs_ioh,
SBBC_PCI_INT_ENABLE, SBBC_PCI_ENABLE_INT_A);
chosen = OF_finddevice("/chosen");
if (OF_getprop(chosen, "iosram", &iosram, sizeof(iosram)) <= 0 ||
PCITAG_NODE(pa->pa_tag) != iosram)
return;
if (OF_getprop(chosen, "iosram-toc", &sc->sc_sram_toc,
sizeof(sc->sc_sram_toc)) <= 0)
sc->sc_sram_toc = 0;
sc->sc_sram = bus_space_vaddr(sc->sc_iot, sc->sc_sram_ioh);
toc = (struct sbbc_sram_toc *)(sc->sc_sram + sc->sc_sram_toc);
for (i = 0; i < toc->toc_ntags; i++) {
if (strcmp(toc->toc_tag[i].tag_key, "SOLSCIE") == 0)
sc->sc_sram_solscie = (uint32_t *)
(sc->sc_sram + toc->toc_tag[i].tag_offset);
if (strcmp(toc->toc_tag[i].tag_key, "SOLSCIR") == 0)
sc->sc_sram_solscir = (uint32_t *)
(sc->sc_sram + toc->toc_tag[i].tag_offset);
if (strcmp(toc->toc_tag[i].tag_key, "SCSOLIE") == 0)
sc->sc_sram_scsolie = (uint32_t *)
(sc->sc_sram + toc->toc_tag[i].tag_offset);
if (strcmp(toc->toc_tag[i].tag_key, "SCSOLIR") == 0)
sc->sc_sram_scsolir = (uint32_t *)
(sc->sc_sram + toc->toc_tag[i].tag_offset);
}
for (i = 0; i < toc->toc_ntags; i++) {
if (strcmp(toc->toc_tag[i].tag_key, "TODDATA") == 0)
sbbc_attach_tod(sc, toc->toc_tag[i].tag_offset);
if (strcmp(toc->toc_tag[i].tag_key, "SOLCONS") == 0)
sbbc_attach_cons(sc, toc->toc_tag[i].tag_offset);
}
return;
unmap_sram:
bus_space_unmap(sc->sc_iot, sc->sc_sram_ioh, SBBC_SRAM_SIZE);
unmap_epld:
bus_space_unmap(sc->sc_iot, sc->sc_sram_ioh, SBBC_EPLD_SIZE);
unmap_regs:
bus_space_unmap(sc->sc_iot, sc->sc_sram_ioh, SBBC_REGS_SIZE);
}
int
sbbc_intr(void *arg)
{
struct sbbc_softc *sc = arg;
uint32_t status, reason;
status = bus_space_read_4(sc->sc_iot, sc->sc_regs_ioh,
SBBC_PCI_INT_STATUS);
if (status == 0)
return (0);
reason = *sc->sc_sram_scsolir;
*sc->sc_sram_scsolir = 0;
sbbc_intr_cons(sc, reason);
bus_space_write_4(sc->sc_iot, sc->sc_regs_ioh,
SBBC_PCI_INT_STATUS, status);
return (1);
}
void
sbbc_send_intr(struct sbbc_softc *sc)
{
bus_space_write_1(sc->sc_iot, sc->sc_epld_ioh,
SBBC_EPLD_INTERRUPT, SBBC_EPLD_INTERRUPT_ON);
}
void
sbbc_attach_tod(struct sbbc_softc *sc, uint32_t offset)
{
struct sbbc_sram_tod *tod;
todr_chip_handle_t handle;
tod = (struct sbbc_sram_tod *)(sc->sc_sram + offset);
if (tod->tod_magic != SBBC_TOD_MAGIC ||
tod->tod_version < SBBC_TOD_VERSION)
return;
handle = malloc(sizeof(struct todr_chip_handle), M_DEVBUF, M_NOWAIT);
if (handle == NULL)
panic("couldn't allocate todr_handle");
handle->cookie = tod;
handle->todr_gettime = sbbc_tod_gettime;
handle->todr_settime = sbbc_tod_settime;
handle->bus_cookie = NULL;
handle->todr_setwen = NULL;
handle->todr_quality = 0;
todr_handle = handle;
}
int
sbbc_tod_gettime(todr_chip_handle_t handle, struct timeval *tv)
{
struct sbbc_sram_tod *tod = handle->cookie;
tv->tv_sec = tod->tod_time + tod->tod_skew;
tv->tv_usec = 0;
return (0);
}
int
sbbc_tod_settime(todr_chip_handle_t handle, struct timeval *tv)
{
struct sbbc_sram_tod *tod = handle->cookie;
tod->tod_skew = tv->tv_sec - tod->tod_time;
return (0);
}
void
sbbc_attach_cons(struct sbbc_softc *sc, uint32_t offset)
{
struct sbbc_sram_cons *cons;
int sgcn_is_input, sgcn_is_output, node, maj;
char buf[32];
if (sc->sc_sram_solscie == NULL || sc->sc_sram_solscir == NULL ||
sc->sc_sram_scsolie == NULL || sc->sc_sram_scsolir == NULL)
return;
cons = (struct sbbc_sram_cons *)(sc->sc_sram + offset);
if (cons->cons_magic != SBBC_CONS_MAGIC ||
cons->cons_version < SBBC_CONS_VERSION)
return;
sc->sc_sram_cons = sc->sc_sram + offset;
sbbc_cons_input = sbbc_cons_output = sc;
sgcn_is_input = sgcn_is_output = 0;
sc->sc_cons_si = softintr_establish(IPL_TTY, sbbc_softintr_cons, sc);
if (sc->sc_cons_si == NULL)
panic("%s: can't establish soft interrupt",
sc->sc_dv.dv_xname);
*sc->sc_sram_solscie |= SBBC_SRAM_CONS_OUT;
*sc->sc_sram_scsolie |= SBBC_SRAM_CONS_IN | SBBC_SRAM_CONS_BRK;
prom_serengeti_set_console_input("CON_CLNT");
node = OF_instance_to_package(OF_stdin());
if (OF_getprop(node, "name", buf, sizeof(buf)) > 0)
sgcn_is_input = (strcmp(buf, "sgcn") == 0);
node = OF_instance_to_package(OF_stdout());
if (OF_getprop(node, "name", buf, sizeof(buf)) > 0)
sgcn_is_output = (strcmp(buf, "sgcn") == 0);
if (sgcn_is_input) {
cn_tab->cn_pollc = nullcnpollc;
cn_tab->cn_getc = sbbc_cngetc;
}
if (sgcn_is_output)
cn_tab->cn_putc = sbbc_cnputc;
if (sgcn_is_input || sgcn_is_output) {
for (maj = 0; maj < nchrdev; maj++)
if (cdevsw[maj].d_open == sbbcopen)
break;
cn_tab->cn_dev = makedev(maj, sc->sc_dv.dv_unit);
DELAY(2000000);
printf("%s: console\n", sc->sc_dv.dv_xname);
}
}
void
sbbc_intr_cons(struct sbbc_softc *sc, uint32_t reason)
{
#ifdef DDB
if ((reason & SBBC_SRAM_CONS_BRK) && sc == sbbc_cons_input) {
if (db_console)
db_enter();
}
#endif
if ((reason & SBBC_SRAM_CONS_IN) && sc->sc_tty)
softintr_schedule(sc->sc_cons_si);
}
void
sbbc_softintr_cons(void *arg)
{
struct sbbc_softc *sc = arg;
struct sbbc_sram_cons *cons = (void *)sc->sc_sram_cons;
uint32_t rdptr = cons->cons_in_rdptr;
struct tty *tp = sc->sc_tty;
int c;
while (rdptr != cons->cons_in_wrptr) {
if (tp->t_state & TS_ISOPEN) {
c = *(sc->sc_sram_cons + rdptr);
(*linesw[tp->t_line].l_rint)(c, tp);
}
if (++rdptr == cons->cons_in_end)
rdptr = cons->cons_in_begin;
}
cons->cons_in_rdptr = rdptr;
}
int
sbbc_cnlookc(dev_t dev, int *cp)
{
struct sbbc_softc *sc = sbbc_cons_input;
struct sbbc_sram_cons *cons = (void *)sc->sc_sram_cons;
uint32_t rdptr = cons->cons_in_rdptr;
if (rdptr == cons->cons_in_wrptr)
return (0);
*cp = *(sc->sc_sram_cons + rdptr);
if (++rdptr == cons->cons_in_end)
rdptr = cons->cons_in_begin;
cons->cons_in_rdptr = rdptr;
return (1);
}
int
sbbc_cngetc(dev_t dev)
{
int c;
while(!sbbc_cnlookc(dev, &c))
;
return (c);
}
void
sbbc_cnputc(dev_t dev, int c)
{
struct sbbc_softc *sc = sbbc_cons_output;
struct sbbc_sram_cons *cons = (void *)sc->sc_sram_cons;
uint32_t wrptr = cons->cons_out_wrptr;
*(sc->sc_sram_cons + wrptr) = c;
if (++wrptr == cons->cons_out_end)
wrptr = cons->cons_out_begin;
cons->cons_out_wrptr = wrptr;
*sc->sc_sram_solscir |= SBBC_SRAM_CONS_OUT;
sbbc_send_intr(sc);
}
int
sbbcopen(dev_t dev, int flag, int mode, struct proc *p)
{
struct sbbc_softc *sc;
struct tty *tp;
int unit = minor(dev);
if (unit >= sbbc_cd.cd_ndevs)
return (ENXIO);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (ENXIO);
if (sc->sc_tty)
tp = sc->sc_tty;
else
tp = sc->sc_tty = ttymalloc(0);
tp->t_oproc = sbbcstart;
tp->t_param = sbbcparam;
tp->t_dev = dev;
if ((tp->t_state & TS_ISOPEN) == 0) {
ttychars(tp);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_cflag = TTYDEF_CFLAG;
tp->t_lflag = TTYDEF_LFLAG;
tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
ttsetwater(tp);
} else if ((tp->t_state & TS_XCLUDE) && suser(p))
return (EBUSY);
tp->t_state |= TS_CARR_ON;
return ((*linesw[tp->t_line].l_open)(dev, tp, p));
}
int
sbbcclose(dev_t dev, int flag, int mode, struct proc *p)
{
struct sbbc_softc *sc;
struct tty *tp;
int unit = minor(dev);
if (unit >= sbbc_cd.cd_ndevs)
return (ENXIO);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
(*linesw[tp->t_line].l_close)(tp, flag, p);
ttyclose(tp);
return (0);
}
int
sbbcread(dev_t dev, struct uio *uio, int flag)
{
struct sbbc_softc *sc;
struct tty *tp;
int unit = minor(dev);
if (unit >= sbbc_cd.cd_ndevs)
return (ENXIO);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
}
int
sbbcwrite(dev_t dev, struct uio *uio, int flag)
{
struct sbbc_softc *sc;
struct tty *tp;
int unit = minor(dev);
if (unit >= sbbc_cd.cd_ndevs)
return (ENXIO);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
}
int
sbbcioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
struct sbbc_softc *sc;
struct tty *tp;
int unit = minor(dev);
int error;
if (unit >= sbbc_cd.cd_ndevs)
return (ENXIO);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (ENXIO);
tp = sc->sc_tty;
error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
if (error >= 0)
return error;
error = ttioctl(tp, cmd, data, flag, p);
if (error >= 0)
return (error);
return (ENOTTY);
}
void
sbbcstart(struct tty *tp)
{
int s;
s = spltty();
if (tp->t_state & (TS_TTSTOP | TS_BUSY)) {
splx(s);
return;
}
ttwakeupwr(tp);
tp->t_state |= TS_BUSY;
while (tp->t_outq.c_cc != 0)
sbbc_cnputc(tp->t_dev, getc(&tp->t_outq));
tp->t_state &= ~TS_BUSY;
splx(s);
}
int
sbbcstop(struct tty *tp, int flag)
{
int s;
s = spltty();
if (tp->t_state & TS_BUSY)
if ((tp->t_state & TS_TTSTOP) == 0)
tp->t_state |= TS_FLUSH;
splx(s);
return (0);
}
struct tty *
sbbctty(dev_t dev)
{
struct sbbc_softc *sc;
int unit = minor(dev);
if (unit >= sbbc_cd.cd_ndevs)
return (NULL);
sc = sbbc_cd.cd_devs[unit];
if (sc == NULL)
return (NULL);
return sc->sc_tty;
}
int
sbbcparam(struct tty *tp, struct termios *t)
{
tp->t_ispeed = t->c_ispeed;
tp->t_ospeed = t->c_ospeed;
tp->t_cflag = t->c_cflag;
return (0);
}