Example #1
0
/**
 * Return the backplane clock speed in Hz.
 * 
 * @param sc driver instance state.
 */
uint32_t
bhnd_pwrctl_getclk_speed(struct bhnd_pwrctl_softc *sc)
{
	const struct bhnd_chipid	*cid;
	struct chipc_caps		*ccaps;
	bus_size_t			 creg;
	uint32_t 			 n, m;
	uint32_t 			 rate;

	PWRCTL_LOCK_ASSERT(sc, MA_OWNED);

	cid = bhnd_get_chipid(sc->chipc_dev);
	ccaps = BHND_CHIPC_GET_CAPS(sc->chipc_dev);

	n = bhnd_bus_read_4(sc->res, CHIPC_CLKC_N);

	/* Get M register offset */
	creg = bhnd_pwrctl_si_clkreg_m(cid, ccaps->pll_type, &rate);
	if (creg == 0) /* fixed rate */
		return (rate);

	/* calculate rate */
	m = bhnd_bus_read_4(sc->res, creg);
	return (bhnd_pwrctl_si_clock_rate(cid, ccaps->pll_type, n, m));
}
Example #2
0
/* return the ILP (slowclock) min or max frequency */
static uint32_t
bhnd_pwrctl_slowclk_freq(struct bhnd_pwrctl_softc *sc, bool max_freq)
{
	bhnd_clksrc	slowclk;
	uint32_t	div;
	uint32_t	hz;

	slowclk = bhnd_pwrctl_slowclk_src(sc);

	/* Determine clock divisor */
	if (PWRCTL_QUIRK(sc, PCICLK_CTL)) {
		if (slowclk == BHND_CLKSRC_PCI)
			div = 64;
		else
			div = 32;
	} else if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
		div = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);
		div = CHIPC_GET_BITS(div, CHIPC_SCC_CD);
		div *= 4;
	} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
		if (max_freq) {
			div = 1;
		} else {
			div = bhnd_bus_read_4(sc->res, CHIPC_SYS_CLK_CTL);
			div = CHIPC_GET_BITS(div, CHIPC_SYCC_CD);
			div = 4 * (div + 1);
		}
	} else {
		device_printf(sc->dev, "unknown device type\n");
		return (0);
	}

	/* Determine clock frequency */
	switch (slowclk) {
	case BHND_CLKSRC_LPO:
		hz = max_freq ? CHIPC_LPOMAXFREQ : CHIPC_LPOMINFREQ;
		break;
	case BHND_CLKSRC_XTAL:
		hz = max_freq ? CHIPC_XTALMAXFREQ : CHIPC_XTALMINFREQ;
		break;
	case BHND_CLKSRC_PCI:
		hz = max_freq ? CHIPC_PCIMAXFREQ : CHIPC_PCIMINFREQ;
		break;
	default:
		device_printf(sc->dev, "unknown slowclk source %#x\n", slowclk);
		return (0);
	}

	return (hz / div);
}
Example #3
0
/* return the slow clock source */
static bhnd_clksrc
bhnd_pwrctl_slowclk_src(struct bhnd_pwrctl_softc *sc)
{
	uint32_t clkreg;
	uint32_t clksrc;

	/* Fetch clock source */
	if (PWRCTL_QUIRK(sc, PCICLK_CTL)) {
		return (bhnd_pwrctl_get_clksrc(sc->chipc_dev, BHND_CLOCK_ILP));
	} else if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
		clkreg = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);
		clksrc = clkreg & CHIPC_SCC_SS_MASK;
	} else {
		/* Instaclock */
		clksrc = CHIPC_SCC_SS_XTAL;
	}

	/* Map to bhnd_clksrc */
	switch (clksrc) {
	case CHIPC_SCC_SS_PCI:
		return (BHND_CLKSRC_PCI);
	case CHIPC_SCC_SS_LPO:
		return (BHND_CLKSRC_LPO);
	case CHIPC_SCC_SS_XTAL:
		return (BHND_CLKSRC_XTAL);
	default:
		return (BHND_CLKSRC_UNKNOWN);
	}
}
Example #4
0
/**
 * If required by this device, revert any GPIO/pin configuration applied
 * to allow SPROM access.
 * 
 * @param sc chipc driver state.
 */
