freebsd-dev/sys/sparc64/pci/sbbc.c
Ian Lepore 4d7abca057 Fix low-level uart drivers that set their fifo sizes in the softc too late.
uart(4) allocates send and receiver buffers in attach() before it calls
the low-level driver's attach routine.  Many low-level drivers set the
fifo sizes in their attach routine, which is too late.  Other drivers set
them in the probe() routine, so that they're available when uart(4)
allocates buffers.  This fixes the ones that were setting the values too
late by moving the code to probe().
2013-04-01 00:44:20 +00:00

1111 lines
30 KiB
C

/* $OpenBSD: sbbc.c,v 1.7 2009/11/09 17:53:39 nicm Exp $ */
/*-
* Copyright (c) 2008 Mark Kettenis
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*-
* Copyright (c) 2010 Marius Strobl <marius@FreeBSD.org>
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/clock.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/openfirm.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/resource.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/uart/uart.h>
#include <dev/uart/uart_cpu.h>
#include <dev/uart/uart_bus.h>
#include "clock_if.h"
#include "uart_if.h"
#define SBBC_PCI_BAR PCIR_BAR(0)
#define SBBC_PCI_VENDOR 0x108e
#define SBBC_PCI_PRODUCT 0xc416
#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 /* 128KB SRAM */
#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_TAG_KEY_SIZE 8
#define SBBC_TAG_KEY_SCSOLIE "SCSOLIE" /* SC -> OS int. enable */
#define SBBC_TAG_KEY_SCSOLIR "SCSOLIR" /* SC -> OS int. reason */
#define SBBC_TAG_KEY_SOLCONS "SOLCONS" /* OS console buffer */
#define SBBC_TAG_KEY_SOLSCIE "SOLSCIE" /* OS -> SC int. enable */
#define SBBC_TAG_KEY_SOLSCIR "SOLSCIR" /* OS -> SC int. reason */
#define SBBC_TAG_KEY_TODDATA "TODDATA" /* OS TOD struct */
#define SBBC_TAG_OFF(x) offsetof(struct sbbc_sram_tag, x)
struct sbbc_sram_tag {
char tag_key[SBBC_TAG_KEY_SIZE];
uint32_t tag_size;
uint32_t tag_offset;
} __packed;
#define SBBC_TOC_MAGIC "TOCSRAM"
#define SBBC_TOC_MAGIC_SIZE 8
#define SBBC_TOC_TAGS_MAX 32
#define SBBC_TOC_OFF(x) offsetof(struct sbbc_sram_toc, x)
struct sbbc_sram_toc {
char toc_magic[SBBC_TOC_MAGIC_SIZE];
uint8_t toc_reserved;
uint8_t toc_type;
uint16_t toc_version;
uint32_t toc_ntags;
struct sbbc_sram_tag toc_tag[SBBC_TOC_TAGS_MAX];
} __packed;
#define SBBC_TOD_MAGIC 0x54443100 /* "TD1" */
#define SBBC_TOD_VERSION 1
#define SBBC_TOD_OFF(x) offsetof(struct sbbc_sram_tod, x)
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;
} __packed;
#define SBBC_CONS_MAGIC 0x434f4e00 /* "CON" */
#define SBBC_CONS_VERSION 1
#define SBBC_CONS_OFF(x) offsetof(struct sbbc_sram_cons, x)
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;
} __packed;
struct sbbc_softc {
struct resource *sc_res;
};
#define SBBC_READ_N(wdth, offs) \
bus_space_read_ ## wdth((bst), (bsh), (offs))
#define SBBC_WRITE_N(wdth, offs, val) \
bus_space_write_ ## wdth((bst), (bsh), (offs), (val))
#define SBBC_READ_1(offs) \
SBBC_READ_N(1, (offs))
#define SBBC_READ_2(offs) \
bswap16(SBBC_READ_N(2, (offs)))
#define SBBC_READ_4(offs) \
bswap32(SBBC_READ_N(4, (offs)))
#define SBBC_READ_8(offs) \
bswap64(SBBC_READ_N(8, (offs)))
#define SBBC_WRITE_1(offs, val) \
SBBC_WRITE_N(1, (offs), (val))
#define SBBC_WRITE_2(offs, val) \
SBBC_WRITE_N(2, (offs), bswap16(val))
#define SBBC_WRITE_4(offs, val) \
SBBC_WRITE_N(4, (offs), bswap32(val))
#define SBBC_WRITE_8(offs, val) \
SBBC_WRITE_N(8, (offs), bswap64(val))
#define SBBC_REGS_READ_1(offs) \
SBBC_READ_1((offs) + SBBC_REGS_OFFSET)
#define SBBC_REGS_READ_2(offs) \
SBBC_READ_2((offs) + SBBC_REGS_OFFSET)
#define SBBC_REGS_READ_4(offs) \
SBBC_READ_4((offs) + SBBC_REGS_OFFSET)
#define SBBC_REGS_READ_8(offs) \
SBBC_READ_8((offs) + SBBC_REGS_OFFSET)
#define SBBC_REGS_WRITE_1(offs, val) \
SBBC_WRITE_1((offs) + SBBC_REGS_OFFSET, (val))
#define SBBC_REGS_WRITE_2(offs, val) \
SBBC_WRITE_2((offs) + SBBC_REGS_OFFSET, (val))
#define SBBC_REGS_WRITE_4(offs, val) \
SBBC_WRITE_4((offs) + SBBC_REGS_OFFSET, (val))
#define SBBC_REGS_WRITE_8(offs, val) \
SBBC_WRITE_8((offs) + SBBC_REGS_OFFSET, (val))
#define SBBC_EPLD_READ_1(offs) \
SBBC_READ_1((offs) + SBBC_EPLD_OFFSET)
#define SBBC_EPLD_READ_2(offs) \
SBBC_READ_2((offs) + SBBC_EPLD_OFFSET)
#define SBBC_EPLD_READ_4(offs) \
SBBC_READ_4((offs) + SBBC_EPLD_OFFSET)
#define SBBC_EPLD_READ_8(offs) \
SBBC_READ_8((offs) + SBBC_EPLD_OFFSET)
#define SBBC_EPLD_WRITE_1(offs, val) \
SBBC_WRITE_1((offs) + SBBC_EPLD_OFFSET, (val))
#define SBBC_EPLD_WRITE_2(offs, val) \
SBBC_WRITE_2((offs) + SBBC_EPLD_OFFSET, (val))
#define SBBC_EPLD_WRITE_4(offs, val) \
SBBC_WRITE_4((offs) + SBBC_EPLD_OFFSET, (val))
#define SBBC_EPLD_WRITE_8(offs, val) \
SBBC_WRITE_8((offs) + SBBC_EPLD_OFFSET, (val))
#define SBBC_SRAM_READ_1(offs) \
SBBC_READ_1((offs) + SBBC_SRAM_OFFSET)
#define SBBC_SRAM_READ_2(offs) \
SBBC_READ_2((offs) + SBBC_SRAM_OFFSET)
#define SBBC_SRAM_READ_4(offs) \
SBBC_READ_4((offs) + SBBC_SRAM_OFFSET)
#define SBBC_SRAM_READ_8(offs) \
SBBC_READ_8((offs) + SBBC_SRAM_OFFSET)
#define SBBC_SRAM_WRITE_1(offs, val) \
SBBC_WRITE_1((offs) + SBBC_SRAM_OFFSET, (val))
#define SBBC_SRAM_WRITE_2(offs, val) \
SBBC_WRITE_2((offs) + SBBC_SRAM_OFFSET, (val))
#define SBBC_SRAM_WRITE_4(offs, val) \
SBBC_WRITE_4((offs) + SBBC_SRAM_OFFSET, (val))
#define SBBC_SRAM_WRITE_8(offs, val) \
SBBC_WRITE_8((offs) + SBBC_SRAM_OFFSET, (val))
#define SUNW_SETCONSINPUT "SUNW,set-console-input"
#define SUNW_SETCONSINPUT_CLNT "CON_CLNT"
#define SUNW_SETCONSINPUT_OBP "CON_OBP"
static u_int sbbc_console;
static uint32_t sbbc_scsolie;
static uint32_t sbbc_scsolir;
static uint32_t sbbc_solcons;
static uint32_t sbbc_solscie;
static uint32_t sbbc_solscir;
static uint32_t sbbc_toddata;
/*
* internal helpers
*/
static int sbbc_parse_toc(bus_space_tag_t bst, bus_space_handle_t bsh);
static inline void sbbc_send_intr(bus_space_tag_t bst,
bus_space_handle_t bsh);
static const char *sbbc_serengeti_set_console_input(char *new);
/*
* SBBC PCI interface
*/
static bus_activate_resource_t sbbc_bus_activate_resource;
static bus_adjust_resource_t sbbc_bus_adjust_resource;
static bus_deactivate_resource_t sbbc_bus_deactivate_resource;
static bus_alloc_resource_t sbbc_bus_alloc_resource;
static bus_release_resource_t sbbc_bus_release_resource;
static bus_get_resource_list_t sbbc_bus_get_resource_list;
static bus_setup_intr_t sbbc_bus_setup_intr;
static bus_teardown_intr_t sbbc_bus_teardown_intr;
static device_attach_t sbbc_pci_attach;
static device_probe_t sbbc_pci_probe;
static clock_gettime_t sbbc_tod_gettime;
static clock_settime_t sbbc_tod_settime;
static device_method_t sbbc_pci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, sbbc_pci_probe),
DEVMETHOD(device_attach, sbbc_pci_attach),
DEVMETHOD(bus_alloc_resource, sbbc_bus_alloc_resource),
DEVMETHOD(bus_activate_resource,sbbc_bus_activate_resource),
DEVMETHOD(bus_deactivate_resource,sbbc_bus_deactivate_resource),
DEVMETHOD(bus_adjust_resource, sbbc_bus_adjust_resource),
DEVMETHOD(bus_release_resource, sbbc_bus_release_resource),
DEVMETHOD(bus_setup_intr, sbbc_bus_setup_intr),
DEVMETHOD(bus_teardown_intr, sbbc_bus_teardown_intr),
DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource),
DEVMETHOD(bus_get_resource_list, sbbc_bus_get_resource_list),
/* clock interface */
DEVMETHOD(clock_gettime, sbbc_tod_gettime),
DEVMETHOD(clock_settime, sbbc_tod_settime),
DEVMETHOD_END
};
static devclass_t sbbc_devclass;
DEFINE_CLASS_0(sbbc, sbbc_driver, sbbc_pci_methods, sizeof(struct sbbc_softc));
DRIVER_MODULE(sbbc, pci, sbbc_driver, sbbc_devclass, NULL, NULL);
static int
sbbc_pci_probe(device_t dev)
{
if (pci_get_vendor(dev) == SBBC_PCI_VENDOR &&
pci_get_device(dev) == SBBC_PCI_PRODUCT) {
device_set_desc(dev, "Sun BootBus controller");
return (BUS_PROBE_DEFAULT);
}
return (ENXIO);
}
static int
sbbc_pci_attach(device_t dev)
{
struct sbbc_softc *sc;
struct timespec ts;
device_t child;
bus_space_tag_t bst;
bus_space_handle_t bsh;
phandle_t node;
int error, rid;
uint32_t val;
/* Nothing to to if we're not the chosen one. */
if ((node = OF_finddevice("/chosen")) == -1) {
device_printf(dev, "failed to find /chosen\n");
return (ENXIO);
}
if (OF_getprop(node, "iosram", &node, sizeof(node)) == -1) {
device_printf(dev, "failed to get iosram\n");
return (ENXIO);
}
if (node != ofw_bus_get_node(dev))
return (0);
sc = device_get_softc(dev);
rid = SBBC_PCI_BAR;
sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->sc_res == NULL) {
device_printf(dev, "failed to allocate resources\n");
return (ENXIO);
}
bst = rman_get_bustag(sc->sc_res);
bsh = rman_get_bushandle(sc->sc_res);
if (sbbc_console != 0) {
/* Once again the interrupt pin isn't set. */
if (pci_get_intpin(dev) == 0)
pci_set_intpin(dev, 1);
child = device_add_child(dev, NULL, -1);
if (child == NULL)
device_printf(dev, "failed to add UART device\n");
error = bus_generic_attach(dev);
if (error != 0)
device_printf(dev, "failed to attach UART device\n");
} else {
error = sbbc_parse_toc(bst, bsh);
if (error != 0) {
device_printf(dev, "failed to parse TOC\n");
if (sbbc_console != 0) {
bus_release_resource(dev, SYS_RES_MEMORY, rid,
sc->sc_res);
return (error);
}
}
}
if (sbbc_toddata != 0) {
if ((val = SBBC_SRAM_READ_4(sbbc_toddata +
SBBC_TOD_OFF(tod_magic))) != SBBC_TOD_MAGIC)
device_printf(dev, "invalid TOD magic %#x\n", val);
else if ((val = SBBC_SRAM_READ_4(sbbc_toddata +
SBBC_TOD_OFF(tod_version))) < SBBC_TOD_VERSION)
device_printf(dev, "invalid TOD version %#x\n", val);
else {
clock_register(dev, 1000000); /* 1 sec. resolution */
if (bootverbose) {
sbbc_tod_gettime(dev, &ts);
device_printf(dev,
"current time: %ld.%09ld\n",
(long)ts.tv_sec, ts.tv_nsec);
}
}
}
return (0);
}
/*
* Note that the bus methods don't pass-through the uart(4) requests but act
* as if they would come from sbbc(4) in order to avoid complications with
* pci(4) (actually, uart(4) isn't a real child but rather a function of
* sbbc(4) anyway).
*/
static struct resource *
sbbc_bus_alloc_resource(device_t dev, device_t child __unused, int type,
int *rid, u_long start, u_long end, u_long count, u_int flags)
{
struct sbbc_softc *sc;
sc = device_get_softc(dev);
switch (type) {
case SYS_RES_IRQ:
return (bus_generic_alloc_resource(dev, dev, type, rid, start,
end, count, flags));
case SYS_RES_MEMORY:
return (sc->sc_res);
default:
return (NULL);
}
}
static int
sbbc_bus_activate_resource(device_t bus, device_t child, int type, int rid,
struct resource *res)
{
if (type == SYS_RES_MEMORY)
return (0);
return (bus_generic_activate_resource(bus, child, type, rid, res));
}
static int
sbbc_bus_deactivate_resource(device_t bus, device_t child, int type, int rid,
struct resource *res)
{
if (type == SYS_RES_MEMORY)
return (0);
return (bus_generic_deactivate_resource(bus, child, type, rid, res));
}
static int
sbbc_bus_adjust_resource(device_t bus __unused, device_t child __unused,
int type __unused, struct resource *res __unused, u_long start __unused,
u_long end __unused)
{
return (ENXIO);
}
static int
sbbc_bus_release_resource(device_t dev, device_t child __unused, int type,
int rid, struct resource *res)
{
if (type == SYS_RES_IRQ)
return (bus_generic_release_resource(dev, dev, type, rid,
res));
return (0);
}
static struct resource_list *
sbbc_bus_get_resource_list(device_t dev, device_t child __unused)
{
return (bus_generic_get_resource_list(dev, dev));
}
static int
sbbc_bus_setup_intr(device_t dev, device_t child __unused,
struct resource *res, int flags, driver_filter_t *filt,
driver_intr_t *intr, void *arg, void **cookiep)
{
return (bus_generic_setup_intr(dev, dev, res, flags, filt, intr, arg,
cookiep));
}
static int
sbbc_bus_teardown_intr(device_t dev, device_t child __unused,
struct resource *res, void *cookie)
{
return (bus_generic_teardown_intr(dev, dev, res, cookie));
}
/*
* internal helpers
*/
static int
sbbc_parse_toc(bus_space_tag_t bst, bus_space_handle_t bsh)
{
char buf[MAX(SBBC_TAG_KEY_SIZE, SBBC_TOC_MAGIC_SIZE)];
bus_size_t tag;
phandle_t node;
uint32_t off, sram_toc;
u_int i, tags;
if ((node = OF_finddevice("/chosen")) == -1)
return (ENXIO);
/* SRAM TOC offset defaults to 0. */
if (OF_getprop(node, "iosram-toc", &sram_toc, sizeof(sram_toc)) <= 0)
sram_toc = 0;
bus_space_read_region_1(bst, bsh, SBBC_SRAM_OFFSET + sram_toc +
SBBC_TOC_OFF(toc_magic), buf, SBBC_TOC_MAGIC_SIZE);
buf[SBBC_TOC_MAGIC_SIZE - 1] = '\0';
if (strcmp(buf, SBBC_TOC_MAGIC) != 0)
return (ENXIO);
tags = SBBC_SRAM_READ_4(sram_toc + SBBC_TOC_OFF(toc_ntags));
for (i = 0; i < tags; i++) {
tag = sram_toc + SBBC_TOC_OFF(toc_tag) +
i * sizeof(struct sbbc_sram_tag);
bus_space_read_region_1(bst, bsh, SBBC_SRAM_OFFSET + tag +
SBBC_TAG_OFF(tag_key), buf, SBBC_TAG_KEY_SIZE);
buf[SBBC_TAG_KEY_SIZE - 1] = '\0';
off = SBBC_SRAM_READ_4(tag + SBBC_TAG_OFF(tag_offset));
if (strcmp(buf, SBBC_TAG_KEY_SCSOLIE) == 0)
sbbc_scsolie = off;
else if (strcmp(buf, SBBC_TAG_KEY_SCSOLIR) == 0)
sbbc_scsolir = off;
else if (strcmp(buf, SBBC_TAG_KEY_SOLCONS) == 0)
sbbc_solcons = off;
else if (strcmp(buf, SBBC_TAG_KEY_SOLSCIE) == 0)
sbbc_solscie = off;
else if (strcmp(buf, SBBC_TAG_KEY_SOLSCIR) == 0)
sbbc_solscir = off;
else if (strcmp(buf, SBBC_TAG_KEY_TODDATA) == 0)
sbbc_toddata = off;
}
return (0);
}
static const char *
sbbc_serengeti_set_console_input(char *new)
{
struct {
cell_t name;
cell_t nargs;
cell_t nreturns;
cell_t new;
cell_t old;
} args = {
(cell_t)SUNW_SETCONSINPUT,
1,
1,
};
args.new = (cell_t)new;
if (ofw_entry(&args) == -1)
return (NULL);
return ((const char *)args.old);
}
static inline void
sbbc_send_intr(bus_space_tag_t bst, bus_space_handle_t bsh)
{
SBBC_EPLD_WRITE_1(SBBC_EPLD_INTERRUPT, SBBC_EPLD_INTERRUPT_ON);
bus_space_barrier(bst, bsh, SBBC_EPLD_OFFSET + SBBC_EPLD_INTERRUPT, 1,
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
}
/*
* TOD interface
*/
static int
sbbc_tod_gettime(device_t dev, struct timespec *ts)
{
struct sbbc_softc *sc;
bus_space_tag_t bst;
bus_space_handle_t bsh;
sc = device_get_softc(dev);
bst = rman_get_bustag(sc->sc_res);
bsh = rman_get_bushandle(sc->sc_res);
ts->tv_sec = SBBC_SRAM_READ_8(sbbc_toddata + SBBC_TOD_OFF(tod_time)) +
SBBC_SRAM_READ_8(sbbc_toddata + SBBC_TOD_OFF(tod_skew));
ts->tv_nsec = 0;
return (0);
}
static int
sbbc_tod_settime(device_t dev, struct timespec *ts)
{
struct sbbc_softc *sc;
bus_space_tag_t bst;
bus_space_handle_t bsh;
sc = device_get_softc(dev);
bst = rman_get_bustag(sc->sc_res);
bsh = rman_get_bushandle(sc->sc_res);
SBBC_SRAM_WRITE_8(sbbc_toddata + SBBC_TOD_OFF(tod_skew), ts->tv_sec -
SBBC_SRAM_READ_8(sbbc_toddata + SBBC_TOD_OFF(tod_time)));
return (0);
}
/*
* UART bus front-end
*/
static device_probe_t sbbc_uart_sbbc_probe;
static device_method_t sbbc_uart_sbbc_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, sbbc_uart_sbbc_probe),
DEVMETHOD(device_attach, uart_bus_attach),
DEVMETHOD(device_detach, uart_bus_detach),
DEVMETHOD_END
};
DEFINE_CLASS_0(uart, sbbc_uart_driver, sbbc_uart_sbbc_methods,
sizeof(struct uart_softc));
DRIVER_MODULE(uart, sbbc, sbbc_uart_driver, uart_devclass, NULL, NULL);
static int
sbbc_uart_sbbc_probe(device_t dev)
{
struct uart_softc *sc;
sc = device_get_softc(dev);
sc->sc_class = &uart_sbbc_class;
device_set_desc(dev, "Serengeti console");
return (uart_bus_probe(dev, 0, 0, SBBC_PCI_BAR, 0));
}
/*
* Low-level UART interface
*/
static int sbbc_uart_probe(struct uart_bas *bas);
static void sbbc_uart_init(struct uart_bas *bas, int baudrate, int databits,
int stopbits, int parity);
static void sbbc_uart_term(struct uart_bas *bas);
static void sbbc_uart_putc(struct uart_bas *bas, int c);
static int sbbc_uart_rxready(struct uart_bas *bas);
static int sbbc_uart_getc(struct uart_bas *bas, struct mtx *hwmtx);
static struct uart_ops sbbc_uart_ops = {
.probe = sbbc_uart_probe,
.init = sbbc_uart_init,
.term = sbbc_uart_term,
.putc = sbbc_uart_putc,
.rxready = sbbc_uart_rxready,
.getc = sbbc_uart_getc,
};
static int
sbbc_uart_probe(struct uart_bas *bas)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
int error;
sbbc_console = 1;
bst = bas->bst;
bsh = bas->bsh;
error = sbbc_parse_toc(bst, bsh);
if (error != 0)
return (error);
if (sbbc_scsolie == 0 || sbbc_scsolir == 0 || sbbc_solcons == 0 ||
sbbc_solscie == 0 || sbbc_solscir == 0)
return (ENXIO);
if (SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_magic)) !=
SBBC_CONS_MAGIC || SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_version)) < SBBC_CONS_VERSION)
return (ENXIO);
return (0);
}
static void
sbbc_uart_init(struct uart_bas *bas, int baudrate __unused,
int databits __unused, int stopbits __unused, int parity __unused)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bst = bas->bst;
bsh = bas->bsh;
/* Enable output to and space in from the SC interrupts. */
SBBC_SRAM_WRITE_4(sbbc_solscie, SBBC_SRAM_READ_4(sbbc_solscie) |
SBBC_SRAM_CONS_OUT | SBBC_SRAM_CONS_SPACE_IN);
uart_barrier(bas);
/* Take over the console input. */
sbbc_serengeti_set_console_input(SUNW_SETCONSINPUT_CLNT);
}
static void
sbbc_uart_term(struct uart_bas *bas __unused)
{
/* Give back the console input. */
sbbc_serengeti_set_console_input(SUNW_SETCONSINPUT_OBP);
}
static void
sbbc_uart_putc(struct uart_bas *bas, int c)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t wrptr;
bst = bas->bst;
bsh = bas->bsh;
wrptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_wrptr));
SBBC_SRAM_WRITE_1(sbbc_solcons + wrptr, c);
uart_barrier(bas);
if (++wrptr == SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_end)))
wrptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_begin));
SBBC_SRAM_WRITE_4(sbbc_solcons + SBBC_CONS_OFF(cons_out_wrptr),
wrptr);
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solscir, SBBC_SRAM_READ_4(sbbc_solscir) |
SBBC_SRAM_CONS_OUT);
uart_barrier(bas);
sbbc_send_intr(bst, bsh);
}
static int
sbbc_uart_rxready(struct uart_bas *bas)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bst = bas->bst;
bsh = bas->bsh;
if (SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_rdptr)) ==
SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_wrptr)))
return (0);
return (1);
}
static int
sbbc_uart_getc(struct uart_bas *bas, struct mtx *hwmtx)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
int c;
uint32_t rdptr;
bst = bas->bst;
bsh = bas->bsh;
uart_lock(hwmtx);
while (sbbc_uart_rxready(bas) == 0) {
uart_unlock(hwmtx);
DELAY(4);
uart_lock(hwmtx);
}
rdptr = SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_rdptr));
c = SBBC_SRAM_READ_1(sbbc_solcons + rdptr);
uart_barrier(bas);
if (++rdptr == SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_end)))
rdptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_begin));
SBBC_SRAM_WRITE_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_rdptr),
rdptr);
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solscir, SBBC_SRAM_READ_4(sbbc_solscir) |
SBBC_SRAM_CONS_SPACE_IN);
uart_barrier(bas);
sbbc_send_intr(bst, bsh);
uart_unlock(hwmtx);
return (c);
}
/*
* High-level UART interface
*/
static int sbbc_uart_bus_attach(struct uart_softc *sc);
static int sbbc_uart_bus_detach(struct uart_softc *sc);
static int sbbc_uart_bus_flush(struct uart_softc *sc, int what);
static int sbbc_uart_bus_getsig(struct uart_softc *sc);
static int sbbc_uart_bus_ioctl(struct uart_softc *sc, int request,
intptr_t data);
static int sbbc_uart_bus_ipend(struct uart_softc *sc);
static int sbbc_uart_bus_param(struct uart_softc *sc, int baudrate,
int databits, int stopbits, int parity);
static int sbbc_uart_bus_probe(struct uart_softc *sc);
static int sbbc_uart_bus_receive(struct uart_softc *sc);
static int sbbc_uart_bus_setsig(struct uart_softc *sc, int sig);
static int sbbc_uart_bus_transmit(struct uart_softc *sc);
static kobj_method_t sbbc_uart_methods[] = {
KOBJMETHOD(uart_attach, sbbc_uart_bus_attach),
KOBJMETHOD(uart_detach, sbbc_uart_bus_detach),
KOBJMETHOD(uart_flush, sbbc_uart_bus_flush),
KOBJMETHOD(uart_getsig, sbbc_uart_bus_getsig),
KOBJMETHOD(uart_ioctl, sbbc_uart_bus_ioctl),
KOBJMETHOD(uart_ipend, sbbc_uart_bus_ipend),
KOBJMETHOD(uart_param, sbbc_uart_bus_param),
KOBJMETHOD(uart_probe, sbbc_uart_bus_probe),
KOBJMETHOD(uart_receive, sbbc_uart_bus_receive),
KOBJMETHOD(uart_setsig, sbbc_uart_bus_setsig),
KOBJMETHOD(uart_transmit, sbbc_uart_bus_transmit),
DEVMETHOD_END
};
struct uart_class uart_sbbc_class = {
"sbbc",
sbbc_uart_methods,
sizeof(struct uart_softc),
.uc_ops = &sbbc_uart_ops,
.uc_range = 1,
.uc_rclk = 0x5bbc /* arbitrary */
};
#define SIGCHG(c, i, s, d) \
if ((c) != 0) { \
i |= (((i) & (s)) != 0) ? (s) : (s) | (d); \
} else { \
i = (((i) & (s)) != 0) ? ((i) & ~(s)) | (d) : (i); \
}
static int
sbbc_uart_bus_attach(struct uart_softc *sc)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t wrptr;
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
uart_lock(sc->sc_hwmtx);
/*
* Let the current output drain before enabling interrupts. Not
* doing so tends to cause lost output when turning them on.
*/
wrptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_wrptr));
while (SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_rdptr)) != wrptr);
cpu_spinwait();
/* Clear and acknowledge possibly outstanding interrupts. */
SBBC_SRAM_WRITE_4(sbbc_scsolir, 0);
uart_barrier(bas);
SBBC_REGS_WRITE_4(SBBC_PCI_INT_STATUS,
SBBC_SRAM_READ_4(sbbc_scsolir));
uart_barrier(bas);
/* Enable PCI interrupts. */
SBBC_REGS_WRITE_4(SBBC_PCI_INT_ENABLE, SBBC_PCI_ENABLE_INT_A);
uart_barrier(bas);
/* Enable input from and output to SC as well as break interrupts. */
SBBC_SRAM_WRITE_4(sbbc_scsolie, SBBC_SRAM_READ_4(sbbc_scsolie) |
SBBC_SRAM_CONS_IN | SBBC_SRAM_CONS_BRK |
SBBC_SRAM_CONS_SPACE_OUT);
uart_barrier(bas);
uart_unlock(sc->sc_hwmtx);
return (0);
}
static int
sbbc_uart_bus_detach(struct uart_softc *sc)
{
/* Give back the console input. */
sbbc_serengeti_set_console_input(SUNW_SETCONSINPUT_OBP);
return (0);
}
static int
sbbc_uart_bus_flush(struct uart_softc *sc, int what)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
if ((what & UART_FLUSH_TRANSMITTER) != 0)
return (ENODEV);
if ((what & UART_FLUSH_RECEIVER) != 0) {
SBBC_SRAM_WRITE_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_rdptr),
SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_wrptr)));
uart_barrier(bas);
}
return (0);
}
static int
sbbc_uart_bus_getsig(struct uart_softc *sc)
{
uint32_t dummy, new, old, sig;
do {
old = sc->sc_hwsig;
sig = old;
dummy = 0;
SIGCHG(dummy, sig, SER_CTS, SER_DCTS);
SIGCHG(dummy, sig, SER_DCD, SER_DDCD);
SIGCHG(dummy, sig, SER_DSR, SER_DDSR);
new = sig & ~SER_MASK_DELTA;
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
return (sig);
}
static int
sbbc_uart_bus_ioctl(struct uart_softc *sc, int request, intptr_t data)
{
int error;
error = 0;
uart_lock(sc->sc_hwmtx);
switch (request) {
case UART_IOCTL_BAUD:
*(int*)data = 9600; /* arbitrary */
break;
default:
error = EINVAL;
break;
}
uart_unlock(sc->sc_hwmtx);
return (error);
}
static int
sbbc_uart_bus_ipend(struct uart_softc *sc)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
int ipend;
uint32_t reason, status;
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
uart_lock(sc->sc_hwmtx);
status = SBBC_REGS_READ_4(SBBC_PCI_INT_STATUS);
if (status == 0) {
uart_unlock(sc->sc_hwmtx);
return (0);
}
/*
* Unfortunately, we can't use compare and swap for non-cachable
* memory.
*/
reason = SBBC_SRAM_READ_4(sbbc_scsolir);
SBBC_SRAM_WRITE_4(sbbc_scsolir, 0);
uart_barrier(bas);
/* Acknowledge the interrupt. */
SBBC_REGS_WRITE_4(SBBC_PCI_INT_STATUS, status);
uart_barrier(bas);
uart_unlock(sc->sc_hwmtx);
ipend = 0;
if ((reason & SBBC_SRAM_CONS_IN) != 0)
ipend |= SER_INT_RXREADY;
if ((reason & SBBC_SRAM_CONS_BRK) != 0)
ipend |= SER_INT_BREAK;
if ((reason & SBBC_SRAM_CONS_SPACE_OUT) != 0 &&
SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_out_rdptr)) ==
SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_out_wrptr)))
ipend |= SER_INT_TXIDLE;
return (ipend);
}
static int
sbbc_uart_bus_param(struct uart_softc *sc __unused, int baudrate __unused,
int databits __unused, int stopbits __unused, int parity __unused)
{
return (0);
}
static int
sbbc_uart_bus_probe(struct uart_softc *sc)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
if (sbbc_console != 0) {
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
sc->sc_rxfifosz = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_end)) - SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_begin)) - 1;
sc->sc_txfifosz = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_end)) - SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_begin)) - 1;
return (0);
}
return (ENXIO);
}
static int
sbbc_uart_bus_receive(struct uart_softc *sc)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
int c;
uint32_t end, rdptr, wrptr;
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
uart_lock(sc->sc_hwmtx);
end = SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_end));
rdptr = SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_rdptr));
wrptr = SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_wrptr));
while (rdptr != wrptr) {
if (uart_rx_full(sc) != 0) {
sc->sc_rxbuf[sc->sc_rxput] = UART_STAT_OVERRUN;
break;
}
c = SBBC_SRAM_READ_1(sbbc_solcons + rdptr);
uart_rx_put(sc, c);
if (++rdptr == end)
rdptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_in_begin));
}
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solcons + SBBC_CONS_OFF(cons_in_rdptr),
rdptr);
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solscir, SBBC_SRAM_READ_4(sbbc_solscir) |
SBBC_SRAM_CONS_SPACE_IN);
uart_barrier(bas);
sbbc_send_intr(bst, bsh);
uart_unlock(sc->sc_hwmtx);
return (0);
}
static int
sbbc_uart_bus_setsig(struct uart_softc *sc, int sig)
{
struct uart_bas *bas;
uint32_t new, old;
bas = &sc->sc_bas;
do {
old = sc->sc_hwsig;
new = old;
if ((sig & SER_DDTR) != 0) {
SIGCHG(sig & SER_DTR, new, SER_DTR, SER_DDTR);
}
if ((sig & SER_DRTS) != 0) {
SIGCHG(sig & SER_RTS, new, SER_RTS, SER_DRTS);
}
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
return (0);
}
static int
sbbc_uart_bus_transmit(struct uart_softc *sc)
{
struct uart_bas *bas;
bus_space_tag_t bst;
bus_space_handle_t bsh;
int i;
uint32_t end, wrptr;
bas = &sc->sc_bas;
bst = bas->bst;
bsh = bas->bsh;
uart_lock(sc->sc_hwmtx);
end = SBBC_SRAM_READ_4(sbbc_solcons + SBBC_CONS_OFF(cons_out_end));
wrptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_wrptr));
for (i = 0; i < sc->sc_txdatasz; i++) {
SBBC_SRAM_WRITE_1(sbbc_solcons + wrptr, sc->sc_txbuf[i]);
if (++wrptr == end)
wrptr = SBBC_SRAM_READ_4(sbbc_solcons +
SBBC_CONS_OFF(cons_out_begin));
}
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solcons + SBBC_CONS_OFF(cons_out_wrptr),
wrptr);
uart_barrier(bas);
SBBC_SRAM_WRITE_4(sbbc_solscir, SBBC_SRAM_READ_4(sbbc_solscir) |
SBBC_SRAM_CONS_OUT);
uart_barrier(bas);
sbbc_send_intr(bst, bsh);
sc->sc_txbusy = 1;
uart_unlock(sc->sc_hwmtx);
return (0);
}