
which bustype should be attached with a specific call to config_found() (from a "mainbus" or a bus bridge). Do it for isa/eisa/mca and pci/agp for now. These buses all attach to an mi interface attribute "isabus", "eisabus" etc., and the autoconf framework now allows to specify an interface attribute on config_found() and config_search(), which limits the search of matching config data to these which attach to that specific attribute. So we basically have to call config_found_ia(..., "foobus", ...) where such a bus is attached. As a consequence, where a "mainbus" or alike also attaches other devices (eg CPUs) which do not attach to a specific attribute yet, we need at least pass an attribute name (different from "foobus") so that the foo bus is not found at these places. This made some minor changes necessary which are not obviously related to the mentioned buses.
1025 lines
27 KiB
C
1025 lines
27 KiB
C
/* $NetBSD: sh5_pci.c,v 1.12 2004/08/30 15:05:18 drochner 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.12 2004/08/30 15:05:18 drochner 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);
|
|
}
|