static void
chipc_disable_sprom_pins(struct chipc_softc *sc)
{
	uint32_t		 cctrl;

	/* Nothing to do? */
	if (!CHIPC_QUIRK(sc, MUX_SPROM))
		return;

	CHIPC_LOCK_ASSERT(sc, MA_OWNED);
	KASSERT(sc->sprom_refcnt != 0, ("sprom pins already disabled"));
	KASSERT(sc->sprom_refcnt == 1, ("sprom pins in use"));

	cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL);

	/* 4331 devices */
	if (CHIPC_QUIRK(sc, 4331_EXTPA_MUX_SPROM)) {
		cctrl |= CHIPC_CCTRL4331_EXTPA_EN;

		if (CHIPC_QUIRK(sc, 4331_GPIO2_5_MUX_SPROM))
			cctrl |= CHIPC_CCTRL4331_EXTPA_ON_GPIO2_5;

		if (CHIPC_QUIRK(sc, 4331_EXTPA2_MUX_SPROM))
			cctrl |= CHIPC_CCTRL4331_EXTPA_EN2;

		bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl);
		return;
	}

	/* 4360 devices */
	if (CHIPC_QUIRK(sc, 4360_FEM_MUX_SPROM)) {
		/* Unimplemented */
	}
}
Example #5
0
/**
 * Determine the NVRAM data source for this device.
 * 
 * The SPROM, OTP, and flash capability flags must be fully populated in
 * @p caps.
 *
 * @param sc chipc driver state.
 * @param caps capability flags to be used to derive NVRAM configuration.
 */
static bhnd_nvram_src
chipc_find_nvram_src(struct chipc_softc *sc, struct chipc_caps *caps)
{
	uint32_t		 otp_st, srom_ctrl;

	/*
	 * We check for hardware presence in order of precedence. For example,
	 * SPROM is is always used in preference to internal OTP if found.
	 */
	if (CHIPC_QUIRK(sc, SUPPORTS_SPROM) && caps->sprom) {
		srom_ctrl = bhnd_bus_read_4(sc->core, CHIPC_SPROM_CTRL);
		if (srom_ctrl & CHIPC_SRC_PRESENT)
			return (BHND_NVRAM_SRC_SPROM);
	}

	/* Check for programmed OTP H/W subregion (contains SROM data) */
	if (CHIPC_QUIRK(sc, SUPPORTS_OTP) && caps->otp_size > 0) {
		/* TODO: need access to HND-OTP device */
		if (!CHIPC_QUIRK(sc, OTP_HND)) {
			device_printf(sc->dev,
			    "NVRAM unavailable: unsupported OTP controller.\n");
			return (BHND_NVRAM_SRC_UNKNOWN);
		}

		otp_st = bhnd_bus_read_4(sc->core, CHIPC_OTPST);
		if (otp_st & CHIPC_OTPS_GUP_HW)
			return (BHND_NVRAM_SRC_OTP);
	}

	/* Check for flash */
	if (caps->flash_type != CHIPC_FLASH_NONE)
		return (BHND_NVRAM_SRC_FLASH);

	/* No NVRAM hardware capability declared */
	return (BHND_NVRAM_SRC_UNKNOWN);
}
Example #6
0
static void
chipc_write_chipctrl(device_t dev, uint32_t value, uint32_t mask)
{
	struct chipc_softc	*sc;
	uint32_t		 cctrl;

	sc = device_get_softc(dev);

	CHIPC_LOCK(sc);

	cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL);
	cctrl = (cctrl & ~mask) | (value | mask);
	bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl);

	CHIPC_UNLOCK(sc);
}
Example #7
0
/* return the value suitable for writing to the dot11 core
 * FAST_PWRUP_DELAY register */
