summaryrefslogtreecommitdiff
path: root/sys/arch/sgi/pci/ioc.c
diff options
context:
space:
mode:
authorMiod Vallat <miod@cvs.openbsd.org>2009-04-12 17:56:59 +0000
committerMiod Vallat <miod@cvs.openbsd.org>2009-04-12 17:56:59 +0000
commitaa9adddfca7778c5667f7860a75baa5bb033974b (patch)
tree7dec8c6dffa27bc6479cb6a3e68ebadaff05e334 /sys/arch/sgi/pci/ioc.c
parent2829c2629cd9ab9ba5ca8dd4c7a45d3e3755098e (diff)
Interrupt support for IOC3 and its subdevices (currently limited to the
SuperIO part, the Ethernet part needs a whole driver); kernel now boot single user (or bsd.rd). Joint work with jsing@
Diffstat (limited to 'sys/arch/sgi/pci/ioc.c')
-rw-r--r--sys/arch/sgi/pci/ioc.c227
1 files changed, 217 insertions, 10 deletions
diff --git a/sys/arch/sgi/pci/ioc.c b/sys/arch/sgi/pci/ioc.c
index b02d4abb9ea..e00a3bc59d0 100644
--- a/sys/arch/sgi/pci/ioc.c
+++ b/sys/arch/sgi/pci/ioc.c
@@ -1,8 +1,8 @@
-/* $OpenBSD: ioc.c,v 1.2 2009/03/29 21:53:52 sthen Exp $ */
+/* $OpenBSD: ioc.c,v 1.3 2009/04/12 17:56:58 miod Exp $ */
/*
* Copyright (c) 2008 Joel Sing.
- * Copyright (c) 2008 Miodrag Vallat.
+ * Copyright (c) 2008, 2009 Miodrag Vallat.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -41,6 +41,7 @@
#include <dev/onewire/onewirereg.h>
#include <dev/onewire/onewirevar.h>
+#include <sgi/dev/if_efreg.h>
#include <sgi/dev/owmacvar.h>
#include <sgi/xbow/xbow.h>
@@ -51,6 +52,16 @@ int ioc_search_onewire(struct device *, void *, void *);
int ioc_search_mundane(struct device *, void *, void *);
int ioc_print(void *, const char *);
+struct ioc_intr {
+ struct ioc_softc *ii_ioc;
+
+ int (*ii_func)(void *);
+ void *ii_arg;
+
+ struct evcount ii_count;
+ int ii_level;
+};
+
struct ioc_softc {
struct device sc_dev;
@@ -59,6 +70,11 @@ struct ioc_softc {
bus_space_tag_t sc_memt;
bus_space_handle_t sc_memh;
bus_dma_tag_t sc_dmat;
+ pci_chipset_tag_t sc_pc;
+
+ void *sc_sih; /* SuperIO interrupt */
+ void *sc_eih; /* Ethernet interrupt */
+ struct ioc_intr *sc_intr[IOC_NDEVS];
struct onewire_bus sc_bus;
@@ -73,6 +89,10 @@ struct cfdriver ioc_cd = {
NULL, "ioc", DV_DULL,
};
+void ioc_intr_dispatch(struct ioc_softc *, int);
+int ioc_intr_ethernet(void *);
+int ioc_intr_superio(void *);
+
int iocow_reset(void *);
int iocow_read_bit(struct ioc_softc *);
int iocow_send_bit(void *, int);
@@ -102,8 +122,8 @@ ioc_print(void *aux, const char *iocname)
if (iaa->iaa_base != 0)
printf(" base 0x%08x", iaa->iaa_base);
- if (iaa->iaa_intr != 0)
- printf(" irq %d", iaa->iaa_intr);
+ if (iaa->iaa_dev != 0)
+ printf(" dev %d", iaa->iaa_dev);
return (UNCONF);
}
@@ -113,9 +133,12 @@ ioc_attach(struct device *parent, struct device *self, void *aux)
{
struct ioc_softc *sc = (struct ioc_softc *)self;
struct pci_attach_args *pa = aux;
+ pci_intr_handle_t sih, eih;
bus_space_tag_t memt;
bus_space_handle_t memh;
bus_size_t memsize;
+ uint32_t data;
+ int dev;
if (pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_TYPE_MEM, 0,
&memt, &memh, NULL, &memsize, 0)) {
@@ -123,9 +146,66 @@ ioc_attach(struct device *parent, struct device *self, void *aux)
return;
}
+ sc->sc_pc = pa->pa_pc;
sc->sc_dmat = pa->pa_dmat;
/*
+ * Initialise IOC3 ASIC.
+ */
+ data = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
+ data |= PCI_COMMAND_MEM_ENABLE | PCI_COMMAND_PARITY_ENABLE |
+ PCI_COMMAND_SERR_ENABLE;
+ pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, data);
+
+ /*
+ * IOC3 is not a real PCI device - it's a poor wrapper over a set
+ * of convenience chips. And it actually needs to use two interrupts,
+ * one for the superio chip, and the other for the Ethernet chip.
+ *
+ * Since our pci layer doesn't handle this, we cheat and compute
+ * the superio interrupt cookie ourselves. This is ugly, and
+ * depends on xbridge knowledge.
+ *
+ * (What the above means is that you should wear peril-sensitive
+ * sunglasses from now on).
+ *
+ * To make things ever worse, some IOC3 boards (real boards, not
+ * on-board components) lack the Ethernet component. We should
+ * eventually handle them there, but it's not worth doing yet...
+ * (and we'll need to parse the ownum serial numbers to know
+ * this anyway)
+ */
+
+ if (pci_intr_map(pa, &eih) != 0) {
+ printf(": failed to map interrupt!\n");
+ goto unmap;
+ }
+ sih = eih + 2; /* XXX ACK GAG BARF */
+
+ /*
+ * Register the superio interrupt.
+ */
+ sc->sc_sih = pci_intr_establish(sc->sc_pc, sih, IPL_TTY,
+ ioc_intr_superio, sc, self->dv_xname);
+ if (sc->sc_sih == NULL) {
+ printf(": failed to establish superio interrupt!\n");
+ goto unmap;
+ }
+
+ /*
+ * Register the ethernet interrupt.
+ */
+ sc->sc_eih = pci_intr_establish(sc->sc_pc, eih, IPL_NET,
+ ioc_intr_ethernet, sc, self->dv_xname);
+ if (sc->sc_eih == NULL) {
+ printf(": failed to establish ethernet interrupt!\n");
+ goto unregister;
+ }
+
+ printf(": superio %s", pci_intr_string(sc->sc_pc, sih));
+ printf(", ethernet %s", pci_intr_string(sc->sc_pc, eih));
+
+ /*
* Build a suitable bus_space_handle by rebasing the xbridge
* inherited one to our BAR, and restoring the original
* non-swapped subword access methods.
@@ -137,9 +217,8 @@ ioc_attach(struct device *parent, struct device *self, void *aux)
sc->sc_mem_bus_space = malloc(sizeof (*sc->sc_mem_bus_space),
M_DEVBUF, M_NOWAIT);
if (sc->sc_mem_bus_space == NULL) {
- bus_space_unmap(memt, memh, memsize);
printf(": can't allocate bus_space\n");
- return;
+ goto unregister2;
}
bcopy(memt, sc->sc_mem_bus_space, sizeof(*sc->sc_mem_bus_space));
@@ -152,7 +231,17 @@ ioc_attach(struct device *parent, struct device *self, void *aux)
sc->sc_memt = sc->sc_mem_bus_space;
sc->sc_memh = memh;
- printf("\n");
+ /* Initialise interrupt handling structures. */
+ for (dev = 0; dev < IOC_NDEVS; dev++)
+ sc->sc_intr[dev] = NULL;
+
+ /*
+ * Acknowledge all pending interrupts, and disable them.
+ */
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IEC, ~0x0);
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IES, 0x0);
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IR,
+ bus_space_read_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IR));
/*
* Attach the 1-Wire interface first, other sub-devices may
@@ -164,6 +253,15 @@ ioc_attach(struct device *parent, struct device *self, void *aux)
* Attach other sub-devices.
*/
config_search(ioc_search_mundane, self, aux);
+
+ return;
+
+unregister2:
+ pci_intr_disestablish(sc->sc_pc, sc->sc_eih);
+unregister:
+ pci_intr_disestablish(sc->sc_pc, sc->sc_sih);
+unmap:
+ bus_space_unmap(memt, memh, memsize);
}
int
@@ -185,9 +283,9 @@ ioc_search_mundane(struct device *parent, void *vcf, void *args)
else
iaa.iaa_base = cf->cf_loc[0];
if (cf->cf_loc[1] == -1)
- iaa.iaa_intr = 0;
+ iaa.iaa_dev = 0;
else
- iaa.iaa_intr = cf->cf_loc[1];
+ iaa.iaa_dev = cf->cf_loc[1];
if (sc->sc_owmac != NULL)
memcpy(iaa.iaa_enaddr, sc->sc_owmac->sc_enaddr, 6);
@@ -231,7 +329,7 @@ ioc_search_onewire(struct device *parent, void *vcf, void *args)
oba.oba_bus = &sc->sc_bus;
oba.oba_flags = ONEWIRE_SCAN_NOW | ONEWIRE_NO_PERIODIC_SCAN;
- /* in case onewire is disabled in UKC */
+ /* In case onewire is disabled in UKC... */
if ((*cf->cf_attach->ca_match)(parent, cf, &oba) == 0)
return 0;
@@ -333,3 +431,112 @@ iocow_pulse(struct ioc_softc *sc, int pulse, int data)
return (mcr_value & 1);
}
+
+/*
+ * Interrupt handling.
+ */
+
+/*
+ * List of interrupt bits to enable for each device.
+ *
+ * We intentionnaly mask the RX high water bits, as they apparently never
+ * clear on some machines regardless of what we do.
+ */
+const uint32_t ioc_intrbits[IOC_NDEVS] = {
+ 0x000001ff & ~0x00000004, /* serial A */
+ 0x0003fe00 & ~0x00000800, /* serial B */
+ 0x003c0000, /* parallel port */
+ 0x00400000, /* PS/2 port */
+ 0x08000000, /* rtc */
+ 0x00000000 /* Ethernet (handled differently) */
+};
+
+void *
+ioc_intr_establish(void *cookie, u_long dev, int level, int (*func)(void *),
+ void *arg, char *name)
+{
+ struct ioc_softc *sc = cookie;
+ struct ioc_intr *ii;
+
+ dev--;
+ if (dev < 0 || dev >= IOC_NDEVS)
+ return NULL;
+
+ ii = (struct ioc_intr *)malloc(sizeof(*ii), M_DEVBUF, M_NOWAIT);
+ if (ii == NULL)
+ return NULL;
+
+ ii->ii_ioc = sc;
+ ii->ii_func = func;
+ ii->ii_arg = arg;
+ ii->ii_level = level;
+
+ evcount_attach(&ii->ii_count, name, &ii->ii_level, &evcount_intr);
+ sc->sc_intr[dev] = ii;
+
+ /* enable hardware source if necessary */
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IES,
+ ioc_intrbits[dev]);
+
+ return (ii);
+}
+
+int
+ioc_intr_superio(void *v)
+{
+ struct ioc_softc *sc = (struct ioc_softc *)v;
+ uint32_t pending;
+ int dev;
+
+ pending = bus_space_read_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IR) &
+ bus_space_read_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IES);
+
+ if (pending == 0)
+ return 0;
+
+ /* Disable pending interrupts */
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, IOC3_SIO_IEC, pending);
+
+ for (dev = 0; dev < IOC_NDEVS - 1 /* skip Ethernet */; dev++) {
+ if (pending & ioc_intrbits[dev]) {
+ ioc_intr_dispatch(sc, dev);
+
+ /* Ack, then reenable, pending interrupts */
+ bus_space_write_4(sc->sc_memt, sc->sc_memh,
+ IOC3_SIO_IR, pending & ioc_intrbits[dev]);
+ bus_space_write_4(sc->sc_memt, sc->sc_memh,
+ IOC3_SIO_IES, pending & ioc_intrbits[dev]);
+ }
+ }
+
+ return 1;
+}
+
+int
+ioc_intr_ethernet(void *v)
+{
+ struct ioc_softc *sc = (struct ioc_softc *)v;
+ uint32_t stat;
+
+ stat = bus_space_read_4(sc->sc_memt, sc->sc_memh, EF_INTR_STATUS);
+
+ if (stat == 0)
+ return 0;
+
+ ioc_intr_dispatch(sc, IOCDEV_EF);
+ bus_space_write_4(sc->sc_memt, sc->sc_memh, EF_INTR_STATUS, stat);
+
+ return 1;
+}
+
+void
+ioc_intr_dispatch(struct ioc_softc *sc, int dev)
+{
+ struct ioc_intr *ii;
+
+ /* Call registered interrupt function. */
+ if ((ii = sc->sc_intr[dev]) != NULL && ii->ii_func != NULL) {
+ if ((*ii->ii_func)(ii->ii_arg) != 0)
+ ii->ii_count.ec_count++;
+ }
+}