xref: /freebsd/sys/dev/bhnd/bhndb/bhndb_pci.c (revision e83ce340355a3a75b0343b46e23fe95ed92157f4)
14ad7e9b0SAdrian Chadd /*-
2bb64eeccSAdrian Chadd  * Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
34ad7e9b0SAdrian Chadd  * All rights reserved.
44ad7e9b0SAdrian Chadd  *
54ad7e9b0SAdrian Chadd  * Redistribution and use in source and binary forms, with or without
64ad7e9b0SAdrian Chadd  * modification, are permitted provided that the following conditions
74ad7e9b0SAdrian Chadd  * are met:
84ad7e9b0SAdrian Chadd  * 1. Redistributions of source code must retain the above copyright
94ad7e9b0SAdrian Chadd  *    notice, this list of conditions and the following disclaimer,
104ad7e9b0SAdrian Chadd  *    without modification.
114ad7e9b0SAdrian Chadd  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
124ad7e9b0SAdrian Chadd  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
134ad7e9b0SAdrian Chadd  *    redistribution must be conditioned upon including a substantially
144ad7e9b0SAdrian Chadd  *    similar Disclaimer requirement for further binary redistribution.
154ad7e9b0SAdrian Chadd  *
164ad7e9b0SAdrian Chadd  * NO WARRANTY
174ad7e9b0SAdrian Chadd  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
184ad7e9b0SAdrian Chadd  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
194ad7e9b0SAdrian Chadd  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
204ad7e9b0SAdrian Chadd  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
214ad7e9b0SAdrian Chadd  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
224ad7e9b0SAdrian Chadd  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
234ad7e9b0SAdrian Chadd  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
244ad7e9b0SAdrian Chadd  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
254ad7e9b0SAdrian Chadd  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
264ad7e9b0SAdrian Chadd  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
274ad7e9b0SAdrian Chadd  * THE POSSIBILITY OF SUCH DAMAGES.
284ad7e9b0SAdrian Chadd  */
294ad7e9b0SAdrian Chadd 
304ad7e9b0SAdrian Chadd #include <sys/cdefs.h>
314ad7e9b0SAdrian Chadd __FBSDID("$FreeBSD$");
324ad7e9b0SAdrian Chadd 
334ad7e9b0SAdrian Chadd /*
344ad7e9b0SAdrian Chadd  * PCI-specific implementation for the BHNDB bridge driver.
354ad7e9b0SAdrian Chadd  *
364ad7e9b0SAdrian Chadd  * Provides support for bridging from a PCI parent bus to a BHND-compatible
374ad7e9b0SAdrian Chadd  * bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point
384ad7e9b0SAdrian Chadd  * mode.
394ad7e9b0SAdrian Chadd  *
40bb64eeccSAdrian Chadd  * This driver handles all host-level PCI interactions with a PCI/PCIe bridge
41bb64eeccSAdrian Chadd  * core operating in endpoint mode. On the bridged bhnd bus, the PCI core
42bb64eeccSAdrian Chadd  * device will be managed by a bhnd_pci_hostb driver.
434ad7e9b0SAdrian Chadd  */
444ad7e9b0SAdrian Chadd 
454ad7e9b0SAdrian Chadd #include <sys/param.h>
464ad7e9b0SAdrian Chadd #include <sys/kernel.h>
474ad7e9b0SAdrian Chadd #include <sys/bus.h>
484ad7e9b0SAdrian Chadd #include <sys/limits.h>
494ad7e9b0SAdrian Chadd #include <sys/malloc.h>
504ad7e9b0SAdrian Chadd #include <sys/module.h>
514ad7e9b0SAdrian Chadd #include <sys/systm.h>
524ad7e9b0SAdrian Chadd 
534ad7e9b0SAdrian Chadd #include <dev/pci/pcireg.h>
544ad7e9b0SAdrian Chadd #include <dev/pci/pcivar.h>
554ad7e9b0SAdrian Chadd 
564ad7e9b0SAdrian Chadd #include <dev/bhnd/bhnd.h>
574ad7e9b0SAdrian Chadd 
584ad7e9b0SAdrian Chadd #include <dev/bhnd/cores/pci/bhnd_pcireg.h>
594ad7e9b0SAdrian Chadd 
604ad7e9b0SAdrian Chadd #include "bhndb_pcireg.h"
614ad7e9b0SAdrian Chadd #include "bhndb_pcivar.h"
624ad7e9b0SAdrian Chadd #include "bhndb_private.h"
634ad7e9b0SAdrian Chadd 
644ad7e9b0SAdrian Chadd static int		bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc);
654ad7e9b0SAdrian Chadd static int		bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc);
664ad7e9b0SAdrian Chadd 
674ad7e9b0SAdrian Chadd static int		bhndb_pci_compat_setregwin(struct bhndb_pci_softc *,
684ad7e9b0SAdrian Chadd 			    const struct bhndb_regwin *, bhnd_addr_t);
694ad7e9b0SAdrian Chadd static int		bhndb_pci_fast_setregwin(struct bhndb_pci_softc *,
704ad7e9b0SAdrian Chadd 			    const struct bhndb_regwin *, bhnd_addr_t);
714ad7e9b0SAdrian Chadd 
72*e83ce340SAdrian Chadd static void		bhndb_init_sromless_pci_config(
73*e83ce340SAdrian Chadd 			    struct bhndb_pci_softc *sc);
74*e83ce340SAdrian Chadd 
75*e83ce340SAdrian Chadd static bus_addr_t	bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc);
76*e83ce340SAdrian Chadd static size_t		bhndb_pci_sprom_size(struct bhndb_pci_softc *sc);
774ad7e9b0SAdrian Chadd 
784ad7e9b0SAdrian Chadd /**
794ad7e9b0SAdrian Chadd  * Default bhndb_pci implementation of device_probe().
804ad7e9b0SAdrian Chadd  *
814ad7e9b0SAdrian Chadd  * Verifies that the parent is a PCI/PCIe device.
824ad7e9b0SAdrian Chadd  */
834ad7e9b0SAdrian Chadd static int
844ad7e9b0SAdrian Chadd bhndb_pci_probe(device_t dev)
854ad7e9b0SAdrian Chadd {
864ad7e9b0SAdrian Chadd 	device_t	parent;
874ad7e9b0SAdrian Chadd 	devclass_t	parent_bus;
884ad7e9b0SAdrian Chadd 	devclass_t	pci;
894ad7e9b0SAdrian Chadd 
904ad7e9b0SAdrian Chadd 	/* Our parent must be a PCI/PCIe device. */
914ad7e9b0SAdrian Chadd 	pci = devclass_find("pci");
924ad7e9b0SAdrian Chadd 	parent = device_get_parent(dev);
934ad7e9b0SAdrian Chadd 	parent_bus = device_get_devclass(device_get_parent(parent));
944ad7e9b0SAdrian Chadd 
954ad7e9b0SAdrian Chadd 	if (parent_bus != pci)
964ad7e9b0SAdrian Chadd 		return (ENXIO);
974ad7e9b0SAdrian Chadd 
984ad7e9b0SAdrian Chadd 	device_set_desc(dev, "PCI-BHND bridge");
994ad7e9b0SAdrian Chadd 
1004ad7e9b0SAdrian Chadd 	return (BUS_PROBE_DEFAULT);
1014ad7e9b0SAdrian Chadd }
1024ad7e9b0SAdrian Chadd 
1034ad7e9b0SAdrian Chadd static int
1044ad7e9b0SAdrian Chadd bhndb_pci_attach(device_t dev)
1054ad7e9b0SAdrian Chadd {
1064ad7e9b0SAdrian Chadd 	struct bhndb_pci_softc	*sc;
1074ad7e9b0SAdrian Chadd 	int			 error, reg;
1084ad7e9b0SAdrian Chadd 
1094ad7e9b0SAdrian Chadd 	sc = device_get_softc(dev);
1104ad7e9b0SAdrian Chadd 	sc->dev = dev;
111*e83ce340SAdrian Chadd 	sc->parent = device_get_parent(dev);
1124ad7e9b0SAdrian Chadd 
1134ad7e9b0SAdrian Chadd 	/* Enable PCI bus mastering */
114*e83ce340SAdrian Chadd 	pci_enable_busmaster(sc->parent);
1154ad7e9b0SAdrian Chadd 
1164ad7e9b0SAdrian Chadd 	/* Determine our bridge device class */
1174ad7e9b0SAdrian Chadd 	sc->pci_devclass = BHND_DEVCLASS_PCI;
118*e83ce340SAdrian Chadd 	if (pci_find_cap(sc->parent, PCIY_EXPRESS, &reg) == 0)
1194ad7e9b0SAdrian Chadd 		sc->pci_devclass = BHND_DEVCLASS_PCIE;
1204ad7e9b0SAdrian Chadd 
121bb64eeccSAdrian Chadd 	/* Enable clocks (if supported by this hardware) */
122bb64eeccSAdrian Chadd 	if ((error = bhndb_enable_pci_clocks(sc)))
1234ad7e9b0SAdrian Chadd 		return (error);
1244ad7e9b0SAdrian Chadd 
1254ad7e9b0SAdrian Chadd 	/* Use siba(4)-compatible regwin handling until we know
1264ad7e9b0SAdrian Chadd 	 * what kind of bus is attached */
1274ad7e9b0SAdrian Chadd 	sc->set_regwin = bhndb_pci_compat_setregwin;
1284ad7e9b0SAdrian Chadd 
1294ad7e9b0SAdrian Chadd 	/* Perform full bridge attach. This should call back into our
1304ad7e9b0SAdrian Chadd 	 * bhndb_pci_init_full_config() implementation once the bridged
1314ad7e9b0SAdrian Chadd 	 * bhnd(4) bus has been enumerated, but before any devices have been
1324ad7e9b0SAdrian Chadd 	 * probed or attached. */
1334ad7e9b0SAdrian Chadd 	if ((error = bhndb_attach(dev, sc->pci_devclass)))
1344ad7e9b0SAdrian Chadd 		return (error);
1354ad7e9b0SAdrian Chadd 
1364ad7e9b0SAdrian Chadd 	/* If supported, switch to the faster regwin handling */
1374ad7e9b0SAdrian Chadd 	if (sc->bhndb.chipid.chip_type != BHND_CHIPTYPE_SIBA) {
1384ad7e9b0SAdrian Chadd 		atomic_store_rel_ptr((volatile void *) &sc->set_regwin,
1394ad7e9b0SAdrian Chadd 		    (uintptr_t) &bhndb_pci_fast_setregwin);
1404ad7e9b0SAdrian Chadd 	}
1414ad7e9b0SAdrian Chadd 
1424ad7e9b0SAdrian Chadd 	return (0);
1434ad7e9b0SAdrian Chadd }
1444ad7e9b0SAdrian Chadd 
1454ad7e9b0SAdrian Chadd static int
1464ad7e9b0SAdrian Chadd bhndb_pci_init_full_config(device_t dev, device_t child,
147bb64eeccSAdrian Chadd     const struct bhndb_hw_priority *hw_prio_table)
1484ad7e9b0SAdrian Chadd {
1494ad7e9b0SAdrian Chadd 	struct bhndb_pci_softc	*sc;
150*e83ce340SAdrian Chadd 	device_t		 nv_dev;
151*e83ce340SAdrian Chadd 	bus_size_t		 nv_sz;
1524ad7e9b0SAdrian Chadd 	int			 error;
1534ad7e9b0SAdrian Chadd 
1544ad7e9b0SAdrian Chadd 	sc = device_get_softc(dev);
1554ad7e9b0SAdrian Chadd 
156bb64eeccSAdrian Chadd 	/* Let our parent perform standard initialization first */
157bb64eeccSAdrian Chadd 	if ((error = bhndb_generic_init_full_config(dev, child, hw_prio_table)))
1584ad7e9b0SAdrian Chadd 		return (error);
1594ad7e9b0SAdrian Chadd 
160bb64eeccSAdrian Chadd 	/* Fix-up power on defaults for SROM-less devices. */
1614ad7e9b0SAdrian Chadd 	bhndb_init_sromless_pci_config(sc);
1624ad7e9b0SAdrian Chadd 
163*e83ce340SAdrian Chadd 	/* If SPROM is mapped directly into BAR0, add NVRAM device. */
164*e83ce340SAdrian Chadd 	nv_sz = bhndb_pci_sprom_size(sc);
165*e83ce340SAdrian Chadd 	if (nv_sz > 0) {
166*e83ce340SAdrian Chadd 		struct bhndb_devinfo	*dinfo;
167*e83ce340SAdrian Chadd 		const char		*dname;
168*e83ce340SAdrian Chadd 
169*e83ce340SAdrian Chadd 		if (bootverbose) {
170*e83ce340SAdrian Chadd 			device_printf(dev, "found SPROM (%zu bytes)\n", nv_sz);
171*e83ce340SAdrian Chadd 		}
172*e83ce340SAdrian Chadd 
173*e83ce340SAdrian Chadd 		/* Add sprom device */
174*e83ce340SAdrian Chadd 		dname = "bhnd_nvram";
175*e83ce340SAdrian Chadd 		if ((nv_dev = BUS_ADD_CHILD(dev, 0, dname, -1)) == NULL) {
176*e83ce340SAdrian Chadd 			device_printf(dev, "failed to add sprom device\n");
177*e83ce340SAdrian Chadd 			return (ENXIO);
178*e83ce340SAdrian Chadd 		}
179*e83ce340SAdrian Chadd 
180*e83ce340SAdrian Chadd 		/* Initialize device address space and resource covering the
181*e83ce340SAdrian Chadd 		 * BAR0 SPROM shadow. */
182*e83ce340SAdrian Chadd 		dinfo = device_get_ivars(nv_dev);
183*e83ce340SAdrian Chadd 		dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE;
184*e83ce340SAdrian Chadd 		error = bus_set_resource(nv_dev, SYS_RES_MEMORY, 0,
185*e83ce340SAdrian Chadd 		    bhndb_pci_sprom_addr(sc), nv_sz);
186*e83ce340SAdrian Chadd 
187*e83ce340SAdrian Chadd 		if (error) {
188*e83ce340SAdrian Chadd 			device_printf(dev,
189*e83ce340SAdrian Chadd 			    "failed to register sprom resources\n");
190*e83ce340SAdrian Chadd 			return (error);
191*e83ce340SAdrian Chadd 		}
192*e83ce340SAdrian Chadd 
193*e83ce340SAdrian Chadd 		/* Attach the device */
194*e83ce340SAdrian Chadd 		if ((error = device_probe_and_attach(nv_dev))) {
195*e83ce340SAdrian Chadd 			device_printf(dev, "sprom attach failed\n");
196*e83ce340SAdrian Chadd 			return (error);
197*e83ce340SAdrian Chadd 		}
198*e83ce340SAdrian Chadd 	}
199*e83ce340SAdrian Chadd 
2004ad7e9b0SAdrian Chadd 	return (0);
2014ad7e9b0SAdrian Chadd }
2024ad7e9b0SAdrian Chadd 
203*e83ce340SAdrian Chadd static const struct bhndb_regwin *
204*e83ce340SAdrian Chadd bhndb_pci_sprom_regwin(struct bhndb_pci_softc *sc)
205*e83ce340SAdrian Chadd {
206*e83ce340SAdrian Chadd 	struct bhndb_resources		*bres;
207*e83ce340SAdrian Chadd 	const struct bhndb_hwcfg	*cfg;
208*e83ce340SAdrian Chadd 	const struct bhndb_regwin	*sprom_win;
209*e83ce340SAdrian Chadd 
210*e83ce340SAdrian Chadd 	bres = sc->bhndb.bus_res;
211*e83ce340SAdrian Chadd 	cfg = bres->cfg;
212*e83ce340SAdrian Chadd 
213*e83ce340SAdrian Chadd 	sprom_win = bhndb_regwin_find_type(cfg->register_windows,
214*e83ce340SAdrian Chadd 	    BHNDB_REGWIN_T_SPROM, BHNDB_PCI_V0_BAR0_SPROM_SIZE);
215*e83ce340SAdrian Chadd 
216*e83ce340SAdrian Chadd 	return (sprom_win);
217*e83ce340SAdrian Chadd }
218*e83ce340SAdrian Chadd 
219*e83ce340SAdrian Chadd static bus_addr_t
220*e83ce340SAdrian Chadd bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc)
221*e83ce340SAdrian Chadd {
222*e83ce340SAdrian Chadd 	const struct bhndb_regwin	*sprom_win;
223*e83ce340SAdrian Chadd 	struct resource			*r;
224*e83ce340SAdrian Chadd 
225*e83ce340SAdrian Chadd 	/* Fetch the SPROM register window */
226*e83ce340SAdrian Chadd 	sprom_win = bhndb_pci_sprom_regwin(sc);
227*e83ce340SAdrian Chadd 	KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+"));
228*e83ce340SAdrian Chadd 
229*e83ce340SAdrian Chadd 	/* Fetch the associated resource */
230*e83ce340SAdrian Chadd 	r = bhndb_find_regwin_resource(sc->bhndb.bus_res, sprom_win);
231*e83ce340SAdrian Chadd 	KASSERT(r != NULL, ("missing resource for sprom window\n"));
232*e83ce340SAdrian Chadd 
233*e83ce340SAdrian Chadd 	return (rman_get_start(r) + sprom_win->win_offset);
234*e83ce340SAdrian Chadd }
235*e83ce340SAdrian Chadd 
236*e83ce340SAdrian Chadd static bus_size_t
237*e83ce340SAdrian Chadd bhndb_pci_sprom_size(struct bhndb_pci_softc *sc)
238*e83ce340SAdrian Chadd {
239*e83ce340SAdrian Chadd 	const struct bhndb_regwin	*sprom_win;
240*e83ce340SAdrian Chadd 	uint32_t			 sctl;
241*e83ce340SAdrian Chadd 	bus_size_t			 sprom_sz;
242*e83ce340SAdrian Chadd 
243*e83ce340SAdrian Chadd 	sprom_win = bhndb_pci_sprom_regwin(sc);
244*e83ce340SAdrian Chadd 
245*e83ce340SAdrian Chadd 	/* PCI_V2 and later devices map SPROM/OTP via ChipCommon */
246*e83ce340SAdrian Chadd 	if (sprom_win == NULL)
247*e83ce340SAdrian Chadd 		return (0);
248*e83ce340SAdrian Chadd 
249*e83ce340SAdrian Chadd 	/* Determine SPROM size */
250*e83ce340SAdrian Chadd 	sctl = pci_read_config(sc->parent, BHNDB_PCI_SPROM_CONTROL, 4);
251*e83ce340SAdrian Chadd 	if (sctl & BHNDB_PCI_SPROM_BLANK)
252*e83ce340SAdrian Chadd 		return (0);
253*e83ce340SAdrian Chadd 
254*e83ce340SAdrian Chadd 	switch (sctl & BHNDB_PCI_SPROM_SZ_MASK) {
255*e83ce340SAdrian Chadd 	case BHNDB_PCI_SPROM_SZ_1KB:
256*e83ce340SAdrian Chadd 		sprom_sz = (1 * 1024);
257*e83ce340SAdrian Chadd 		break;
258*e83ce340SAdrian Chadd 
259*e83ce340SAdrian Chadd 	case BHNDB_PCI_SPROM_SZ_4KB:
260*e83ce340SAdrian Chadd 		sprom_sz = (4 * 1024);
261*e83ce340SAdrian Chadd 		break;
262*e83ce340SAdrian Chadd 
263*e83ce340SAdrian Chadd 	case BHNDB_PCI_SPROM_SZ_16KB:
264*e83ce340SAdrian Chadd 		sprom_sz = (16 * 1024);
265*e83ce340SAdrian Chadd 		break;
266*e83ce340SAdrian Chadd 
267*e83ce340SAdrian Chadd 	case BHNDB_PCI_SPROM_SZ_RESERVED:
268*e83ce340SAdrian Chadd 	default:
269*e83ce340SAdrian Chadd 		device_printf(sc->dev, "invalid PCI sprom size 0x%x\n", sctl);
270*e83ce340SAdrian Chadd 		return (0);
271*e83ce340SAdrian Chadd 	}
272*e83ce340SAdrian Chadd 
273*e83ce340SAdrian Chadd 	if (sprom_sz > sprom_win->win_size) {
274*e83ce340SAdrian Chadd 		device_printf(sc->dev,
275*e83ce340SAdrian Chadd 		    "PCI sprom size (0x%x) overruns defined register window\n",
276*e83ce340SAdrian Chadd 		    sctl);
277*e83ce340SAdrian Chadd 		return (0);
278*e83ce340SAdrian Chadd 	}
279*e83ce340SAdrian Chadd 
280*e83ce340SAdrian Chadd 	return (sprom_sz);
281*e83ce340SAdrian Chadd }
282*e83ce340SAdrian Chadd 
2834ad7e9b0SAdrian Chadd /*
2844ad7e9b0SAdrian Chadd  * On devices without a SROM, the PCI(e) cores will be initialized with
285bb64eeccSAdrian Chadd  * their Power-on-Reset defaults; this can leave two of the BAR0 PCI windows
286bb64eeccSAdrian Chadd  * mapped to the wrong core.
2874ad7e9b0SAdrian Chadd  *
288bb64eeccSAdrian Chadd  * This function updates the SROM shadow to point the BAR0 windows at the
2894ad7e9b0SAdrian Chadd  * current PCI core.
2904ad7e9b0SAdrian Chadd  *
291bb64eeccSAdrian Chadd  * Applies to all PCI/PCIe revisions.
2924ad7e9b0SAdrian Chadd  */
2934ad7e9b0SAdrian Chadd static void
2944ad7e9b0SAdrian Chadd bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc)
2954ad7e9b0SAdrian Chadd {
296bb64eeccSAdrian Chadd 	struct bhndb_resources		*bres;
297bb64eeccSAdrian Chadd 	const struct bhndb_hwcfg	*cfg;
298bb64eeccSAdrian Chadd 	const struct bhndb_regwin	*win;
299bb64eeccSAdrian Chadd 	struct resource			*core_regs;
300bb64eeccSAdrian Chadd 	bus_size_t			 srom_offset;
301bb64eeccSAdrian Chadd 	u_int				 pci_cidx, sprom_cidx;
3024ad7e9b0SAdrian Chadd 	uint16_t			 val;
3034ad7e9b0SAdrian Chadd 
304bb64eeccSAdrian Chadd 	bres = sc->bhndb.bus_res;
305bb64eeccSAdrian Chadd 	cfg = bres->cfg;
306bb64eeccSAdrian Chadd 
307bb64eeccSAdrian Chadd 	if (bhnd_get_vendor(sc->bhndb.hostb_dev) != BHND_MFGID_BCM)
308bb64eeccSAdrian Chadd 		return;
309bb64eeccSAdrian Chadd 
310bb64eeccSAdrian Chadd 	switch (bhnd_get_device(sc->bhndb.hostb_dev)) {
311bb64eeccSAdrian Chadd 	case BHND_COREID_PCI:
312bb64eeccSAdrian Chadd 		srom_offset = BHND_PCI_SRSH_PI_OFFSET;
313bb64eeccSAdrian Chadd 		break;
314bb64eeccSAdrian Chadd 	case BHND_COREID_PCIE:
315bb64eeccSAdrian Chadd 		srom_offset = BHND_PCIE_SRSH_PI_OFFSET;
316bb64eeccSAdrian Chadd 		break;
317bb64eeccSAdrian Chadd 	default:
318bb64eeccSAdrian Chadd 		device_printf(sc->dev, "unsupported PCI host bridge device\n");
319bb64eeccSAdrian Chadd 		return;
320bb64eeccSAdrian Chadd 	}
321bb64eeccSAdrian Chadd 
322bb64eeccSAdrian Chadd 	/* Locate the static register window mapping the PCI core */
323bb64eeccSAdrian Chadd 	win = bhndb_regwin_find_core(cfg->register_windows, sc->pci_devclass,
324bb64eeccSAdrian Chadd 	    0, BHND_PORT_DEVICE, 0, 0);
325bb64eeccSAdrian Chadd 	if (win == NULL) {
326bb64eeccSAdrian Chadd 		device_printf(sc->dev, "missing PCI core register window\n");
327bb64eeccSAdrian Chadd 		return;
328bb64eeccSAdrian Chadd 	}
329bb64eeccSAdrian Chadd 
330bb64eeccSAdrian Chadd 	/* Fetch the resource containing the register window */
331bb64eeccSAdrian Chadd 	core_regs = bhndb_find_regwin_resource(bres, win);
332bb64eeccSAdrian Chadd 	if (core_regs == NULL) {
333bb64eeccSAdrian Chadd 		device_printf(sc->dev, "missing PCI core register resource\n");
334bb64eeccSAdrian Chadd 		return;
335bb64eeccSAdrian Chadd 	}
336bb64eeccSAdrian Chadd 
3374ad7e9b0SAdrian Chadd 	/* Fetch the SPROM's configured core index */
338bb64eeccSAdrian Chadd 	val = bus_read_2(core_regs, win->win_offset + srom_offset);
339bb64eeccSAdrian Chadd 	sprom_cidx = (val & BHND_PCI_SRSH_PI_MASK) >> BHND_PCI_SRSH_PI_SHIFT;
3404ad7e9b0SAdrian Chadd 
3414ad7e9b0SAdrian Chadd 	/* If it doesn't match host bridge's core index, update the index
3424ad7e9b0SAdrian Chadd 	 * value */
343bb64eeccSAdrian Chadd 	pci_cidx = bhnd_get_core_index(sc->bhndb.hostb_dev);
344bb64eeccSAdrian Chadd 	if (sprom_cidx != pci_cidx) {
345bb64eeccSAdrian Chadd 		val &= ~BHND_PCI_SRSH_PI_MASK;
346bb64eeccSAdrian Chadd 		val |= (pci_cidx << BHND_PCI_SRSH_PI_SHIFT);
347bb64eeccSAdrian Chadd 		bus_write_2(core_regs,
348bb64eeccSAdrian Chadd 		    win->win_offset + srom_offset, val);
3494ad7e9b0SAdrian Chadd 	}
3504ad7e9b0SAdrian Chadd }
3514ad7e9b0SAdrian Chadd 
3524ad7e9b0SAdrian Chadd static int
3534ad7e9b0SAdrian Chadd bhndb_pci_resume(device_t dev)
3544ad7e9b0SAdrian Chadd {
3554ad7e9b0SAdrian Chadd 	struct bhndb_pci_softc	*sc;
3564ad7e9b0SAdrian Chadd 	int			 error;
3574ad7e9b0SAdrian Chadd 
3584ad7e9b0SAdrian Chadd 	sc = device_get_softc(dev);
3594ad7e9b0SAdrian Chadd 
360bb64eeccSAdrian Chadd 	/* Enable clocks (if supported by this hardware) */
361bb64eeccSAdrian Chadd 	if ((error = bhndb_enable_pci_clocks(sc)))
3624ad7e9b0SAdrian Chadd 		return (error);
3634ad7e9b0SAdrian Chadd 
364bb64eeccSAdrian Chadd 	/* Perform resume */
365bb64eeccSAdrian Chadd 	return (bhndb_generic_resume(dev));
366bb64eeccSAdrian Chadd }
367bb64eeccSAdrian Chadd 
368bb64eeccSAdrian Chadd static int
369bb64eeccSAdrian Chadd bhndb_pci_suspend(device_t dev)
370bb64eeccSAdrian Chadd {
371bb64eeccSAdrian Chadd 	struct bhndb_pci_softc	*sc;
372bb64eeccSAdrian Chadd 	int			 error;
373bb64eeccSAdrian Chadd 
374bb64eeccSAdrian Chadd 	sc = device_get_softc(dev);
375bb64eeccSAdrian Chadd 
376bb64eeccSAdrian Chadd 	/* Disable clocks (if supported by this hardware) */
377bb64eeccSAdrian Chadd 	if ((error = bhndb_disable_pci_clocks(sc)))
3784ad7e9b0SAdrian Chadd 		return (error);
3794ad7e9b0SAdrian Chadd 
380bb64eeccSAdrian Chadd 	/* Perform suspend */
381bb64eeccSAdrian Chadd 	return (bhndb_generic_suspend(dev));
382bb64eeccSAdrian Chadd }
383bb64eeccSAdrian Chadd 
384bb64eeccSAdrian Chadd static int
385bb64eeccSAdrian Chadd bhndb_pci_detach(device_t dev)
386bb64eeccSAdrian Chadd {
387bb64eeccSAdrian Chadd 	struct bhndb_pci_softc	*sc;
388bb64eeccSAdrian Chadd 	int			 error;
389bb64eeccSAdrian Chadd 
390bb64eeccSAdrian Chadd 	sc = device_get_softc(dev);
391bb64eeccSAdrian Chadd 
392bb64eeccSAdrian Chadd 	/* Disable clocks (if supported by this hardware) */
393bb64eeccSAdrian Chadd 	if ((error = bhndb_disable_pci_clocks(sc)))
394bb64eeccSAdrian Chadd 		return (error);
395bb64eeccSAdrian Chadd 
396bb64eeccSAdrian Chadd 	/* Perform detach */
397bb64eeccSAdrian Chadd 	if ((error = bhndb_generic_detach(dev)))
398bb64eeccSAdrian Chadd 		return (error);
399bb64eeccSAdrian Chadd 
400bb64eeccSAdrian Chadd 	/* Disable PCI bus mastering */
401*e83ce340SAdrian Chadd 	pci_disable_busmaster(sc->parent);
402bb64eeccSAdrian Chadd 
4034ad7e9b0SAdrian Chadd 	return (0);
4044ad7e9b0SAdrian Chadd }
4054ad7e9b0SAdrian Chadd 
4064ad7e9b0SAdrian Chadd static int
4074ad7e9b0SAdrian Chadd bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw,
4084ad7e9b0SAdrian Chadd     bhnd_addr_t addr)
4094ad7e9b0SAdrian Chadd {
4104ad7e9b0SAdrian Chadd 	struct bhndb_pci_softc *sc = device_get_softc(dev);
4114ad7e9b0SAdrian Chadd 	return (sc->set_regwin(sc, rw, addr));
4124ad7e9b0SAdrian Chadd }
4134ad7e9b0SAdrian Chadd 
4144ad7e9b0SAdrian Chadd /**
4154ad7e9b0SAdrian Chadd  * A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation.
4164ad7e9b0SAdrian Chadd  *
4174ad7e9b0SAdrian Chadd  * On siba(4) devices, it's possible that writing a PCI window register may
4184ad7e9b0SAdrian Chadd  * not succeed; it's necessary to immediately read the configuration register
4194ad7e9b0SAdrian Chadd  * and retry if not set to the desired value.
4204ad7e9b0SAdrian Chadd  *
4214ad7e9b0SAdrian Chadd  * This is not necessary on bcma(4) devices, but other than the overhead of
4224ad7e9b0SAdrian Chadd  * validating the register, there's no harm in performing the verification.
4234ad7e9b0SAdrian Chadd  */
4244ad7e9b0SAdrian Chadd static int
4254ad7e9b0SAdrian Chadd bhndb_pci_compat_setregwin(struct bhndb_pci_softc *sc,
4264ad7e9b0SAdrian Chadd     const struct bhndb_regwin *rw, bhnd_addr_t addr)
4274ad7e9b0SAdrian Chadd {
4284ad7e9b0SAdrian Chadd 	int		error;
429*e83ce340SAdrian Chadd 	int		reg;
4304ad7e9b0SAdrian Chadd 
4314ad7e9b0SAdrian Chadd 	if (rw->win_type != BHNDB_REGWIN_T_DYN)
4324ad7e9b0SAdrian Chadd 		return (ENODEV);
4334ad7e9b0SAdrian Chadd 
434*e83ce340SAdrian Chadd 	reg = rw->d.dyn.cfg_offset;
4354ad7e9b0SAdrian Chadd 	for (u_int i = 0; i < BHNDB_PCI_BARCTRL_WRITE_RETRY; i++) {
4364ad7e9b0SAdrian Chadd 		if ((error = bhndb_pci_fast_setregwin(sc, rw, addr)))
4374ad7e9b0SAdrian Chadd 			return (error);
4384ad7e9b0SAdrian Chadd 
439*e83ce340SAdrian Chadd 		if (pci_read_config(sc->parent, reg, 4) == addr)
4404ad7e9b0SAdrian Chadd 			return (0);
4414ad7e9b0SAdrian Chadd 
4424ad7e9b0SAdrian Chadd 		DELAY(10);
4434ad7e9b0SAdrian Chadd 	}
4444ad7e9b0SAdrian Chadd 
4454ad7e9b0SAdrian Chadd 	/* Unable to set window */
4464ad7e9b0SAdrian Chadd 	return (ENODEV);
4474ad7e9b0SAdrian Chadd }
4484ad7e9b0SAdrian Chadd 
4494ad7e9b0SAdrian Chadd /**
4504ad7e9b0SAdrian Chadd  * A bcma(4)-only bhndb_set_window_addr implementation.
4514ad7e9b0SAdrian Chadd  */
4524ad7e9b0SAdrian Chadd static int
4534ad7e9b0SAdrian Chadd bhndb_pci_fast_setregwin(struct bhndb_pci_softc *sc,
4544ad7e9b0SAdrian Chadd     const struct bhndb_regwin *rw, bhnd_addr_t addr)
4554ad7e9b0SAdrian Chadd {
4564ad7e9b0SAdrian Chadd 	/* The PCI bridge core only supports 32-bit addressing, regardless
4574ad7e9b0SAdrian Chadd 	 * of the bus' support for 64-bit addressing */
4584ad7e9b0SAdrian Chadd 	if (addr > UINT32_MAX)
4594ad7e9b0SAdrian Chadd 		return (ERANGE);
4604ad7e9b0SAdrian Chadd 
4614ad7e9b0SAdrian Chadd 	switch (rw->win_type) {
4624ad7e9b0SAdrian Chadd 	case BHNDB_REGWIN_T_DYN:
4634ad7e9b0SAdrian Chadd 		/* Addresses must be page aligned */
4644ad7e9b0SAdrian Chadd 		if (addr % rw->win_size != 0)
4654ad7e9b0SAdrian Chadd 			return (EINVAL);
4664ad7e9b0SAdrian Chadd 
467*e83ce340SAdrian Chadd 		pci_write_config(sc->parent, rw->d.dyn.cfg_offset, addr, 4);
4684ad7e9b0SAdrian Chadd 		break;
4694ad7e9b0SAdrian Chadd 	default:
4704ad7e9b0SAdrian Chadd 		return (ENODEV);
4714ad7e9b0SAdrian Chadd 	}
4724ad7e9b0SAdrian Chadd 
4734ad7e9b0SAdrian Chadd 	return (0);
4744ad7e9b0SAdrian Chadd }
4754ad7e9b0SAdrian Chadd 
4764ad7e9b0SAdrian Chadd /**
477bb64eeccSAdrian Chadd  * Enable externally managed clocks, if required.
4784ad7e9b0SAdrian Chadd  *
479bb64eeccSAdrian Chadd  * Some PCI chipsets (BCM4306, possibly others) chips do not support
480bb64eeccSAdrian Chadd  * the idle low-power clock. Clocking must be bootstrapped at
481bb64eeccSAdrian Chadd  * attach/resume by directly adjusting GPIO registers exposed in the
482bb64eeccSAdrian Chadd  * PCI config space, and correspondingly, explicitly shutdown at
483bb64eeccSAdrian Chadd  * detach/suspend.
4844ad7e9b0SAdrian Chadd  *
4854ad7e9b0SAdrian Chadd  * @param sc Bridge driver state.
4864ad7e9b0SAdrian Chadd  */
4874ad7e9b0SAdrian Chadd static int
4884ad7e9b0SAdrian Chadd bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc)
4894ad7e9b0SAdrian Chadd {
4904ad7e9b0SAdrian Chadd 	uint32_t		gpio_in, gpio_out, gpio_en;
4914ad7e9b0SAdrian Chadd 	uint32_t		gpio_flags;
4924ad7e9b0SAdrian Chadd 	uint16_t		pci_status;
4934ad7e9b0SAdrian Chadd 
494bb64eeccSAdrian Chadd 	/* Only supported and required on PCI devices */
495bb64eeccSAdrian Chadd 	if (sc->pci_devclass != BHND_DEVCLASS_PCI)
496bb64eeccSAdrian Chadd 		return (0);
4974ad7e9b0SAdrian Chadd 
4984ad7e9b0SAdrian Chadd 	/* Read state of XTAL pin */
499*e83ce340SAdrian Chadd 	gpio_in = pci_read_config(sc->parent, BHNDB_PCI_GPIO_IN, 4);
5004ad7e9b0SAdrian Chadd 	if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON)
5014ad7e9b0SAdrian Chadd 		return (0); /* already enabled */
5024ad7e9b0SAdrian Chadd 
5034ad7e9b0SAdrian Chadd 	/* Fetch current config */
504*e83ce340SAdrian Chadd 	gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4);
505*e83ce340SAdrian Chadd 	gpio_en = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, 4);
5064ad7e9b0SAdrian Chadd 
5074ad7e9b0SAdrian Chadd 	/* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */
5084ad7e9b0SAdrian Chadd 	gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON);
5094ad7e9b0SAdrian Chadd 	gpio_out |= gpio_flags;
5104ad7e9b0SAdrian Chadd 	gpio_en |= gpio_flags;
5114ad7e9b0SAdrian Chadd 
512*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
513*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4);
5144ad7e9b0SAdrian Chadd 	DELAY(1000);
5154ad7e9b0SAdrian Chadd 
5164ad7e9b0SAdrian Chadd 	/* Reset PLL_OFF */
5174ad7e9b0SAdrian Chadd 	gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF;
518*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
5194ad7e9b0SAdrian Chadd 	DELAY(5000);
5204ad7e9b0SAdrian Chadd 
5214ad7e9b0SAdrian Chadd 	/* Clear any PCI 'sent target-abort' flag. */
522*e83ce340SAdrian Chadd 	pci_status = pci_read_config(sc->parent, PCIR_STATUS, 2);
5234ad7e9b0SAdrian Chadd 	pci_status &= ~PCIM_STATUS_STABORT;
524*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, PCIR_STATUS, pci_status, 2);
5254ad7e9b0SAdrian Chadd 
5264ad7e9b0SAdrian Chadd 	return (0);
5274ad7e9b0SAdrian Chadd }
5284ad7e9b0SAdrian Chadd 
5294ad7e9b0SAdrian Chadd /**
530bb64eeccSAdrian Chadd  * Disable externally managed clocks, if required.
5314ad7e9b0SAdrian Chadd  *
5324ad7e9b0SAdrian Chadd  * @param sc Bridge driver state.
5334ad7e9b0SAdrian Chadd  */
5344ad7e9b0SAdrian Chadd static int
5354ad7e9b0SAdrian Chadd bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc)
5364ad7e9b0SAdrian Chadd {
5374ad7e9b0SAdrian Chadd 	uint32_t	gpio_out, gpio_en;
5384ad7e9b0SAdrian Chadd 
539bb64eeccSAdrian Chadd 	/* Only supported and required on PCI devices */
540bb64eeccSAdrian Chadd 	if (sc->pci_devclass != BHND_DEVCLASS_PCI)
541bb64eeccSAdrian Chadd 		return (0);
5424ad7e9b0SAdrian Chadd 
5434ad7e9b0SAdrian Chadd 	// TODO: Check board flags for BFL2_XTALBUFOUTEN?
5444ad7e9b0SAdrian Chadd 	// TODO: Check PCI core revision?
5454ad7e9b0SAdrian Chadd 	// TODO: Switch to 'slow' clock?
5464ad7e9b0SAdrian Chadd 
5474ad7e9b0SAdrian Chadd 	/* Fetch current config */
548*e83ce340SAdrian Chadd 	gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4);
549*e83ce340SAdrian Chadd 	gpio_en = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, 4);
5504ad7e9b0SAdrian Chadd 
5514ad7e9b0SAdrian Chadd 	/* Set PLL_OFF to HIGH, XTAL_ON to LOW. */
5524ad7e9b0SAdrian Chadd 	gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON;
5534ad7e9b0SAdrian Chadd 	gpio_out |= BHNDB_PCI_GPIO_PLL_OFF;
554*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4);
5554ad7e9b0SAdrian Chadd 
5564ad7e9b0SAdrian Chadd 	/* Enable both output pins */
5574ad7e9b0SAdrian Chadd 	gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON);
558*e83ce340SAdrian Chadd 	pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4);
5594ad7e9b0SAdrian Chadd 
5604ad7e9b0SAdrian Chadd 	return (0);
5614ad7e9b0SAdrian Chadd }
5624ad7e9b0SAdrian Chadd 
5634ad7e9b0SAdrian Chadd static device_method_t bhndb_pci_methods[] = {
5644ad7e9b0SAdrian Chadd 	/* Device interface */
5654ad7e9b0SAdrian Chadd 	DEVMETHOD(device_probe,			bhndb_pci_probe),
5664ad7e9b0SAdrian Chadd 	DEVMETHOD(device_attach,		bhndb_pci_attach),
5674ad7e9b0SAdrian Chadd 	DEVMETHOD(device_resume,		bhndb_pci_resume),
568bb64eeccSAdrian Chadd 	DEVMETHOD(device_suspend,		bhndb_pci_suspend),
569bb64eeccSAdrian Chadd 	DEVMETHOD(device_detach,		bhndb_pci_detach),
5704ad7e9b0SAdrian Chadd 
5714ad7e9b0SAdrian Chadd 	/* BHNDB interface */
5724ad7e9b0SAdrian Chadd 	DEVMETHOD(bhndb_init_full_config,	bhndb_pci_init_full_config),
5734ad7e9b0SAdrian Chadd 	DEVMETHOD(bhndb_set_window_addr,	bhndb_pci_set_window_addr),
5744ad7e9b0SAdrian Chadd 
5754ad7e9b0SAdrian Chadd 	DEVMETHOD_END
5764ad7e9b0SAdrian Chadd };
5774ad7e9b0SAdrian Chadd 
5784ad7e9b0SAdrian Chadd DEFINE_CLASS_1(bhndb, bhndb_pci_driver, bhndb_pci_methods,
5794ad7e9b0SAdrian Chadd     sizeof(struct bhndb_pci_softc), bhndb_driver);
5804ad7e9b0SAdrian Chadd 
5814ad7e9b0SAdrian Chadd MODULE_VERSION(bhndb_pci, 1);
582148ed571SAdrian Chadd MODULE_DEPEND(bhndb_pci, bhnd_pci_hostb, 1, 1, 1);
5834ad7e9b0SAdrian Chadd MODULE_DEPEND(bhndb_pci, pci, 1, 1, 1);
5844ad7e9b0SAdrian Chadd MODULE_DEPEND(bhndb_pci, bhndb, 1, 1, 1);
585148ed571SAdrian Chadd MODULE_DEPEND(bhndb_pci, bhnd, 1, 1, 1);
586