uint16_t
bhnd_pwrctl_fast_pwrup_delay(struct bhnd_pwrctl_softc *sc)
{
	uint32_t pll_on_delay, slowminfreq;
	uint16_t fpdelay;

	fpdelay = 0;

	slowminfreq = bhnd_pwrctl_slowclk_freq(sc, false);

	pll_on_delay = bhnd_bus_read_4(sc->res, CHIPC_PLL_ON_DELAY) + 2;
	pll_on_delay *= 1000000;
	pll_on_delay += (slowminfreq - 1);
	fpdelay = pll_on_delay / slowminfreq;

	return (fpdelay);
}
Example #8
0
/**
 * Determine the preferred NVRAM data source.
 */
static bhnd_nvram_src_t
chipc_nvram_src(device_t dev)
{
	struct chipc_softc	*sc;
	uint32_t		 srom_ctrl;

	sc = device_get_softc(dev);

	/* Very early devices always included a SPROM */
	if (CHIPC_QUIRK(sc, ALWAYS_HAS_SPROM))
		return (BHND_NVRAM_SRC_SPROM);

	/* Most other early devices require checking ChipStatus flags */
	if (CHIPC_QUIRK(sc, SPROM_CHECK_CHIPST))
		return (chipc_nvram_src_chipst(sc));

	/*
	 * Later chipset revisions standardized the NVRAM capability flags and
	 * register interfaces.
	 * 
	 * We check for hardware presence in order of precedence. For example,
	 * SPROM is is always used in preference to internal OTP if found.
	 */
	if (CHIPC_CAP(sc, CAP_SPROM)) {
		srom_ctrl = bhnd_bus_read_4(sc->core, CHIPC_SPROM_CTRL);
		if (srom_ctrl & CHIPC_SRC_PRESENT)
			return (BHND_NVRAM_SRC_SPROM);
	}

	/* Check for OTP */
	if (CHIPC_CAP(sc, CAP_OTP_SIZE))
		return (BHND_NVRAM_SRC_OTP);

	/*
	 * Finally, Northstar chipsets (and possibly other chipsets?) support
	 * external NAND flash. 
	 */
	if (CHIPC_QUIRK(sc, SUPPORTS_NFLASH) && CHIPC_CAP(sc, CAP_NFLASH))
		return (BHND_NVRAM_SRC_NFLASH);

	/* No NVRAM hardware capability declared */
	return (BHND_NVRAM_SRC_NONE);
}
Example #9
0
/**
 * Read a 32-bit value from @p offset relative to the base address of
 * the given @p core_idx.
 * 
 * @param io EROM I/O context.
 * @param core_idx Core index.
 * @param offset Core register offset.
 */
static uint32_t
siba_eio_read_4(struct siba_erom_io *io, u_int core_idx, bus_size_t offset)
{
	bus_size_t core_offset;

	/* Sanity check core index and offset */
	if (core_idx >= io->ncores)
		panic("core index %u out of range (ncores=%u)", core_idx,
		    io->ncores);

	if (offset > SIBA_CORE_SIZE - sizeof(uint32_t))
		panic("invalid core offset %#jx", (uintmax_t)offset);

	/* Perform read */
	core_offset = io->offset + SIBA_CORE_OFFSET(core_idx) + offset;
	if (io->res != NULL)
		return (bhnd_bus_read_4(io->res, core_offset));
	else
		return (bus_space_read_4(io->bst, io->bsh, core_offset));
}
Example #10
0
/**
 * If required by this device, enable access to the SPROM.
 * 
 * @param sc chipc driver state.
 */
