1766 lines
46 KiB
C
1766 lines
46 KiB
C
/*-
|
|
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
|
|
* Copyright (c) 2017 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this software were developed by Landon Fuller
|
|
* under sponsorship from the FreeBSD Foundation.
|
|
*
|
|
* 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,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
|
|
* redistribution must be conditioned upon including a substantially
|
|
* similar Disclaimer requirement for further binary redistribution.
|
|
*
|
|
* NO WARRANTY
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* PCI-specific implementation for the BHNDB bridge driver.
|
|
*
|
|
* Provides support for bridging from a PCI parent bus to a BHND-compatible
|
|
* bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point
|
|
* mode.
|
|
*
|
|
* This driver handles all initial generic host-level PCI interactions with a
|
|
* PCI/PCIe bridge core operating in endpoint mode. Once the bridged bhnd(4)
|
|
* bus has been enumerated, this driver works in tandem with a core-specific
|
|
* bhnd_pci_hostb driver to manage the PCI core.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#include <dev/bhnd/bhnd.h>
|
|
#include <dev/bhnd/bhndreg.h>
|
|
|
|
#include <dev/bhnd/bhnd_erom.h>
|
|
#include <dev/bhnd/bhnd_eromvar.h>
|
|
|
|
#include <dev/bhnd/siba/sibareg.h>
|
|
|
|
#include <dev/bhnd/cores/pci/bhnd_pcireg.h>
|
|
|
|
#include "bhnd_pwrctl_hostb_if.h"
|
|
|
|
#include "bhndb_pcireg.h"
|
|
#include "bhndb_pcivar.h"
|
|
#include "bhndb_private.h"
|
|
|
|
struct bhndb_pci_eio;
|
|
struct bhndb_pci_probe;
|
|
|
|
static int bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc,
|
|
int *msi_count);
|
|
|
|
static int bhndb_pci_add_children(struct bhndb_pci_softc *sc);
|
|
|
|
static bhnd_devclass_t bhndb_expected_pci_devclass(device_t dev);
|
|
static bool bhndb_is_pcie_attached(device_t dev);
|
|
|
|
static int bhndb_enable_pci_clocks(device_t dev);
|
|
static int bhndb_disable_pci_clocks(device_t dev);
|
|
|
|
static int bhndb_pci_compat_setregwin(device_t dev,
|
|
device_t pci_dev, const struct bhndb_regwin *,
|
|
bhnd_addr_t);
|
|
static int bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev,
|
|
const struct bhndb_regwin *, bhnd_addr_t);
|
|
|
|
static void bhndb_pci_write_core(struct bhndb_pci_softc *sc,
|
|
bus_size_t offset, uint32_t value, u_int width);
|
|
static uint32_t bhndb_pci_read_core(struct bhndb_pci_softc *sc,
|
|
bus_size_t offset, u_int width);
|
|
|
|
static int bhndb_pci_srsh_pi_war(struct bhndb_pci_softc *sc,
|
|
struct bhndb_pci_probe *probe);
|
|
|
|
static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc);
|
|
static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc);
|
|
|
|
static int bhndb_pci_probe_alloc(struct bhndb_pci_probe **probe,
|
|
device_t dev, bhnd_devclass_t pci_devclass);
|
|
static void bhndb_pci_probe_free(struct bhndb_pci_probe *probe);
|
|
|
|
static int bhndb_pci_probe_copy_core_table(
|
|
struct bhndb_pci_probe *probe,
|
|
struct bhnd_core_info **cores, u_int *ncores);
|
|
static void bhndb_pci_probe_free_core_table(
|
|
struct bhnd_core_info *cores);
|
|
|
|
static void bhndb_pci_probe_write(struct bhndb_pci_probe *sc,
|
|
bhnd_addr_t addr, bhnd_size_t offset,
|
|
uint32_t value, u_int width);
|
|
static uint32_t bhndb_pci_probe_read(struct bhndb_pci_probe *sc,
|
|
bhnd_addr_t addr, bhnd_size_t offset, u_int width);
|
|
|
|
static void bhndb_pci_eio_init(struct bhndb_pci_eio *eio,
|
|
struct bhndb_pci_probe *probe);
|
|
static int bhndb_pci_eio_map(struct bhnd_erom_io *eio,
|
|
bhnd_addr_t addr, bhnd_size_t size);
|
|
static int bhndb_pci_eio_tell(struct bhnd_erom_io *eio,
|
|
bhnd_addr_t *addr, bhnd_size_t *size);
|
|
static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio,
|
|
bhnd_size_t offset, u_int width);
|
|
|
|
#define BHNDB_PCI_MSI_COUNT 1
|
|
|
|
static struct bhndb_pci_quirk bhndb_pci_quirks[];
|
|
static struct bhndb_pci_quirk bhndb_pcie_quirks[];
|
|
static struct bhndb_pci_quirk bhndb_pcie2_quirks[];
|
|
|
|
static struct bhndb_pci_core bhndb_pci_cores[] = {
|
|
BHNDB_PCI_CORE(PCI, bhndb_pci_quirks),
|
|
BHNDB_PCI_CORE(PCIE, bhndb_pcie_quirks),
|
|
BHNDB_PCI_CORE(PCIE2, bhndb_pcie2_quirks),
|
|
BHNDB_PCI_CORE_END
|
|
};
|
|
|
|
/* bhndb_pci erom I/O instance state */
|
|
struct bhndb_pci_eio {
|
|
struct bhnd_erom_io eio;
|
|
bool mapped; /**< true if a valid mapping exists */
|
|
bhnd_addr_t addr; /**< mapped address */
|
|
bhnd_size_t size; /**< mapped size */
|
|
struct bhndb_pci_probe *probe; /**< borrowed probe reference */
|
|
};
|
|
|
|
/**
|
|
* Provides early bus access to the bridged device's cores and core enumeration
|
|
* table.
|
|
*
|
|
* May be safely used during probe or early device attach, prior to calling
|
|
* bhndb_attach().
|
|
*/
|
|
struct bhndb_pci_probe {
|
|
device_t dev; /**< bridge device */
|
|
device_t pci_dev; /**< parent PCI device */
|
|
struct bhnd_chipid cid; /**< chip identification */
|
|
struct bhnd_core_info hostb_core; /**< PCI bridge core info */
|
|
|
|
struct bhndb_pci_eio erom_io; /**< erom I/O instance */
|
|
bhnd_erom_class_t *erom_class; /**< probed erom class */
|
|
bhnd_erom_t *erom; /**< erom parser */
|
|
struct bhnd_core_info *cores; /**< erom-owned core table */
|
|
u_int ncores; /**< number of cores */
|
|
|
|
const struct bhndb_regwin *m_win; /**< mapped register window, or NULL if no mapping */
|
|
struct resource *m_res; /**< resource containing the register window, or NULL if no window mapped */
|
|
bhnd_addr_t m_target; /**< base address mapped by m_win */
|
|
bhnd_addr_t m_addr; /**< mapped address */
|
|
bhnd_size_t m_size; /**< mapped size */
|
|
bool m_valid; /**< true if a valid mapping exists, false otherwise */
|
|
|
|
struct bhndb_host_resources *hr; /**< backing host resources */
|
|
};
|
|
|
|
static struct bhndb_pci_quirk bhndb_pci_quirks[] = {
|
|
/* Backplane interrupt flags must be routed via siba-specific
|
|
* SIBA_CFG0_INTVEC configuration register; the BHNDB_PCI_INT_MASK
|
|
* PCI configuration register is unsupported. */
|
|
{{ BHND_MATCH_CHIP_TYPE (SIBA) },
|
|
{ BHND_MATCH_CORE_REV (HWREV_LTE(5)) },
|
|
BHNDB_PCI_QUIRK_SIBA_INTVEC },
|
|
|
|
/* All PCI core revisions require the SRSH work-around */
|
|
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
|
|
BHNDB_PCI_QUIRK_END
|
|
};
|
|
|
|
static struct bhndb_pci_quirk bhndb_pcie_quirks[] = {
|
|
/* All PCIe-G1 core revisions require the SRSH work-around */
|
|
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
|
|
BHNDB_PCI_QUIRK_END
|
|
};
|
|
|
|
static struct bhndb_pci_quirk bhndb_pcie2_quirks[] = {
|
|
BHNDB_PCI_QUIRK_END
|
|
};
|
|
|
|
/**
|
|
* Return the device table entry for @p ci, or NULL if none.
|
|
*/
|
|
static struct bhndb_pci_core *
|
|
bhndb_pci_find_core(struct bhnd_core_info *ci)
|
|
{
|
|
for (size_t i = 0; !BHNDB_PCI_IS_CORE_END(&bhndb_pci_cores[i]); i++) {
|
|
struct bhndb_pci_core *entry = &bhndb_pci_cores[i];
|
|
|
|
if (bhnd_core_matches(ci, &entry->match))
|
|
return (entry);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/**
|
|
* Return all quirk flags for the given @p cid and @p ci.
|
|
*/
|
|
static uint32_t
|
|
bhndb_pci_get_core_quirks(struct bhnd_chipid *cid, struct bhnd_core_info *ci)
|
|
{
|
|
struct bhndb_pci_core *entry;
|
|
struct bhndb_pci_quirk *qtable;
|
|
uint32_t quirks;
|
|
|
|
quirks = 0;
|
|
|
|
/* No core entry? */
|
|
if ((entry = bhndb_pci_find_core(ci)) == NULL)
|
|
return (quirks);
|
|
|
|
/* No quirks? */
|
|
if ((qtable = entry->quirks) == NULL)
|
|
return (quirks);
|
|
|
|
for (size_t i = 0; !BHNDB_PCI_IS_QUIRK_END(&qtable[i]); i++) {
|
|
struct bhndb_pci_quirk *q = &qtable[i];
|
|
|
|
if (!bhnd_chip_matches(cid, &q->chip_desc))
|
|
continue;
|
|
|
|
if (!bhnd_core_matches(ci, &q->core_desc))
|
|
continue;
|
|
|
|
quirks |= q->quirks;
|
|
}
|
|
|
|
return (quirks);
|
|
}
|
|
|
|
/**
|
|
* Default bhndb_pci implementation of device_probe().
|
|
*
|
|
* Verifies that the parent is a PCI/PCIe device.
|
|
*/
|
|
static int
|
|
bhndb_pci_probe(device_t dev)
|
|
{
|
|
struct bhndb_pci_probe *probe;
|
|
struct bhndb_pci_core *entry;
|
|
bhnd_devclass_t hostb_devclass;
|
|
device_t parent, parent_bus;
|
|
devclass_t pci, bus_devclass;
|
|
int error;
|
|
|
|
probe = NULL;
|
|
|
|
/* Our parent must be a PCI/PCIe device. */
|
|
pci = devclass_find("pci");
|
|
parent = device_get_parent(dev);
|
|
parent_bus = device_get_parent(parent);
|
|
if (parent_bus == NULL)
|
|
return (ENXIO);
|
|
|
|
/* The bus device class may inherit from 'pci' */
|
|
for (bus_devclass = device_get_devclass(parent_bus);
|
|
bus_devclass != NULL;
|
|
bus_devclass = devclass_get_parent(bus_devclass))
|
|
{
|
|
if (bus_devclass == pci)
|
|
break;
|
|
}
|
|
|
|
if (bus_devclass != pci)
|
|
return (ENXIO);
|
|
|
|
/* Enable clocks */
|
|
if ((error = bhndb_enable_pci_clocks(dev)))
|
|
return (error);
|
|
|
|
/* Identify the chip and enumerate the bridged cores */
|
|
hostb_devclass = bhndb_expected_pci_devclass(dev);
|
|
if ((error = bhndb_pci_probe_alloc(&probe, dev, hostb_devclass)))
|
|
goto cleanup;
|
|
|
|
/* Look for a matching core table entry */
|
|
if ((entry = bhndb_pci_find_core(&probe->hostb_core)) == NULL) {
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
device_set_desc(dev, "PCI-BHND bridge");
|
|
|
|
/* fall-through */
|
|
error = BUS_PROBE_DEFAULT;
|
|
|
|
cleanup:
|
|
if (probe != NULL)
|
|
bhndb_pci_probe_free(probe);
|
|
|
|
bhndb_disable_pci_clocks(dev);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Attempt to allocate MSI interrupts, returning the count in @p msi_count
|
|
* on success.
|
|
*/
|
|
static int
|
|
bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count)
|
|
{
|
|
int error, count;
|
|
|
|
/* Is MSI available? */
|
|
if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT)
|
|
return (ENXIO);
|
|
|
|
/* Allocate expected message count */
|
|
count = BHNDB_PCI_MSI_COUNT;
|
|
if ((error = pci_alloc_msi(sc->parent, &count))) {
|
|
device_printf(sc->dev, "failed to allocate MSI interrupts: "
|
|
"%d\n", error);
|
|
|
|
return (error);
|
|
}
|
|
|
|
if (count < BHNDB_PCI_MSI_COUNT) {
|
|
pci_release_msi(sc->parent);
|
|
return (ENXIO);
|
|
}
|
|
|
|
*msi_count = count;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_attach(device_t dev)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
struct bhnd_chipid cid;
|
|
struct bhnd_core_info *cores, hostb_core;
|
|
bhnd_erom_class_t *erom_class;
|
|
struct bhndb_pci_probe *probe;
|
|
u_int ncores;
|
|
int irq_rid;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->dev = dev;
|
|
sc->parent = device_get_parent(dev);
|
|
sc->pci_devclass = bhndb_expected_pci_devclass(dev);
|
|
sc->pci_quirks = 0;
|
|
sc->set_regwin = NULL;
|
|
|
|
BHNDB_PCI_LOCK_INIT(sc);
|
|
|
|
probe = NULL;
|
|
cores = NULL;
|
|
|
|
/* Enable PCI bus mastering */
|
|
pci_enable_busmaster(sc->parent);
|
|
|
|
/* Enable clocks (if required by this hardware) */
|
|
if ((error = bhndb_enable_pci_clocks(sc->dev)))
|
|
goto cleanup;
|
|
|
|
/* Identify the chip and enumerate the bridged cores */
|
|
error = bhndb_pci_probe_alloc(&probe, dev, sc->pci_devclass);
|
|
if (error)
|
|
goto cleanup;
|
|
|
|
sc->pci_quirks = bhndb_pci_get_core_quirks(&probe->cid,
|
|
&probe->hostb_core);
|
|
|
|
/* Select the appropriate register window handler */
|
|
if (probe->cid.chip_type == BHND_CHIPTYPE_SIBA) {
|
|
sc->set_regwin = bhndb_pci_compat_setregwin;
|
|
} else {
|
|
sc->set_regwin = bhndb_pci_fast_setregwin;
|
|
}
|
|
|
|
/*
|
|
* Fix up our PCI base address in the SPROM shadow, if necessary.
|
|
*
|
|
* This must be done prior to accessing any static register windows
|
|
* that map the PCI core.
|
|
*/
|
|
if ((error = bhndb_pci_srsh_pi_war(sc, probe)))
|
|
goto cleanup;
|
|
|
|
/* Set up PCI interrupt handling */
|
|
if (bhndb_pci_alloc_msi(sc, &sc->msi_count) == 0) {
|
|
/* MSI uses resource IDs starting at 1 */
|
|
irq_rid = 1;
|
|
|
|
device_printf(dev, "Using MSI interrupts on %s\n",
|
|
device_get_nameunit(sc->parent));
|
|
} else {
|
|
sc->msi_count = 0;
|
|
irq_rid = 0;
|
|
|
|
device_printf(dev, "Using INTx interrupts on %s\n",
|
|
device_get_nameunit(sc->parent));
|
|
}
|
|
|
|
sc->isrc = bhndb_alloc_intr_isrc(sc->parent, irq_rid, 0, RM_MAX_END, 1,
|
|
RF_SHAREABLE | RF_ACTIVE);
|
|
if (sc->isrc == NULL) {
|
|
device_printf(sc->dev, "failed to allocate interrupt "
|
|
"resource\n");
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Copy out the probe results and then free our probe state, releasing
|
|
* its exclusive ownership of host bridge resources.
|
|
*
|
|
* This must be done prior to full configuration of the bridge via
|
|
* bhndb_attach().
|
|
*/
|
|
cid = probe->cid;
|
|
erom_class = probe->erom_class;
|
|
hostb_core = probe->hostb_core;
|
|
|
|
error = bhndb_pci_probe_copy_core_table(probe, &cores, &ncores);
|
|
if (error) {
|
|
cores = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
bhndb_pci_probe_free(probe);
|
|
probe = NULL;
|
|
|
|
/* Perform bridge attach */
|
|
error = bhndb_attach(dev, &cid, cores, ncores, &hostb_core, erom_class);
|
|
if (error)
|
|
goto cleanup;
|
|
|
|
/* Add any additional child devices */
|
|
if ((error = bhndb_pci_add_children(sc)))
|
|
goto cleanup;
|
|
|
|
/* Probe and attach our children */
|
|
if ((error = bus_generic_attach(dev)))
|
|
goto cleanup;
|
|
|
|
bhndb_pci_probe_free_core_table(cores);
|
|
|
|
return (0);
|
|
|
|
cleanup:
|
|
device_delete_children(dev);
|
|
|
|
if (sc->isrc != NULL)
|
|
bhndb_free_intr_isrc(sc->isrc);
|
|
|
|
if (sc->msi_count > 0)
|
|
pci_release_msi(sc->parent);
|
|
|
|
if (cores != NULL)
|
|
bhndb_pci_probe_free_core_table(cores);
|
|
|
|
if (probe != NULL)
|
|
bhndb_pci_probe_free(probe);
|
|
|
|
bhndb_disable_pci_clocks(sc->dev);
|
|
|
|
pci_disable_busmaster(sc->parent);
|
|
|
|
BHNDB_PCI_LOCK_DESTROY(sc);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_detach(device_t dev)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Attempt to detach our children */
|
|
if ((error = bus_generic_detach(dev)))
|
|
return (error);
|
|
|
|
/* Perform generic bridge detach */
|
|
if ((error = bhndb_generic_detach(dev)))
|
|
return (error);
|
|
|
|
/* Disable clocks (if required by this hardware) */
|
|
if ((error = bhndb_disable_pci_clocks(sc->dev)))
|
|
return (error);
|
|
|
|
/* Free our interrupt resources */
|
|
bhndb_free_intr_isrc(sc->isrc);
|
|
|
|
/* Release MSI interrupts */
|
|
if (sc->msi_count > 0)
|
|
pci_release_msi(sc->parent);
|
|
|
|
/* Disable PCI bus mastering */
|
|
pci_disable_busmaster(sc->parent);
|
|
|
|
BHNDB_PCI_LOCK_DESTROY(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_add_children(struct bhndb_pci_softc *sc)
|
|
{
|
|
bus_size_t nv_sz;
|
|
int error;
|
|
|
|
/**
|
|
* If SPROM is mapped directly into BAR0, add child NVRAM
|
|
* device.
|
|
*/
|
|
nv_sz = bhndb_pci_sprom_size(sc);
|
|
if (nv_sz > 0) {
|
|
struct bhndb_devinfo *dinfo;
|
|
device_t child;
|
|
|
|
if (bootverbose) {
|
|
device_printf(sc->dev, "found SPROM (%ju bytes)\n",
|
|
(uintmax_t)nv_sz);
|
|
}
|
|
|
|
/* Add sprom device, ordered early enough to be available
|
|
* before the bridged bhnd(4) bus is attached. */
|
|
child = BUS_ADD_CHILD(sc->dev,
|
|
BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY, "bhnd_nvram", -1);
|
|
if (child == NULL) {
|
|
device_printf(sc->dev, "failed to add sprom device\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Initialize device address space and resource covering the
|
|
* BAR0 SPROM shadow. */
|
|
dinfo = device_get_ivars(child);
|
|
dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE;
|
|
|
|
error = bus_set_resource(child, SYS_RES_MEMORY, 0,
|
|
bhndb_pci_sprom_addr(sc), nv_sz);
|
|
if (error) {
|
|
device_printf(sc->dev,
|
|
"failed to register sprom resources\n");
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static const struct bhndb_regwin *
|
|
bhndb_pci_sprom_regwin(struct bhndb_pci_softc *sc)
|
|
{
|
|
struct bhndb_resources *bres;
|
|
const struct bhndb_hwcfg *cfg;
|
|
const struct bhndb_regwin *sprom_win;
|
|
|
|
bres = sc->bhndb.bus_res;
|
|
cfg = bres->cfg;
|
|
|
|
sprom_win = bhndb_regwin_find_type(cfg->register_windows,
|
|
BHNDB_REGWIN_T_SPROM, BHNDB_PCI_V0_BAR0_SPROM_SIZE);
|
|
|
|
return (sprom_win);
|
|
}
|
|
|
|
static bus_addr_t
|
|
bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc)
|
|
{
|
|
const struct bhndb_regwin *sprom_win;
|
|
struct resource *r;
|
|
|
|
/* Fetch the SPROM register window */
|
|
sprom_win = bhndb_pci_sprom_regwin(sc);
|
|
KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+"));
|
|
|
|
/* Fetch the associated resource */
|
|
r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, sprom_win);
|
|
KASSERT(r != NULL, ("missing resource for sprom window\n"));
|
|
|
|
return (rman_get_start(r) + sprom_win->win_offset);
|
|
}
|
|
|
|
static bus_size_t
|
|
bhndb_pci_sprom_size(struct bhndb_pci_softc *sc)
|
|
{
|
|
const struct bhndb_regwin *sprom_win;
|
|
uint32_t sctl;
|
|
bus_size_t sprom_sz;
|
|
|
|
sprom_win = bhndb_pci_sprom_regwin(sc);
|
|
|
|
/* PCI_V2 and later devices map SPROM/OTP via ChipCommon */
|
|
if (sprom_win == NULL)
|
|
return (0);
|
|
|
|
/* Determine SPROM size */
|
|
sctl = pci_read_config(sc->parent, BHNDB_PCI_SPROM_CONTROL, 4);
|
|
if (sctl & BHNDB_PCI_SPROM_BLANK)
|
|
return (0);
|
|
|
|
switch (sctl & BHNDB_PCI_SPROM_SZ_MASK) {
|
|
case BHNDB_PCI_SPROM_SZ_1KB:
|
|
sprom_sz = (1 * 1024);
|
|
break;
|
|
|
|
case BHNDB_PCI_SPROM_SZ_4KB:
|
|
sprom_sz = (4 * 1024);
|
|
break;
|
|
|
|
case BHNDB_PCI_SPROM_SZ_16KB:
|
|
sprom_sz = (16 * 1024);
|
|
break;
|
|
|
|
case BHNDB_PCI_SPROM_SZ_RESERVED:
|
|
default:
|
|
device_printf(sc->dev, "invalid PCI sprom size 0x%x\n", sctl);
|
|
return (0);
|
|
}
|
|
|
|
/* If the device has a larger SPROM than can be addressed via our SPROM
|
|
* register window, the SPROM image data will still be located within
|
|
* the window's addressable range */
|
|
sprom_sz = MIN(sprom_sz, sprom_win->win_size);
|
|
|
|
return (sprom_sz);
|
|
}
|
|
|
|
/**
|
|
* Return the host resource providing a static mapping of the PCI core's
|
|
* registers.
|
|
*
|
|
* @param sc bhndb PCI driver state.
|
|
* @param offset The required readable offset within the PCI core
|
|
* register block.
|
|
* @param size The required readable size at @p offset.
|
|
* @param[out] res On success, the host resource containing our PCI
|
|
* core's register window.
|
|
* @param[out] res_offset On success, the @p offset relative to @p res.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENXIO if a valid static register window mapping the PCI core
|
|
* registers is not available.
|
|
*/
|
|
static int
|
|
bhndb_pci_get_core_regs(struct bhndb_pci_softc *sc, bus_size_t offset,
|
|
bus_size_t size, struct resource **res, bus_size_t *res_offset)
|
|
{
|
|
const struct bhndb_regwin *win;
|
|
struct resource *r;
|
|
|
|
/* Locate the static register window mapping the requested offset */
|
|
win = bhndb_regwin_find_core(sc->bhndb.bus_res->cfg->register_windows,
|
|
sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0, offset, size);
|
|
if (win == NULL) {
|
|
device_printf(sc->dev, "missing PCI core register window\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Fetch the resource containing the register window */
|
|
r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, win);
|
|
if (r == NULL) {
|
|
device_printf(sc->dev, "missing PCI core register resource\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
KASSERT(offset >= win->d.core.offset, ("offset %#jx outside of "
|
|
"register window", (uintmax_t)offset));
|
|
|
|
*res = r;
|
|
*res_offset = win->win_offset + (offset - win->d.core.offset);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Write a 1, 2, or 4 byte data item to the PCI core's registers at @p offset.
|
|
*
|
|
* @param sc bhndb PCI driver state.
|
|
* @param offset register write offset.
|
|
* @param value value to be written.
|
|
* @param width item width (1, 2, or 4 bytes).
|
|
*/
|
|
static void
|
|
bhndb_pci_write_core(struct bhndb_pci_softc *sc, bus_size_t offset,
|
|
uint32_t value, u_int width)
|
|
{
|
|
struct resource *r;
|
|
bus_size_t r_offset;
|
|
int error;
|
|
|
|
error = bhndb_pci_get_core_regs(sc, offset, width, &r, &r_offset);
|
|
if (error) {
|
|
panic("no PCI register window mapping %#jx+%#x: %d",
|
|
(uintmax_t)offset, width, error);
|
|
}
|
|
|
|
switch (width) {
|
|
case 1:
|
|
bus_write_1(r, r_offset, value);
|
|
break;
|
|
case 2:
|
|
bus_write_2(r, r_offset, value);
|
|
break;
|
|
case 4:
|
|
bus_write_4(r, r_offset, value);
|
|
break;
|
|
default:
|
|
panic("invalid width: %u", width);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read a 1, 2, or 4 byte data item from the PCI core's registers
|
|
* at @p offset.
|
|
*
|
|
* @param sc bhndb PCI driver state.
|
|
* @param offset register read offset.
|
|
* @param width item width (1, 2, or 4 bytes).
|
|
*/
|
|
static uint32_t
|
|
bhndb_pci_read_core(struct bhndb_pci_softc *sc, bus_size_t offset, u_int width)
|
|
{
|
|
struct resource *r;
|
|
bus_size_t r_offset;
|
|
int error;
|
|
|
|
error = bhndb_pci_get_core_regs(sc, offset, width, &r, &r_offset);
|
|
if (error) {
|
|
panic("no PCI register window mapping %#jx+%#x: %d",
|
|
(uintmax_t)offset, width, error);
|
|
}
|
|
|
|
switch (width) {
|
|
case 1:
|
|
return (bus_read_1(r, r_offset));
|
|
case 2:
|
|
return (bus_read_2(r, r_offset));
|
|
case 4:
|
|
return (bus_read_4(r, r_offset));
|
|
default:
|
|
panic("invalid width: %u", width);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fix-up power on defaults for SPROM-less devices.
|
|
*
|
|
* On SPROM-less devices, the PCI(e) cores will be initialized with their their
|
|
* Power-on-Reset defaults; this can leave the BHND_PCI_SRSH_PI value pointing
|
|
* to the wrong backplane address. This value is used by the PCI core when
|
|
* performing address translation between static register windows in BAR0 that
|
|
* map the PCI core's register block, and backplane address space.
|
|
*
|
|
* When translating accesses via these BAR0 regions, the PCI bridge determines
|
|
* the base address of the PCI core by concatenating:
|
|
*
|
|
* [bits] [source]
|
|
* 31:16 bits [31:16] of the enumeration space address (e.g. 0x18000000)
|
|
* 15:12 value of BHND_PCI_SRSH_PI from the PCI core's SPROM shadow
|
|
* 11:0 bits [11:0] of the PCI bus address
|
|
*
|
|
* For example, on a PCI_V0 device, the following PCI core register offsets are
|
|
* mapped into BAR0:
|
|
*
|
|
* [BAR0 offset] [description] [PCI core offset]
|
|
* 0x1000-0x17FF sprom shadow 0x800-0xFFF
|
|
* 0x1800-0x1DFF device registers 0x000-0x5FF
|
|
* 0x1E00+0x1FFF siba config registers 0xE00-0xFFF
|
|
*
|
|
* This function checks -- and if necessary, corrects -- the BHND_PCI_SRSH_PI
|
|
* value in the SPROM shadow.
|
|
*
|
|
* This workaround must applied prior to accessing any static register windows
|
|
* that map the PCI core.
|
|
*
|
|
* Applies to all PCI and PCIe-G1 core revisions.
|
|
*/
|
|
static int
|
|
bhndb_pci_srsh_pi_war(struct bhndb_pci_softc *sc,
|
|
struct bhndb_pci_probe *probe)
|
|
{
|
|
struct bhnd_core_match md;
|
|
bhnd_addr_t pci_addr;
|
|
bhnd_size_t pci_size;
|
|
bus_size_t srsh_offset;
|
|
uint16_t srsh_val, pci_val;
|
|
uint16_t val;
|
|
int error;
|
|
|
|
if ((sc->pci_quirks & BHNDB_PCI_QUIRK_SRSH_WAR) == 0)
|
|
return (0);
|
|
|
|
/* Use an equality match descriptor to look up our PCI core's base
|
|
* address in the EROM */
|
|
md = bhnd_core_get_match_desc(&probe->hostb_core);
|
|
error = bhnd_erom_lookup_core_addr(probe->erom, &md, BHND_PORT_DEVICE,
|
|
0, 0, NULL, &pci_addr, &pci_size);
|
|
if (error) {
|
|
device_printf(sc->dev, "no base address found for the PCI host "
|
|
"bridge core: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
/* Fetch the SPROM SRSH_PI value */
|
|
srsh_offset = BHND_PCI_SPROM_SHADOW + BHND_PCI_SRSH_PI_OFFSET;
|
|
val = bhndb_pci_probe_read(probe, pci_addr, srsh_offset, sizeof(val));
|
|
srsh_val = (val & BHND_PCI_SRSH_PI_MASK) >> BHND_PCI_SRSH_PI_SHIFT;
|
|
|
|
/* If it doesn't match PCI core's base address, update the SPROM
|
|
* shadow */
|
|
pci_val = (pci_addr & BHND_PCI_SRSH_PI_ADDR_MASK) >>
|
|
BHND_PCI_SRSH_PI_ADDR_SHIFT;
|
|
if (srsh_val != pci_val) {
|
|
val &= ~BHND_PCI_SRSH_PI_MASK;
|
|
val |= (pci_val << BHND_PCI_SRSH_PI_SHIFT);
|
|
bhndb_pci_probe_write(probe, pci_addr, srsh_offset, val,
|
|
sizeof(val));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_resume(device_t dev)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Enable clocks (if supported by this hardware) */
|
|
if ((error = bhndb_enable_pci_clocks(sc->dev)))
|
|
return (error);
|
|
|
|
/* Perform resume */
|
|
return (bhndb_generic_resume(dev));
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_suspend(device_t dev)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Disable clocks (if supported by this hardware) */
|
|
if ((error = bhndb_disable_pci_clocks(sc->dev)))
|
|
return (error);
|
|
|
|
/* Perform suspend */
|
|
return (bhndb_generic_suspend(dev));
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw,
|
|
bhnd_addr_t addr)
|
|
{
|
|
struct bhndb_pci_softc *sc = device_get_softc(dev);
|
|
return (sc->set_regwin(sc->dev, sc->parent, rw, addr));
|
|
}
|
|
|
|
/**
|
|
* A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation.
|
|
*
|
|
* On siba(4) devices, it's possible that writing a PCI window register may
|
|
* not succeed; it's necessary to immediately read the configuration register
|
|
* and retry if not set to the desired value.
|
|
*
|
|
* This is not necessary on bcma(4) devices, but other than the overhead of
|
|
* validating the register, there's no harm in performing the verification.
|
|
*/
|
|
static int
|
|
bhndb_pci_compat_setregwin(device_t dev, device_t pci_dev,
|
|
const struct bhndb_regwin *rw, bhnd_addr_t addr)
|
|
{
|
|
int error;
|
|
int reg;
|
|
|
|
if (rw->win_type != BHNDB_REGWIN_T_DYN)
|
|
return (ENODEV);
|
|
|
|
reg = rw->d.dyn.cfg_offset;
|
|
for (u_int i = 0; i < BHNDB_PCI_BARCTRL_WRITE_RETRY; i++) {
|
|
if ((error = bhndb_pci_fast_setregwin(dev, pci_dev, rw, addr)))
|
|
return (error);
|
|
|
|
if (pci_read_config(pci_dev, reg, 4) == addr)
|
|
return (0);
|
|
|
|
DELAY(10);
|
|
}
|
|
|
|
/* Unable to set window */
|
|
return (ENODEV);
|
|
}
|
|
|
|
/**
|
|
* A bcma(4)-only bhndb_set_window_addr implementation.
|
|
*/
|
|
static int
|
|
bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev,
|
|
const struct bhndb_regwin *rw, bhnd_addr_t addr)
|
|
{
|
|
/* The PCI bridge core only supports 32-bit addressing, regardless
|
|
* of the bus' support for 64-bit addressing */
|
|
if (addr > UINT32_MAX)
|
|
return (ERANGE);
|
|
|
|
switch (rw->win_type) {
|
|
case BHNDB_REGWIN_T_DYN:
|
|
/* Addresses must be page aligned */
|
|
if (addr % rw->win_size != 0)
|
|
return (EINVAL);
|
|
|
|
pci_write_config(pci_dev, rw->d.dyn.cfg_offset, addr, 4);
|
|
break;
|
|
default:
|
|
return (ENODEV);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_populate_board_info(device_t dev, device_t child,
|
|
struct bhnd_board_info *info)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/*
|
|
* On a subset of Apple BCM4360 modules, always prefer the
|
|
* PCI subdevice to the SPROM-supplied boardtype.
|
|
*
|
|
* TODO:
|
|
*
|
|
* Broadcom's own drivers implement this override, and then later use
|
|
* the remapped BCM4360 board type to determine the required
|
|
* board-specific workarounds.
|
|
*
|
|
* Without access to this hardware, it's unclear why this mapping
|
|
* is done, and we must do the same. If we can survey the hardware
|
|
* in question, it may be possible to replace this behavior with
|
|
* explicit references to the SPROM-supplied boardtype(s) in our
|
|
* quirk definitions.
|
|
*/
|
|
if (pci_get_subvendor(sc->parent) == PCI_VENDOR_APPLE) {
|
|
switch (info->board_type) {
|
|
case BHND_BOARD_BCM94360X29C:
|
|
case BHND_BOARD_BCM94360X29CP2:
|
|
case BHND_BOARD_BCM94360X51:
|
|
case BHND_BOARD_BCM94360X51P2:
|
|
info->board_type = 0; /* allow override below */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If NVRAM did not supply vendor/type/devid info, provide the PCI
|
|
* subvendor/subdevice/device values. */
|
|
if (info->board_vendor == 0)
|
|
info->board_vendor = pci_get_subvendor(sc->parent);
|
|
|
|
if (info->board_type == 0)
|
|
info->board_type = pci_get_subdevice(sc->parent);
|
|
|
|
if (info->board_devid == 0)
|
|
info->board_devid = pci_get_device(sc->parent);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Examine the bridge device @p dev and return the expected host bridge
|
|
* device class.
|
|
*
|
|
* @param dev The bhndb bridge device
|
|
*/
|
|
static bhnd_devclass_t
|
|
bhndb_expected_pci_devclass(device_t dev)
|
|
{
|
|
if (bhndb_is_pcie_attached(dev))
|
|
return (BHND_DEVCLASS_PCIE);
|
|
else
|
|
return (BHND_DEVCLASS_PCI);
|
|
}
|
|
|
|
/**
|
|
* Return true if the bridge device @p dev is attached via PCIe,
|
|
* false otherwise.
|
|
*
|
|
* @param dev The bhndb bridge device
|
|
*/
|
|
static bool
|
|
bhndb_is_pcie_attached(device_t dev)
|
|
{
|
|
int reg;
|
|
|
|
if (pci_find_cap(device_get_parent(dev), PCIY_EXPRESS, ®) == 0)
|
|
return (true);
|
|
|
|
return (false);
|
|
}
|
|
|
|
/**
|
|
* Enable externally managed clocks, if required.
|
|
*
|
|
* Some PCI chipsets (BCM4306, possibly others) chips do not support
|
|
* the idle low-power clock. Clocking must be bootstrapped at
|
|
* attach/resume by directly adjusting GPIO registers exposed in the
|
|
* PCI config space, and correspondingly, explicitly shutdown at
|
|
* detach/suspend.
|
|
*
|
|
* @note This function may be safely called prior to device attach, (e.g.
|
|
* from DEVICE_PROBE).
|
|
*
|
|
* @param dev The bhndb bridge device
|
|
*/
|
|
static int
|
|
bhndb_enable_pci_clocks(device_t dev)
|
|
{
|
|
device_t pci_dev;
|
|
uint32_t gpio_in, gpio_out, gpio_en;
|
|
uint32_t gpio_flags;
|
|
uint16_t pci_status;
|
|
|
|
pci_dev = device_get_parent(dev);
|
|
|
|
/* Only supported and required on PCI devices */
|
|
if (bhndb_is_pcie_attached(dev))
|
|
return (0);
|
|
|
|
/* Read state of XTAL pin */
|
|
gpio_in = pci_read_config(pci_dev, BHNDB_PCI_GPIO_IN, 4);
|
|
if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON)
|
|
return (0); /* already enabled */
|
|
|
|
/* Fetch current config */
|
|
gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4);
|
|
gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4);
|
|
|
|
/* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */
|
|
gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON);
|
|
gpio_out |= gpio_flags;
|
|
gpio_en |= gpio_flags;
|
|
|
|
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
|
|
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4);
|
|
DELAY(1000);
|
|
|
|
/* Reset PLL_OFF */
|
|
gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF;
|
|
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
|
|
DELAY(5000);
|
|
|
|
/* Clear any PCI 'sent target-abort' flag. */
|
|
pci_status = pci_read_config(pci_dev, PCIR_STATUS, 2);
|
|
pci_status &= ~PCIM_STATUS_STABORT;
|
|
pci_write_config(pci_dev, PCIR_STATUS, pci_status, 2);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Disable externally managed clocks, if required.
|
|
*
|
|
* This function may be safely called prior to device attach, (e.g.
|
|
* from DEVICE_PROBE).
|
|
*
|
|
* @param dev The bhndb bridge device
|
|
*/
|
|
static int
|
|
bhndb_disable_pci_clocks(device_t dev)
|
|
{
|
|
device_t pci_dev;
|
|
uint32_t gpio_out, gpio_en;
|
|
|
|
pci_dev = device_get_parent(dev);
|
|
|
|
/* Only supported and required on PCI devices */
|
|
if (bhndb_is_pcie_attached(dev))
|
|
return (0);
|
|
|
|
/* Fetch current config */
|
|
gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4);
|
|
gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4);
|
|
|
|
/* Set PLL_OFF to HIGH, XTAL_ON to LOW. */
|
|
gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON;
|
|
gpio_out |= BHNDB_PCI_GPIO_PLL_OFF;
|
|
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
|
|
|
|
/* Enable both output pins */
|
|
gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON);
|
|
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static bhnd_clksrc
|
|
bhndb_pci_pwrctl_get_clksrc(device_t dev, device_t child,
|
|
bhnd_clock clock)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
uint32_t gpio_out;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Only supported on PCI devices */
|
|
if (bhndb_is_pcie_attached(sc->dev))
|
|
return (BHND_CLKSRC_UNKNOWN);
|
|
|
|
/* Only ILP is supported */
|
|
if (clock != BHND_CLOCK_ILP)
|
|
return (BHND_CLKSRC_UNKNOWN);
|
|
|
|
gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4);
|
|
if (gpio_out & BHNDB_PCI_GPIO_SCS)
|
|
return (BHND_CLKSRC_PCI);
|
|
else
|
|
return (BHND_CLKSRC_XTAL);
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_pwrctl_gate_clock(device_t dev, device_t child,
|
|
bhnd_clock clock)
|
|
{
|
|
struct bhndb_pci_softc *sc = device_get_softc(dev);
|
|
|
|
/* Only supported on PCI devices */
|
|
if (bhndb_is_pcie_attached(sc->dev))
|
|
return (ENODEV);
|
|
|
|
/* Only HT is supported */
|
|
if (clock != BHND_CLOCK_HT)
|
|
return (ENXIO);
|
|
|
|
return (bhndb_disable_pci_clocks(sc->dev));
|
|
}
|
|
|
|
static int
|
|
bhndb_pci_pwrctl_ungate_clock(device_t dev, device_t child,
|
|
bhnd_clock clock)
|
|
{
|
|
struct bhndb_pci_softc *sc = device_get_softc(dev);
|
|
|
|
/* Only supported on PCI devices */
|
|
if (bhndb_is_pcie_attached(sc->dev))
|
|
return (ENODEV);
|
|
|
|
/* Only HT is supported */
|
|
if (clock != BHND_CLOCK_HT)
|
|
return (ENXIO);
|
|
|
|
return (bhndb_enable_pci_clocks(sc->dev));
|
|
}
|
|
|
|
/**
|
|
* BHNDB_MAP_INTR_ISRC()
|
|
*/
|
|
static int
|
|
bhndb_pci_map_intr_isrc(device_t dev, struct resource *irq,
|
|
struct bhndb_intr_isrc **isrc)
|
|
{
|
|
struct bhndb_pci_softc *sc = device_get_softc(dev);
|
|
|
|
/* There's only one bridged interrupt to choose from */
|
|
*isrc = sc->isrc;
|
|
return (0);
|
|
}
|
|
|
|
/* siba-specific implementation of BHNDB_ROUTE_INTERRUPTS() */
|
|
static int
|
|
bhndb_pci_route_siba_interrupts(struct bhndb_pci_softc *sc, device_t child)
|
|
{
|
|
uint32_t sbintvec;
|
|
u_int ivec;
|
|
int error;
|
|
|
|
KASSERT(sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC,
|
|
("route_siba_interrupts not supported by this hardware"));
|
|
|
|
/* Fetch the sbflag# for the child */
|
|
if ((error = bhnd_get_intr_ivec(child, 0, &ivec)))
|
|
return (error);
|
|
|
|
if (ivec > (sizeof(sbintvec)*8) - 1 /* aka '31' */) {
|
|
/* This should never be an issue in practice */
|
|
device_printf(sc->dev, "cannot route interrupts to high "
|
|
"sbflag# %u\n", ivec);
|
|
return (ENXIO);
|
|
}
|
|
|
|
BHNDB_PCI_LOCK(sc);
|
|
|
|
sbintvec = bhndb_pci_read_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), 4);
|
|
sbintvec |= (1 << ivec);
|
|
bhndb_pci_write_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), sbintvec, 4);
|
|
|
|
BHNDB_PCI_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* BHNDB_ROUTE_INTERRUPTS() */
|
|
static int
|
|
bhndb_pci_route_interrupts(device_t dev, device_t child)
|
|
{
|
|
struct bhndb_pci_softc *sc;
|
|
struct bhnd_core_info core;
|
|
uint32_t core_bit;
|
|
uint32_t intmask;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if (sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC)
|
|
return (bhndb_pci_route_siba_interrupts(sc, child));
|
|
|
|
core = bhnd_get_core_info(child);
|
|
if (core.core_idx > BHNDB_PCI_SBIM_COREIDX_MAX) {
|
|
/* This should never be an issue in practice */
|
|
device_printf(dev, "cannot route interrupts to high core "
|
|
"index %u\n", core.core_idx);
|
|
return (ENXIO);
|
|
}
|
|
|
|
BHNDB_PCI_LOCK(sc);
|
|
|
|
core_bit = (1<<core.core_idx) << BHNDB_PCI_SBIM_SHIFT;
|
|
intmask = pci_read_config(sc->parent, BHNDB_PCI_INT_MASK, 4);
|
|
intmask |= core_bit;
|
|
pci_write_config(sc->parent, BHNDB_PCI_INT_MASK, intmask, 4);
|
|
|
|
BHNDB_PCI_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Using the generic PCI bridge hardware configuration, allocate, initialize
|
|
* and return a new bhndb_pci probe state instance.
|
|
*
|
|
* On success, the caller assumes ownership of the returned probe instance, and
|
|
* is responsible for releasing this reference using bhndb_pci_probe_free().
|
|
*
|
|
* @param[out] probe On success, the newly allocated probe instance.
|
|
* @param dev The bhndb_pci bridge device.
|
|
* @param hostb_devclass The expected device class of the bridge core.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero if allocating the probe state fails, a regular
|
|
* unix error code will be returned.
|
|
*
|
|
* @note This function requires exclusive ownership over allocating and
|
|
* configuring host bridge resources, and should only be called prior to
|
|
* completion of device attach and full configuration of the bridge.
|
|
*/
|
|
static int
|
|
bhndb_pci_probe_alloc(struct bhndb_pci_probe **probe, device_t dev,
|
|
bhnd_devclass_t hostb_devclass)
|
|
{
|
|
struct bhndb_pci_probe *p;
|
|
struct bhnd_erom_io *eio;
|
|
const struct bhndb_hwcfg *hwcfg;
|
|
const struct bhnd_chipid *hint;
|
|
device_t parent_dev;
|
|
int error;
|
|
|
|
parent_dev = device_get_parent(dev);
|
|
eio = NULL;
|
|
|
|
p = malloc(sizeof(*p), M_BHND, M_ZERO|M_WAITOK);
|
|
p->dev = dev;
|
|
p->pci_dev = parent_dev;
|
|
|
|
/* Our register window mapping state must be initialized at this point,
|
|
* as bhndb_pci_eio will begin making calls into
|
|
* bhndb_pci_probe_(read|write|get_mapping) */
|
|
p->m_win = NULL;
|
|
p->m_res = NULL;
|
|
p->m_valid = false;
|
|
|
|
bhndb_pci_eio_init(&p->erom_io, p);
|
|
eio = &p->erom_io.eio;
|
|
|
|
/* Fetch our chipid hint (if any) and generic hardware configuration */
|
|
hwcfg = BHNDB_BUS_GET_GENERIC_HWCFG(parent_dev, dev);
|
|
hint = BHNDB_BUS_GET_CHIPID(parent_dev, dev);
|
|
|
|
/* Allocate our host resources */
|
|
error = bhndb_alloc_host_resources(&p->hr, dev, parent_dev, hwcfg);
|
|
if (error) {
|
|
p->hr = NULL;
|
|
goto failed;
|
|
}
|
|
|
|
/* Map the first bus core from our bridged bhnd(4) bus */
|
|
error = bhnd_erom_io_map(eio, BHND_DEFAULT_CHIPC_ADDR,
|
|
BHND_DEFAULT_CORE_SIZE);
|
|
if (error)
|
|
goto failed;
|
|
|
|
/* Probe for a usable EROM class, and read the chip identifier */
|
|
p->erom_class = bhnd_erom_probe_driver_classes(
|
|
device_get_devclass(dev), eio, hint, &p->cid);
|
|
if (p->erom_class == NULL) {
|
|
device_printf(dev, "device enumeration unsupported; no "
|
|
"compatible driver found\n");
|
|
|
|
error = ENXIO;
|
|
goto failed;
|
|
}
|
|
|
|
/* Allocate EROM parser */
|
|
p->erom = bhnd_erom_alloc(p->erom_class, &p->cid, eio);
|
|
if (p->erom == NULL) {
|
|
device_printf(dev, "failed to allocate device enumeration "
|
|
"table parser\n");
|
|
error = ENXIO;
|
|
goto failed;
|
|
}
|
|
|
|
/* The EROM I/O instance is now owned by our EROM parser */
|
|
eio = NULL;
|
|
|
|
/* Read the full core table */
|
|
error = bhnd_erom_get_core_table(p->erom, &p->cores, &p->ncores);
|
|
if (error) {
|
|
device_printf(p->dev, "error fetching core table: %d\n",
|
|
error);
|
|
|
|
p->cores = NULL;
|
|
goto failed;
|
|
}
|
|
|
|
/* Identify the host bridge core */
|
|
error = bhndb_find_hostb_core(p->cores, p->ncores, hostb_devclass,
|
|
&p->hostb_core);
|
|
if (error) {
|
|
device_printf(dev, "failed to identify the host bridge "
|
|
"core: %d\n", error);
|
|
|
|
goto failed;
|
|
}
|
|
|
|
*probe = p;
|
|
return (0);
|
|
|
|
failed:
|
|
if (eio != NULL) {
|
|
KASSERT(p->erom == NULL, ("I/O instance will be freed by "
|
|
"its owning parser"));
|
|
|
|
bhnd_erom_io_fini(eio);
|
|
}
|
|
|
|
if (p->erom != NULL) {
|
|
if (p->cores != NULL)
|
|
bhnd_erom_free_core_table(p->erom, p->cores);
|
|
|
|
bhnd_erom_free(p->erom);
|
|
} else {
|
|
KASSERT(p->cores == NULL, ("cannot free erom-owned core table "
|
|
"without erom reference"));
|
|
}
|
|
|
|
if (p->hr != NULL)
|
|
bhndb_release_host_resources(p->hr);
|
|
|
|
free(p, M_BHND);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Free the given @p probe instance and any associated host bridge resources.
|
|
*/
|
|
static void
|
|
bhndb_pci_probe_free(struct bhndb_pci_probe *probe)
|
|
{
|
|
bhnd_erom_free_core_table(probe->erom, probe->cores);
|
|
bhnd_erom_free(probe->erom);
|
|
bhndb_release_host_resources(probe->hr);
|
|
free(probe, M_BHND);
|
|
}
|
|
|
|
/**
|
|
* Return a copy of probed core table from @p probe.
|
|
*
|
|
* @param probe The probe instance.
|
|
* @param[out] cores On success, a copy of the probed core table. The
|
|
* caller is responsible for freeing this table
|
|
* bhndb_pci_probe_free_core_table().
|
|
* @param[out] ncores On success, the number of cores found in
|
|
* @p cores.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero if enumerating the bridged bhnd(4) bus fails, a regular
|
|
* unix error code will be returned.
|
|
*/
|
|
static int
|
|
bhndb_pci_probe_copy_core_table(struct bhndb_pci_probe *probe,
|
|
struct bhnd_core_info **cores, u_int *ncores)
|
|
{
|
|
size_t len = sizeof(**cores) * probe->ncores;
|
|
|
|
*cores = malloc(len, M_BHND, M_WAITOK);
|
|
memcpy(*cores, probe->cores, len);
|
|
|
|
*ncores = probe->ncores;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Free a core table previously returned by bhndb_pci_probe_copy_core_table().
|
|
*
|
|
* @param cores The core table to be freed.
|
|
*/
|
|
static void
|
|
bhndb_pci_probe_free_core_table(struct bhnd_core_info *cores)
|
|
{
|
|
free(cores, M_BHND);
|
|
}
|
|
|
|
/**
|
|
* Return true if @p addr and @p size are mapped by the dynamic register window
|
|
* backing @p probe.
|
|
*/
|
|
static bool
|
|
bhndb_pci_probe_has_mapping(struct bhndb_pci_probe *probe, bhnd_addr_t addr,
|
|
bhnd_size_t size)
|
|
{
|
|
if (!probe->m_valid)
|
|
return (false);
|
|
|
|
KASSERT(probe->m_win != NULL, ("missing register window"));
|
|
KASSERT(probe->m_res != NULL, ("missing regwin resource"));
|
|
KASSERT(probe->m_win->win_type == BHNDB_REGWIN_T_DYN,
|
|
("unexpected window type %d", probe->m_win->win_type));
|
|
|
|
if (addr < probe->m_target)
|
|
return (false);
|
|
|
|
if (addr >= probe->m_target + probe->m_win->win_size)
|
|
return (false);
|
|
|
|
if ((probe->m_target + probe->m_win->win_size) - addr < size)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/**
|
|
* Attempt to adjust the dynamic register window backing @p probe to permit
|
|
* accessing @p size bytes at @p addr.
|
|
*
|
|
* @param probe The bhndb_pci probe state to be modified.
|
|
* @param addr The address at which @p size bytes will mapped.
|
|
* @param size The number of bytes to be mapped.
|
|
* @param[out] res On success, will be set to the host resource
|
|
* mapping @p size bytes at @p addr.
|
|
* @param[out] res_offset On success, will be set to the offset of @addr
|
|
* within @p res.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero if an error occurs adjusting the backing dynamic
|
|
* register window.
|
|
*/
|
|
static int
|
|
bhndb_pci_probe_map(struct bhndb_pci_probe *probe, bhnd_addr_t addr,
|
|
bhnd_size_t offset, bhnd_size_t size, struct resource **res,
|
|
bus_size_t *res_offset)
|
|
{
|
|
const struct bhndb_regwin *regwin, *regwin_table;
|
|
struct resource *regwin_res;
|
|
bhnd_addr_t target;
|
|
int error;
|
|
|
|
/* Determine the absolute address */
|
|
if (BHND_SIZE_MAX - offset < addr) {
|
|
device_printf(probe->dev, "invalid offset %#jx+%#jx\n", addr,
|
|
offset);
|
|
return (ENXIO);
|
|
}
|
|
|
|
addr += offset;
|
|
|
|
/* Can we use the existing mapping? */
|
|
if (bhndb_pci_probe_has_mapping(probe, addr, size)) {
|
|
*res = probe->m_res;
|
|
*res_offset = (addr - probe->m_target) +
|
|
probe->m_win->win_offset;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Locate a useable dynamic register window */
|
|
regwin_table = probe->hr->cfg->register_windows;
|
|
regwin = bhndb_regwin_find_type(regwin_table,
|
|
BHNDB_REGWIN_T_DYN, size);
|
|
if (regwin == NULL) {
|
|
device_printf(probe->dev, "unable to map %#jx+%#jx; no "
|
|
"usable dynamic register window found\n", addr,
|
|
size);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Locate the host resource mapping our register window */
|
|
regwin_res = bhndb_host_resource_for_regwin(probe->hr, regwin);
|
|
if (regwin_res == NULL) {
|
|
device_printf(probe->dev, "unable to map %#jx+%#jx; no "
|
|
"usable register resource found\n", addr, size);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Page-align the target address */
|
|
target = addr - (addr % regwin->win_size);
|
|
|
|
/* Configure the register window */
|
|
error = bhndb_pci_compat_setregwin(probe->dev, probe->pci_dev,
|
|
regwin, target);
|
|
if (error) {
|
|
device_printf(probe->dev, "failed to configure dynamic "
|
|
"register window: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
/* Update our mapping state */
|
|
probe->m_win = regwin;
|
|
probe->m_res = regwin_res;
|
|
probe->m_addr = addr;
|
|
probe->m_size = size;
|
|
probe->m_target = target;
|
|
probe->m_valid = true;
|
|
|
|
*res = regwin_res;
|
|
*res_offset = (addr - target) + regwin->win_offset;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Write a data item to the bridged address space at the given @p offset from
|
|
* @p addr.
|
|
*
|
|
* A dynamic register window will be used to map @p addr.
|
|
*
|
|
* @param probe The bhndb_pci probe state to be used to perform the
|
|
* write.
|
|
* @param addr The base address.
|
|
* @param offset The offset from @p addr at which @p value will be
|
|
* written.
|
|
* @param value The data item to be written.
|
|
* @param width The data item width (1, 2, or 4 bytes).
|
|
*/
|
|
static void
|
|
bhndb_pci_probe_write(struct bhndb_pci_probe *probe, bhnd_addr_t addr,
|
|
bhnd_size_t offset, uint32_t value, u_int width)
|
|
{
|
|
struct resource *r;
|
|
bus_size_t res_offset;
|
|
int error;
|
|
|
|
/* Map the target address */
|
|
error = bhndb_pci_probe_map(probe, addr, offset, width, &r,
|
|
&res_offset);
|
|
if (error) {
|
|
device_printf(probe->dev, "error mapping %#jx+%#jx for "
|
|
"writing: %d\n", addr, offset, error);
|
|
return;
|
|
}
|
|
|
|
/* Perform write */
|
|
switch (width) {
|
|
case 1:
|
|
return (bus_write_1(r, res_offset, value));
|
|
case 2:
|
|
return (bus_write_2(r, res_offset, value));
|
|
case 4:
|
|
return (bus_write_4(r, res_offset, value));
|
|
default:
|
|
panic("unsupported width: %u", width);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read a data item from the bridged address space at the given @p offset
|
|
* from @p addr.
|
|
*
|
|
* A dynamic register window will be used to map @p addr.
|
|
*
|
|
* @param probe The bhndb_pci probe state to be used to perform the
|
|
* read.
|
|
* @param addr The base address.
|
|
* @param offset The offset from @p addr at which to read a data item of
|
|
* @p width bytes.
|
|
* @param width Item width (1, 2, or 4 bytes).
|
|
*/
|
|
static uint32_t
|
|
bhndb_pci_probe_read(struct bhndb_pci_probe *probe, bhnd_addr_t addr,
|
|
bhnd_size_t offset, u_int width)
|
|
{
|
|
struct resource *r;
|
|
bus_size_t res_offset;
|
|
int error;
|
|
|
|
/* Map the target address */
|
|
error = bhndb_pci_probe_map(probe, addr, offset, width, &r,
|
|
&res_offset);
|
|
if (error) {
|
|
device_printf(probe->dev, "error mapping %#jx+%#jx for "
|
|
"reading: %d\n", addr, offset, error);
|
|
return (UINT32_MAX);
|
|
}
|
|
|
|
/* Perform read */
|
|
switch (width) {
|
|
case 1:
|
|
return (bus_read_1(r, res_offset));
|
|
case 2:
|
|
return (bus_read_2(r, res_offset));
|
|
case 4:
|
|
return (bus_read_4(r, res_offset));
|
|
default:
|
|
panic("unsupported width: %u", width);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize a new bhndb PCI bridge EROM I/O instance. All I/O will be
|
|
* performed using @p probe.
|
|
*
|
|
* @param pio The instance to be initialized.
|
|
* @param probe The bhndb_pci probe state to be used to perform all
|
|
* I/O.
|
|
*/
|
|
static void
|
|
bhndb_pci_eio_init(struct bhndb_pci_eio *pio, struct bhndb_pci_probe *probe)
|
|
{
|
|
memset(pio, 0, sizeof(*pio));
|
|
|
|
pio->eio.map = bhndb_pci_eio_map;
|
|
pio->eio.tell = bhndb_pci_eio_tell;
|
|
pio->eio.read = bhndb_pci_eio_read;
|
|
pio->eio.fini = NULL;
|
|
|
|
pio->mapped = false;
|
|
pio->addr = 0;
|
|
pio->size = 0;
|
|
pio->probe = probe;
|
|
}
|
|
|
|
/* bhnd_erom_io_map() implementation */
|
|
static int
|
|
bhndb_pci_eio_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,
|
|
bhnd_size_t size)
|
|
{
|
|
struct bhndb_pci_eio *pio = (struct bhndb_pci_eio *)eio;
|
|
|
|
if (BHND_ADDR_MAX - addr < size)
|
|
return (EINVAL); /* addr+size would overflow */
|
|
|
|
pio->addr = addr;
|
|
pio->size = size;
|
|
pio->mapped = true;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* bhnd_erom_io_tell() implementation */
|
|
static int
|
|
bhndb_pci_eio_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,
|
|
bhnd_size_t *size)
|
|
{
|
|
struct bhndb_pci_eio *pio = (struct bhndb_pci_eio *)eio;
|
|
|
|
if (!pio->mapped)
|
|
return (ENXIO);
|
|
|
|
*addr = pio->addr;
|
|
*size = pio->size;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* bhnd_erom_io_read() implementation */
|
|
static uint32_t
|
|
bhndb_pci_eio_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)
|
|
{
|
|
struct bhndb_pci_eio *pio = (struct bhndb_pci_eio *)eio;
|
|
|
|
/* Must have a valid mapping */
|
|
if (!pio->mapped)
|
|
panic("no active mapping");
|
|
|
|
/* The requested subrange must fall within the existing mapped range */
|
|
if (offset > pio->size ||
|
|
width > pio->size ||
|
|
pio->size - offset < width)
|
|
{
|
|
panic("invalid offset %#jx", offset);
|
|
}
|
|
|
|
return (bhndb_pci_probe_read(pio->probe, pio->addr, offset, width));
|
|
}
|
|
|
|
static device_method_t bhndb_pci_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, bhndb_pci_probe),
|
|
DEVMETHOD(device_attach, bhndb_pci_attach),
|
|
DEVMETHOD(device_resume, bhndb_pci_resume),
|
|
DEVMETHOD(device_suspend, bhndb_pci_suspend),
|
|
DEVMETHOD(device_detach, bhndb_pci_detach),
|
|
|
|
/* BHNDB interface */
|
|
DEVMETHOD(bhndb_set_window_addr, bhndb_pci_set_window_addr),
|
|
DEVMETHOD(bhndb_populate_board_info, bhndb_pci_populate_board_info),
|
|
DEVMETHOD(bhndb_map_intr_isrc, bhndb_pci_map_intr_isrc),
|
|
DEVMETHOD(bhndb_route_interrupts, bhndb_pci_route_interrupts),
|
|
|
|
/* BHND PWRCTL hostb interface */
|
|
DEVMETHOD(bhnd_pwrctl_hostb_get_clksrc, bhndb_pci_pwrctl_get_clksrc),
|
|
DEVMETHOD(bhnd_pwrctl_hostb_gate_clock, bhndb_pci_pwrctl_gate_clock),
|
|
DEVMETHOD(bhnd_pwrctl_hostb_ungate_clock, bhndb_pci_pwrctl_ungate_clock),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
DEFINE_CLASS_1(bhndb, bhndb_pci_driver, bhndb_pci_methods,
|
|
sizeof(struct bhndb_pci_softc), bhndb_driver);
|
|
|
|
MODULE_VERSION(bhndb_pci, 1);
|
|
MODULE_DEPEND(bhndb_pci, bhnd_pci_hostb, 1, 1, 1);
|
|
MODULE_DEPEND(bhndb_pci, pci, 1, 1, 1);
|
|
MODULE_DEPEND(bhndb_pci, bhndb, 1, 1, 1);
|
|
MODULE_DEPEND(bhndb_pci, bhnd, 1, 1, 1);
|