#include <sys/cdefs.h>
#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
};
struct bhndb_pci_eio {
struct bhnd_erom_io eio;
bool mapped;
bhnd_addr_t addr;
bhnd_size_t size;
struct bhndb_pci_probe *probe;
};
struct bhndb_pci_probe {
device_t dev;
device_t pci_dev;
struct bhnd_chipid cid;
struct bhnd_core_info hostb_core;
struct bhndb_pci_eio erom_io;
bhnd_erom_class_t *erom_class;
bhnd_erom_t *erom;
struct bhnd_core_info *cores;
u_int ncores;
const struct bhndb_regwin *m_win;
struct resource *m_res;
bhnd_addr_t m_target;
bhnd_addr_t m_addr;
bhnd_size_t m_size;
bool m_valid;
struct bhndb_host_resources *hr;
};
static struct bhndb_pci_quirk bhndb_pci_quirks[] = {
{{ BHND_MATCH_CHIP_TYPE (SIBA) },
{ BHND_MATCH_CORE_REV (HWREV_LTE(5)) },
BHNDB_PCI_QUIRK_SIBA_INTVEC },
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
BHNDB_PCI_QUIRK_END
};
static struct bhndb_pci_quirk bhndb_pcie_quirks[] = {
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
};
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);
}
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;
if ((entry = bhndb_pci_find_core(ci)) == NULL)
return (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);
}
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;
pci = devclass_find("pci");
parent = device_get_parent(dev);
parent_bus = device_get_parent(parent);
if (parent_bus == NULL)
return (ENXIO);
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);
if ((error = bhndb_enable_pci_clocks(dev)))
return (error);
hostb_devclass = bhndb_expected_pci_devclass(dev);
if ((error = bhndb_pci_probe_alloc(&probe, dev, hostb_devclass)))
goto cleanup;
if ((entry = bhndb_pci_find_core(&probe->hostb_core)) == NULL) {
error = ENXIO;
goto cleanup;
}
device_set_desc(dev, "PCI-BHND bridge");
error = BUS_PROBE_DEFAULT;
cleanup:
if (probe != NULL)
bhndb_pci_probe_free(probe);
bhndb_disable_pci_clocks(dev);
return (error);
}
static int
bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count)
{
int error, count;
if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT)
return (ENXIO);
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;
pci_enable_busmaster(sc->parent);
if ((error = bhndb_enable_pci_clocks(sc->dev)))
goto cleanup;
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);
if (probe->cid.chip_type == BHND_CHIPTYPE_SIBA) {
sc->set_regwin = bhndb_pci_compat_setregwin;
} else {
sc->set_regwin = bhndb_pci_fast_setregwin;
}
if ((error = bhndb_pci_srsh_pi_war(sc, probe)))
goto cleanup;
if (bhndb_pci_alloc_msi(sc, &sc->msi_count) == 0) {
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;
}
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;
error = bhndb_attach(dev, &cid, cores, ncores, &hostb_core, erom_class);
if (error)
goto cleanup;
if ((error = bhndb_pci_add_children(sc)))
goto cleanup;
bus_attach_children(dev);
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);
if ((error = bus_generic_detach(dev)))
return (error);
if ((error = bhndb_generic_detach(dev)))
return (error);
if ((error = bhndb_disable_pci_clocks(sc->dev)))
return (error);
bhndb_free_intr_isrc(sc->isrc);
if (sc->msi_count > 0)
pci_release_msi(sc->parent);
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;
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);
}
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);
}
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;
sprom_win = bhndb_pci_sprom_regwin(sc);
KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+"));
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);
if (sprom_win == NULL)
return (0);
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);
}
sprom_sz = MIN(sprom_sz, sprom_win->win_size);
return (sprom_sz);
}
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;
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);
}
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);
}
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);
}
}
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);
}
}
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);
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);
}
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;
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);
if ((error = bhndb_enable_pci_clocks(sc->dev)))
return (error);
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);
if ((error = bhndb_disable_pci_clocks(sc->dev)))
return (error);
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));
}
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);
}
return (ENODEV);
}
static int
bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev,
const struct bhndb_regwin *rw, bhnd_addr_t addr)
{
if (addr > UINT32_MAX)
return (ERANGE);
switch (rw->win_type) {
case BHNDB_REGWIN_T_DYN:
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);
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;
break;
default:
break;
}
}
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);
}
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);
}
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);
}
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);
if (bhndb_is_pcie_attached(dev))
return (0);
gpio_in = pci_read_config(pci_dev, BHNDB_PCI_GPIO_IN, 4);
if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON)
return (0);
gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4);
gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4);
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);
gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF;
pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
DELAY(5000);
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);
}
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);
if (bhndb_is_pcie_attached(dev))
return (0);
gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4);
gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4);
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);
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);
if (bhndb_is_pcie_attached(sc->dev))
return (BHND_CLKSRC_UNKNOWN);
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);
if (bhndb_is_pcie_attached(sc->dev))
return (ENODEV);
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);
if (bhndb_is_pcie_attached(sc->dev))
return (ENODEV);
if (clock != BHND_CLOCK_HT)
return (ENXIO);
return (bhndb_enable_pci_clocks(sc->dev));
}
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);
*isrc = sc->isrc;
return (0);
}
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"));
if ((error = bhnd_get_intr_ivec(child, 0, &ivec)))
return (error);
if (ivec > (sizeof(sbintvec)*8) - 1 ) {
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);
}
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) {
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);
}
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;
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;
hwcfg = BHNDB_BUS_GET_GENERIC_HWCFG(parent_dev, dev);
hint = BHNDB_BUS_GET_CHIPID(parent_dev, dev);
error = bhndb_alloc_host_resources(&p->hr, dev, parent_dev, hwcfg);
if (error) {
p->hr = NULL;
goto failed;
}
error = bhnd_erom_io_map(eio, BHND_DEFAULT_CHIPC_ADDR,
BHND_DEFAULT_CORE_SIZE);
if (error)
goto failed;
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;
}
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;
}
eio = NULL;
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;
}
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);
}
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);
}
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);
}
static void
bhndb_pci_probe_free_core_table(struct bhnd_core_info *cores)
{
free(cores, M_BHND);
}
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);
}
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;
if (BHND_SIZE_MAX - offset < addr) {
device_printf(probe->dev, "invalid offset %#jx+%#jx\n", addr,
offset);
return (ENXIO);
}
addr += offset;
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);
}
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);
}
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);
}
target = addr - (addr % regwin->win_size);
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);
}
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);
}
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;
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;
}
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);
}
}
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;
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);
}
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);
}
}
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;
}
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);
pio->addr = addr;
pio->size = size;
pio->mapped = true;
return (0);
}
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);
}
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;
if (!pio->mapped)
panic("no active mapping");
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[] = {
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),
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),
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);