static int
chipc_enable_sprom_pins(struct chipc_softc *sc)
{
	uint32_t		 cctrl;

	CHIPC_LOCK_ASSERT(sc, MA_OWNED);
	KASSERT(sc->sprom_refcnt == 0, ("sprom pins already enabled"));

	/* Nothing to do? */
	if (!CHIPC_QUIRK(sc, MUX_SPROM))
		return (0);

	/* Check whether bus is busy */
	if (!chipc_should_enable_muxed_sprom(sc))
		return (EBUSY);

	cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL);

	/* 4331 devices */
	if (CHIPC_QUIRK(sc, 4331_EXTPA_MUX_SPROM)) {
		cctrl &= ~CHIPC_CCTRL4331_EXTPA_EN;

		if (CHIPC_QUIRK(sc, 4331_GPIO2_5_MUX_SPROM))
			cctrl &= ~CHIPC_CCTRL4331_EXTPA_ON_GPIO2_5;

		if (CHIPC_QUIRK(sc, 4331_EXTPA2_MUX_SPROM))
			cctrl &= ~CHIPC_CCTRL4331_EXTPA_EN2;

		bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl);
		return (0);
	}

	/* 4360 devices */
	if (CHIPC_QUIRK(sc, 4360_FEM_MUX_SPROM)) {
		/* Unimplemented */
	}

	/* Refuse to proceed on unsupported devices with muxed SPROM pins */
	device_printf(sc->dev, "muxed sprom lines on unrecognized device\n");
	return (ENXIO);
}
Example #11
0
static int
bhnd_pmu_chipc_probe(device_t dev)
{
	struct bhnd_pmu_softc	*sc;
	struct chipc_caps	*ccaps;
	struct chipc_softc	*chipc_sc;
	device_t		 chipc;
	char			 desc[34];
	int			 error;
	uint32_t		 pcaps;
	uint8_t			 rev;

	sc = device_get_softc(dev);

	/* Look for chipc parent */
	chipc = device_get_parent(dev);
	if (device_get_devclass(chipc) != devclass_find("bhnd_chipc"))
		return (ENXIO);

	/* Check the chipc PMU capability flag. */
	ccaps = BHND_CHIPC_GET_CAPS(chipc);
	if (!ccaps->pmu)
		return (ENXIO);

	/* Delegate to common driver implementation */
	if ((error = bhnd_pmu_probe(dev)) > 0)
		return (error);

	/* Fetch PMU capability flags */
	chipc_sc = device_get_softc(chipc);
	pcaps = bhnd_bus_read_4(chipc_sc->core, BHND_PMU_CAP);

	/* Set description */
	rev = BHND_PMU_GET_BITS(pcaps, BHND_PMU_CAP_REV);
	snprintf(desc, sizeof(desc), "Broadcom ChipCommon PMU, rev %hhu", rev);
	device_set_desc_copy(dev, desc);

	return (BUS_PROBE_NOWILDCARD);
}
Example #12
0
static int
chipc_attach(device_t dev)
{
	struct chipc_softc		*sc;
	bhnd_addr_t			 enum_addr;
	uint32_t			 ccid_reg;
	uint8_t				 chip_type;
	int				 error;

	sc = device_get_softc(dev);
	sc->dev = dev;
	sc->quirks = bhnd_device_quirks(dev, chipc_devices,
	    sizeof(chipc_devices[0]));

	/* Allocate bus resources */
	memcpy(sc->rspec, chipc_rspec, sizeof(sc->rspec));
	if ((error = bhnd_alloc_resources(dev, sc->rspec, sc->res)))
		return (error);

	sc->core = sc->res[0];
	
	/* Fetch our chipset identification data */
	ccid_reg = bhnd_bus_read_4(sc->core, CHIPC_ID);
	chip_type = CHIPC_GET_ATTR(ccid_reg, ID_BUS);

	switch (chip_type) {
	case BHND_CHIPTYPE_SIBA:
		/* enumeration space starts at the ChipCommon register base. */
		enum_addr = rman_get_start(sc->core->res);
		break;
	case BHND_CHIPTYPE_BCMA:
	case BHND_CHIPTYPE_BCMA_ALT:
		enum_addr = bhnd_bus_read_4(sc->core, CHIPC_EROMPTR);
		break;
	default:
		device_printf(dev, "unsupported chip type %hhu\n", chip_type);
		error = ENODEV;
		goto cleanup;
	}

	sc->ccid = bhnd_parse_chipid(ccid_reg, enum_addr);

	/* Fetch capability and status register values */
	sc->caps = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES);
	sc->cst = bhnd_bus_read_4(sc->core, CHIPC_CHIPST);

	// TODO
	switch (bhnd_chipc_nvram_src(dev)) {
	case BHND_NVRAM_SRC_CIS:
		device_printf(dev, "NVRAM source: CIS\n");
		break;
	case BHND_NVRAM_SRC_SPROM:
		device_printf(dev, "NVRAM source: SPROM\n");
		break;
	case BHND_NVRAM_SRC_OTP:
		device_printf(dev, "NVRAM source: OTP\n");
		break;
	case BHND_NVRAM_SRC_NFLASH:
		device_printf(dev, "NVRAM source: NFLASH\n");
		break;
	case BHND_NVRAM_SRC_NONE:
		device_printf(dev, "NVRAM source: NONE\n");
		break;
	}

	return (0);
	
