NetBSD/sys/arch/sh5/pci/sh5_pci.c
2005-12-11 12:16:03 +00:00

1025 lines
27 KiB
C

/* $NetBSD: sh5_pci.c,v 1.13 2005/12/11 12:19:02 christos Exp $ */
/*
* Copyright 2002 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Steve C. Woodford for Wasabi Systems, Inc.
*
* 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 for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
* 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.
*/
/*
* SH-5 Host-PCI Bridge Controller
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sh5_pci.c,v 1.13 2005/12/11 12:19:02 christos Exp $");
#include "opt_pci.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/extent.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#if defined(PCI_NETBSD_CONFIGURE)
#include <dev/pci/pciconf.h>
#endif
#include <machine/cpu.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <sh5/dev/superhywayvar.h>
#include <sh5/pci/sh5_pcivar.h>
#include <sh5/pci/sh5_pcireg.h>
#include "locators.h"
/*
* XXX: The following is good enough for 1GB of RAM, starting
* at physical address 0x80000000.
*
* Need to revist this to make it configurable by board-specific
* code at runtime.
*/
#define SH5PCI_RAM_PHYS_BASE 0x80000000
struct sh5pci_map {
bus_addr_t m_start;
bus_addr_t m_end;
bus_addr_t m_pcibase;
};
struct sh5pci_softc {
struct device sc_dev;
bus_space_tag_t sc_bust;
bus_dma_tag_t sc_dmat;
bus_space_handle_t sc_csrh;
bus_addr_t sc_base;
struct sh5pci_map sc_map[SH5PCI_NUM_MBARS];
const struct sh5pci_intr_hooks *sc_intr;
void *sc_intr_arg;
void *sc_ih_serr;
void *sc_ih_err;
};
struct sh5pci_icookie {
pci_intr_handle_t ic_ih;
int (*ic_func)(void *);
void *ic_arg;
SLIST_ENTRY(sh5pci_icookie) ic_next;
};
static int sh5pcimatch(struct device *, struct cfdata *, void *);
static void sh5pciattach(struct device *, struct device *, void *);
static int sh5pciprint(void *, const char *);
CFATTACH_DECL(sh5pci, sizeof(struct sh5pci_softc),
sh5pcimatch, sh5pciattach, NULL, NULL);
extern struct cfdriver sh5pci_cd;
static int sh5pci_dmamap_create(void *, bus_size_t, int, bus_size_t,
bus_size_t, int, bus_dmamap_t *);
static void sh5pci_dmamap_destroy(void *, bus_dmamap_t);
static int sh5pci_dmamap_load_direct(void *, bus_dmamap_t,
void *, bus_size_t, struct proc *, int);
static int sh5pci_dmamap_load_mbuf(void *,
bus_dmamap_t, struct mbuf *, int);
static int sh5pci_dmamap_load_uio(void *, bus_dmamap_t,
struct uio *, int);
static int sh5pci_dmamap_load_raw(void *,
bus_dmamap_t, bus_dma_segment_t *, int, bus_size_t, int);
static int sh5pci_dmamap_load_common(struct sh5pci_softc *, bus_dmamap_t);
static void sh5pci_dmamap_unload(void *, bus_dmamap_t);
static void sh5pci_dmamap_sync(void *, bus_dmamap_t, bus_addr_t,
bus_size_t, int);
static int sh5pci_dmamem_alloc(void *, bus_size_t, bus_size_t, bus_size_t,
bus_dma_segment_t *, int, int *, int);
static void sh5pci_dmamem_free(void *, bus_dma_segment_t *, int);
static int sh5pci_dmamem_map(void *, bus_dma_segment_t *, int, size_t,
caddr_t *, int);
static void sh5pci_dmamem_unmap(void *, caddr_t, size_t);
static paddr_t sh5pci_dmamem_mmap(void *, bus_dma_segment_t *, int,
off_t, int, int);
static int sh5pci_mem_map(void *, bus_addr_t, bus_size_t, int,
bus_space_handle_t *);
static int sh5pci_io_map(void *, bus_addr_t, bus_size_t, int,
bus_space_handle_t *);
static void sh5pci_attach_hook(void *, struct device *, struct device *,
struct pcibus_attach_args *);
static int sh5pci_maxdevs(void *, int);
static pcitag_t sh5pci_make_tag(void *, int, int, int);
static void sh5pci_decompose_tag(void *, pcitag_t, int *, int *, int *);
static pcireg_t sh5pci_conf_read(void *, pcitag_t, int);
static void sh5pci_conf_write(void *, pcitag_t, int, pcireg_t);
static void sh5pci_conf_interrupt(void *, int, int, int, int, int *);
static int sh5pci_intr_map(void *, struct pci_attach_args *,
pci_intr_handle_t *);
static const char *sh5pci_intr_string(void *, pci_intr_handle_t);
static const struct evcnt *sh5pci_intr_evcnt(void *, pci_intr_handle_t);
static void * sh5pci_intr_establish(void *, pci_intr_handle_t,
int, int (*)(void *), void *);
static void sh5pci_intr_disestablish(void *, void *);
static int sh5pci_intr_dispatch(void *);
static void sh5pci_bridge_init(struct sh5pci_softc *);
static int sh5pci_check_master_abort(struct sh5pci_softc *);
static int sh5pci_interrupt(void *);
/*
* XXX: These should be allocated dynamically to allow multiple instances.
*/
static struct sh5_bus_space_tag sh5pci_mem_tag;
static struct sh5_bus_space_tag sh5pci_io_tag;
static struct sh5_bus_dma_tag sh5pci_dma_tag = {
NULL,
sh5pci_dmamap_create,
sh5pci_dmamap_destroy,
sh5pci_dmamap_load_direct,
sh5pci_dmamap_load_mbuf,
sh5pci_dmamap_load_uio,
sh5pci_dmamap_load_raw,
sh5pci_dmamap_unload,
sh5pci_dmamap_sync,
sh5pci_dmamem_alloc,
sh5pci_dmamem_free,
sh5pci_dmamem_map,
sh5pci_dmamem_unmap,
sh5pci_dmamem_mmap,
};
static struct sh5_pci_chipset_tag sh5pci_chipset_tag = {
NULL,
sh5pci_attach_hook,
sh5pci_maxdevs,
sh5pci_make_tag,
sh5pci_decompose_tag,
sh5pci_conf_read,
sh5pci_conf_write,
sh5pci_conf_interrupt,
sh5pci_intr_map,
sh5pci_intr_string,
sh5pci_intr_evcnt,
sh5pci_intr_establish,
sh5pci_intr_disestablish
};
#define sh5pci_csr_read(sc, reg) \
bus_space_read_4((sc)->sc_bust, (sc)->sc_csrh, (reg))
#define sh5pci_csr_write(sc, reg, val) \
bus_space_write_4((sc)->sc_bust, (sc)->sc_csrh, (reg), (val))
/*ARGSUSED*/
static int
sh5pcimatch(struct device *parent, struct cfdata *cf, void *args)
{
struct superhyway_attach_args *sa = args;
bus_space_handle_t bh;
bus_addr_t vcrbase;
u_int64_t vcr;
if (strcmp(sa->sa_name, sh5pci_cd.cd_name))
return (0);
sa->sa_pport = 0;
vcrbase = SUPERHYWAY_PPORT_TO_BUSADDR(cf->cf_loc[SUPERHYWAYCF_PPORT]);
bus_space_map(sa->sa_bust, vcrbase + SH5PCI_VCR_OFFSET,
SUPERHYWAY_REG_SZ, 0, &bh);
vcr = bus_space_read_8(sa->sa_bust, bh, SUPERHYWAY_REG_VCR);
bus_space_unmap(sa->sa_bust, bh, SUPERHYWAY_REG_SZ);
if (SUPERHYWAY_VCR_MOD_ID(vcr) != SH5PCI_MODULE_ID)
return (0);
sa->sa_pport = cf->cf_loc[SUPERHYWAYCF_PPORT];
return (1);
}
/*ARGSUSED*/
static void
sh5pciattach(struct device *parent, struct device *self, void *args)
{
struct sh5pci_softc *sc = (struct sh5pci_softc *)self;
struct superhyway_attach_args *sa = args;
struct pcibus_attach_args pba;
bus_space_handle_t bh;
u_int64_t vcr;
#if defined(PCI_NETBSD_CONFIGURE)
struct extent *ioext, *memext;
u_long cfg_ioaddr;
#endif
sc->sc_bust = sa->sa_bust;
sc->sc_dmat = sa->sa_dmat;
sc->sc_base = SUPERHYWAY_PPORT_TO_BUSADDR(sa->sa_pport);
/* Fetch the VCR */
bus_space_map(sc->sc_bust, sc->sc_base + SH5PCI_VCR_OFFSET,
SUPERHYWAY_REG_SZ, 0, &bh);
vcr = bus_space_read_8(sc->sc_bust, bh, SUPERHYWAY_REG_VCR);
bus_space_unmap(sc->sc_bust, bh, SUPERHYWAY_REG_SZ);
/*
* Map the PCI CSR Registers
*/
bus_space_map(sa->sa_bust, sc->sc_base + SH5PCI_CSR_OFFSET,
SH5PCI_CSR_SIZE, 0, &sc->sc_csrh);
printf(": SH-5 PCIbus Bridge, Version 0x%x\n",
(int)SUPERHYWAY_VCR_MOD_VERS(vcr));
#ifdef DEBUG
printf("%s: CSR at %p\n", sc->sc_dev.dv_xname, (void *)sc->sc_csrh);
#endif
/*
* Fix up the memory and i/o tags by copying our
* parent's tag, and modifying the cookie and bus_space_map fields.
*
* This is a wee bit naughty; we really shouldn't interpret our
* parent's tag, but it's a lot more efficient than writing a whole
* bunch of stubs which just call the parent's methods.
*
* XXX: We get away with this because we *know* our parent is
* the base-level bus_space(9) implementation; which doesn't
* interpret the "cookie" field of the tag...
*/
sh5pci_mem_tag = *sc->sc_bust;
sh5pci_mem_tag.bs_cookie = sc;
sh5pci_mem_tag.bs_map = sh5pci_mem_map;
sh5pci_io_tag = *sc->sc_bust;
sh5pci_io_tag.bs_cookie = sc;
sh5pci_io_tag.bs_map = sh5pci_io_map;
/*
* Initialise our DMA tag
*/
sh5pci_dma_tag.bd_cookie = sc;
/*
* Initialise the cookie field of our chipset tag
*/
sh5pci_chipset_tag.ct_cookie = sc;
/*
* Connect to, and initialise, the board-specific interrupt
* routing interface.
*/
sc->sc_intr = sh5pci_get_intr_hooks(&sh5pci_chipset_tag);
sc->sc_intr_arg = (sc->sc_intr->ih_init)(&sh5pci_chipset_tag,
&sc->sc_ih_serr, sh5pci_interrupt, sc,
&sc->sc_ih_err, sh5pci_interrupt, sc);
/*
* Initialise the host-pci hardware
*/
sh5pci_bridge_init(sc);
#if defined(PCI_NETBSD_CONFIGURE)
/*
* Configure the devices on the PCIbus
*/
memext = extent_create("pcimem", sh5pci_csr_read(sc, SH5PCI_CSR_MBR),
sh5pci_csr_read(sc, SH5PCI_CSR_MBR) + (SH5PCI_MEMORY_SIZE - 1),
M_DEVBUF, NULL, 0, EX_NOWAIT);
ioext = extent_create("pciio", sh5pci_csr_read(sc, SH5PCI_CSR_IOBR),
sh5pci_csr_read(sc, SH5PCI_CSR_IOBR) + (SH5PCI_IO_SIZE - 1),
M_DEVBUF, NULL, 0, EX_NOWAIT);
/*
* Reserve the lowest 256 bytes of i/o space. Some (older) PCI
* devices don't like to be assigned such low addresses...
*/
extent_alloc(ioext, 0x100, 0x100, 0, EX_NOWAIT, &cfg_ioaddr);
/*
* The SH5 Host-PCI bridge appears to be unable to see its own
* configuration registers in PCI config space, so manually fix
* up some values.
*/
sh5pci_csr_write(sc, SH5PCI_CONF_IOBAR, 0x40000);
{
u_int32_t reg;
reg = sh5pci_csr_read(sc, PCI_BHLC_REG);
reg |= (0x80 << PCI_LATTIMER_SHIFT);
sh5pci_csr_write(sc, PCI_BHLC_REG, reg);
}
/*
* Configure up the PCI bus
*/
pci_configure_bus(&sh5pci_chipset_tag, ioext, memext, NULL, 0, 32);
extent_destroy(ioext);
extent_destroy(memext);
/* Clear any accumulated master aborts */
(void) sh5pci_check_master_abort(sc);
#endif
pba.pba_pc = &sh5pci_chipset_tag;
pba.pba_bus = 0;
pba.pba_bridgetag = NULL;
pba.pba_flags = PCI_FLAGS_IO_ENABLED | PCI_FLAGS_MEM_ENABLED |
PCI_FLAGS_MRL_OKAY | PCI_FLAGS_MRM_OKAY | PCI_FLAGS_MWI_OKAY;
pba.pba_dmat = &sh5pci_dma_tag;
pba.pba_iot = &sh5pci_io_tag;
pba.pba_memt = &sh5pci_mem_tag;
config_found_ia(self, "pcibus", &pba, sh5pciprint);
}
/*ARGSUSED*/
static int
sh5pciprint(void *arg, const char *cp)
{
if (cp == NULL)
return (UNCONF);
return (QUIET);
}
static int
sh5pci_dmamap_create(void *arg, bus_size_t size, int nsegs,
bus_size_t maxsegsz, bus_size_t boundary, int flags, bus_dmamap_t *dmamp)
{
struct sh5pci_softc *sc = arg;
return (bus_dmamap_create(sc->sc_dmat, size, nsegs, maxsegsz,
boundary, flags, dmamp));
}
static void
sh5pci_dmamap_destroy(void *arg, bus_dmamap_t map)
{
struct sh5pci_softc *sc = arg;
bus_dmamap_destroy(sc->sc_dmat, map);
}
static int
sh5pci_dmamap_load_direct(void *arg, bus_dmamap_t map,
void *buf, bus_size_t buflen, struct proc *p, int flags)
{
struct sh5pci_softc *sc = arg;
int rv;
rv = bus_dmamap_load(sc->sc_dmat, map, buf, buflen, p, flags);
if (rv != 0)
return (rv);
return (sh5pci_dmamap_load_common(sc, map));
}
static int
sh5pci_dmamap_load_mbuf(void *arg, bus_dmamap_t map, struct mbuf *m, int flags)
{
struct sh5pci_softc *sc = arg;
int rv;
rv = bus_dmamap_load_mbuf(sc->sc_dmat, map, m, flags);
if (rv != 0)
return (rv);
return (sh5pci_dmamap_load_common(sc, map));
}
static int
sh5pci_dmamap_load_uio(void *arg, bus_dmamap_t map, struct uio *uio, int flags)
{
struct sh5pci_softc *sc = arg;
int rv;
rv = bus_dmamap_load_uio(sc->sc_dmat, map, uio, flags);
if (rv != 0)
return (rv);
return (sh5pci_dmamap_load_common(sc, map));
}
static int
sh5pci_dmamap_load_raw(void *arg, bus_dmamap_t map,
bus_dma_segment_t *segs, int nsegs, bus_size_t size, int flags)
{
struct sh5pci_softc *sc = arg;
int rv;
rv = bus_dmamap_load_raw(sc->sc_dmat, map, segs, nsegs, size, flags);
if (rv != 0)
return (rv);
return (sh5pci_dmamap_load_common(sc, map));
}
static int
sh5pci_dmamap_load_common(struct sh5pci_softc *sc, bus_dmamap_t map)
{
bus_dma_segment_t *ds;
u_int32_t mask;
int rv, i;
/*
* If all goes well, "rv" will be zero at the end of the loop.
* (It is decremented each time we successfully translate
* a segment).
*/
rv = map->dm_nsegs;
/*
* Traverse the list of segments which make up this map, and
* convert the CPU-relative addresses therein to PCIbus addresses.
*/
for (ds = &map->dm_segs[0]; ds < &map->dm_segs[map->dm_nsegs]; ds++) {
for (i = 0; i < SH5PCI_NUM_MBARS; i++) {
if (sc->sc_map[i].m_start == ~0)
continue;
if (ds->_ds_cpuaddr < sc->sc_map[i].m_start ||
(ds->_ds_cpuaddr + ds->ds_len) >=
sc->sc_map[i].m_end)
continue;
mask = sc->sc_map[i].m_end - sc->sc_map[i].m_start;
mask -= 1;
/*
* Looks like we found the window through which this
* segment is visible. Convert to PCIbus address.
*/
ds->ds_addr = sc->sc_map[i].m_pcibase +
(ds->_ds_cpuaddr & mask);
rv -= 1;
break;
}
}
return (rv ? 1 : 0);
}
static void
sh5pci_dmamap_unload(void *arg, bus_dmamap_t map)
{
struct sh5pci_softc *sc = arg;
/* XXX Deal with bounce buffers (for when we have > 1GB RAM) */
bus_dmamap_unload(sc->sc_dmat, map);
}
static void
sh5pci_dmamap_sync(void *arg, bus_dmamap_t map, bus_addr_t offset,
bus_size_t len, int ops)
{
struct sh5pci_softc *sc = arg;
/* XXX Deal with bounce buffers (for when we have > 1GB RAM) */
bus_dmamap_sync(sc->sc_dmat, map, offset, len, ops);
}
static int
sh5pci_dmamem_alloc(void *arg, bus_size_t size, bus_size_t alignment,
bus_size_t boundary, bus_dma_segment_t *segs, int nsegs,
int *rsegs, int flags)
{
struct sh5pci_softc *sc = arg;
/* XXX Deal with systems with > 1GB RAM */
/*
* Allocate physical memory.
*
* Note: This fills in the segments with CPU-relative physical
* addresses. A further call to bus_dmamap_load_raw() must be
* made before the addresses in the segments can be used.
* The segments of the DMA map will then contain PCIbus-relative
* physical addresses of the memory allocated here.
*/
return (bus_dmamem_alloc(sc->sc_dmat, size, alignment, boundary,
segs, nsegs, rsegs, flags));
}
static void
sh5pci_dmamem_free(void *arg, bus_dma_segment_t *segs, int nsegs)
{
struct sh5pci_softc *sc = arg;
bus_dmamem_free(sc->sc_dmat, segs, nsegs);
}
static int
sh5pci_dmamem_map(void *arg, bus_dma_segment_t *segs, int nsegs,
size_t size, caddr_t *kvap, int flags)
{
struct sh5pci_softc *sc = arg;
return (bus_dmamem_map(sc->sc_dmat, segs, nsegs, size, kvap, flags));
}
static void
sh5pci_dmamem_unmap(void *arg, caddr_t kva, size_t size)
{
struct sh5pci_softc *sc = arg;
bus_dmamem_unmap(sc->sc_dmat, kva, size);
}
static paddr_t
sh5pci_dmamem_mmap(void *arg, bus_dma_segment_t *segs, int nsegs,
off_t off, int prot, int flags)
{
struct sh5pci_softc *sc = arg;
return (bus_dmamem_mmap(sc->sc_dmat, segs, nsegs, off, prot, flags));
}
static int
sh5pci_mem_map(void *arg, bus_addr_t addr, bus_size_t size, int flags,
bus_space_handle_t *bushp)
{
struct sh5pci_softc *sc = arg;
u_int32_t mbr;
mbr = sh5pci_csr_read(sc, SH5PCI_CSR_MBR);
/*
* Can we access the required PCIbus address range through
* our window?
*/
if (addr < mbr)
return (EINVAL);
/*
* Convert the PCIbus address to an offset within the window
*/
addr -= mbr;
/*
* One final check that the address range fits inside the window.
*/
if ((addr + size) >= SH5PCI_MEMORY_SIZE)
return (EINVAL);
/*
* Convert to the correct offset into our SuperHyway address space
* before mapping in the normal way.
*/
addr += sc->sc_base + SH5PCI_MEMORY_OFFSET;
return (bus_space_map(sc->sc_bust, addr, size, flags, bushp));
}
static int
sh5pci_io_map(void *arg, bus_addr_t addr, bus_size_t size, int flags,
bus_space_handle_t *bushp)
{
struct sh5pci_softc *sc = arg;
u_int32_t iobr;
iobr = sh5pci_csr_read(sc, SH5PCI_CSR_IOBR);
/*
* Can we access the required PCIbus address range through
* our window?
*/
if (addr < iobr)
return (EINVAL);
/*
* Convert the PCIbus address to an offset within the window
*/
addr -= iobr;
/*
* One final check that the address range fits inside the window.
*/
if ((addr + size) >= SH5PCI_IO_SIZE)
return (EINVAL);
/*
* Convert to the correct offset into our SuperHyway address space
* before mapping in the normal way.
*/
addr += sc->sc_base + SH5PCI_IO_OFFSET;
return (bus_space_map(sc->sc_bust, addr, size, flags, bushp));
}
/*ARGSUSED*/
static void
sh5pci_attach_hook(void *arg, struct device *parent, struct device *self,
struct pcibus_attach_args *pba)
{
}
/*ARGSUSED*/
static int
sh5pci_maxdevs(void *arg, int busno)
{
if (busno == 0)
return (4);
return (32);
}
/*ARGSUSED*/
static pcitag_t
sh5pci_make_tag(void *arg, int bus, int dev, int func)
{
return ((pcitag_t)SH5PCI_CSR_PAR_MAKE(bus, dev, func, 0));
}
/*ARGSUSED*/
static void
sh5pci_decompose_tag(void *arg, pcitag_t tag, int *bp, int *dp, int *fp)
{
if (bp != NULL)
*bp = (int)SH5PCI_CSR_PAR_PCI_BN(tag);
if (dp != NULL)
*dp = (int)SH5PCI_CSR_PAR_PCI_DN(tag);
if (fp != NULL)
*fp = (int)SH5PCI_CSR_PAR_PCI_FN(tag);
}
static pcireg_t
sh5pci_conf_read(void *arg, pcitag_t tag, int reg)
{
struct sh5pci_softc *sc = arg;
pcireg_t rv;
int s;
KDASSERT((reg & 3) == 0);
s = splhigh();
sh5pci_csr_write(sc, SH5PCI_CSR_PAR, (u_int32_t)(tag | (pcitag_t)reg));
rv = (pcireg_t)sh5pci_csr_read(sc, SH5PCI_CSR_PDR);
splx(s);
return (rv);
}
static void
sh5pci_conf_write(void *arg, pcitag_t tag, int reg, pcireg_t data)
{
struct sh5pci_softc *sc = arg;
int s;
KDASSERT((reg & 3) == 0);
s = splhigh();
sh5pci_csr_write(sc, SH5PCI_CSR_PAR, (u_int32_t)(tag | (pcitag_t)reg));
sh5pci_csr_write(sc, SH5PCI_CSR_PDR, (u_int32_t)data);
splx(s);
}
static void
sh5pci_conf_interrupt(void *arg, int bus, int dev, int pin, int swiz, int *line)
{
struct sh5pci_softc *sc = arg;
(*sc->sc_intr->ih_intr_conf)(sc->sc_intr_arg, bus, dev, pin,
swiz, line);
}
static int
sh5pci_intr_map(void *arg, struct pci_attach_args *pa, pci_intr_handle_t *ih)
{
struct sh5pci_softc *sc = arg;
return ((*sc->sc_intr->ih_intr_map)(sc->sc_intr_arg, pa, ih));
}
static const char *
sh5pci_intr_string(void *arg, pci_intr_handle_t ih)
{
struct sh5pci_softc *sc = arg;
struct sh5pci_ihead *ihead;
static char intstr[16];
ihead = (*sc->sc_intr->ih_intr_ihead)(sc->sc_intr_arg, ih);
if (ihead == NULL)
return (NULL);
if (ihead->ih_level)
sprintf(intstr, "ipl %d", ihead->ih_level);
else
sprintf(intstr, "intevt 0x%x", ihead->ih_intevt);
return (intstr);
}
static const struct evcnt *
sh5pci_intr_evcnt(void *arg, pci_intr_handle_t ih)
{
struct sh5pci_softc *sc = arg;
struct sh5pci_ihead *ihead;
ihead = (*sc->sc_intr->ih_intr_ihead)(sc->sc_intr_arg, ih);
if (ihead == NULL)
return (NULL);
return (ihead->ih_evcnt);
}
static void *
sh5pci_intr_establish(void *arg, pci_intr_handle_t ih,
int level, int (*func)(void *), void *fnarg)
{
struct sh5pci_softc *sc = arg;
struct sh5pci_ihead *ihead;
struct sh5pci_icookie *ic;
int s;
ihead = (*sc->sc_intr->ih_intr_ihead)(sc->sc_intr_arg, ih);
if (ihead == NULL)
return (NULL);
if ((ic = sh5_intr_alloc_handle(sizeof(*ic))) == NULL)
return (NULL);
s = splhigh();
if (ihead->ih_cookie != NULL && ihead->ih_level != level) {
splx(s);
sh5_intr_free_handle(ic);
printf("sh5pci_intr_establish: shared level mismatch\n");
return (NULL);
}
ic->ic_ih = ih;
ic->ic_func = func;
ic->ic_arg = fnarg;
SLIST_INSERT_HEAD(&ihead->ih_handlers, ic, ic_next);
if (ihead->ih_cookie) {
splx(s);
return (ic);
}
/*
* First time through. Hook the real interrupt.
*/
ihead->ih_level = level;
ihead->ih_cookie = (*sc->sc_intr->ih_intr_establish)(sc->sc_intr_arg,
ih, level, sh5pci_intr_dispatch, ihead);
KDASSERT(ihead->ih_cookie);
splx(s);
return (ic);
}
static void
sh5pci_intr_disestablish(void *arg, void *cookie)
{
struct sh5pci_softc *sc = arg;
struct sh5pci_ihead *ihead;
struct sh5pci_icookie *ic = cookie;
int s;
ihead = (*sc->sc_intr->ih_intr_ihead)(sc->sc_intr_arg, ic->ic_ih);
if (ihead == NULL)
return; /* XXX: Panic instead? */
KDASSERT(ihead->ih_cookie != NULL);
s = splhigh();
SLIST_REMOVE(&ihead->ih_handlers, ic, sh5pci_icookie, ic_next);
/*
* If we're removing the last handler, unhook the interrupt.
*/
if (SLIST_EMPTY(&ihead->ih_handlers)) {
(*sc->sc_intr->ih_intr_disestablish)(sc->sc_intr_arg,
ic->ic_ih, ihead->ih_cookie);
/*
* Note that ihead is likely to be invalid now if the back-end
* does lazy-allocation of the sh5pci_ihead structures...
*/
}
splx(s);
sh5_intr_free_handle(ic);
}
static int
sh5pci_intr_dispatch(void *arg)
{
struct sh5pci_ihead *ihead = arg;
struct sh5pci_icookie *ic;
int rv = 0;
/*
* Call all the handlers registered for a particular interrupt pin
* and accumulate their "handled" status.
*/
SLIST_FOREACH(ic, &ihead->ih_handlers, ic_next) {
if ((*ic->ic_func)(ic->ic_arg)) {
if (ihead->ih_evcnt)
ihead->ih_evcnt->ev_count++;
rv++;
}
}
return (rv);
}
static void
sh5pci_bridge_init(struct sh5pci_softc *sc)
{
u_int32_t reg;
int i;
/* Disable the bridge */
reg = sh5pci_csr_read(sc, SH5PCI_CSR_CR);
reg &= ~SH5PCI_CSR_CR_PCI_CFINT_WR(1);
reg |= SH5PCI_CSR_CR_PCI_CFINT_WR(0);
sh5pci_csr_write(sc, SH5PCI_CSR_CR, reg);
/*
* Disable snoop
*/
sh5pci_csr_write(sc, SH5PCI_CSR_CSCR0, SH5PCI_CSR_CSCR_SNPMD_DISABLED);
sh5pci_csr_write(sc, SH5PCI_CSR_CSAR0, 0);
sh5pci_csr_write(sc, SH5PCI_CSR_CSCR1, SH5PCI_CSR_CSCR_SNPMD_DISABLED);
sh5pci_csr_write(sc, SH5PCI_CSR_CSAR1, 0);
/*
* Disable general, arbiter, and power-management interrupts.
*/
sh5pci_csr_write(sc, SH5PCI_CSR_INTM, 0);
sh5pci_csr_write(sc, SH5PCI_CSR_AINTM, 0);
sh5pci_csr_write(sc, SH5PCI_CSR_PINTM, 0);
/*
* Now enable the bridge.
*/
reg = sh5pci_csr_read(sc, SH5PCI_CSR_CR);
reg |= SH5PCI_CSR_CR_PCI_FTO_WR(1); /* TRDY and IRDY Enable */
reg |= SH5PCI_CSR_CR_PCI_PFE_WR(1); /* Pre-fetch Enable */
reg |= SH5PCI_CSR_CR_PCI_BMAM_WR(1); /* Round-robin arbitration */
reg |= SH5PCI_CSR_CR_PCI_PFCS(1); /* 32-bytes prefetching */
sh5pci_csr_write(sc, SH5PCI_CSR_CR, reg);
reg |= SH5PCI_CSR_CR_PCI_CFINT_WR(1); /* Take bridge out of reset */
sh5pci_csr_write(sc, SH5PCI_CSR_CR, reg);
/*
* Enable Memory and I/O spaces, and enable the bridge to
* be a bus master.
*/
reg = sh5pci_csr_read(sc, PCI_COMMAND_STATUS_REG);
reg &= ~(PCI_COMMAND_MASK << PCI_COMMAND_SHIFT);
reg |= (PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE |
PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_STEPPING_ENABLE)
<< PCI_COMMAND_SHIFT;
sh5pci_csr_write(sc, PCI_COMMAND_STATUS_REG, reg);
/*
* Specify the base addresses in PCI memory and I/O space to
* which SuperHyway accesses are mapped.
*
* For PCI memory space, we position this to the top 512MB
* of the PCIbus address space.
*
* For PCI i/o space, we position this at PCIbus address 0 to
* remain compatible with the PeeCee scheme of things.
*/
sh5pci_csr_write(sc, SH5PCI_CSR_MBMR,
SH5PCI_CSR_MBMR_PCI_MSBAMR(SH5PCI_MB2MSBAMR(512)));
sh5pci_csr_write(sc, SH5PCI_CSR_MBR, (~SH5PCI_MEMORY_SIZE) + 1);
sh5pci_csr_write(sc, SH5PCI_CSR_IOBMR,
SH5PCI_CSR_IOBMR_PCI_IOBAMR(SH5PCI_KB2IOBAMR(256)));
sh5pci_csr_write(sc, SH5PCI_CSR_IOBR, 0);
/*
* Set up the PCI target images such that other PCIbus Masters
* can access system memory.
*
* XXX: Really shouldn't hard-code these.
*/
sh5pci_csr_write(sc, SH5PCI_CSR_LSR(0),
SH5PCI_CSR_LSR_PCI_LSR_WR(SH5PCI_MB2LSR(512)) |
SH5PCI_CSR_LSR_PCI_MBARE);
sh5pci_csr_write(sc, SH5PCI_CSR_LAR(0), SH5PCI_RAM_PHYS_BASE);
sh5pci_csr_write(sc, SH5PCI_CONF_MBAR(0),
0x80000000 | PCI_MAPREG_TYPE_MEM |
PCI_MAPREG_MEM_TYPE_32BIT | PCI_MAPREG_MEM_PREFETCHABLE_MASK);
sh5pci_csr_write(sc, SH5PCI_CSR_LSR(1),
SH5PCI_CSR_LSR_PCI_LSR_WR(SH5PCI_MB2LSR(512)) |
SH5PCI_CSR_LSR_PCI_MBARE);
sh5pci_csr_write(sc, SH5PCI_CSR_LAR(1),
SH5PCI_RAM_PHYS_BASE + 0x20000000);
sh5pci_csr_write(sc, SH5PCI_CONF_MBAR(1),
0xa0000000 | PCI_MAPREG_TYPE_MEM |
PCI_MAPREG_MEM_TYPE_32BIT | PCI_MAPREG_MEM_PREFETCHABLE_MASK);
/*
* Fetch the mapping registers for the benefit of bus_dma mappings.
*/
for (i = 0; i < SH5PCI_NUM_MBARS; i++) {
reg = sh5pci_csr_read(sc, SH5PCI_CSR_LSR(i));
if (reg & SH5PCI_CSR_LSR_PCI_MBARE) {
sc->sc_map[i].m_start =
sh5pci_csr_read(sc, SH5PCI_CSR_LAR(i));
sc->sc_map[i].m_end = sc->sc_map[i].m_start +
SH5PCI_CSR_LSR_PCI_LSR_SIZE(reg);
sc->sc_map[i].m_pcibase =
PCI_MAPREG_MEM_ADDR(sh5pci_csr_read(sc,
SH5PCI_CONF_MBAR(i)));
} else
sc->sc_map[i].m_start = ~0;
}
/*
* Enable PCI error/arbiter interrupts
*/
sh5pci_csr_write(sc, SH5PCI_CSR_INTM, ~0);
sh5pci_csr_write(sc, SH5PCI_CSR_AINTM, ~0);
}
static int
sh5pci_check_master_abort(struct sh5pci_softc *sc)
{
u_int32_t reg;
int s, rv = 0;
s = splhigh();
reg = sh5pci_csr_read(sc, PCI_COMMAND_STATUS_REG);
if ((reg & PCI_STATUS_MASTER_ABORT) != 0) {
sh5pci_csr_write(sc, PCI_COMMAND_STATUS_REG, reg);
sh5pci_csr_write(sc, SH5PCI_CSR_INT, SH5PCI_CSR_INT_PCI_MADIM);
rv = 1;
}
splx(s);
return (rv);
}
static int
sh5pci_interrupt(void *arg)
{
struct sh5pci_softc *sc = arg;
u_int32_t pci_int, pci_air, pci_cir;
pci_int = sh5pci_csr_read(sc, SH5PCI_CSR_INT);
pci_cir = sh5pci_csr_read(sc, SH5PCI_CSR_CIR);
pci_air = sh5pci_csr_read(sc, SH5PCI_CSR_AIR);
if (pci_int) {
printf("%s: PCI IRQ: INT 0x%x, CIR 0x%x, AIR 0x%x\n",
sc->sc_dev.dv_xname, pci_int, pci_cir, pci_air);
sh5pci_csr_write(sc, SH5PCI_CSR_INT, pci_int);
}
pci_int = sh5pci_csr_read(sc, SH5PCI_CSR_AINT);
if (pci_int) {
printf("%s: PCI Arbiter IRQ: AINT 0x%x, CIR 0x%x, AIR 0x%x\n",
sc->sc_dev.dv_xname, pci_int, pci_cir, pci_air);
sh5pci_csr_write(sc, SH5PCI_CSR_AINT, pci_int);
}
return (1);
}