7c7c726bca
Previously, the address regions described by disabled admatch entries would be treated as being mapped to the given core; while incorrect, this was essentially harmless given that the entries describe unused address space on the few affected devices. We now perform parsing of per-core admatch registers and interrupt flags in siba_erom, correctly skip any disabled admatch entries, and use the siba_erom API in siba_add_children() to perform enumeration of attached cores.
1445 lines
35 KiB
C
1445 lines
35 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$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/refcount.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <machine/bus.h>
|
|
|
|
#include <dev/bhnd/cores/chipc/chipc.h>
|
|
#include <dev/bhnd/cores/chipc/pwrctl/bhnd_pwrctl.h>
|
|
|
|
#include "siba_eromvar.h"
|
|
|
|
#include "sibareg.h"
|
|
#include "sibavar.h"
|
|
|
|
/* RID used when allocating EROM resources */
|
|
#define SIBA_EROM_RID 0
|
|
|
|
static bhnd_erom_class_t *
|
|
siba_get_erom_class(driver_t *driver)
|
|
{
|
|
return (&siba_erom_parser);
|
|
}
|
|
|
|
int
|
|
siba_probe(device_t dev)
|
|
{
|
|
device_set_desc(dev, "SIBA BHND bus");
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
/**
|
|
* Default siba(4) bus driver implementation of DEVICE_ATTACH().
|
|
*
|
|
* This implementation initializes internal siba(4) state and performs
|
|
* bus enumeration, and must be called by subclassing drivers in
|
|
* DEVICE_ATTACH() before any other bus methods.
|
|
*/
|
|
int
|
|
siba_attach(device_t dev)
|
|
{
|
|
struct siba_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->dev = dev;
|
|
|
|
SIBA_LOCK_INIT(sc);
|
|
|
|
/* Enumerate children */
|
|
if ((error = siba_add_children(dev))) {
|
|
device_delete_children(dev);
|
|
SIBA_LOCK_DESTROY(sc);
|
|
return (error);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
siba_detach(device_t dev)
|
|
{
|
|
struct siba_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if ((error = bhnd_generic_detach(dev)))
|
|
return (error);
|
|
|
|
SIBA_LOCK_DESTROY(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
siba_resume(device_t dev)
|
|
{
|
|
return (bhnd_generic_resume(dev));
|
|
}
|
|
|
|
int
|
|
siba_suspend(device_t dev)
|
|
{
|
|
return (bhnd_generic_suspend(dev));
|
|
}
|
|
|
|
static int
|
|
siba_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
|
|
{
|
|
struct siba_softc *sc;
|
|
const struct siba_devinfo *dinfo;
|
|
const struct bhnd_core_info *cfg;
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
cfg = &dinfo->core_id.core_info;
|
|
|
|
switch (index) {
|
|
case BHND_IVAR_VENDOR:
|
|
*result = cfg->vendor;
|
|
return (0);
|
|
case BHND_IVAR_DEVICE:
|
|
*result = cfg->device;
|
|
return (0);
|
|
case BHND_IVAR_HWREV:
|
|
*result = cfg->hwrev;
|
|
return (0);
|
|
case BHND_IVAR_DEVICE_CLASS:
|
|
*result = bhnd_core_class(cfg);
|
|
return (0);
|
|
case BHND_IVAR_VENDOR_NAME:
|
|
*result = (uintptr_t) bhnd_vendor_name(cfg->vendor);
|
|
return (0);
|
|
case BHND_IVAR_DEVICE_NAME:
|
|
*result = (uintptr_t) bhnd_core_name(cfg);
|
|
return (0);
|
|
case BHND_IVAR_CORE_INDEX:
|
|
*result = cfg->core_idx;
|
|
return (0);
|
|
case BHND_IVAR_CORE_UNIT:
|
|
*result = cfg->unit;
|
|
return (0);
|
|
case BHND_IVAR_PMU_INFO:
|
|
SIBA_LOCK(sc);
|
|
switch (dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
*result = (uintptr_t)NULL;
|
|
SIBA_UNLOCK(sc);
|
|
return (0);
|
|
|
|
case SIBA_PMU_BHND:
|
|
*result = (uintptr_t)dinfo->pmu.bhnd_info;
|
|
SIBA_UNLOCK(sc);
|
|
return (0);
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
case SIBA_PMU_FIXED:
|
|
*result = (uintptr_t)NULL;
|
|
SIBA_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
return (ENXIO);
|
|
|
|
default:
|
|
return (ENOENT);
|
|
}
|
|
}
|
|
|
|
static int
|
|
siba_write_ivar(device_t dev, device_t child, int index, uintptr_t value)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
switch (index) {
|
|
case BHND_IVAR_VENDOR:
|
|
case BHND_IVAR_DEVICE:
|
|
case BHND_IVAR_HWREV:
|
|
case BHND_IVAR_DEVICE_CLASS:
|
|
case BHND_IVAR_VENDOR_NAME:
|
|
case BHND_IVAR_DEVICE_NAME:
|
|
case BHND_IVAR_CORE_INDEX:
|
|
case BHND_IVAR_CORE_UNIT:
|
|
return (EINVAL);
|
|
case BHND_IVAR_PMU_INFO:
|
|
SIBA_LOCK(sc);
|
|
switch (dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
case SIBA_PMU_BHND:
|
|
dinfo->pmu.bhnd_info = (void *)value;
|
|
dinfo->pmu_state = SIBA_PMU_BHND;
|
|
SIBA_UNLOCK(sc);
|
|
return (0);
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
case SIBA_PMU_FIXED:
|
|
panic("bhnd_set_pmu_info() called with siba PMU state "
|
|
"%d", dinfo->pmu_state);
|
|
return (ENXIO);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
return (ENXIO);
|
|
|
|
default:
|
|
return (ENOENT);
|
|
}
|
|
}
|
|
|
|
static struct resource_list *
|
|
siba_get_resource_list(device_t dev, device_t child)
|
|
{
|
|
struct siba_devinfo *dinfo = device_get_ivars(child);
|
|
return (&dinfo->resources);
|
|
}
|
|
|
|
/* BHND_BUS_ALLOC_PMU() */
|
|
static int
|
|
siba_alloc_pmu(device_t dev, device_t child)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
device_t chipc;
|
|
device_t pwrctl;
|
|
struct chipc_caps ccaps;
|
|
siba_pmu_state pmu_state;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
pwrctl = NULL;
|
|
|
|
/* Fetch ChipCommon capability flags */
|
|
chipc = bhnd_retain_provider(child, BHND_SERVICE_CHIPC);
|
|
if (chipc != NULL) {
|
|
ccaps = *BHND_CHIPC_GET_CAPS(chipc);
|
|
bhnd_release_provider(child, chipc, BHND_SERVICE_CHIPC);
|
|
} else {
|
|
memset(&ccaps, 0, sizeof(ccaps));
|
|
}
|
|
|
|
/* Defer to bhnd(4)'s PMU implementation if ChipCommon exists and
|
|
* advertises PMU support */
|
|
if (ccaps.pmu) {
|
|
if ((error = bhnd_generic_alloc_pmu(dev, child)))
|
|
return (error);
|
|
|
|
KASSERT(dinfo->pmu_state == SIBA_PMU_BHND,
|
|
("unexpected PMU state: %d", dinfo->pmu_state));
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* This is either a legacy PWRCTL chipset, or the device does not
|
|
* support dynamic clock control.
|
|
*
|
|
* We need to map all bhnd(4) bus PMU to PWRCTL or no-op operations.
|
|
*/
|
|
if (ccaps.pwr_ctrl) {
|
|
pmu_state = SIBA_PMU_PWRCTL;
|
|
pwrctl = bhnd_retain_provider(child, BHND_SERVICE_PWRCTL);
|
|
if (pwrctl == NULL) {
|
|
device_printf(dev, "PWRCTL not found\n");
|
|
return (ENODEV);
|
|
}
|
|
} else {
|
|
pmu_state = SIBA_PMU_FIXED;
|
|
pwrctl = NULL;
|
|
}
|
|
|
|
SIBA_LOCK(sc);
|
|
|
|
/* Per-core PMU state already allocated? */
|
|
if (dinfo->pmu_state != SIBA_PMU_NONE) {
|
|
panic("duplicate PMU allocation for %s",
|
|
device_get_nameunit(child));
|
|
}
|
|
|
|
/* Update the child's PMU allocation state, and transfer ownership of
|
|
* the PWRCTL provider reference (if any) */
|
|
dinfo->pmu_state = pmu_state;
|
|
dinfo->pmu.pwrctl = pwrctl;
|
|
|
|
SIBA_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* BHND_BUS_RELEASE_PMU() */
|
|
static int
|
|
siba_release_pmu(device_t dev, device_t child)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
device_t pwrctl;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("pmu over-release for %s", device_get_nameunit(child));
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_release_pmu(dev, child));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
/* Requesting BHND_CLOCK_DYN releases any outstanding clock
|
|
* reservations */
|
|
pwrctl = dinfo->pmu.pwrctl;
|
|
error = bhnd_pwrctl_request_clock(pwrctl, child,
|
|
BHND_CLOCK_DYN);
|
|
if (error) {
|
|
SIBA_UNLOCK(sc);
|
|
return (error);
|
|
}
|
|
|
|
/* Clean up the child's PMU state */
|
|
dinfo->pmu_state = SIBA_PMU_NONE;
|
|
dinfo->pmu.pwrctl = NULL;
|
|
SIBA_UNLOCK(sc);
|
|
|
|
/* Release the provider reference */
|
|
bhnd_release_provider(child, pwrctl, BHND_SERVICE_PWRCTL);
|
|
return (0);
|
|
|
|
case SIBA_PMU_FIXED:
|
|
/* Clean up the child's PMU state */
|
|
KASSERT(dinfo->pmu.pwrctl == NULL,
|
|
("PWRCTL reference with FIXED state"));
|
|
|
|
dinfo->pmu_state = SIBA_PMU_NONE;
|
|
dinfo->pmu.pwrctl = NULL;
|
|
SIBA_UNLOCK(sc);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_GET_CLOCK_LATENCY() */
|
|
static int
|
|
siba_get_clock_latency(device_t dev, device_t child, bhnd_clock clock,
|
|
u_int *latency)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_get_clock_latency(dev, child, clock,
|
|
latency));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
error = bhnd_pwrctl_get_clock_latency(dinfo->pmu.pwrctl, clock,
|
|
latency);
|
|
SIBA_UNLOCK(sc);
|
|
|
|
return (error);
|
|
|
|
case SIBA_PMU_FIXED:
|
|
SIBA_UNLOCK(sc);
|
|
|
|
/* HT clock is always available, and incurs no transition
|
|
* delay. */
|
|
switch (clock) {
|
|
case BHND_CLOCK_HT:
|
|
*latency = 0;
|
|
return (0);
|
|
|
|
default:
|
|
return (ENODEV);
|
|
}
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_GET_CLOCK_FREQ() */
|
|
static int
|
|
siba_get_clock_freq(device_t dev, device_t child, bhnd_clock clock,
|
|
u_int *freq)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_get_clock_freq(dev, child, clock, freq));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
error = bhnd_pwrctl_get_clock_freq(dinfo->pmu.pwrctl, clock,
|
|
freq);
|
|
SIBA_UNLOCK(sc);
|
|
|
|
return (error);
|
|
|
|
case SIBA_PMU_FIXED:
|
|
SIBA_UNLOCK(sc);
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_REQUEST_EXT_RSRC() */
|
|
static int
|
|
siba_request_ext_rsrc(device_t dev, device_t child, u_int rsrc)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_request_ext_rsrc(dev, child, rsrc));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
case SIBA_PMU_FIXED:
|
|
/* HW does not support per-core external resources */
|
|
SIBA_UNLOCK(sc);
|
|
return (ENODEV);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_RELEASE_EXT_RSRC() */
|
|
static int
|
|
siba_release_ext_rsrc(device_t dev, device_t child, u_int rsrc)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_release_ext_rsrc(dev, child, rsrc));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
case SIBA_PMU_FIXED:
|
|
/* HW does not support per-core external resources */
|
|
SIBA_UNLOCK(sc);
|
|
return (ENODEV);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_REQUEST_CLOCK() */
|
|
static int
|
|
siba_request_clock(device_t dev, device_t child, bhnd_clock clock)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_request_clock(dev, child, clock));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
error = bhnd_pwrctl_request_clock(dinfo->pmu.pwrctl, child,
|
|
clock);
|
|
SIBA_UNLOCK(sc);
|
|
|
|
return (error);
|
|
|
|
case SIBA_PMU_FIXED:
|
|
SIBA_UNLOCK(sc);
|
|
|
|
/* HT clock is always available, and fulfills any of the
|
|
* following clock requests */
|
|
switch (clock) {
|
|
case BHND_CLOCK_DYN:
|
|
case BHND_CLOCK_ILP:
|
|
case BHND_CLOCK_ALP:
|
|
case BHND_CLOCK_HT:
|
|
return (0);
|
|
|
|
default:
|
|
return (ENODEV);
|
|
}
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
/* BHND_BUS_ENABLE_CLOCKS() */
|
|
static int
|
|
siba_enable_clocks(device_t dev, device_t child, uint32_t clocks)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
SIBA_LOCK(sc);
|
|
switch(dinfo->pmu_state) {
|
|
case SIBA_PMU_NONE:
|
|
panic("no active PMU request state");
|
|
|
|
SIBA_UNLOCK(sc);
|
|
return (ENXIO);
|
|
|
|
case SIBA_PMU_BHND:
|
|
SIBA_UNLOCK(sc);
|
|
return (bhnd_generic_enable_clocks(dev, child, clocks));
|
|
|
|
case SIBA_PMU_PWRCTL:
|
|
case SIBA_PMU_FIXED:
|
|
SIBA_UNLOCK(sc);
|
|
|
|
/* All (supported) clocks are already enabled by default */
|
|
clocks &= ~(BHND_CLOCK_DYN |
|
|
BHND_CLOCK_ILP |
|
|
BHND_CLOCK_ALP |
|
|
BHND_CLOCK_HT);
|
|
|
|
if (clocks != 0) {
|
|
device_printf(dev, "%s requested unknown clocks: %#x\n",
|
|
device_get_nameunit(child), clocks);
|
|
return (ENODEV);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
panic("invalid PMU state: %d", dinfo->pmu_state);
|
|
}
|
|
|
|
static int
|
|
siba_read_iost(device_t dev, device_t child, uint16_t *iost)
|
|
{
|
|
uint32_t tmhigh;
|
|
int error;
|
|
|
|
error = bhnd_read_config(child, SIBA_CFG0_TMSTATEHIGH, &tmhigh, 4);
|
|
if (error)
|
|
return (error);
|
|
|
|
*iost = (SIBA_REG_GET(tmhigh, TMH_SISF));
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
siba_read_ioctl(device_t dev, device_t child, uint16_t *ioctl)
|
|
{
|
|
uint32_t ts_low;
|
|
int error;
|
|
|
|
if ((error = bhnd_read_config(child, SIBA_CFG0_TMSTATELOW, &ts_low, 4)))
|
|
return (error);
|
|
|
|
*ioctl = (SIBA_REG_GET(ts_low, TML_SICF));
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
siba_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
struct bhnd_resource *r;
|
|
uint32_t ts_low, ts_mask;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
/* Fetch CFG0 mapping */
|
|
dinfo = device_get_ivars(child);
|
|
if ((r = dinfo->cfg_res[0]) == NULL)
|
|
return (ENODEV);
|
|
|
|
/* Mask and set TMSTATELOW core flag bits */
|
|
ts_mask = (mask << SIBA_TML_SICF_SHIFT) & SIBA_TML_SICF_MASK;
|
|
ts_low = (value << SIBA_TML_SICF_SHIFT) & ts_mask;
|
|
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
|
|
ts_low, ts_mask);
|
|
return (0);
|
|
}
|
|
|
|
static bool
|
|
siba_is_hw_suspended(device_t dev, device_t child)
|
|
{
|
|
uint32_t ts_low;
|
|
uint16_t ioctl;
|
|
int error;
|
|
|
|
/* Fetch target state */
|
|
error = bhnd_read_config(child, SIBA_CFG0_TMSTATELOW, &ts_low, 4);
|
|
if (error) {
|
|
device_printf(child, "error reading HW reset state: %d\n",
|
|
error);
|
|
return (true);
|
|
}
|
|
|
|
/* Is core held in RESET? */
|
|
if (ts_low & SIBA_TML_RESET)
|
|
return (true);
|
|
|
|
/* Is target reject enabled? */
|
|
if (ts_low & SIBA_TML_REJ_MASK)
|
|
return (true);
|
|
|
|
/* Is core clocked? */
|
|
ioctl = SIBA_REG_GET(ts_low, TML_SICF);
|
|
if (!(ioctl & BHND_IOCTL_CLK_EN))
|
|
return (true);
|
|
|
|
return (false);
|
|
}
|
|
|
|
static int
|
|
siba_reset_hw(device_t dev, device_t child, uint16_t ioctl,
|
|
uint16_t reset_ioctl)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
struct bhnd_resource *r;
|
|
uint32_t ts_low, imstate;
|
|
uint16_t clkflags;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Can't suspend the core without access to the CFG0 registers */
|
|
if ((r = dinfo->cfg_res[0]) == NULL)
|
|
return (ENODEV);
|
|
|
|
/* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
|
|
clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
|
|
if (ioctl & clkflags)
|
|
return (EINVAL);
|
|
|
|
/* Place core into known RESET state */
|
|
if ((error = bhnd_suspend_hw(child, reset_ioctl)))
|
|
return (error);
|
|
|
|
/* Set RESET, clear REJ, set the caller's IOCTL flags, and
|
|
* force clocks to ensure the signal propagates throughout the
|
|
* core. */
|
|
ts_low = SIBA_TML_RESET |
|
|
(ioctl << SIBA_TML_SICF_SHIFT) |
|
|
(BHND_IOCTL_CLK_EN << SIBA_TML_SICF_SHIFT) |
|
|
(BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT);
|
|
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
|
|
ts_low, UINT32_MAX);
|
|
|
|
/* Clear any target errors */
|
|
if (bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH) & SIBA_TMH_SERR) {
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATEHIGH,
|
|
0x0, SIBA_TMH_SERR);
|
|
}
|
|
|
|
/* Clear any initiator errors */
|
|
imstate = bhnd_bus_read_4(r, SIBA_CFG0_IMSTATE);
|
|
if (imstate & (SIBA_IM_IBE|SIBA_IM_TO)) {
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0x0,
|
|
SIBA_IM_IBE|SIBA_IM_TO);
|
|
}
|
|
|
|
/* Release from RESET while leaving clocks forced, ensuring the
|
|
* signal propagates throughout the core */
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
|
|
SIBA_TML_RESET);
|
|
|
|
/* The core should now be active; we can clear the BHND_IOCTL_CLK_FORCE
|
|
* bit and allow the core to manage clock gating. */
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
|
|
(BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT));
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
siba_suspend_hw(device_t dev, device_t child, uint16_t ioctl)
|
|
{
|
|
struct siba_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
struct bhnd_resource *r;
|
|
uint32_t idl, ts_low, ts_mask;
|
|
uint16_t cflags, clkflags;
|
|
int error;
|
|
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Can't suspend the core without access to the CFG0 registers */
|
|
if ((r = dinfo->cfg_res[0]) == NULL)
|
|
return (ENODEV);
|
|
|
|
/* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
|
|
clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
|
|
if (ioctl & clkflags)
|
|
return (EINVAL);
|
|
|
|
/* Already in RESET? */
|
|
ts_low = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATELOW);
|
|
if (ts_low & SIBA_TML_RESET)
|
|
return (0);
|
|
|
|
/* If clocks are already disabled, we can place the core directly
|
|
* into RESET|REJ while setting the caller's IOCTL flags. */
|
|
cflags = SIBA_REG_GET(ts_low, TML_SICF);
|
|
if (!(cflags & BHND_IOCTL_CLK_EN)) {
|
|
ts_low = SIBA_TML_RESET | SIBA_TML_REJ |
|
|
(ioctl << SIBA_TML_SICF_SHIFT);
|
|
ts_mask = SIBA_TML_RESET | SIBA_TML_REJ | SIBA_TML_SICF_MASK;
|
|
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
|
|
ts_low, ts_mask);
|
|
return (0);
|
|
}
|
|
|
|
/* Reject further transactions reaching this core */
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
|
|
SIBA_TML_REJ, SIBA_TML_REJ);
|
|
|
|
/* Wait for transaction busy flag to clear for all transactions
|
|
* initiated by this core */
|
|
error = siba_wait_target_state(child, dinfo, SIBA_CFG0_TMSTATEHIGH,
|
|
0x0, SIBA_TMH_BUSY, 100000);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* If this is an initiator core, we need to reject initiator
|
|
* transactions too. */
|
|
idl = bhnd_bus_read_4(r, SIBA_CFG0_IDLOW);
|
|
if (idl & SIBA_IDL_INIT) {
|
|
/* Reject further initiator transactions */
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE,
|
|
SIBA_IM_RJ, SIBA_IM_RJ);
|
|
|
|
/* Wait for initiator busy flag to clear */
|
|
error = siba_wait_target_state(child, dinfo, SIBA_CFG0_IMSTATE,
|
|
0x0, SIBA_IM_BY, 100000);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
|
|
/* Put the core into RESET, set the caller's IOCTL flags, and
|
|
* force clocks to ensure the RESET signal propagates throughout the
|
|
* core. */
|
|
ts_low = SIBA_TML_RESET |
|
|
(ioctl << SIBA_TML_SICF_SHIFT) |
|
|
(BHND_IOCTL_CLK_EN << SIBA_TML_SICF_SHIFT) |
|
|
(BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT);
|
|
ts_mask = SIBA_TML_RESET |
|
|
SIBA_TML_SICF_MASK;
|
|
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, ts_low,
|
|
ts_mask);
|
|
|
|
/* Give RESET ample time */
|
|
DELAY(10);
|
|
|
|
/* Clear previously asserted initiator reject */
|
|
if (idl & SIBA_IDL_INIT) {
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0x0,
|
|
SIBA_IM_RJ);
|
|
}
|
|
|
|
/* Disable all clocks, leaving RESET and REJ asserted */
|
|
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
|
|
(BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << SIBA_TML_SICF_SHIFT);
|
|
|
|
/*
|
|
* Core is now in RESET.
|
|
*
|
|
* If the core holds any PWRCTL clock reservations, we need to release
|
|
* those now. This emulates the standard bhnd(4) PMU behavior of RESET
|
|
* automatically clearing clkctl
|
|
*/
|
|
SIBA_LOCK(sc);
|
|
if (dinfo->pmu_state == SIBA_PMU_PWRCTL) {
|
|
error = bhnd_pwrctl_request_clock(dinfo->pmu.pwrctl, child,
|
|
BHND_CLOCK_DYN);
|
|
SIBA_UNLOCK(sc);
|
|
|
|
if (error) {
|
|
device_printf(child, "failed to release clock request: "
|
|
"%d", error);
|
|
return (error);
|
|
}
|
|
|
|
return (0);
|
|
} else {
|
|
SIBA_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
static int
|
|
siba_read_config(device_t dev, device_t child, bus_size_t offset, void *value,
|
|
u_int width)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
rman_res_t r_size;
|
|
|
|
/* Must be directly attached */
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
/* CFG0 registers must be available */
|
|
dinfo = device_get_ivars(child);
|
|
if (dinfo->cfg_res[0] == NULL)
|
|
return (ENODEV);
|
|
|
|
/* Offset must fall within CFG0 */
|
|
r_size = rman_get_size(dinfo->cfg_res[0]->res);
|
|
if (r_size < offset || r_size - offset < width)
|
|
return (EFAULT);
|
|
|
|
switch (width) {
|
|
case 1:
|
|
*((uint8_t *)value) = bhnd_bus_read_1(dinfo->cfg_res[0],
|
|
offset);
|
|
return (0);
|
|
case 2:
|
|
*((uint16_t *)value) = bhnd_bus_read_2(dinfo->cfg_res[0],
|
|
offset);
|
|
return (0);
|
|
case 4:
|
|
*((uint32_t *)value) = bhnd_bus_read_4(dinfo->cfg_res[0],
|
|
offset);
|
|
return (0);
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
static int
|
|
siba_write_config(device_t dev, device_t child, bus_size_t offset,
|
|
const void *value, u_int width)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
struct bhnd_resource *r;
|
|
rman_res_t r_size;
|
|
|
|
/* Must be directly attached */
|
|
if (device_get_parent(child) != dev)
|
|
return (EINVAL);
|
|
|
|
/* CFG0 registers must be available */
|
|
dinfo = device_get_ivars(child);
|
|
if ((r = dinfo->cfg_res[0]) == NULL)
|
|
return (ENODEV);
|
|
|
|
/* Offset must fall within CFG0 */
|
|
r_size = rman_get_size(r->res);
|
|
if (r_size < offset || r_size - offset < width)
|
|
return (EFAULT);
|
|
|
|
switch (width) {
|
|
case 1:
|
|
bhnd_bus_write_1(r, offset, *(const uint8_t *)value);
|
|
return (0);
|
|
case 2:
|
|
bhnd_bus_write_2(r, offset, *(const uint8_t *)value);
|
|
return (0);
|
|
case 4:
|
|
bhnd_bus_write_4(r, offset, *(const uint8_t *)value);
|
|
return (0);
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
static u_int
|
|
siba_get_port_count(device_t dev, device_t child, bhnd_port_type type)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), child,
|
|
type));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
return (siba_port_count(&dinfo->core_id, type));
|
|
}
|
|
|
|
static u_int
|
|
siba_get_region_count(device_t dev, device_t child, bhnd_port_type type,
|
|
u_int port)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), child,
|
|
type, port));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
return (siba_port_region_count(&dinfo->core_id, type, port));
|
|
}
|
|
|
|
static int
|
|
siba_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type,
|
|
u_int port_num, u_int region_num)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
struct siba_addrspace *addrspace;
|
|
struct siba_cfg_block *cfg;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_GET_PORT_RID(device_get_parent(dev), child,
|
|
port_type, port_num, region_num));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Look for a matching addrspace entry */
|
|
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
|
|
if (addrspace != NULL)
|
|
return (addrspace->sa_rid);
|
|
|
|
/* Try the config blocks */
|
|
cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num);
|
|
if (cfg != NULL)
|
|
return (cfg->cb_rid);
|
|
|
|
/* Not found */
|
|
return (-1);
|
|
}
|
|
|
|
static int
|
|
siba_decode_port_rid(device_t dev, device_t child, int type, int rid,
|
|
bhnd_port_type *port_type, u_int *port_num, u_int *region_num)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), child,
|
|
type, rid, port_type, port_num, region_num));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Ports are always memory mapped */
|
|
if (type != SYS_RES_MEMORY)
|
|
return (EINVAL);
|
|
|
|
/* Look for a matching addrspace entry */
|
|
for (u_int i = 0; i < dinfo->core_id.num_admatch; i++) {
|
|
if (dinfo->addrspace[i].sa_rid != rid)
|
|
continue;
|
|
|
|
*port_type = BHND_PORT_DEVICE;
|
|
*port_num = siba_addrspace_device_port(i);
|
|
*region_num = siba_addrspace_device_region(i);
|
|
return (0);
|
|
}
|
|
|
|
/* Try the config blocks */
|
|
for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) {
|
|
if (dinfo->cfg[i].cb_rid != rid)
|
|
continue;
|
|
|
|
*port_type = BHND_PORT_AGENT;
|
|
*port_num = siba_cfg_agent_port(i);
|
|
*region_num = siba_cfg_agent_region(i);
|
|
return (0);
|
|
}
|
|
|
|
/* Not found */
|
|
return (ENOENT);
|
|
}
|
|
|
|
static int
|
|
siba_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type,
|
|
u_int port_num, u_int region_num, bhnd_addr_t *addr, bhnd_size_t *size)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
struct siba_addrspace *addrspace;
|
|
struct siba_cfg_block *cfg;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev) {
|
|
return (BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), child,
|
|
port_type, port_num, region_num, addr, size));
|
|
}
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Look for a matching addrspace */
|
|
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
|
|
if (addrspace != NULL) {
|
|
*addr = addrspace->sa_base;
|
|
*size = addrspace->sa_size - addrspace->sa_bus_reserved;
|
|
return (0);
|
|
}
|
|
|
|
/* Look for a matching cfg block */
|
|
cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num);
|
|
if (cfg != NULL) {
|
|
*addr = cfg->cb_base;
|
|
*size = cfg->cb_size;
|
|
return (0);
|
|
}
|
|
|
|
/* Not found */
|
|
return (ENOENT);
|
|
}
|
|
|
|
/**
|
|
* Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT().
|
|
*/
|
|
u_int
|
|
siba_get_intr_count(device_t dev, device_t child)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), child));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
if (!dinfo->core_id.intr_en) {
|
|
/* No interrupts */
|
|
return (0);
|
|
} else {
|
|
/* One assigned interrupt */
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_IVEC().
|
|
*/
|
|
int
|
|
siba_get_intr_ivec(device_t dev, device_t child, u_int intr, u_int *ivec)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
|
|
/* delegate non-bus-attached devices to our parent */
|
|
if (device_get_parent(child) != dev)
|
|
return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), child,
|
|
intr, ivec));
|
|
|
|
/* Must be a valid interrupt ID */
|
|
if (intr >= siba_get_intr_count(dev, child))
|
|
return (ENXIO);
|
|
|
|
KASSERT(intr == 0, ("invalid ivec %u", intr));
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
KASSERT(dinfo->core_id.intr_en,
|
|
("core does not have an interrupt assigned"));
|
|
|
|
*ivec = dinfo->core_id.intr_flag;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Map per-core configuration blocks for @p dinfo.
|
|
*
|
|
* @param dev The siba bus device.
|
|
* @param dinfo The device info instance on which to map all per-core
|
|
* configuration blocks.
|
|
*/
|
|
static int
|
|
siba_map_cfg_resources(device_t dev, struct siba_devinfo *dinfo)
|
|
{
|
|
struct siba_addrspace *addrspace;
|
|
rman_res_t r_start, r_count, r_end;
|
|
uint8_t num_cfg;
|
|
int rid;
|
|
|
|
num_cfg = dinfo->core_id.num_cfg_blocks;
|
|
if (num_cfg > SIBA_MAX_CFG) {
|
|
device_printf(dev, "config block count %hhu out of range\n",
|
|
num_cfg);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Fetch the core register address space */
|
|
addrspace = siba_find_addrspace(dinfo, BHND_PORT_DEVICE, 0, 0);
|
|
if (addrspace == NULL) {
|
|
device_printf(dev, "missing device registers\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Map the per-core configuration blocks
|
|
*/
|
|
for (uint8_t i = 0; i < num_cfg; i++) {
|
|
/* Add to child's resource list */
|
|
r_start = addrspace->sa_base + SIBA_CFG_OFFSET(i);
|
|
r_count = SIBA_CFG_SIZE;
|
|
r_end = r_start + r_count - 1;
|
|
|
|
rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY,
|
|
r_start, r_end, r_count);
|
|
|
|
/* Initialize config block descriptor */
|
|
dinfo->cfg[i] = ((struct siba_cfg_block) {
|
|
.cb_base = r_start,
|
|
.cb_size = SIBA_CFG_SIZE,
|
|
.cb_rid = rid
|
|
});
|
|
|
|
/* Map the config resource for bus-level access */
|
|
dinfo->cfg_rid[i] = SIBA_CFG_RID(dinfo, i);
|
|
dinfo->cfg_res[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev,
|
|
SYS_RES_MEMORY, &dinfo->cfg_rid[i], r_start, r_end,
|
|
r_count, RF_ACTIVE|RF_SHAREABLE);
|
|
|
|
if (dinfo->cfg_res[i] == NULL) {
|
|
device_printf(dev, "failed to allocate SIBA_CFG%hhu\n",
|
|
i);
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static device_t
|
|
siba_add_child(device_t dev, u_int order, const char *name, int unit)
|
|
{
|
|
struct siba_devinfo *dinfo;
|
|
device_t child;
|
|
|
|
child = device_add_child_ordered(dev, order, name, unit);
|
|
if (child == NULL)
|
|
return (NULL);
|
|
|
|
if ((dinfo = siba_alloc_dinfo(dev)) == NULL) {
|
|
device_delete_child(dev, child);
|
|
return (NULL);
|
|
}
|
|
|
|
device_set_ivars(child, dinfo);
|
|
|
|
return (child);
|
|
}
|
|
|
|
static void
|
|
siba_child_deleted(device_t dev, device_t child)
|
|
{
|
|
struct bhnd_softc *sc;
|
|
struct siba_devinfo *dinfo;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Call required bhnd(4) implementation */
|
|
bhnd_generic_child_deleted(dev, child);
|
|
|
|
/* Free siba device info */
|
|
if ((dinfo = device_get_ivars(child)) != NULL)
|
|
siba_free_dinfo(dev, child, dinfo);
|
|
|
|
device_set_ivars(child, NULL);
|
|
}
|
|
|
|
/**
|
|
* Scan the core table and add all valid discovered cores to
|
|
* the bus.
|
|
*
|
|
* @param dev The siba bus device.
|
|
*/
|
|
int
|
|
siba_add_children(device_t dev)
|
|
{
|
|
bhnd_erom_t *erom;
|
|
struct siba_erom *siba_erom;
|
|
struct bhnd_erom_io *eio;
|
|
const struct bhnd_chipid *cid;
|
|
struct siba_core_id *cores;
|
|
device_t *children;
|
|
int error;
|
|
|
|
cid = BHND_BUS_GET_CHIPID(dev, dev);
|
|
|
|
/* Allocate our EROM parser */
|
|
eio = bhnd_erom_iores_new(dev, SIBA_EROM_RID);
|
|
erom = bhnd_erom_alloc(&siba_erom_parser, cid, eio);
|
|
if (erom == NULL) {
|
|
bhnd_erom_io_fini(eio);
|
|
return (ENODEV);
|
|
}
|
|
|
|
/* Allocate our temporary core and device table */
|
|
cores = malloc(sizeof(*cores) * cid->ncores, M_BHND, M_WAITOK);
|
|
children = malloc(sizeof(*children) * cid->ncores, M_BHND,
|
|
M_WAITOK | M_ZERO);
|
|
|
|
/*
|
|
* Add child devices for all discovered cores.
|
|
*
|
|
* On bridged devices, we'll exhaust our available register windows if
|
|
* we map config blocks on unpopulated/disabled cores. To avoid this, we
|
|
* defer mapping of the per-core siba(4) config blocks until all cores
|
|
* have been enumerated and otherwise configured.
|
|
*/
|
|
siba_erom = (struct siba_erom *)erom;
|
|
for (u_int i = 0; i < cid->ncores; i++) {
|
|
struct siba_devinfo *dinfo;
|
|
device_t child;
|
|
|
|
if ((error = siba_erom_get_core_id(siba_erom, i, &cores[i])))
|
|
goto failed;
|
|
|
|
/* Add the child device */
|
|
child = BUS_ADD_CHILD(dev, 0, NULL, -1);
|
|
if (child == NULL) {
|
|
error = ENXIO;
|
|
goto failed;
|
|
}
|
|
|
|
children[i] = child;
|
|
|
|
/* Initialize per-device bus info */
|
|
if ((dinfo = device_get_ivars(child)) == NULL) {
|
|
error = ENXIO;
|
|
goto failed;
|
|
}
|
|
|
|
if ((error = siba_init_dinfo(dev, child, dinfo, &cores[i])))
|
|
goto failed;
|
|
|
|
/* If pins are floating or the hardware is otherwise
|
|
* unpopulated, the device shouldn't be used. */
|
|
if (bhnd_is_hw_disabled(child))
|
|
device_disable(child);
|
|
}
|
|
|
|
/* Free EROM (and any bridge register windows it might hold) */
|
|
bhnd_erom_free(erom);
|
|
erom = NULL;
|
|
|
|
/* Map all valid core's config register blocks and perform interrupt
|
|
* assignment */
|
|
for (u_int i = 0; i < cid->ncores; i++) {
|
|
struct siba_devinfo *dinfo;
|
|
device_t child;
|
|
|
|
child = children[i];
|
|
|
|
/* Skip if core is disabled */
|
|
if (bhnd_is_hw_disabled(child))
|
|
continue;
|
|
|
|
dinfo = device_get_ivars(child);
|
|
|
|
/* Map the core's config blocks */
|
|
if ((error = siba_map_cfg_resources(dev, dinfo)))
|
|
goto failed;
|
|
|
|
/* Issue bus callback for fully initialized child. */
|
|
BHND_BUS_CHILD_ADDED(dev, child);
|
|
}
|
|
|
|
free(cores, M_BHND);
|
|
free(children, M_BHND);
|
|
|
|
return (0);
|
|
|
|
failed:
|
|
for (u_int i = 0; i < cid->ncores; i++) {
|
|
if (children[i] == NULL)
|
|
continue;
|
|
|
|
device_delete_child(dev, children[i]);
|
|
}
|
|
|
|
free(cores, M_BHND);
|
|
free(children, M_BHND);
|
|
if (erom != NULL)
|
|
bhnd_erom_free(erom);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static device_method_t siba_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, siba_probe),
|
|
DEVMETHOD(device_attach, siba_attach),
|
|
DEVMETHOD(device_detach, siba_detach),
|
|
DEVMETHOD(device_resume, siba_resume),
|
|
DEVMETHOD(device_suspend, siba_suspend),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_add_child, siba_add_child),
|
|
DEVMETHOD(bus_child_deleted, siba_child_deleted),
|
|
DEVMETHOD(bus_read_ivar, siba_read_ivar),
|
|
DEVMETHOD(bus_write_ivar, siba_write_ivar),
|
|
DEVMETHOD(bus_get_resource_list, siba_get_resource_list),
|
|
|
|
/* BHND interface */
|
|
DEVMETHOD(bhnd_bus_get_erom_class, siba_get_erom_class),
|
|
DEVMETHOD(bhnd_bus_alloc_pmu, siba_alloc_pmu),
|
|
DEVMETHOD(bhnd_bus_release_pmu, siba_release_pmu),
|
|
DEVMETHOD(bhnd_bus_request_clock, siba_request_clock),
|
|
DEVMETHOD(bhnd_bus_enable_clocks, siba_enable_clocks),
|
|
DEVMETHOD(bhnd_bus_request_ext_rsrc, siba_request_ext_rsrc),
|
|
DEVMETHOD(bhnd_bus_release_ext_rsrc, siba_release_ext_rsrc),
|
|
DEVMETHOD(bhnd_bus_get_clock_freq, siba_get_clock_freq),
|
|
DEVMETHOD(bhnd_bus_get_clock_latency, siba_get_clock_latency),
|
|
DEVMETHOD(bhnd_bus_read_ioctl, siba_read_ioctl),
|
|
DEVMETHOD(bhnd_bus_write_ioctl, siba_write_ioctl),
|
|
DEVMETHOD(bhnd_bus_read_iost, siba_read_iost),
|
|
DEVMETHOD(bhnd_bus_is_hw_suspended, siba_is_hw_suspended),
|
|
DEVMETHOD(bhnd_bus_reset_hw, siba_reset_hw),
|
|
DEVMETHOD(bhnd_bus_suspend_hw, siba_suspend_hw),
|
|
DEVMETHOD(bhnd_bus_read_config, siba_read_config),
|
|
DEVMETHOD(bhnd_bus_write_config, siba_write_config),
|
|
DEVMETHOD(bhnd_bus_get_port_count, siba_get_port_count),
|
|
DEVMETHOD(bhnd_bus_get_region_count, siba_get_region_count),
|
|
DEVMETHOD(bhnd_bus_get_port_rid, siba_get_port_rid),
|
|
DEVMETHOD(bhnd_bus_decode_port_rid, siba_decode_port_rid),
|
|
DEVMETHOD(bhnd_bus_get_region_addr, siba_get_region_addr),
|
|
DEVMETHOD(bhnd_bus_get_intr_count, siba_get_intr_count),
|
|
DEVMETHOD(bhnd_bus_get_intr_ivec, siba_get_intr_ivec),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
DEFINE_CLASS_1(bhnd, siba_driver, siba_methods, sizeof(struct siba_softc), bhnd_driver);
|
|
|
|
MODULE_VERSION(siba, 1);
|
|
MODULE_DEPEND(siba, bhnd, 1, 1, 1);
|