cleanup:
	bhnd_release_resources(dev, sc->rspec, sc->res);
	return (error);
}
Example #13
0
static uint32_t
bhnd_pmu_read_4(bus_size_t reg, void *ctx)
{
	struct bhnd_pmu_softc *sc = ctx;
	return (bhnd_bus_read_4(sc->res, reg));
}
Example #14
0
/**
 * Default bhnd_pmu driver implementation of DEVICE_ATTACH().
 * 
 * @param dev PMU device.
 * @param res The PMU device registers. The driver will maintain a borrowed
 * reference to this resource for the lifetime of the device.
 */
int
bhnd_pmu_attach(device_t dev, struct bhnd_resource *res)
{
	struct bhnd_pmu_softc	*sc;
	struct sysctl_ctx_list	*ctx;
	struct sysctl_oid	*tree;
	devclass_t		 bhnd_class;
	device_t		 core, bus;
	int			 error;

	sc = device_get_softc(dev);
	sc->dev = dev;
	sc->quirks = 0;
	sc->res = res;

	/* Fetch capability flags */
	sc->caps = bhnd_bus_read_4(sc->res, BHND_PMU_CAP);

	/* Find the bus-attached core */
	bhnd_class = devclass_find("bhnd");
	core = sc->dev;
	while ((bus = device_get_parent(core)) != NULL) {
		if (device_get_devclass(bus) == bhnd_class)
			break;

		core = bus;
	}

	if (core == NULL) {
		device_printf(sc->dev, "bhnd bus not found\n");
		return (ENXIO);
	}

	/* Fetch chip and board info */
	sc->cid = *bhnd_get_chipid(core);

	if ((error = bhnd_read_board_info(core, &sc->board))) {
		device_printf(sc->dev, "error fetching board info: %d\n",
		    error);
		return (ENXIO);
	}

	/* Locate ChipCommon device */
	sc->chipc_dev = bhnd_find_child(bus, BHND_DEVCLASS_CC, 0);
	if (sc->chipc_dev == NULL) {
		device_printf(sc->dev, "chipcommon device not found\n");
		return (ENXIO);
	}

	/* Initialize query state */
	error = bhnd_pmu_query_init(&sc->query, dev, sc->cid, &bhnd_pmu_res_io,
	    sc);
	if (error)
		return (error);
	sc->io = sc->query.io; 
	sc->io_ctx = sc->query.io_ctx;

	BPMU_LOCK_INIT(sc);

	/* Set quirk flags */
	switch (sc->cid.chip_id) {
	case BHND_CHIPID_BCM4328:
	case BHND_CHIPID_BCM5354:
		/* HTAVAIL/ALPAVAIL are bitswapped in CLKCTL */
		sc->quirks |= BPMU_QUIRK_CLKCTL_CCS0;
		break;
	default:
		break;
	}

	/* Initialize PMU */
	if ((error = bhnd_pmu_init(sc))) {
		device_printf(sc->dev, "PMU init failed: %d\n", error);
		goto failed;
	}

	/* Set up sysctl nodes */
	ctx = device_get_sysctl_ctx(dev);
	tree = device_get_sysctl_tree(dev);

	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
	    "bus_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0,
	    bhnd_pmu_sysctl_bus_freq, "IU", "Bus clock frequency");

	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
	    "cpu_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0,
	    bhnd_pmu_sysctl_cpu_freq, "IU", "CPU clock frequency");
	
	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
	    "mem_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0,
	    bhnd_pmu_sysctl_mem_freq, "IU", "Memory clock frequency");

	return (0);

