1025 lines
27 KiB
C
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);
|
|
}
|