failed:
	BPMU_LOCK_DESTROY(sc);
	bhnd_pmu_query_fini(&sc->query);
	return (error);
}
Example #15
0
/**
 * Distribute @p clock on backplane.
 * 
 * @param sc Driver instance state.
 * @param clock Clock to enable.
 * 
 * @retval 0 success
 * @retval ENODEV If @p clock is unsupported, or if the device does not
 * 		  support dynamic clock control.
 */
int
bhnd_pwrctl_setclk(struct bhnd_pwrctl_softc *sc, bhnd_clock clock)
{
	uint32_t	scc;

	PWRCTL_LOCK_ASSERT(sc, MA_OWNED);

	/* Is dynamic clock control supported? */
	if (PWRCTL_QUIRK(sc, FIXED_CLK))
		return (ENODEV);

	/* Chips with ccrev 10 are EOL and they don't have SYCC_HR used below */
	if (bhnd_get_hwrev(sc->chipc_dev) == 10)
		return (ENODEV);

	scc = bhnd_bus_read_4(sc->res, CHIPC_PLL_SLOWCLK_CTL);

	switch (clock) {
	case BHND_CLOCK_HT:
		/* fast (pll) clock */
		if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
			scc &= ~(CHIPC_SCC_XC | CHIPC_SCC_FS | CHIPC_SCC_IP);
			scc |= CHIPC_SCC_IP;

			/* force xtal back on before clearing SCC_DYN_XTAL.. */
			bhnd_pwrctl_ungate_clock(sc->chipc_dev, BHND_CLOCK_HT);
		} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
			scc |= CHIPC_SYCC_HR;
		} else {
			return (ENODEV);
		}

		bhnd_bus_write_4(sc->res, CHIPC_PLL_SLOWCLK_CTL, scc);
		DELAY(CHIPC_PLL_DELAY);

		break;		

	case BHND_CLOCK_DYN:
		/* enable dynamic clock control */
		if (PWRCTL_QUIRK(sc, SLOWCLK_CTL)) {
			scc &= ~(CHIPC_SCC_FS | CHIPC_SCC_IP | CHIPC_SCC_XC);
			if ((scc & CHIPC_SCC_SS_MASK) != CHIPC_SCC_SS_XTAL)
				scc |= CHIPC_SCC_XC;
	
			bhnd_bus_write_4(sc->res, CHIPC_PLL_SLOWCLK_CTL, scc);

			/* for dynamic control, we have to release our xtal_pu
			 * "force on" */
			if (scc & CHIPC_SCC_XC) {
				bhnd_pwrctl_gate_clock(sc->chipc_dev,
				    BHND_CLOCK_HT);
			}
		} else if (PWRCTL_QUIRK(sc, INSTACLK_CTL)) {
			/* Instaclock */
			scc &= ~CHIPC_SYCC_HR;
			bhnd_bus_write_4(sc->res, CHIPC_SYS_CLK_CTL, scc);
		} else {
			return (ENODEV);
		}

		break;

	default:
		return (ENODEV);
	}

	return (0);
}
Example #16
0
static uint32_t
chipc_read_chipst(device_t dev)
{
	struct chipc_softc *sc = device_get_softc(dev);
	return (bhnd_bus_read_4(sc->core, CHIPC_CHIPST));
}
Example #17
0
/* Read and parse chipc capabilities */
static int
chipc_read_caps(struct chipc_softc *sc, struct chipc_caps *caps)
{
	uint32_t	cap_reg;
	uint32_t	cap_ext_reg;
	uint32_t	regval;

	/* Fetch cap registers */
	cap_reg = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES);
	cap_ext_reg = 0;
	if (CHIPC_QUIRK(sc, SUPPORTS_CAP_EXT))
		cap_ext_reg = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES_EXT);

	/* Extract values */
	caps->num_uarts		= CHIPC_GET_BITS(cap_reg, CHIPC_CAP_NUM_UART);
	caps->mipseb		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_MIPSEB);
	caps->uart_gpio		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_UARTGPIO);
	caps->uart_clock	= CHIPC_GET_BITS(cap_reg, CHIPC_CAP_UCLKSEL);

	caps->extbus_type	= CHIPC_GET_BITS(cap_reg, CHIPC_CAP_EXTBUS);
	caps->pwr_ctrl		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_PWR_CTL);
	caps->jtag_master	= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_JTAGP);

	caps->pll_type		= CHIPC_GET_BITS(cap_reg, CHIPC_CAP_PLL);
	caps->backplane_64	= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_BKPLN64);
	caps->boot_rom		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_ROM);
	caps->pmu		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_PMU);
	caps->eci		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_ECI);
	caps->sprom		= CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_SPROM);
	caps->otp_size		= CHIPC_GET_BITS(cap_reg, CHIPC_CAP_OTP_SIZE);

	caps->seci		= CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_SECI);
	caps->gsio		= CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_GSIO);
	caps->aob		= CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_AOB);

	/* Fetch OTP size for later IPX controller revisions */
	if (CHIPC_QUIRK(sc, IPX_OTPL_SIZE)) {
		regval = bhnd_bus_read_4(sc->core, CHIPC_OTPLAYOUT);
		caps->otp_size = CHIPC_GET_BITS(regval, CHIPC_OTPL_SIZE);
	}

	/* Determine flash type and parameters */
	caps->cfi_width = 0;
	switch (CHIPC_GET_BITS(cap_reg, CHIPC_CAP_FLASH)) {
	case CHIPC_CAP_SFLASH_ST:
		caps->flash_type = CHIPC_SFLASH_ST;
		break;
	case CHIPC_CAP_SFLASH_AT:
		caps->flash_type = CHIPC_SFLASH_AT;
		break;
	case CHIPC_CAP_NFLASH:
		/* unimplemented */
		caps->flash_type = CHIPC_NFLASH;
		break;
	case CHIPC_CAP_PFLASH:
		caps->flash_type = CHIPC_PFLASH_CFI;

		/* determine cfi width */
		regval = bhnd_bus_read_4(sc->core, CHIPC_FLASH_CFG);
		if (CHIPC_GET_FLAG(regval, CHIPC_FLASH_CFG_DS))
			caps->cfi_width = 2;
		else
			caps->cfi_width = 1;

		break;
	case CHIPC_CAP_FLASH_NONE:
		caps->flash_type = CHIPC_FLASH_NONE;
		break;
			
	}

	/* Handle 4706_NFLASH fallback */
	if (CHIPC_QUIRK(sc, 4706_NFLASH) &&
	    CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_4706_NFLASH))
	{
		caps->flash_type = CHIPC_NFLASH_4706;
	}


	/* Determine NVRAM source. Must occur after the SPROM/OTP/flash
	 * capability flags have been populated. */
	caps->nvram_src = chipc_find_nvram_src(sc, caps);

	/* Determine the SPROM offset within OTP (if any). SPROM-formatted
	 * data is placed within the OTP general use region. */
	caps->sprom_offset = 0;
	if (caps->nvram_src == BHND_NVRAM_SRC_OTP) {
		CHIPC_ASSERT_QUIRK(sc, OTP_IPX);

		/* Bit offset to GUP HW subregion containing SPROM data */
		regval = bhnd_bus_read_4(sc->core, CHIPC_OTPLAYOUT);
		caps->sprom_offset = CHIPC_GET_BITS(regval, CHIPC_OTPL_GUP);

		/* Convert to bytes */
		caps->sprom_offset /= 8;
	}

	return (0);
}