Index: head/sys/dev/bhnd/bcma/bcma.c =================================================================== --- head/sys/dev/bhnd/bcma/bcma.c (revision 305443) +++ head/sys/dev/bhnd/bcma/bcma.c (revision 305444) @@ -1,553 +1,635 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "bcmavar.h" +#include "bcma_dmp.h" + #include "bcma_eromreg.h" #include "bcma_eromvar.h" + #include /* RID used when allocating EROM table */ #define BCMA_EROM_RID 0 static bhnd_erom_class_t * bcma_get_erom_class(driver_t *driver) { return (&bcma_erom_parser); } int bcma_probe(device_t dev) { device_set_desc(dev, "BCMA BHND bus"); return (BUS_PROBE_DEFAULT); } /** * Default bcma(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation initializes internal bcma(4) state and performs * bus enumeration, and must be called by subclassing drivers in * DEVICE_ATTACH() before any other bus methods. */ int bcma_attach(device_t dev) { int error; /* Enumerate children */ if ((error = bcma_add_children(dev))) { device_delete_children(dev); return (error); } return (0); } int bcma_detach(device_t dev) { return (bhnd_generic_detach(dev)); } static int bcma_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { const struct bcma_devinfo *dinfo; const struct bhnd_core_info *ci; dinfo = device_get_ivars(child); ci = &dinfo->corecfg->core_info; switch (index) { case BHND_IVAR_VENDOR: *result = ci->vendor; return (0); case BHND_IVAR_DEVICE: *result = ci->device; return (0); case BHND_IVAR_HWREV: *result = ci->hwrev; return (0); case BHND_IVAR_DEVICE_CLASS: *result = bhnd_core_class(ci); return (0); case BHND_IVAR_VENDOR_NAME: *result = (uintptr_t) bhnd_vendor_name(ci->vendor); return (0); case BHND_IVAR_DEVICE_NAME: *result = (uintptr_t) bhnd_core_name(ci); return (0); case BHND_IVAR_CORE_INDEX: *result = ci->core_idx; return (0); case BHND_IVAR_CORE_UNIT: *result = ci->unit; return (0); default: return (ENOENT); } } static int bcma_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { 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); default: return (ENOENT); } } static struct resource_list * bcma_get_resource_list(device_t dev, device_t child) { struct bcma_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static int bcma_reset_core(device_t dev, device_t child, uint16_t flags) { struct bcma_devinfo *dinfo; if (device_get_parent(child) != dev) BHND_BUS_RESET_CORE(device_get_parent(dev), child, flags); dinfo = device_get_ivars(child); /* Can't reset the core without access to the agent registers */ if (dinfo->res_agent == NULL) return (ENODEV); /* Start reset */ bhnd_bus_write_4(dinfo->res_agent, BHND_RESET_CF, BHND_RESET_CF_ENABLE); bhnd_bus_read_4(dinfo->res_agent, BHND_RESET_CF); DELAY(10); /* Disable clock */ bhnd_bus_write_4(dinfo->res_agent, BHND_CF, flags); bhnd_bus_read_4(dinfo->res_agent, BHND_CF); DELAY(10); /* Enable clocks & force clock gating */ bhnd_bus_write_4(dinfo->res_agent, BHND_CF, BHND_CF_CLOCK_EN | BHND_CF_FGC | flags); bhnd_bus_read_4(dinfo->res_agent, BHND_CF); DELAY(10); /* Complete reset */ bhnd_bus_write_4(dinfo->res_agent, BHND_RESET_CF, 0); bhnd_bus_read_4(dinfo->res_agent, BHND_RESET_CF); DELAY(10); /* Release force clock gating */ bhnd_bus_write_4(dinfo->res_agent, BHND_CF, BHND_CF_CLOCK_EN | flags); bhnd_bus_read_4(dinfo->res_agent, BHND_CF); DELAY(10); return (0); } static int bcma_suspend_core(device_t dev, device_t child) { struct bcma_devinfo *dinfo; if (device_get_parent(child) != dev) BHND_BUS_SUSPEND_CORE(device_get_parent(dev), child); dinfo = device_get_ivars(child); /* Can't suspend the core without access to the agent registers */ if (dinfo->res_agent == NULL) return (ENODEV); // TODO - perform suspend return (ENXIO); } static uint32_t bcma_read_config(device_t dev, device_t child, bus_size_t offset, u_int width) { struct bcma_devinfo *dinfo; struct bhnd_resource *r; /* Must be a directly attached child core */ if (device_get_parent(child) != dev) return (UINT32_MAX); /* Fetch the agent registers */ dinfo = device_get_ivars(child); if ((r = dinfo->res_agent) == NULL) return (UINT32_MAX); /* Verify bounds */ if (offset > rman_get_size(r->res)) return (UINT32_MAX); if (rman_get_size(r->res) - offset < width) return (UINT32_MAX); switch (width) { case 1: return (bhnd_bus_read_1(r, offset)); case 2: return (bhnd_bus_read_2(r, offset)); case 4: return (bhnd_bus_read_4(r, offset)); default: return (UINT32_MAX); } } static void bcma_write_config(device_t dev, device_t child, bus_size_t offset, uint32_t val, u_int width) { struct bcma_devinfo *dinfo; struct bhnd_resource *r; /* Must be a directly attached child core */ if (device_get_parent(child) != dev) return; /* Fetch the agent registers */ dinfo = device_get_ivars(child); if ((r = dinfo->res_agent) == NULL) return; /* Verify bounds */ if (offset > rman_get_size(r->res)) return; if (rman_get_size(r->res) - offset < width) return; switch (width) { case 1: bhnd_bus_write_1(r, offset, val); break; case 2: bhnd_bus_write_2(r, offset, val); break; case 4: bhnd_bus_write_4(r, offset, val); break; default: break; } } static u_int bcma_get_port_count(device_t dev, device_t child, bhnd_port_type type) { struct bcma_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); switch (type) { case BHND_PORT_DEVICE: return (dinfo->corecfg->num_dev_ports); case BHND_PORT_BRIDGE: return (dinfo->corecfg->num_bridge_ports); case BHND_PORT_AGENT: return (dinfo->corecfg->num_wrapper_ports); default: device_printf(dev, "%s: unknown type (%d)\n", __func__, type); return (0); } } static u_int bcma_get_region_count(device_t dev, device_t child, bhnd_port_type type, u_int port_num) { struct bcma_devinfo *dinfo; struct bcma_sport_list *ports; struct bcma_sport *port; /* 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_num)); dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, type); STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num == port_num) return (port->sp_num_maps); } /* not found */ return (0); } static int bcma_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type, u_int port_num, u_int region_num) { struct bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, port_type); STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num != port_num) continue; STAILQ_FOREACH(map, &port->sp_maps, m_link) if (map->m_region_num == region_num) return map->m_rid; } return -1; } static int bcma_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 bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); /* Ports are always memory mapped */ if (type != SYS_RES_MEMORY) return (EINVAL); /* Starting with the most likely device list, search all three port * lists */ bhnd_port_type types[] = { BHND_PORT_DEVICE, BHND_PORT_AGENT, BHND_PORT_BRIDGE }; for (int i = 0; i < nitems(types); i++) { ports = bcma_corecfg_get_port_list(dinfo->corecfg, types[i]); STAILQ_FOREACH(port, ports, sp_link) { STAILQ_FOREACH(map, &port->sp_maps, m_link) { if (map->m_rid != rid) continue; *port_type = port->sp_type; *port_num = port->sp_num; *region_num = map->m_region_num; return (0); } } } return (ENOENT); } static int bcma_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 bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, port_type); /* Search the port list */ STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num != port_num) continue; STAILQ_FOREACH(map, &port->sp_maps, m_link) { if (map->m_region_num != region_num) continue; /* Found! */ *addr = map->m_base; *size = map->m_size; return (0); } } return (ENOENT); } +/** + * Default bcma(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT(). + * + * This implementation consults @p child's agent register block, + * returning the number of interrupt output lines routed to @p child. + */ +int +bcma_get_intr_count(device_t dev, device_t child) +{ + struct bcma_devinfo *dinfo; + uint32_t dmpcfg, oobw; + + dinfo = device_get_ivars(child); + + /* Agent block must be mapped */ + if (dinfo->res_agent == NULL) + return (0); + + /* Agent must support OOB */ + dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG); + if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB)) + return (0); + + /* Return OOB width as interrupt count */ + oobw = bhnd_bus_read_4(dinfo->res_agent, + BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR)); + if (oobw > BCMA_OOB_NUM_SEL) { + device_printf(dev, "ignoring invalid OOBOUTWIDTH for core %u: " + "%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw); + return (0); + } + + return (oobw); +} + +/** + * Default bcma(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC(). + * + * This implementation consults @p child's agent register block, + * returning the interrupt output line routed to @p child, at OOB selector + * @p intr. + */ +int +bcma_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec) +{ + struct bcma_devinfo *dinfo; + uint32_t oobsel; + + dinfo = device_get_ivars(child); + + /* Interrupt ID must be valid. */ + if (intr >= bcma_get_intr_count(dev, child)) + return (ENXIO); + + /* Fetch OOBSEL busline value */ + KASSERT(dinfo->res_agent != NULL, ("missing agent registers")); + oobsel = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT( + BCMA_OOB_BANK_INTR, intr)); + *ivec = (oobsel >> BCMA_DMP_OOBSEL_SHIFT(intr)) & + BCMA_DMP_OOBSEL_BUSLINE_MASK; + + return (0); +} + static struct bhnd_devinfo * bcma_alloc_bhnd_dinfo(device_t dev) { struct bcma_devinfo *dinfo = bcma_alloc_dinfo(dev); return ((struct bhnd_devinfo *)dinfo); } static void bcma_free_bhnd_dinfo(device_t dev, struct bhnd_devinfo *dinfo) { bcma_free_dinfo(dev, (struct bcma_devinfo *)dinfo); } /** * Scan the device enumeration ROM table, adding all valid discovered cores to * the bus. * * @param bus The bcma bus. */ int bcma_add_children(device_t bus) { bhnd_erom_t *erom; struct bcma_erom *bcma_erom; const struct bhnd_chipid *cid; struct bcma_corecfg *corecfg; struct bcma_devinfo *dinfo; device_t child; int error; cid = BHND_BUS_GET_CHIPID(bus, bus); corecfg = NULL; /* Allocate our EROM parser */ erom = bhnd_erom_alloc(&bcma_erom_parser, cid, bus, BCMA_EROM_RID); if (erom == NULL) return (ENODEV); /* Add all cores. */ bcma_erom = (struct bcma_erom *)erom; while ((error = bcma_erom_next_corecfg(bcma_erom, &corecfg)) == 0) { + int nintr; + /* Add the child device */ child = BUS_ADD_CHILD(bus, 0, NULL, -1); if (child == NULL) { error = ENXIO; goto cleanup; } /* Initialize device ivars */ dinfo = device_get_ivars(child); if ((error = bcma_init_dinfo(bus, dinfo, corecfg))) goto cleanup; /* The dinfo instance now owns the corecfg value */ corecfg = NULL; /* Allocate device's agent registers, if any */ if ((error = bcma_dinfo_alloc_agent(bus, child, dinfo))) goto cleanup; + /* Assign interrupts */ + nintr = bhnd_get_intr_count(child); + for (int rid = 0; rid < nintr; rid++) { + error = BHND_BUS_ASSIGN_INTR(bus, child, rid); + if (error) { + device_printf(bus, "failed to assign interrupt " + "%d to core %u: %d\n", rid, + BCMA_DINFO_COREIDX(dinfo), error); + } + } + /* 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); /* Issue bus callback for fully initialized child. */ BHND_BUS_CHILD_ADDED(bus, child); } /* EOF while parsing cores is expected */ if (error == ENOENT) error = 0; cleanup: bhnd_erom_free(erom); if (corecfg != NULL) bcma_free_corecfg(corecfg); if (error) device_delete_children(bus); return (error); } static device_method_t bcma_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcma_probe), DEVMETHOD(device_attach, bcma_attach), DEVMETHOD(device_detach, bcma_detach), /* Bus interface */ DEVMETHOD(bus_read_ivar, bcma_read_ivar), DEVMETHOD(bus_write_ivar, bcma_write_ivar), DEVMETHOD(bus_get_resource_list, bcma_get_resource_list), /* BHND interface */ DEVMETHOD(bhnd_bus_get_erom_class, bcma_get_erom_class), DEVMETHOD(bhnd_bus_alloc_devinfo, bcma_alloc_bhnd_dinfo), DEVMETHOD(bhnd_bus_free_devinfo, bcma_free_bhnd_dinfo), DEVMETHOD(bhnd_bus_reset_core, bcma_reset_core), DEVMETHOD(bhnd_bus_suspend_core, bcma_suspend_core), DEVMETHOD(bhnd_bus_read_config, bcma_read_config), DEVMETHOD(bhnd_bus_write_config, bcma_write_config), DEVMETHOD(bhnd_bus_get_port_count, bcma_get_port_count), DEVMETHOD(bhnd_bus_get_region_count, bcma_get_region_count), DEVMETHOD(bhnd_bus_get_port_rid, bcma_get_port_rid), DEVMETHOD(bhnd_bus_decode_port_rid, bcma_decode_port_rid), DEVMETHOD(bhnd_bus_get_region_addr, bcma_get_region_addr), + DEVMETHOD(bhnd_bus_get_intr_count, bcma_get_intr_count), + DEVMETHOD(bhnd_bus_get_core_ivec, bcma_get_core_ivec), DEVMETHOD_END }; DEFINE_CLASS_1(bhnd, bcma_driver, bcma_methods, sizeof(struct bcma_softc), bhnd_driver); MODULE_VERSION(bcma, 1); MODULE_DEPEND(bcma, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bcma/bcma_dmp.h =================================================================== --- head/sys/dev/bhnd/bcma/bcma_dmp.h (revision 305443) +++ head/sys/dev/bhnd/bcma/bcma_dmp.h (revision 305444) @@ -1,206 +1,259 @@ /*- * Copyright (c) 2015 Landon Fuller * Copyright (c) 2010 Broadcom Corporation * * Portions of this file were derived from the aidmp.h header * distributed with Broadcom's initial brcm80211 Linux driver release, as * contributed to the Linux staging repository. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BCMA_BCMA_DMP_H_ #define _BCMA_BCMA_DMP_H_ /* * PL-368 Device Management Plugin (DMP) Registers & Constants * * The "DMP" core used in Broadcom HND devices has been described * by Broadcom engineers (and in published header files) as being * ARM's PL-368 "Device Management Plugin" system IP, included with * the CoreLink AMBA Designer tooling. * * Documentation for the PL-368 is not publicly available, however, * and the only public reference by ARM to its existence appears to be * in the proprietary "NIC-301 Interconnect Device Management (PL368)" * errata publication, available to licensees as part of ARM's * CoreLink Controllers and Peripherals Engineering Errata. + * + * As such, the exact interpretation of these register definitions is + * unconfirmed, and may be incorrect. */ +#define BCMA_DMP_GET_FLAG(_value, _flag) \ + (((_value) & _flag) != 0) +#define BCMA_DMP_GET_BITS(_value, _field) \ + ((_value & _field ## _MASK) >> _field ## _SHIFT) +#define BHND_DMP_SET_BITS(_value, _field) \ + (((_value) << _field ## _SHIFT) & _field ## _MASK) + /* Out-of-band Router registers */ #define BCMA_OOB_BUSCONFIG 0x020 #define BCMA_OOB_STATUSA 0x100 #define BCMA_OOB_STATUSB 0x104 #define BCMA_OOB_STATUSC 0x108 #define BCMA_OOB_STATUSD 0x10c #define BCMA_OOB_ENABLEA0 0x200 #define BCMA_OOB_ENABLEA1 0x204 #define BCMA_OOB_ENABLEA2 0x208 #define BCMA_OOB_ENABLEA3 0x20c #define BCMA_OOB_ENABLEB0 0x280 #define BCMA_OOB_ENABLEB1 0x284 #define BCMA_OOB_ENABLEB2 0x288 #define BCMA_OOB_ENABLEB3 0x28c #define BCMA_OOB_ENABLEC0 0x300 #define BCMA_OOB_ENABLEC1 0x304 #define BCMA_OOB_ENABLEC2 0x308 #define BCMA_OOB_ENABLEC3 0x30c #define BCMA_OOB_ENABLED0 0x380 #define BCMA_OOB_ENABLED1 0x384 #define BCMA_OOB_ENABLED2 0x388 #define BCMA_OOB_ENABLED3 0x38c #define BCMA_OOB_ITCR 0xf00 #define BCMA_OOB_ITIPOOBA 0xf10 #define BCMA_OOB_ITIPOOBB 0xf14 #define BCMA_OOB_ITIPOOBC 0xf18 #define BCMA_OOB_ITIPOOBD 0xf1c #define BCMA_OOB_ITOPOOBA 0xf30 #define BCMA_OOB_ITOPOOBB 0xf34 #define BCMA_OOB_ITOPOOBC 0xf38 #define BCMA_OOB_ITOPOOBD 0xf3c -/* DMP wrapper registers */ -#define BCMA_DMP_OOBSELINA30 0x000 -#define BCMA_DMP_OOBSELINA74 0x004 -#define BCMA_DMP_OOBSELINB30 0x020 -#define BCMA_DMP_OOBSELINB74 0x024 -#define BCMA_DMP_OOBSELINC30 0x040 -#define BCMA_DMP_OOBSELINC74 0x044 -#define BCMA_DMP_OOBSELIND30 0x060 -#define BCMA_DMP_OOBSELIND74 0x064 -#define BCMA_DMP_OOBSELOUTA30 0x100 -#define BCMA_DMP_OOBSELOUTA74 0x104 -#define BCMA_DMP_OOBSELOUTB30 0x120 -#define BCMA_DMP_OOBSELOUTB74 0x124 -#define BCMA_DMP_OOBSELOUTC30 0x140 -#define BCMA_DMP_OOBSELOUTC74 0x144 -#define BCMA_DMP_OOBSELOUTD30 0x160 -#define BCMA_DMP_OOBSELOUTD74 0x164 +/* Common definitions */ +#define BCMA_OOB_NUM_BANKS 4 /**< number of OOB banks (A, B, C, D) */ +#define BCMA_OOB_NUM_SEL 8 /**< number of OOB selectors per bank */ +#define BCMA_OOB_NUM_BUSLINES 32 /**< number of bus lines managed by OOB core */ + +#define BCMA_OOB_BANKA 0 /**< bank A index */ +#define BCMA_OOB_BANKB 1 /**< bank B index */ +#define BCMA_OOB_BANKC 2 /**< bank C index */ +#define BCMA_OOB_BANKD 3 /**< bank D index */ + +/** OOB bank used for interrupt lines */ +#define BCMA_OOB_BANK_INTR BCMA_OOB_BANKA + +/* DMP agent registers */ +#define BCMA_DMP_OOBSELINA30 0x000 /**< A0-A3 input selectors */ +#define BCMA_DMP_OOBSELINA74 0x004 /**< A4-A7 input selectors */ +#define BCMA_DMP_OOBSELINB30 0x020 /**< B0-B3 input selectors */ +#define BCMA_DMP_OOBSELINB74 0x024 /**< B4-B7 input selectors */ +#define BCMA_DMP_OOBSELINC30 0x040 /**< C0-C3 input selectors */ +#define BCMA_DMP_OOBSELINC74 0x044 /**< C4-C7 input selectors */ +#define BCMA_DMP_OOBSELIND30 0x060 /**< D0-D3 input selectors */ +#define BCMA_DMP_OOBSELIND74 0x064 /**< D4-D7 input selectors */ +#define BCMA_DMP_OOBSELOUTA30 0x100 /**< A0-A3 output selectors */ +#define BCMA_DMP_OOBSELOUTA74 0x104 /**< A4-A7 output selectors */ +#define BCMA_DMP_OOBSELOUTB30 0x120 /**< B0-B3 output selectors */ +#define BCMA_DMP_OOBSELOUTB74 0x124 /**< B4-B7 output selectors */ +#define BCMA_DMP_OOBSELOUTC30 0x140 /**< C0-C3 output selectors */ +#define BCMA_DMP_OOBSELOUTC74 0x144 /**< C4-C7 output selectors */ +#define BCMA_DMP_OOBSELOUTD30 0x160 /**< D0-D3 output selectors */ +#define BCMA_DMP_OOBSELOUTD74 0x164 /**< D4-D7 output selectors */ #define BCMA_DMP_OOBSYNCA 0x200 #define BCMA_DMP_OOBSELOUTAEN 0x204 #define BCMA_DMP_OOBSYNCB 0x220 #define BCMA_DMP_OOBSELOUTBEN 0x224 #define BCMA_DMP_OOBSYNCC 0x240 #define BCMA_DMP_OOBSELOUTCEN 0x244 #define BCMA_DMP_OOBSYNCD 0x260 #define BCMA_DMP_OOBSELOUTDEN 0x264 #define BCMA_DMP_OOBAEXTWIDTH 0x300 #define BCMA_DMP_OOBAINWIDTH 0x304 #define BCMA_DMP_OOBAOUTWIDTH 0x308 #define BCMA_DMP_OOBBEXTWIDTH 0x320 #define BCMA_DMP_OOBBINWIDTH 0x324 #define BCMA_DMP_OOBBOUTWIDTH 0x328 #define BCMA_DMP_OOBCEXTWIDTH 0x340 #define BCMA_DMP_OOBCINWIDTH 0x344 #define BCMA_DMP_OOBCOUTWIDTH 0x348 #define BCMA_DMP_OOBDEXTWIDTH 0x360 #define BCMA_DMP_OOBDINWIDTH 0x364 #define BCMA_DMP_OOBDOUTWIDTH 0x368 -/* The exact interpretation of these bits is unverified; these - * are our best guesses as to their use */ -#define BCMA_DMP_OOBSEL_MASK 0xFF /**< OOBSEL config mask */ -#define BCMA_DMP_OOBSEL_0_MASK BCMA_DMP_OOBSEL_MASK -#define BCMA_DMP_OOBSEL_1_MASK BCMA_DMP_OOBSEL_MASK -#define BCMA_DMP_OOBSEL_2_MASK BCMA_DMP_OOBSEL_MASK -#define BCMA_DMP_OOBSEL_3_MASK BCMA_DMP_OOBSEL_MASK -#define BCMA_DMP_OOBSEL_0_SHIFT 0 /**< first OOBSEL config */ -#define BCMA_DMP_OOBSEL_1_SHIFT 8 /**< second OOBSEL config */ -#define BCMA_DMP_OOBSEL_2_SHIFT 16 /**< third OOBSEL config */ -#define BCMA_DMP_OOBSEL_3_SHIFT 24 /**< fouth OOBSEL config */ -#define BCMA_DMP_OOBSEL_EN (1 << 7) /**< enable bit */ +#define BCMA_DMP_OOBSEL(_base, _bank, _sel) \ + (_base + (_bank * 8) + (_sel >= 4 ? 4 : 0)) +#define BCMA_DMP_OOBSELIN(_bank, _sel) \ + BCMA_DMP_OOBSEL(BCMA_DMP_OOBSELINA30, _bank, _sel) + +#define BCMA_DMP_OOBSELOUT(_bank, _sel) \ + BCMA_DMP_OOBSEL(BCMA_DMP_OOBSELOUTA30, _bank, _sel) + +#define BCMA_DMP_OOBSYNC(_bank) (BCMA_DMP_OOBSYNCA + (_bank * 8)) +#define BCMA_DMP_OOBSELOUT_EN(_bank) (BCMA_DMP_OOBSELOUTAEN + (_bank * 8)) +#define BCMA_DMP_OOB_EXTWIDTH(_bank) (BCMA_DMP_OOBAEXTWIDTH + (_bank * 12)) +#define BCMA_DMP_OOB_INWIDTH(_bank) (BCMA_DMP_OOBAINWIDTH + (_bank * 12)) +#define BCMA_DMP_OOB_OUTWIDTH(_bank) (BCMA_DMP_OOBAOUTWIDTH + (_bank * 12)) + // This was inherited from Broadcom's aidmp.h header // Is it required for any of our use-cases? #if 0 /* defined(IL_BIGENDIAN) && defined(BCMHND74K) */ /* Selective swapped defines for those registers we need in * big-endian code. */ #define BCMA_DMP_IOCTRLSET 0x404 #define BCMA_DMP_IOCTRLCLEAR 0x400 #define BCMA_DMP_IOCTRL 0x40c #define BCMA_DMP_IOSTATUS 0x504 #define BCMA_DMP_RESETCTRL 0x804 #define BCMA_DMP_RESETSTATUS 0x800 #else /* !IL_BIGENDIAN || !BCMHND74K */ #define BCMA_DMP_IOCTRLSET 0x400 #define BCMA_DMP_IOCTRLCLEAR 0x404 #define BCMA_DMP_IOCTRL 0x408 #define BCMA_DMP_IOSTATUS 0x500 #define BCMA_DMP_RESETCTRL 0x800 #define BCMA_DMP_RESETSTATUS 0x804 #endif /* IL_BIGENDIAN && BCMHND74K */ #define BCMA_DMP_IOCTRLWIDTH 0x700 #define BCMA_DMP_IOSTATUSWIDTH 0x704 #define BCMA_DMP_RESETREADID 0x808 #define BCMA_DMP_RESETWRITEID 0x80c #define BCMA_DMP_ERRLOGCTRL 0xa00 #define BCMA_DMP_ERRLOGDONE 0xa04 #define BCMA_DMP_ERRLOGSTATUS 0xa08 #define BCMA_DMP_ERRLOGADDRLO 0xa0c #define BCMA_DMP_ERRLOGADDRHI 0xa10 #define BCMA_DMP_ERRLOGID 0xa14 #define BCMA_DMP_ERRLOGUSER 0xa18 #define BCMA_DMP_ERRLOGFLAGS 0xa1c #define BCMA_DMP_INTSTATUS 0xa00 #define BCMA_DMP_CONFIG 0xe00 #define BCMA_DMP_ITCR 0xf00 #define BCMA_DMP_ITIPOOBA 0xf10 #define BCMA_DMP_ITIPOOBB 0xf14 #define BCMA_DMP_ITIPOOBC 0xf18 #define BCMA_DMP_ITIPOOBD 0xf1c #define BCMA_DMP_ITIPOOBAOUT 0xf30 #define BCMA_DMP_ITIPOOBBOUT 0xf34 #define BCMA_DMP_ITIPOOBCOUT 0xf38 #define BCMA_DMP_ITIPOOBDOUT 0xf3c #define BCMA_DMP_ITOPOOBA 0xf50 #define BCMA_DMP_ITOPOOBB 0xf54 #define BCMA_DMP_ITOPOOBC 0xf58 #define BCMA_DMP_ITOPOOBD 0xf5c #define BCMA_DMP_ITOPOOBAIN 0xf70 #define BCMA_DMP_ITOPOOBBIN 0xf74 #define BCMA_DMP_ITOPOOBCIN 0xf78 #define BCMA_DMP_ITOPOOBDIN 0xf7c #define BCMA_DMP_ITOPRESET 0xf90 #define BCMA_DMP_PERIPHERIALID4 0xfd0 #define BCMA_DMP_PERIPHERIALID5 0xfd4 #define BCMA_DMP_PERIPHERIALID6 0xfd8 #define BCMA_DMP_PERIPHERIALID7 0xfdc #define BCMA_DMP_PERIPHERIALID0 0xfe0 #define BCMA_DMP_PERIPHERIALID1 0xfe4 #define BCMA_DMP_PERIPHERIALID2 0xfe8 #define BCMA_DMP_PERIPHERIALID3 0xfec #define BCMA_DMP_COMPONENTID0 0xff0 #define BCMA_DMP_COMPONENTID1 0xff4 #define BCMA_DMP_COMPONENTID2 0xff8 #define BCMA_DMP_COMPONENTID3 0xffc + + +/* OOBSEL(IN|OUT) */ +#define BCMA_DMP_OOBSEL_MASK 0xFF /**< OOB selector mask */ +#define BCMA_DMP_OOBSEL_EN (1<<7) /**< OOB selector enable bit */ +#define BCMA_DMP_OOBSEL_SHIFT(_sel) ((_sel % BCMA_OOB_NUM_SEL) * 8) +#define BCMA_DMP_OOBSEL_BUSLINE_MASK 0x7F /**< OOB selector bus line mask */ +#define BCMA_DMP_OOBSEL_BUSLINE_SHIFT 0 + +#define BCMA_DMP_OOBSEL_0_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_1_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_2_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_3_MASK BCMA_DMP_OOBSEL_MASK + +#define BCMA_DMP_OOBSEL_4_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_5_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_6_MASK BCMA_DMP_OOBSEL_MASK +#define BCMA_DMP_OOBSEL_7_MASK BCMA_DMP_OOBSEL_MASK + +#define BCMA_DMP_OOBSEL_0_SHIFT BCMA_DMP_OOBSEL_SHIFT(0) +#define BCMA_DMP_OOBSEL_1_SHIFT BCMA_DMP_OOBSEL_SHIFT(1) +#define BCMA_DMP_OOBSEL_2_SHIFT BCMA_DMP_OOBSEL_SHIFT(2) +#define BCMA_DMP_OOBSEL_3_SHIFT BCMA_DMP_OOBSEL_SHIFT(3) + +#define BCMA_DMP_OOBSEL_4_SHIFT BCMA_DMP_OOBSEL_0_SHIFT +#define BCMA_DMP_OOBSEL_5_SHIFT BCMA_DMP_OOBSEL_1_SHIFT +#define BCMA_DMP_OOBSEL_6_SHIFT BCMA_DMP_OOBSEL_2_SHIFT +#define BCMA_DMP_OOBSEL_7_SHIFT BCMA_DMP_OOBSEL_3_SHIFT /* resetctrl */ #define BMCA_DMP_RC_RESET 1 /* config */ #define BCMA_DMP_CFG_OOB 0x00000020 #define BCMA_DMP_CFG_IOS 0x00000010 #define BCMA_DMP_CFGIOC 0x00000008 #define BCMA_DMP_CFGTO 0x00000004 #define BCMA_DMP_CFGERRL 0x00000002 #define BCMA_DMP_CFGRST 0x00000001 #endif /* _BCMA_BCMA_DMP_H_ */ Index: head/sys/dev/bhnd/bcma/bcmavar.h =================================================================== --- head/sys/dev/bhnd/bcma/bcmavar.h (revision 305443) +++ head/sys/dev/bhnd/bcma/bcmavar.h (revision 305444) @@ -1,166 +1,169 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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. * * $FreeBSD$ */ #ifndef _BCMA_BCMAVAR_H_ #define _BCMA_BCMAVAR_H_ #include #include #include #include #include #include "bcma.h" /* * Internal definitions shared by bcma(4) driver implementations. */ /** Base resource ID for per-core agent register allocations */ #define BCMA_AGENT_RID_BASE 100 /** * Return the device's core index. * * @param _dinfo The bcma_devinfo instance to query. */ #define BCMA_DINFO_COREIDX(_dinfo) \ ((_dinfo)->corecfg->core_info.core_idx) /** BCMA port identifier. */ typedef u_int bcma_pid_t; #define BCMA_PID_MAX UINT_MAX /**< Maximum bcma_pid_t value */ /** BCMA per-port region map identifier. */ typedef u_int bcma_rmid_t; #define BCMA_RMID_MAX UINT_MAX /**< Maximum bcma_rmid_t value */ struct bcma_devinfo; struct bcma_corecfg; struct bcma_map; struct bcma_mport; struct bcma_sport; int bcma_probe(device_t dev); int bcma_attach(device_t dev); int bcma_detach(device_t dev); +int bcma_get_intr_count(device_t dev, device_t child); +int bcma_get_core_ivec(device_t dev, device_t child, + u_int intr, uint32_t *ivec); int bcma_add_children(device_t bus); struct bcma_sport_list *bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type); struct bcma_devinfo *bcma_alloc_dinfo(device_t bus); int bcma_init_dinfo(device_t bus, struct bcma_devinfo *dinfo, struct bcma_corecfg *corecfg); int bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo); void bcma_free_dinfo(device_t bus, struct bcma_devinfo *dinfo); struct bcma_corecfg *bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor, uint16_t device, uint8_t hwrev); void bcma_free_corecfg(struct bcma_corecfg *corecfg); struct bcma_sport *bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type); void bcma_free_sport(struct bcma_sport *sport); /** BCMA master port descriptor */ struct bcma_mport { bcma_pid_t mp_num; /**< AXI port identifier (bus-unique) */ bcma_pid_t mp_vid; /**< AXI master virtual ID (core-unique) */ STAILQ_ENTRY(bcma_mport) mp_link; }; /** BCMA memory region descriptor */ struct bcma_map { bcma_rmid_t m_region_num; /**< region identifier (port-unique). */ bhnd_addr_t m_base; /**< base address */ bhnd_size_t m_size; /**< size */ int m_rid; /**< bus resource id, or -1. */ STAILQ_ENTRY(bcma_map) m_link; }; /** BCMA slave port descriptor */ struct bcma_sport { bcma_pid_t sp_num; /**< slave port number (core-unique) */ bhnd_port_type sp_type; /**< port type */ u_long sp_num_maps; /**< number of regions mapped to this port */ STAILQ_HEAD(, bcma_map) sp_maps; STAILQ_ENTRY(bcma_sport) sp_link; }; STAILQ_HEAD(bcma_mport_list, bcma_mport); STAILQ_HEAD(bcma_sport_list, bcma_sport); /** BCMA IP core/block configuration */ struct bcma_corecfg { struct bhnd_core_info core_info; /**< standard core info */ u_long num_master_ports; /**< number of master port descriptors. */ struct bcma_mport_list master_ports; /**< master port descriptors */ u_long num_dev_ports; /**< number of device slave port descriptors. */ struct bcma_sport_list dev_ports; /**< device port descriptors */ u_long num_bridge_ports; /**< number of bridge slave port descriptors. */ struct bcma_sport_list bridge_ports; /**< bridge port descriptors */ u_long num_wrapper_ports; /**< number of wrapper slave port descriptors. */ struct bcma_sport_list wrapper_ports; /**< wrapper port descriptors */ }; /** * BCMA per-device info */ struct bcma_devinfo { struct bhnd_devinfo bhnd_dinfo; /**< superclass device info. */ struct resource_list resources; /**< Slave port memory regions. */ struct bcma_corecfg *corecfg; /**< IP core/block config */ struct bhnd_resource *res_agent; /**< Agent (wrapper) resource, or NULL. Not * all bcma(4) cores have or require an agent. */ int rid_agent; /**< Agent resource ID, or -1 */ }; /** BMCA per-instance state */ struct bcma_softc { struct bhnd_softc bhnd_sc; /**< bhnd state */ }; #endif /* _BCMA_BCMAVAR_H_ */ Index: head/sys/dev/bhnd/bhnd.c =================================================================== --- head/sys/dev/bhnd/bhnd.c (revision 305443) +++ head/sys/dev/bhnd/bhnd.c (revision 305444) @@ -1,1330 +1,1337 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); /* * Broadcom Home Networking Division (HND) Bus Driver. * * The Broadcom HND family of devices consists of both SoCs and host-connected * networking chipsets containing a common family of Broadcom IP cores, * including an integrated MIPS and/or ARM cores. * * HND devices expose a nearly identical interface whether accessible over a * native SoC interconnect, or when connected via a host interface such as * PCIe. As a result, the majority of hardware support code should be re-usable * across host drivers for HND networking chipsets, as well as FreeBSD support * for Broadcom MIPS/ARM HND SoCs. * * Earlier HND models used the siba(4) on-chip interconnect, while later models * use bcma(4); the programming model is almost entirely independent * of the actual underlying interconect. */ #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd.h" #include "bhndvar.h" MALLOC_DEFINE(M_BHND, "bhnd", "bhnd bus data structures"); /* Bus pass at which all bus-required children must be available, and * attachment may be finalized. */ #define BHND_FINISH_ATTACH_PASS BUS_PASS_DEFAULT /** * bhnd_generic_probe_nomatch() reporting configuration. */ static const struct bhnd_nomatch { uint16_t vendor; /**< core designer */ uint16_t device; /**< core id */ bool if_verbose; /**< print when bootverbose is set. */ } bhnd_nomatch_table[] = { { BHND_MFGID_ARM, BHND_COREID_OOB_ROUTER, true }, { BHND_MFGID_ARM, BHND_COREID_EROM, true }, { BHND_MFGID_ARM, BHND_COREID_PL301, true }, { BHND_MFGID_ARM, BHND_COREID_APB_BRIDGE, true }, { BHND_MFGID_ARM, BHND_COREID_AXI_UNMAPPED, false }, { BHND_MFGID_INVALID, BHND_COREID_INVALID, false } }; static int bhnd_delete_children(struct bhnd_softc *sc); static int bhnd_finish_attach(struct bhnd_softc *sc); static device_t bhnd_find_chipc(struct bhnd_softc *sc); static struct chipc_caps *bhnd_find_chipc_caps(struct bhnd_softc *sc); static device_t bhnd_find_platform_dev(struct bhnd_softc *sc, const char *classname); static device_t bhnd_find_pmu(struct bhnd_softc *sc); static device_t bhnd_find_nvram(struct bhnd_softc *sc); static int compare_ascending_probe_order(const void *lhs, const void *rhs); static int compare_descending_probe_order(const void *lhs, const void *rhs); /** * Default bhnd(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation calls device_probe_and_attach() for each of the device's * children, in bhnd probe order. */ int bhnd_generic_attach(device_t dev) { struct bhnd_softc *sc; device_t *devs; int ndevs; int error; if (device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); sc->dev = dev; if ((error = device_get_children(dev, &devs, &ndevs))) return (error); /* Probe and attach all children */ qsort(devs, ndevs, sizeof(*devs), compare_ascending_probe_order); for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; device_probe_and_attach(child); } /* Try to finalize attachment */ if (bus_current_pass >= BHND_FINISH_ATTACH_PASS) { if ((error = bhnd_finish_attach(sc))) goto cleanup; } cleanup: free(devs, M_TEMP); if (error) bhnd_delete_children(sc); return (error); } /** * Detach and delete all children, in reverse of their attach order. */ static int bhnd_delete_children(struct bhnd_softc *sc) { device_t *devs; int ndevs; int error; if ((error = device_get_children(sc->dev, &devs, &ndevs))) return (error); /* Detach in the reverse of attach order */ qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_delete_child(sc->dev, child))) goto cleanup; } cleanup: free(devs, M_TEMP); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_DETACH(). * * This implementation calls device_detach() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_detach() fails. */ int bhnd_generic_detach(device_t dev) { struct bhnd_softc *sc; if (!device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); return (bhnd_delete_children(sc)); } /** * Default bhnd(4) bus driver implementation of DEVICE_SHUTDOWN(). * * This implementation calls device_shutdown() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_shutdown() fails. */ int bhnd_generic_shutdown(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); if ((error = device_get_children(dev, &devs, &ndevs))) return (error); /* Shutdown in the reverse of attach order */ qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_shutdown(child))) goto cleanup; } cleanup: free(devs, M_TEMP); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_RESUME(). * * This implementation calls BUS_RESUME_CHILD() for each of the device's * children in bhnd probe order, terminating if any call to BUS_RESUME_CHILD() * fails. */ int bhnd_generic_resume(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); if ((error = device_get_children(dev, &devs, &ndevs))) return (error); qsort(devs, ndevs, sizeof(*devs), compare_ascending_probe_order); for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = BUS_RESUME_CHILD(device_get_parent(child), child))) goto cleanup; } cleanup: free(devs, M_TEMP); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_SUSPEND(). * * This implementation calls BUS_SUSPEND_CHILD() for each of the device's * children in reverse bhnd probe order. If any call to BUS_SUSPEND_CHILD() * fails, the suspend operation is terminated and any devices that were * suspended are resumed immediately by calling their BUS_RESUME_CHILD() * methods. */ int bhnd_generic_suspend(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); if ((error = device_get_children(dev, &devs, &ndevs))) return (error); /* Suspend in the reverse of attach order */ qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; error = BUS_SUSPEND_CHILD(device_get_parent(child), child); /* On error, resume suspended devices and then terminate */ if (error) { for (int j = 0; j < i; j++) { BUS_RESUME_CHILD(device_get_parent(devs[j]), devs[j]); } goto cleanup; } } cleanup: free(devs, M_TEMP); return (error); } static void bhnd_new_pass(device_t dev) { struct bhnd_softc *sc; int error; sc = device_get_softc(dev); /* Attach any permissible children */ bus_generic_new_pass(dev); /* Finalize attachment */ if (!sc->attach_done && bus_current_pass >= BHND_FINISH_ATTACH_PASS) { if ((error = bhnd_finish_attach(sc))) { panic("bhnd_finish_attach() failed: %d", error); } } } /* * Finish any pending bus attachment operations. * * When attached as a SoC bus (as opposed to a bridged WiFi device), our * platform devices may not be attached until later bus passes, necessitating * delayed initialization on our part. */ static int bhnd_finish_attach(struct bhnd_softc *sc) { struct chipc_caps *ccaps; GIANT_REQUIRED; /* for newbus */ KASSERT(bus_current_pass >= BHND_FINISH_ATTACH_PASS, ("bhnd_finish_attach() called in pass %d", bus_current_pass)); KASSERT(!sc->attach_done, ("duplicate call to bhnd_finish_attach()")); /* Locate chipc device */ if ((sc->chipc_dev = bhnd_find_chipc(sc)) == NULL) { device_printf(sc->dev, "error: ChipCommon device not found\n"); return (ENXIO); } ccaps = BHND_CHIPC_GET_CAPS(sc->chipc_dev); /* Look for NVRAM device */ if (ccaps->nvram_src != BHND_NVRAM_SRC_UNKNOWN) { if ((sc->nvram_dev = bhnd_find_nvram(sc)) == NULL) { device_printf(sc->dev, "warning: NVRAM %s device not found\n", bhnd_nvram_src_name(ccaps->nvram_src)); } } /* Look for a PMU */ if (ccaps->pmu || ccaps->pwr_ctrl) { if ((sc->pmu_dev = bhnd_find_pmu(sc)) == NULL) { device_printf(sc->dev, "attach failed: supported PMU not found\n"); return (ENXIO); } } /* Mark attach as completed */ sc->attach_done = true; return (0); } /* Locate the ChipCommon core. */ static device_t bhnd_find_chipc(struct bhnd_softc *sc) { device_t chipc; /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* chipc_dev is initialized during attachment */ if (sc->attach_done) { if ((chipc = sc->chipc_dev) == NULL) return (NULL); goto found; } /* Locate chipc core with a core unit of 0 */ chipc = bhnd_find_child(sc->dev, BHND_DEVCLASS_CC, 0); if (chipc == NULL) return (NULL); found: if (device_get_state(chipc) < DS_ATTACHING) { device_printf(sc->dev, "chipc found, but did not attach\n"); return (NULL); } return (chipc); } /* Locate the ChipCommon core and return the device capabilities */ static struct chipc_caps * bhnd_find_chipc_caps(struct bhnd_softc *sc) { device_t chipc; if ((chipc = bhnd_find_chipc(sc)) == NULL) { device_printf(sc->dev, "chipc unavailable; cannot fetch capabilities\n"); return (NULL); } return (BHND_CHIPC_GET_CAPS(chipc)); } /** * Find an attached platform device on @p dev, searching first for cores * matching @p classname, and if not found, searching the children of the first * bhnd_chipc device on the bus. * * @param sc Driver state. * @param chipc Attached ChipCommon device. * @param classname Device class to search for. * * @retval device_t A matching device. * @retval NULL If no matching device is found. */ static device_t bhnd_find_platform_dev(struct bhnd_softc *sc, const char *classname) { device_t chipc, child; /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* Look for a directly-attached child */ child = device_find_child(sc->dev, classname, -1); if (child != NULL) goto found; /* Look for the first matching ChipCommon child */ if ((chipc = bhnd_find_chipc(sc)) == NULL) { device_printf(sc->dev, "chipc unavailable; cannot locate %s\n", classname); return (NULL); } child = device_find_child(chipc, classname, -1); if (child != NULL) goto found; /* Look for a parent-attached device (e.g. nexus0 -> bhnd_nvram) */ child = device_find_child(device_get_parent(sc->dev), classname, -1); if (child == NULL) return (NULL); found: if (device_get_state(child) < DS_ATTACHING) return (NULL); return (child); } /* Locate the PMU device, if any */ static device_t bhnd_find_pmu(struct bhnd_softc *sc) { /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* pmu_dev is initialized during attachment */ if (sc->attach_done) { if (sc->pmu_dev == NULL) return (NULL); if (device_get_state(sc->pmu_dev) < DS_ATTACHING) return (NULL); return (sc->pmu_dev); } return (bhnd_find_platform_dev(sc, "bhnd_pmu")); } /* Locate the NVRAM device, if any */ static device_t bhnd_find_nvram(struct bhnd_softc *sc) { struct chipc_caps *ccaps; /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* nvram_dev is initialized during attachment */ if (sc->attach_done) { if (sc->nvram_dev == NULL) return (NULL); if (device_get_state(sc->nvram_dev) < DS_ATTACHING) return (NULL); return (sc->nvram_dev); } if ((ccaps = bhnd_find_chipc_caps(sc)) == NULL) return (NULL); if (ccaps->nvram_src == BHND_NVRAM_SRC_UNKNOWN) return (NULL); return (bhnd_find_platform_dev(sc, "bhnd_nvram")); } /* * Ascending comparison of bhnd device's probe order. */ static int compare_ascending_probe_order(const void *lhs, const void *rhs) { device_t ldev, rdev; int lorder, rorder; ldev = (*(const device_t *) lhs); rdev = (*(const device_t *) rhs); lorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(ldev), ldev); rorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(rdev), rdev); if (lorder < rorder) { return (-1); } else if (lorder > rorder) { return (1); } else { return (0); } } /* * Descending comparison of bhnd device's probe order. */ static int compare_descending_probe_order(const void *lhs, const void *rhs) { return (compare_ascending_probe_order(rhs, lhs)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_PROBE_ORDER(). * * This implementation determines probe ordering based on the device's class * and other properties, including whether the device is serving as a host * bridge. */ int bhnd_generic_get_probe_order(device_t dev, device_t child) { switch (bhnd_get_class(child)) { case BHND_DEVCLASS_CC: /* Must be early enough to provide NVRAM access to the * host bridge */ return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_CC_B: /* fall through */ case BHND_DEVCLASS_PMU: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_SOC_ROUTER: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LATE); case BHND_DEVCLASS_SOC_BRIDGE: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LAST); case BHND_DEVCLASS_CPU: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_RAM: /* fall through */ case BHND_DEVCLASS_MEMC: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_NVRAM: return (BHND_PROBE_RESOURCE + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_PCI: case BHND_DEVCLASS_PCIE: case BHND_DEVCLASS_PCCARD: case BHND_DEVCLASS_ENET: case BHND_DEVCLASS_ENET_MAC: case BHND_DEVCLASS_ENET_PHY: case BHND_DEVCLASS_WLAN: case BHND_DEVCLASS_WLAN_MAC: case BHND_DEVCLASS_WLAN_PHY: case BHND_DEVCLASS_EROM: case BHND_DEVCLASS_OTHER: case BHND_DEVCLASS_INVALID: if (bhnd_find_hostb_device(dev) == child) return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY); return (BHND_PROBE_DEFAULT); default: return (BHND_PROBE_DEFAULT); } } /** * Default bhnd(4) bus driver implementation of BHND_BUS_ALLOC_PMU(). */ int bhnd_generic_alloc_pmu(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_resource *br; struct chipc_caps *ccaps; struct bhnd_devinfo *dinfo; struct bhnd_core_pmu_info *pm; struct resource_list *rl; struct resource_list_entry *rle; device_t pmu_dev; bhnd_addr_t r_addr; bhnd_size_t r_size; bus_size_t pmu_regs; int error; GIANT_REQUIRED; /* for newbus */ sc = device_get_softc(dev); dinfo = device_get_ivars(child); pmu_regs = BHND_CLK_CTL_ST; if ((ccaps = bhnd_find_chipc_caps(sc)) == NULL) { device_printf(sc->dev, "alloc_pmu failed: chipc " "capabilities unavailable\n"); return (ENXIO); } if ((pmu_dev = bhnd_find_pmu(sc)) == NULL) { device_printf(sc->dev, "pmu unavailable; cannot allocate request state\n"); return (ENXIO); } /* already allocated? */ if (dinfo->pmu_info != NULL) { panic("duplicate PMU allocation for %s", device_get_nameunit(child)); } /* Determine address+size of the core's PMU register block */ error = bhnd_get_region_addr(child, BHND_PORT_DEVICE, 0, 0, &r_addr, &r_size); if (error) { device_printf(sc->dev, "error fetching register block info for " "%s: %d\n", device_get_nameunit(child), error); return (error); } if (r_size < (pmu_regs + sizeof(uint32_t))) { device_printf(sc->dev, "pmu offset %#jx would overrun %s " "register block\n", (uintmax_t)pmu_regs, device_get_nameunit(child)); return (ENODEV); } /* Locate actual resource containing the core's register block */ if ((rl = BUS_GET_RESOURCE_LIST(dev, child)) == NULL) { device_printf(dev, "NULL resource list returned for %s\n", device_get_nameunit(child)); return (ENXIO); } if ((rle = resource_list_find(rl, SYS_RES_MEMORY, 0)) == NULL) { device_printf(dev, "cannot locate core register resource " "for %s\n", device_get_nameunit(child)); return (ENXIO); } if (rle->res == NULL) { device_printf(dev, "core register resource unallocated for " "%s\n", device_get_nameunit(child)); return (ENXIO); } if (r_addr+pmu_regs < rman_get_start(rle->res) || r_addr+pmu_regs >= rman_get_end(rle->res)) { device_printf(dev, "core register resource does not map PMU " "registers at %#jx\n for %s\n", r_addr+pmu_regs, device_get_nameunit(child)); return (ENXIO); } /* Adjust PMU register offset relative to the actual start address * of the core's register block allocation. * * XXX: The saved offset will be invalid if bus_adjust_resource is * used to modify the resource's start address. */ if (rman_get_start(rle->res) > r_addr) pmu_regs -= rman_get_start(rle->res) - r_addr; else pmu_regs -= r_addr - rman_get_start(rle->res); /* Allocate and initialize PMU info */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) return (ENOMEM); br->res = rle->res; br->direct = ((rman_get_flags(rle->res) & RF_ACTIVE) != 0); pm = malloc(sizeof(*dinfo->pmu_info), M_BHND, M_NOWAIT); if (pm == NULL) { free(br, M_BHND); return (ENOMEM); } pm->pm_dev = child; pm->pm_pmu = pmu_dev; pm->pm_res = br; pm->pm_regs = pmu_regs; dinfo->pmu_info = pm; return (0); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_RELEASE_PMU(). */ int bhnd_generic_release_pmu(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; device_t pmu; int error; GIANT_REQUIRED; /* for newbus */ sc = device_get_softc(dev); dinfo = device_get_ivars(child); if ((pmu = bhnd_find_pmu(sc)) == NULL) { device_printf(sc->dev, "pmu unavailable; cannot release request state\n"); return (ENXIO); } /* dispatch release request */ if (dinfo->pmu_info == NULL) panic("pmu over-release for %s", device_get_nameunit(child)); if ((error = BHND_PMU_CORE_RELEASE(pmu, dinfo->pmu_info))) return (error); /* free PMU info */ free(dinfo->pmu_info->pm_res, M_BHND); free(dinfo->pmu_info, M_BHND); dinfo->pmu_info = NULL; return (0); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_REQUEST_CLOCK(). */ int bhnd_generic_request_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); dinfo = device_get_ivars(child); if ((pm = dinfo->pmu_info) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_REQ_CLOCK(pm->pm_pmu, pm, clock)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_ENABLE_CLOCKS(). */ int bhnd_generic_enable_clocks(device_t dev, device_t child, uint32_t clocks) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); dinfo = device_get_ivars(child); if ((pm = dinfo->pmu_info) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_EN_CLOCKS(pm->pm_pmu, pm, clocks)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_REQUEST_EXT_RSRC(). */ int bhnd_generic_request_ext_rsrc(device_t dev, device_t child, u_int rsrc) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); dinfo = device_get_ivars(child); if ((pm = dinfo->pmu_info) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_REQ_EXT_RSRC(pm->pm_pmu, pm, rsrc)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_RELEASE_EXT_RSRC(). */ int bhnd_generic_release_ext_rsrc(device_t dev, device_t child, u_int rsrc) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); dinfo = device_get_ivars(child); if ((pm = dinfo->pmu_info) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_RELEASE_EXT_RSRC(pm->pm_pmu, pm, rsrc)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_IS_REGION_VALID(). * * This implementation assumes that port and region numbers are 0-indexed and * are allocated non-sparsely, using BHND_BUS_GET_PORT_COUNT() and * BHND_BUS_GET_REGION_COUNT() to determine if @p port and @p region fall * within the defined range. */ static bool bhnd_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region) { if (port >= bhnd_get_port_count(child, type)) return (false); if (region >= bhnd_get_region_count(child, type, port)) return (false); return (true); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a usable NVRAM child device. * * If no usable child device is found on @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { struct bhnd_softc *sc; device_t nvram, parent; sc = device_get_softc(dev); /* If a NVRAM device is available, consult it first */ if ((nvram = bhnd_find_nvram(sc)) != NULL) return BHND_NVRAM_GETVAR(nvram, name, buf, size, type); /* Otherwise, try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Default bhnd(4) bus driver implementation of BUS_PRINT_CHILD(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ int bhnd_generic_print_child(device_t dev, device_t child) { struct resource_list *rl; int retval = 0; retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); + + if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); + + retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, + "%#jd"); } retval += printf(" at core %u", bhnd_get_core_index(child)); retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } /** * Default bhnd(4) bus driver implementation of BUS_PROBE_NOMATCH(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ void bhnd_generic_probe_nomatch(device_t dev, device_t child) { struct resource_list *rl; const struct bhnd_nomatch *nm; bool report; /* Fetch reporting configuration for this device */ report = true; for (nm = bhnd_nomatch_table; nm->device != BHND_COREID_INVALID; nm++) { if (nm->vendor != bhnd_get_vendor(child)) continue; if (nm->device != bhnd_get_device(child)) continue; report = false; if (bootverbose && nm->if_verbose) report = true; break; } if (!report) return; /* Print the non-matched device info */ device_printf(dev, "<%s %s>", bhnd_get_vendor_name(child), bhnd_get_device_name(child)); rl = BUS_GET_RESOURCE_LIST(dev, child); - if (rl != NULL) + if (rl != NULL) { resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); + resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%#jd"); + } printf(" at core %u (no driver attached)\n", bhnd_get_core_index(child)); } /** * Default implementation of BUS_CHILD_PNPINFO_STR(). */ static int bhnd_child_pnpinfo_str(device_t dev, device_t child, char *buf, size_t buflen) { if (device_get_parent(child) != dev) { return (BUS_CHILD_PNPINFO_STR(device_get_parent(dev), child, buf, buflen)); } snprintf(buf, buflen, "vendor=0x%hx device=0x%hx rev=0x%hhx", bhnd_get_vendor(child), bhnd_get_device(child), bhnd_get_hwrev(child)); return (0); } /** * Default implementation of BUS_CHILD_LOCATION_STR(). */ static int bhnd_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { bhnd_addr_t addr; bhnd_size_t size; if (device_get_parent(child) != dev) { return (BUS_CHILD_LOCATION_STR(device_get_parent(dev), child, buf, buflen)); } if (bhnd_get_region_addr(child, BHND_PORT_DEVICE, 0, 0, &addr, &size)) { /* No device default port/region */ if (buflen > 0) *buf = '\0'; return (0); } snprintf(buf, buflen, "port0.0=0x%llx", (unsigned long long) addr); return (0); } /** * Default bhnd(4) bus driver implementation of BUS_ADD_CHILD(). * * This implementation manages internal bhnd(4) state, and must be called * by subclassing drivers. */ device_t bhnd_generic_add_child(device_t dev, u_int order, const char *name, int unit) { struct bhnd_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); if ((dinfo = BHND_BUS_ALLOC_DEVINFO(dev)) == NULL) { device_delete_child(dev, child); return (NULL); } device_set_ivars(child, dinfo); return (child); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_CHILD_ADDED(). * * This implementation manages internal bhnd(4) state, and must be called * by subclassing drivers. */ void bhnd_generic_child_added(device_t dev, device_t child) { } /** * Default bhnd(4) bus driver implementation of BUS_CHILD_DELETED(). * * This implementation manages internal bhnd(4) state, and must be called * by subclassing drivers. */ void bhnd_generic_child_deleted(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_devinfo *dinfo; sc = device_get_softc(dev); /* Free device info */ if ((dinfo = device_get_ivars(child)) != NULL) { if (dinfo->pmu_info != NULL) { /* Releasing PMU requests automatically would be nice, * but we can't reference per-core PMU register * resource after driver detach */ panic("%s leaked device pmu state\n", device_get_nameunit(child)); } BHND_BUS_FREE_DEVINFO(dev, dinfo); } /* Clean up platform device references */ if (sc->chipc_dev == child) { sc->chipc_dev = NULL; } else if (sc->nvram_dev == child) { sc->nvram_dev = NULL; } else if (sc->pmu_dev == child) { sc->pmu_dev = NULL; } } /** * Helper function for implementing BUS_SUSPEND_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_suspend_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); return bus_generic_suspend_child(dev, child); } /** * Helper function for implementing BUS_RESUME_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_resume_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_RESUME_CHILD(device_get_parent(dev), child); return bus_generic_resume_child(dev, child); } /* * Delegate all indirect I/O to the parent device. When inherited by * non-bridged bus implementations, resources will never be marked as * indirect, and these methods will never be called. */ #define BHND_IO_READ(_type, _name, _method) \ static _type \ bhnd_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ return (BHND_BUS_READ_ ## _method( \ device_get_parent(dev), child, r, offset)); \ } #define BHND_IO_WRITE(_type, _name, _method) \ static void \ bhnd_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ return (BHND_BUS_WRITE_ ## _method( \ device_get_parent(dev), child, r, offset, \ value)); \ } #define BHND_IO_MISC(_type, _op, _method) \ static void \ bhnd_ ## _op (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type datap, \ bus_size_t count) \ { \ BHND_BUS_ ## _method(device_get_parent(dev), child, r, \ offset, datap, count); \ } #define BHND_IO_METHODS(_type, _size) \ BHND_IO_READ(_type, _size, _size) \ BHND_IO_WRITE(_type, _size, _size) \ \ BHND_IO_READ(_type, stream_ ## _size, STREAM_ ## _size) \ BHND_IO_WRITE(_type, stream_ ## _size, STREAM_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_ ## _size, \ READ_MULTI_ ## _size) \ BHND_IO_MISC(_type*, write_multi_ ## _size, \ WRITE_MULTI_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_stream_ ## _size, \ READ_MULTI_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_multi_stream_ ## _size, \ WRITE_MULTI_STREAM_ ## _size) \ \ BHND_IO_MISC(_type, set_multi_ ## _size, SET_MULTI_ ## _size) \ BHND_IO_MISC(_type, set_region_ ## _size, SET_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_ ## _size, \ READ_REGION_ ## _size) \ BHND_IO_MISC(_type*, write_region_ ## _size, \ WRITE_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_stream_ ## _size, \ READ_REGION_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_region_stream_ ## _size, \ WRITE_REGION_STREAM_ ## _size) \ BHND_IO_METHODS(uint8_t, 1); BHND_IO_METHODS(uint16_t, 2); BHND_IO_METHODS(uint32_t, 4); static void bhnd_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHND_BUS_BARRIER(device_get_parent(dev), child, r, offset, length, flags); } static device_method_t bhnd_methods[] = { /* Device interface */ \ DEVMETHOD(device_attach, bhnd_generic_attach), DEVMETHOD(device_detach, bhnd_generic_detach), DEVMETHOD(device_shutdown, bhnd_generic_shutdown), DEVMETHOD(device_suspend, bhnd_generic_suspend), DEVMETHOD(device_resume, bhnd_generic_resume), /* Bus interface */ DEVMETHOD(bus_new_pass, bhnd_new_pass), DEVMETHOD(bus_add_child, bhnd_generic_add_child), DEVMETHOD(bus_child_deleted, bhnd_generic_child_deleted), DEVMETHOD(bus_probe_nomatch, bhnd_generic_probe_nomatch), DEVMETHOD(bus_print_child, bhnd_generic_print_child), DEVMETHOD(bus_child_pnpinfo_str, bhnd_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, bhnd_child_location_str), DEVMETHOD(bus_suspend_child, bhnd_generic_suspend_child), DEVMETHOD(bus_resume_child, bhnd_generic_resume_child), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_config_intr, bus_generic_config_intr), DEVMETHOD(bus_bind_intr, bus_generic_bind_intr), DEVMETHOD(bus_describe_intr, bus_generic_describe_intr), DEVMETHOD(bus_get_dma_tag, bus_generic_get_dma_tag), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhnd_bus_generic_get_chipid), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bus_generic_is_hw_disabled), DEVMETHOD(bhnd_bus_read_board_info, bhnd_bus_generic_read_board_info), DEVMETHOD(bhnd_bus_get_probe_order, bhnd_generic_get_probe_order), DEVMETHOD(bhnd_bus_alloc_pmu, bhnd_generic_alloc_pmu), DEVMETHOD(bhnd_bus_release_pmu, bhnd_generic_release_pmu), DEVMETHOD(bhnd_bus_request_clock, bhnd_generic_request_clock), DEVMETHOD(bhnd_bus_enable_clocks, bhnd_generic_enable_clocks), DEVMETHOD(bhnd_bus_request_ext_rsrc, bhnd_generic_request_ext_rsrc), DEVMETHOD(bhnd_bus_release_ext_rsrc, bhnd_generic_release_ext_rsrc), DEVMETHOD(bhnd_bus_child_added, bhnd_generic_child_added), DEVMETHOD(bhnd_bus_is_region_valid, bhnd_generic_is_region_valid), DEVMETHOD(bhnd_bus_get_nvram_var, bhnd_generic_get_nvram_var), /* BHND interface (bus I/O) */ DEVMETHOD(bhnd_bus_read_1, bhnd_read_1), DEVMETHOD(bhnd_bus_read_2, bhnd_read_2), DEVMETHOD(bhnd_bus_read_4, bhnd_read_4), DEVMETHOD(bhnd_bus_write_1, bhnd_write_1), DEVMETHOD(bhnd_bus_write_2, bhnd_write_2), DEVMETHOD(bhnd_bus_write_4, bhnd_write_4), DEVMETHOD(bhnd_bus_read_stream_1, bhnd_read_stream_1), DEVMETHOD(bhnd_bus_read_stream_2, bhnd_read_stream_2), DEVMETHOD(bhnd_bus_read_stream_4, bhnd_read_stream_4), DEVMETHOD(bhnd_bus_write_stream_1, bhnd_write_stream_1), DEVMETHOD(bhnd_bus_write_stream_2, bhnd_write_stream_2), DEVMETHOD(bhnd_bus_write_stream_4, bhnd_write_stream_4), DEVMETHOD(bhnd_bus_read_multi_1, bhnd_read_multi_1), DEVMETHOD(bhnd_bus_read_multi_2, bhnd_read_multi_2), DEVMETHOD(bhnd_bus_read_multi_4, bhnd_read_multi_4), DEVMETHOD(bhnd_bus_write_multi_1, bhnd_write_multi_1), DEVMETHOD(bhnd_bus_write_multi_2, bhnd_write_multi_2), DEVMETHOD(bhnd_bus_write_multi_4, bhnd_write_multi_4), DEVMETHOD(bhnd_bus_read_multi_stream_1, bhnd_read_multi_stream_1), DEVMETHOD(bhnd_bus_read_multi_stream_2, bhnd_read_multi_stream_2), DEVMETHOD(bhnd_bus_read_multi_stream_4, bhnd_read_multi_stream_4), DEVMETHOD(bhnd_bus_write_multi_stream_1,bhnd_write_multi_stream_1), DEVMETHOD(bhnd_bus_write_multi_stream_2,bhnd_write_multi_stream_2), DEVMETHOD(bhnd_bus_write_multi_stream_4,bhnd_write_multi_stream_4), DEVMETHOD(bhnd_bus_set_multi_1, bhnd_set_multi_1), DEVMETHOD(bhnd_bus_set_multi_2, bhnd_set_multi_2), DEVMETHOD(bhnd_bus_set_multi_4, bhnd_set_multi_4), DEVMETHOD(bhnd_bus_set_region_1, bhnd_set_region_1), DEVMETHOD(bhnd_bus_set_region_2, bhnd_set_region_2), DEVMETHOD(bhnd_bus_set_region_4, bhnd_set_region_4), DEVMETHOD(bhnd_bus_read_region_1, bhnd_read_region_1), DEVMETHOD(bhnd_bus_read_region_2, bhnd_read_region_2), DEVMETHOD(bhnd_bus_read_region_4, bhnd_read_region_4), DEVMETHOD(bhnd_bus_write_region_1, bhnd_write_region_1), DEVMETHOD(bhnd_bus_write_region_2, bhnd_write_region_2), DEVMETHOD(bhnd_bus_write_region_4, bhnd_write_region_4), DEVMETHOD(bhnd_bus_read_region_stream_1,bhnd_read_region_stream_1), DEVMETHOD(bhnd_bus_read_region_stream_2,bhnd_read_region_stream_2), DEVMETHOD(bhnd_bus_read_region_stream_4,bhnd_read_region_stream_4), DEVMETHOD(bhnd_bus_write_region_stream_1, bhnd_write_region_stream_1), DEVMETHOD(bhnd_bus_write_region_stream_2, bhnd_write_region_stream_2), DEVMETHOD(bhnd_bus_write_region_stream_4, bhnd_write_region_stream_4), DEVMETHOD(bhnd_bus_barrier, bhnd_barrier), DEVMETHOD_END }; devclass_t bhnd_devclass; /**< bhnd bus. */ devclass_t bhnd_hostb_devclass; /**< bhnd bus host bridge. */ devclass_t bhnd_nvram_devclass; /**< bhnd NVRAM device */ DEFINE_CLASS_0(bhnd, bhnd_driver, bhnd_methods, sizeof(struct bhnd_softc)); MODULE_VERSION(bhnd, 1); Index: head/sys/dev/bhnd/bhnd.h =================================================================== --- head/sys/dev/bhnd/bhnd.h (revision 305443) +++ head/sys/dev/bhnd/bhnd.h (revision 305444) @@ -1,1226 +1,1265 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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. * * $FreeBSD$ */ #ifndef _BHND_BHND_H_ #define _BHND_BHND_H_ #include #include #include #include "bhnd_ids.h" #include "bhnd_types.h" #include "bhnd_erom_types.h" #include "bhnd_debug.h" #include "bhnd_bus_if.h" #include "bhnd_match.h" #include "nvram/bhnd_nvram.h" extern devclass_t bhnd_devclass; extern devclass_t bhnd_hostb_devclass; extern devclass_t bhnd_nvram_devclass; #define BHND_CHIPID_MAX_NAMELEN 32 /**< maximum buffer required for a bhnd_format_chip_id() */ /** * bhnd child instance variables */ enum bhnd_device_vars { BHND_IVAR_VENDOR, /**< Designer's JEP-106 manufacturer ID. */ BHND_IVAR_DEVICE, /**< Part number */ BHND_IVAR_HWREV, /**< Core revision */ BHND_IVAR_DEVICE_CLASS, /**< Core class (@sa bhnd_devclass_t) */ BHND_IVAR_VENDOR_NAME, /**< Core vendor name */ BHND_IVAR_DEVICE_NAME, /**< Core name */ BHND_IVAR_CORE_INDEX, /**< Bus-assigned core number */ BHND_IVAR_CORE_UNIT, /**< Bus-assigned core unit number, assigned sequentially (starting at 0) for each vendor/device pair. */ }; /** * bhnd device probe priority bands. */ enum { BHND_PROBE_ROOT = 0, /**< Nexus or host bridge */ BHND_PROBE_BUS = 1000, /**< Busses and bridges */ BHND_PROBE_CPU = 2000, /**< CPU devices */ BHND_PROBE_INTERRUPT = 3000, /**< Interrupt controllers. */ BHND_PROBE_TIMER = 4000, /**< Timers and clocks. */ BHND_PROBE_RESOURCE = 5000, /**< Resource discovery (including NVRAM/SPROM) */ BHND_PROBE_DEFAULT = 6000, /**< Default device priority */ }; /** * Constants defining fine grained ordering within a BHND_PROBE_* priority band. * * Example: * @code * BHND_PROBE_BUS + BHND_PROBE_ORDER_FIRST * @endcode */ enum { BHND_PROBE_ORDER_FIRST = 0, BHND_PROBE_ORDER_EARLY = 25, BHND_PROBE_ORDER_MIDDLE = 50, BHND_PROBE_ORDER_LATE = 75, BHND_PROBE_ORDER_LAST = 100 }; /* * Simplified accessors for bhnd device ivars */ #define BHND_ACCESSOR(var, ivar, type) \ __BUS_ACCESSOR(bhnd, var, BHND, ivar, type) BHND_ACCESSOR(vendor, VENDOR, uint16_t); BHND_ACCESSOR(device, DEVICE, uint16_t); BHND_ACCESSOR(hwrev, HWREV, uint8_t); BHND_ACCESSOR(class, DEVICE_CLASS, bhnd_devclass_t); BHND_ACCESSOR(vendor_name, VENDOR_NAME, const char *); BHND_ACCESSOR(device_name, DEVICE_NAME, const char *); BHND_ACCESSOR(core_index, CORE_INDEX, u_int); BHND_ACCESSOR(core_unit, CORE_UNIT, int); #undef BHND_ACCESSOR /** * A bhnd(4) board descriptor. */ struct bhnd_board_info { uint16_t board_vendor; /**< PCI-SIG vendor ID (even on non-PCI * devices). * * On PCI devices, this will generally * be the subsystem vendor ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_type; /**< Board type (See BHND_BOARD_*) * * On PCI devices, this will generally * be the subsystem device ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_rev; /**< Board revision. */ uint8_t board_srom_rev; /**< Board SROM format revision */ uint32_t board_flags; /**< Board flags (see BHND_BFL_*) */ uint32_t board_flags2; /**< Board flags 2 (see BHND_BFL2_*) */ uint32_t board_flags3; /**< Board flags 3 (see BHND_BFL3_*) */ }; /** * Chip Identification * * This is read from the ChipCommon ID register; on earlier bhnd(4) devices * where ChipCommon is unavailable, known values must be supplied. */ struct bhnd_chipid { uint16_t chip_id; /**< chip id (BHND_CHIPID_*) */ uint8_t chip_rev; /**< chip revision */ uint8_t chip_pkg; /**< chip package (BHND_PKGID_*) */ uint8_t chip_type; /**< chip type (BHND_CHIPTYPE_*) */ bhnd_addr_t enum_addr; /**< chip_type-specific enumeration * address; either the siba(4) base * core register block, or the bcma(4) * EROM core address. */ uint8_t ncores; /**< number of cores, if known. 0 if * not available. */ }; /** * A bhnd(4) core descriptor. */ struct bhnd_core_info { uint16_t vendor; /**< JEP-106 vendor (BHND_MFGID_*) */ uint16_t device; /**< device */ uint16_t hwrev; /**< hardware revision */ u_int core_idx; /**< bus-assigned core index */ int unit; /**< bus-assigned core unit */ }; /** * A bhnd(4) bus resource. * * This provides an abstract interface to per-core resources that may require * bus-level remapping of address windows prior to access. */ struct bhnd_resource { struct resource *res; /**< the system resource. */ bool direct; /**< false if the resource requires * bus window remapping before it * is MMIO accessible. */ }; /** Wrap the active resource @p _r in a bhnd_resource structure */ #define BHND_DIRECT_RESOURCE(_r) ((struct bhnd_resource) { \ .res = (_r), \ .direct = true, \ }) /** * Device quirk table descriptor. */ struct bhnd_device_quirk { struct bhnd_device_match desc; /**< device match descriptor */ uint32_t quirks; /**< quirk flags */ }; #define BHND_CORE_QUIRK(_rev, _flags) \ {{ BHND_MATCH_CORE_REV(_rev) }, (_flags) } #define BHND_CHIP_QUIRK(_chip, _rev, _flags) \ {{ BHND_CHIP_IR(BCM ## _chip, _rev) }, (_flags) } #define BHND_PKG_QUIRK(_chip, _pkg, _flags) \ {{ BHND_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) } #define BHND_BOARD_QUIRK(_board, _flags) \ {{ BHND_MATCH_BOARD_TYPE(_board) }, \ (_flags) } #define BHND_DEVICE_QUIRK_END { { BHND_MATCH_ANY }, 0 } #define BHND_DEVICE_QUIRK_IS_END(_q) \ (((_q)->desc.m.match_flags == 0) && (_q)->quirks == 0) enum { BHND_DF_ANY = 0, BHND_DF_HOSTB = (1<<0), /**< core is serving as the bus' host * bridge. implies BHND_DF_ADAPTER */ BHND_DF_SOC = (1<<1), /**< core is attached to a native bus (BHND_ATTACH_NATIVE) */ BHND_DF_ADAPTER = (1<<2), /**< core is attached to a bridged * adapter (BHND_ATTACH_ADAPTER) */ }; /** Device probe table descriptor */ struct bhnd_device { const struct bhnd_device_match core; /**< core match descriptor */ const char *desc; /**< device description, or NULL. */ const struct bhnd_device_quirk *quirks_table; /**< quirks table for this device, or NULL */ uint32_t device_flags; /**< required BHND_DF_* flags */ }; #define _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ _flags, ...) \ { { BHND_MATCH_CORE(BHND_MFGID_ ## _vendor, \ BHND_COREID_ ## _device) }, _desc, _quirks, \ _flags } #define BHND_DEVICE(_vendor, _device, _desc, _quirks, ...) \ _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ ## __VA_ARGS__, 0) #define BHND_DEVICE_END { { BHND_MATCH_ANY }, NULL, NULL, 0 } #define BHND_DEVICE_IS_END(_d) \ (BHND_MATCH_IS_ANY(&(_d)->core) && (_d)->desc == NULL) const char *bhnd_vendor_name(uint16_t vendor); const char *bhnd_port_type_name(bhnd_port_type port_type); const char *bhnd_nvram_src_name(bhnd_nvram_src nvram_src); const char *bhnd_find_core_name(uint16_t vendor, uint16_t device); bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device); const char *bhnd_core_name(const struct bhnd_core_info *ci); bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci); int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id); device_t bhnd_match_child(device_t dev, const struct bhnd_core_match *desc); device_t bhnd_find_child(device_t dev, bhnd_devclass_t class, int unit); device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class); const struct bhnd_core_info *bhnd_match_core( const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc); const struct bhnd_core_info *bhnd_find_core( const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class); struct bhnd_core_match bhnd_core_get_match_desc( const struct bhnd_core_info *core); bool bhnd_cores_equal( const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs); bool bhnd_core_matches( const struct bhnd_core_info *core, const struct bhnd_core_match *desc); bool bhnd_chip_matches( const struct bhnd_chipid *chipid, const struct bhnd_chip_match *desc); bool bhnd_board_matches( const struct bhnd_board_info *info, const struct bhnd_board_match *desc); bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc); bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc); const struct bhnd_device *bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size); uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size); struct bhnd_core_info bhnd_get_core_info(device_t dev); int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res); void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res); struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr); int bhnd_chipid_fixed_ncores( const struct bhnd_chipid *cid, uint16_t chipc_hwrev, uint8_t *ncores); int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result); void bhnd_set_custom_core_desc(device_t dev, const char *name); void bhnd_set_default_core_desc(device_t dev); void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id); int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen); int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value); int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value); int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value); int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value); int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value); int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value); int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t count, bhnd_nvram_type type); bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child); bool bhnd_bus_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region); int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type); const struct bhnd_chipid *bhnd_bus_generic_get_chipid(device_t dev, device_t child); int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info); struct bhnd_resource *bhnd_bus_generic_alloc_resource (device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int bhnd_bus_generic_release_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_activate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_deactivate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); bhnd_attach_type bhnd_bus_generic_get_attach_type(device_t dev, device_t child); /** * Return the bhnd(4) bus driver's device enumeration parser class * * @param driver A bhnd bus driver instance. */ static inline bhnd_erom_class_t * bhnd_driver_get_erom_class(driver_t *driver) { return (BHND_BUS_GET_EROM_CLASS(driver)); } /** * Return the active host bridge core for the bhnd bus, if any, or NULL if * not found. * * @param dev A bhnd bus device. */ static inline device_t bhnd_find_hostb_device(device_t dev) { return (BHND_BUS_FIND_HOSTB_DEVICE(dev)); } /** * Return true if the hardware components required by @p dev are known to be * unpopulated or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p dev should * be disabled. * * @param dev A bhnd bus child device. */ static inline bool bhnd_is_hw_disabled(device_t dev) { return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), dev)); } /** * Return the BHND chip identification info for the bhnd bus. * * @param dev A bhnd bus child device. */ static inline const struct bhnd_chipid * bhnd_get_chipid(device_t dev) { return (BHND_BUS_GET_CHIPID(device_get_parent(dev), dev)); }; /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ static inline bhnd_clksrc bhnd_pwrctl_get_clksrc(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GET_CLKSRC(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, gate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_gate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, ungate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_ungate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_UNGATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * Return the BHND attachment type of the parent bhnd bus. * * @param dev A bhnd bus child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ static inline bhnd_attach_type bhnd_get_attach_type (device_t dev) { return (BHND_BUS_GET_ATTACH_TYPE(device_get_parent(dev), dev)); } /** * Attempt to read the BHND board identification from the bhnd bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_read_board_info(device_t dev, struct bhnd_board_info *info) { return (BHND_BUS_READ_BOARD_INFO(device_get_parent(dev), dev, info)); } /** + * Return the number of interrupts to be assigned to @p child via + * BHND_BUS_ASSIGN_INTR(). + * + * @param dev A bhnd bus child device. + */ +static inline int +bhnd_get_intr_count(device_t dev) +{ + return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), dev)); +} + +/** + * Return the backplane interrupt vector corresponding to @p dev's given + * @p intr number. + * + * @param dev A bhnd bus child device. + * @param intr The interrupt number being queried. This is equivalent to the + * bus resource ID for the interrupt. + * @param[out] ivec On success, the assigned hardware interrupt vector be + * written to this pointer. + * + * On bcma(4) devices, this returns the OOB bus line assigned to the + * interrupt. + * + * On siba(4) devices, this returns the target OCP slave flag number assigned + * to the interrupt. + * + * @retval 0 success + * @retval ENXIO If @p intr exceeds the number of interrupts available + * to @p child. + */ +static inline int +bhnd_get_core_ivec(device_t dev, u_int intr, uint32_t *ivec) +{ + return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), dev, intr, + ivec)); +} + +/** * Allocate and enable per-core PMU request handling for @p child. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling bhnd_alloc_pmu(), and must not be released until after * calling bhnd_release_pmu(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If allocating PMU request state otherwise fails, a * regular unix error code will be returned. */ static inline int bhnd_alloc_pmu(device_t dev) { return (BHND_BUS_ALLOC_PMU(device_get_parent(dev), dev)); } /** * Release any per-core PMU resources allocated for @p child. Any outstanding * PMU requests are are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If releasing PMU request state otherwise fails, a * regular unix error code will be returned, and * the core state will be left unmodified. */ static inline int bhnd_release_pmu(device_t dev) { return (BHND_BUS_RELEASE_PMU(device_get_parent(dev), dev)); } /** * Request that @p clock (or faster) be routed to @p dev. * * A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * Request multiplexing is managed by the bus. * * @param dev The bhnd(4) device to which @p clock should be routed. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_request_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_REQUEST_CLOCK(device_get_parent(dev), dev, clock)); } /** * Request that @p clocks be powered on behalf of @p dev. * * This will power any clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p dev. * * Request multiplexing is managed by the bus. * * A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * @param dev The requesting bhnd(4) device. * @param clocks The clock(s) to be enabled. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_enable_clocks(device_t dev, uint32_t clocks) { return (BHND_BUS_ENABLE_CLOCKS(device_get_parent(dev), dev, clocks)); } /** * Power up an external PMU-managed resource assigned to @p dev. * * A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_request_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_REQUEST_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Power down an external PMU-managed resource assigned to @p dev. * * A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_release_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_RELEASE_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. */ static inline uint32_t bhnd_read_config(device_t dev, bus_size_t offset, u_int width) { return (BHND_BUS_READ_CONFIG(device_get_parent(dev), dev, offset, width)); } /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. */ static inline void bhnd_write_config(device_t dev, bus_size_t offset, uint32_t val, u_int width) { BHND_BUS_WRITE_CONFIG(device_get_parent(dev), dev, offset, val, width); } /** * Read an NVRAM variable, coerced to the requested @p type. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the requested value will be written to * this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] len The maximum capacity of @p buf. On success, * will be set to the actual size of the requested * value. * @param type The desired data representation to be written * to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EOPNOTSUPP If the value cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_nvram_getvar(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), dev, name, buf, len, type)); } /** * Allocate a resource from a device's parent bhnd(4) bus. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param start The start address of the resource. * @param end The end address of the resource. * @param count The size of the resource. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * To request the resource's default addresses, pass @p start and * @p end values of @c 0 and @c ~0, respectively, and * a @p count of @c 1. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource(device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { return BHND_BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, type, rid, start, end, count, flags); } /** * Allocate a resource from a device's parent bhnd(4) bus, using the * resource's default start, end, and count values. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource_any(device_t dev, int type, int *rid, u_int flags) { return bhnd_alloc_resource(dev, type, rid, 0, ~0, 1, flags); } /** * Activate a previously allocated bhnd resource. * * @param dev The device holding ownership of the allocated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_activate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Deactivate a previously activated bhnd resource. * * @param dev The device holding ownership of the activated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_deactivate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Free a resource allocated by bhnd_alloc_resource(). * * @param dev The device holding ownership of the resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_release_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Return true if @p region_num is a valid region on @p port_num of * @p type attached to @p dev. * * @param dev A bhnd bus child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ static inline bool bhnd_is_region_valid(device_t dev, bhnd_port_type type, u_int port_num, u_int region_num) { return (BHND_BUS_IS_REGION_VALID(device_get_parent(dev), dev, type, port_num, region_num)); } /** * Return the number of ports of type @p type attached to @p def. * * @param dev A bhnd bus child device. * @param type The port type being queried. */ static inline u_int bhnd_get_port_count(device_t dev, bhnd_port_type type) { return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), dev, type)); } /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev A bhnd bus child device. * @param port The port number being queried. * @param type The port type being queried. */ static inline u_int bhnd_get_region_count(device_t dev, bhnd_port_type type, u_int port) { return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), dev, type, port)); } /** * Return the resource-ID for a memory region on the given device port. * * @param dev A bhnd bus child device. * @param type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * * @retval int The RID for the given @p port and @p region on @p device. * @retval -1 No such port/region found. */ static inline int bhnd_get_port_rid(device_t dev, bhnd_port_type type, u_int port, u_int region) { return BHND_BUS_GET_PORT_RID(device_get_parent(dev), dev, type, port, region); } /** * Decode a port / region pair on @p dev defined by @p rid. * * @param dev A bhnd bus child device. * @param type The resource type. * @param rid The resource identifier. * @param[out] port_type The decoded port type. * @param[out] port The decoded port identifier. * @param[out] region The decoded region identifier. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_decode_port_rid(device_t dev, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), dev, type, rid, port_type, port, region); } /** * Get the address and size of @p region on @p port. * * @param dev A bhnd bus child device. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_get_region_addr(device_t dev, bhnd_port_type port_type, u_int port, u_int region, bhnd_addr_t *region_addr, bhnd_size_t *region_size) { return BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), dev, port_type, port, region, region_addr, region_size); } /* * bhnd bus-level equivalents of the bus_(read|write|set|barrier|...) * macros (compatible with bhnd_resource). * * Generated with bhnd/tools/bus_macro.sh */ #define bhnd_bus_barrier(r, o, l, f) \ ((r)->direct) ? \ bus_barrier((r)->res, (o), (l), (f)) : \ BHND_BUS_BARRIER( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (l), (f)) #define bhnd_bus_read_1(r, o) \ ((r)->direct) ? \ bus_read_1((r)->res, (o)) : \ BHND_BUS_READ_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_1(r, o, v) \ ((r)->direct) ? \ bus_write_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_1(r, o) \ ((r)->direct) ? \ bus_read_stream_1((r)->res, (o)) : \ BHND_BUS_READ_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_1(r, o, v) \ ((r)->direct) ? \ bus_write_stream_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_2(r, o) \ ((r)->direct) ? \ bus_read_2((r)->res, (o)) : \ BHND_BUS_READ_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_2(r, o, v) \ ((r)->direct) ? \ bus_write_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_2(r, o) \ ((r)->direct) ? \ bus_read_stream_2((r)->res, (o)) : \ BHND_BUS_READ_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_2(r, o, v) \ ((r)->direct) ? \ bus_write_stream_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_4(r, o) \ ((r)->direct) ? \ bus_read_4((r)->res, (o)) : \ BHND_BUS_READ_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_4(r, o, v) \ ((r)->direct) ? \ bus_write_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_4(r, o) \ ((r)->direct) ? \ bus_read_stream_4((r)->res, (o)) : \ BHND_BUS_READ_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_4(r, o, v) \ ((r)->direct) ? \ bus_write_stream_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #endif /* _BHND_BHND_H_ */ Index: head/sys/dev/bhnd/bhnd_bus_if.m =================================================================== --- head/sys/dev/bhnd/bhnd_bus_if.m (revision 305443) +++ head/sys/dev/bhnd/bhnd_bus_if.m (revision 305444) @@ -1,1254 +1,1345 @@ #- -# Copyright (c) 2015 Landon Fuller +# Copyright (c) 2015-2016 Landon Fuller # All rights reserved. # # 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. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, 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 DAMAGE. # # $FreeBSD$ #include #include #include #include #include INTERFACE bhnd_bus; # # bhnd(4) bus interface # HEADER { /* forward declarations */ struct bhnd_board_info; struct bhnd_core_info; struct bhnd_chipid; struct bhnd_devinfo; struct bhnd_resource; } CODE { #include #include static bhnd_erom_class_t * bhnd_bus_null_get_erom_class(driver_t *driver) { return (NULL); } static struct bhnd_chipid * bhnd_bus_null_get_chipid(device_t dev, device_t child) { panic("bhnd_bus_get_chipid unimplemented"); } static bhnd_attach_type bhnd_bus_null_get_attach_type(device_t dev, device_t child) { panic("bhnd_bus_get_attach_type unimplemented"); } static bhnd_clksrc bhnd_bus_null_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { return (BHND_CLKSRC_UNKNOWN); } static int bhnd_bus_null_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { panic("bhnd_bus_read_boardinfo unimplemented"); } - + + static int + bhnd_bus_null_get_intr_count(device_t dev, device_t child) + { + panic("bhnd_bus_get_intr_count unimplemented"); + } + + static int + bhnd_bus_null_assign_intr(device_t dev, device_t child, int rid) + { + panic("bhnd_bus_assign_intr unimplemented"); + } + + static int + bhnd_bus_null_get_core_ivec(device_t dev, device_t child, u_int intr, + uint32_t *ivec) + { + panic("bhnd_bus_get_core_ivec unimplemented"); + } + static void bhnd_bus_null_child_added(device_t dev, device_t child) { } static int bhnd_bus_null_alloc_pmu(device_t dev, device_t child) { panic("bhnd_bus_alloc_pmu unimplemented"); } static int bhnd_bus_null_release_pmu(device_t dev, device_t child) { panic("bhnd_bus_release_pmu unimplemented"); } static int bhnd_bus_null_request_clock(device_t dev, device_t child, bhnd_clock clock) { panic("bhnd_bus_request_clock unimplemented"); } static int bhnd_bus_null_enable_clocks(device_t dev, device_t child, uint32_t clocks) { panic("bhnd_bus_enable_clocks unimplemented"); } static int bhnd_bus_null_request_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_request_ext_rsrc unimplemented"); } static int bhnd_bus_null_release_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_release_ext_rsrc unimplemented"); } static uint32_t bhnd_bus_null_read_config(device_t dev, device_t child, bus_size_t offset, u_int width) { panic("bhnd_bus_null_read_config unimplemented"); } static void bhnd_bus_null_write_config(device_t dev, device_t child, bus_size_t offset, uint32_t val, u_int width) { panic("bhnd_bus_null_write_config unimplemented"); } static device_t bhnd_bus_null_find_hostb_device(device_t dev) { return (NULL); } static bool bhnd_bus_null_is_hw_disabled(device_t dev, device_t child) { panic("bhnd_bus_is_hw_disabled unimplemented"); } static int bhnd_bus_null_get_probe_order(device_t dev, device_t child) { panic("bhnd_bus_get_probe_order unimplemented"); } static int bhnd_bus_null_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type, u_int port, u_int region) { return (-1); } static int bhnd_bus_null_decode_port_rid(device_t dev, device_t child, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return (ENOENT); } static int bhnd_bus_null_get_region_addr(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region, bhnd_addr_t *addr, bhnd_size_t *size) { return (ENOENT); } static int bhnd_bus_null_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { return (ENODEV); } } /** * Return the bhnd(4) bus driver's device enumeration parser class. * * @param driver The bhnd bus driver instance. */ STATICMETHOD bhnd_erom_class_t * get_erom_class { driver_t *driver; } DEFAULT bhnd_bus_null_get_erom_class; /** * Return the active host bridge core for the bhnd bus, if any. * * @param dev The bhnd bus device. * * @retval device_t if a hostb device exists * @retval NULL if no hostb device is found. */ METHOD device_t find_hostb_device { device_t dev; } DEFAULT bhnd_bus_null_find_hostb_device; /** * Return true if the hardware components required by @p child are unpopulated * or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p child should * be disabled. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD bool is_hw_disabled { device_t dev; device_t child; } DEFAULT bhnd_bus_null_is_hw_disabled; /** * Return the probe (and attach) order for @p child. * * All devices on the bhnd(4) bus will be probed, attached, or resumed in * ascending order; they will be suspended, shutdown, and detached in * descending order. * * The following device methods will be dispatched in ascending probe order * by the bus: * * - DEVICE_PROBE() * - DEVICE_ATTACH() * - DEVICE_RESUME() * * The following device methods will be dispatched in descending probe order * by the bus: * * - DEVICE_SHUTDOWN() * - DEVICE_DETACH() * - DEVICE_SUSPEND() * * @param dev The device whose child is being examined. * @param child The child device. * * Refer to BHND_PROBE_* and BHND_PROBE_ORDER_* for the standard set of * priorities. */ METHOD int get_probe_order { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_probe_order; /** * Return the BHND chip identification for the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD const struct bhnd_chipid * get_chipid { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_chipid; /** * Return the BHND attachment type of the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ METHOD bhnd_attach_type get_attach_type { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_attach_type; /** * Attempt to read the BHND board identification from the parent bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int read_board_info { device_t dev; device_t child; struct bhnd_board_info *info; } DEFAULT bhnd_bus_null_read_board_info; /** * Allocate and zero-initialize a buffer suitably sized and aligned for a * bhnd_devinfo structure. * * @param dev The bhnd bus device. * * @retval non-NULL success * @retval NULL allocation failed */ METHOD struct bhnd_devinfo * alloc_devinfo { device_t dev; }; /** * Release memory previously allocated for @p devinfo. * * @param dev The bhnd bus device. * @param dinfo A devinfo buffer previously allocated via * BHND_BUS_ALLOC_DEVINFO(). */ METHOD void free_devinfo { device_t dev; struct bhnd_devinfo *dinfo; }; + + +/** + * Return the number of interrupts to be assigned to @p child via + * BHND_BUS_ASSIGN_INTR(). + * + * @param dev The bhnd bus parent of @p child. + * @param child The bhnd device for which a count should be returned. + * + * @retval 0 If no interrupts should be assigned. + * @retval non-zero The count of interrupt resource IDs to be + * assigned, starting at rid 0. + */ +METHOD int get_intr_count { + device_t dev; + device_t child; +} DEFAULT bhnd_bus_null_get_intr_count; + +/** + * Assign an interrupt to @p child via bus_set_resource(). + * + * The default bus implementation of this method should assign backplane + * interrupt values to @p child. + * + * Bridge-attached bus implementations may instead override standard + * interconnect IRQ assignment, providing IRQs inherited from the parent bus. + * + * TODO: Once we can depend on INTRNG, investigate replacing this with a + * bridge-level interrupt controller. + * + * @param dev The bhnd bus parent of @p child. + * @param child The bhnd device to which an interrupt should be assigned. + * @param rid The interrupt resource ID to be assigned. + * + * @retval 0 If an interrupt was assigned. + * @retval non-zero If assigning an interrupt otherwise fails, a regular + * unix error code will be returned. + */ +METHOD int assign_intr { + device_t dev; + device_t child; + int rid; +} DEFAULT bhnd_bus_null_assign_intr; + +/** + * Return the backplane interrupt vector corresponding to @p child's given + * @p intr number. + * + * @param dev The bhnd bus parent of @p child. + * @param child The bhnd device for which the assigned interrupt vector should + * be queried. + * @param intr The interrupt number being queried. This is equivalent to the + * bus resource ID for the interrupt. + * @param[out] ivec On success, the assigned hardware interrupt vector be + * written to this pointer. + * + * On bcma(4) devices, this returns the OOB bus line assigned to the + * interrupt. + * + * On siba(4) devices, this returns the target OCP slave flag number assigned + * to the interrupt. + * + * @retval 0 success + * @retval ENXIO If @p intr exceeds the number of interrupts available + * to @p child. + */ +METHOD int get_core_ivec { + device_t dev; + device_t child; + u_int intr; + uint32_t *ivec; +} DEFAULT bhnd_bus_null_get_core_ivec; /** * Notify a bhnd bus that a child was added. * * This method must be called by concrete bhnd(4) driver impementations * after @p child's bus state is fully initialized. * * @param dev The bhnd bus whose child is being added. * @param child The child added to @p dev. */ METHOD void child_added { device_t dev; device_t child; } DEFAULT bhnd_bus_null_child_added; /** * Reset the device's hardware core. * * @param dev The parent of @p child. * @param child The device to be reset. * @param flags Device-specific core flags to be supplied on reset. * * @retval 0 success * @retval non-zero error */ METHOD int reset_core { device_t dev; device_t child; uint16_t flags; } /** * Suspend a device hardware core. * * @param dev The parent of @p child. * @param child The device to be reset. * * @retval 0 success * @retval non-zero error */ METHOD int suspend_core { device_t dev; device_t child; } /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting a clock source. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ METHOD bhnd_clksrc pwrctl_get_clksrc { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_get_clksrc; /** * If supported by the chipset, gate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_gate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_gate_clock; /** * If supported by the chipset, ungate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_ungate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_ungate_clock; /** * Allocate and enable per-core PMU request handling for @p child. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling BHND_BUS_ALLOC_PMU(), and must not be released until after * calling BHND_BUS_RELEASE_PMU(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int alloc_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_alloc_pmu; /** * Release per-core PMU resources allocated for @p child. Any * outstanding PMU requests are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int release_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_release_pmu; /** * Request that @p clock (or faster) be routed to @p child. * * A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * Request multiplexing is managed by the bus. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_request_clock; /** * Request that @p clocks be powered on behalf of @p child. * * This will power on clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p child. * * Request multiplexing is managed by the bus. * * A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int enable_clocks { device_t dev; device_t child; uint32_t clocks; } DEFAULT bhnd_bus_null_enable_clocks; /** * Power up an external PMU-managed resource assigned to @p child. * * A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_request_ext_rsrc; /** * Power down an external PMU-managed resource assigned to @p child. * * A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource number. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int release_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_release_ext_rsrc; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. On a bcma(4) bus, this * method provides access to the first agent port of @p child; on a siba(4) bus, * this method provides access to the core's CFG0 register block. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. */ METHOD uint32_t read_config { device_t dev; device_t child; bus_size_t offset; u_int width; } DEFAULT bhnd_bus_null_read_config; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. */ METHOD void write_config { device_t dev; device_t child; bus_size_t offset; uint32_t val; u_int width; } DEFAULT bhnd_bus_null_write_config; /** * Allocate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ALLOC_RESOURCE for complete documentation. */ METHOD struct bhnd_resource * alloc_resource { device_t dev; device_t child; int type; int *rid; rman_res_t start; rman_res_t end; rman_res_t count; u_int flags; } DEFAULT bhnd_bus_generic_alloc_resource; /** * Release a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_RELEASE_RESOURCE for complete documentation. */ METHOD int release_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *res; } DEFAULT bhnd_bus_generic_release_resource; /** * Activate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ACTIVATE_RESOURCE for complete documentation. */ METHOD int activate_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *r; } DEFAULT bhnd_bus_generic_activate_resource; /** * Deactivate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_DEACTIVATE_RESOURCE for complete documentation. */ METHOD int deactivate_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *r; } DEFAULT bhnd_bus_generic_deactivate_resource; /** * Return true if @p region_num is a valid region on @p port_num of * @p type attached to @p child. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ METHOD bool is_region_valid { device_t dev; device_t child; bhnd_port_type type; u_int port_num; u_int region_num; }; /** * Return the number of ports of type @p type attached to @p child. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. */ METHOD u_int get_port_count { device_t dev; device_t child; bhnd_port_type type; }; /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev The device whose child is being examined. * @param child The child device. * @param port The port number being queried. * @param type The port type being queried. */ METHOD u_int get_region_count { device_t dev; device_t child; bhnd_port_type type; u_int port; }; /** * Return the SYS_RES_MEMORY resource-ID for a port/region pair attached to * @p child. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port_num The index of the child interconnect port. * @param region_num The index of the port-mapped address region. * * @retval -1 No such port/region found. */ METHOD int get_port_rid { device_t dev; device_t child; bhnd_port_type port_type; u_int port_num; u_int region_num; } DEFAULT bhnd_bus_null_get_port_rid; /** * Decode a port / region pair on @p child defined by @p type and @p rid. * * @param dev The bus device. * @param child The bhnd child. * @param type The resource type. * @param rid The resource ID. * @param[out] port_type The port's type. * @param[out] port The port identifier. * @param[out] region The identifier of the memory region on @p port. * * @retval 0 success * @retval non-zero No matching type/rid found. */ METHOD int decode_port_rid { device_t dev; device_t child; int type; int rid; bhnd_port_type *port_type; u_int *port; u_int *region; } DEFAULT bhnd_bus_null_decode_port_rid; /** * Get the address and size of @p region on @p port. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ METHOD int get_region_addr { device_t dev; device_t child; bhnd_port_type port_type; u_int port; u_int region; bhnd_addr_t *region_addr; bhnd_size_t *region_size; } DEFAULT bhnd_bus_null_get_region_addr; /** * Read an NVRAM variable. * * It is the responsibility of the bus to delegate this request to * the appropriate NVRAM child device, or to a parent bus implementation. * * @param dev The bus device. * @param child The requesting device. * @param name The NVRAM variable name. * @param[out] buf On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] size The capacity of @p buf. On success, will be set * to the actual size of the requested value. * @param type The data type to be written to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p size is too * small to hold the requested value. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the @p name's data type cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int get_nvram_var { device_t dev; device_t child; const char *name; void *buf; size_t *size; bhnd_nvram_type type; } DEFAULT bhnd_bus_null_get_nvram_var; /** An implementation of bus_read_1() compatible with bhnd_resource */ METHOD uint8_t read_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_2() compatible with bhnd_resource */ METHOD uint16_t read_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_4() compatible with bhnd_resource */ METHOD uint32_t read_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_1() compatible with bhnd_resource */ METHOD void write_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_2() compatible with bhnd_resource */ METHOD void write_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_4() compatible with bhnd_resource */ METHOD void write_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_stream_1() compatible with bhnd_resource */ METHOD uint8_t read_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_2() compatible with bhnd_resource */ METHOD uint16_t read_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_4() compatible with bhnd_resource */ METHOD uint32_t read_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_stream_1() compatible with bhnd_resource */ METHOD void write_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_stream_2() compatible with bhnd_resource */ METHOD void write_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_stream_4() compatible with bhnd_resource */ METHOD void write_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_multi_1() compatible with bhnd_resource */ METHOD void read_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_2() compatible with bhnd_resource */ METHOD void read_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_4() compatible with bhnd_resource */ METHOD void read_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_1() compatible with bhnd_resource */ METHOD void write_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_2() compatible with bhnd_resource */ METHOD void write_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_4() compatible with bhnd_resource */ METHOD void write_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_1() compatible * bhnd_resource */ METHOD void read_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_2() compatible * bhnd_resource */ METHOD void read_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_4() compatible * bhnd_resource */ METHOD void read_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_1() compatible * bhnd_resource */ METHOD void write_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_2() compatible with * bhnd_resource */ METHOD void write_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_4() compatible with * bhnd_resource */ METHOD void write_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_set_multi_1() compatible with bhnd_resource */ METHOD void set_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_multi_2() compatible with bhnd_resource */ METHOD void set_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_multi_4() compatible with bhnd_resource */ METHOD void set_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_set_region_1() compatible with bhnd_resource */ METHOD void set_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_region_2() compatible with bhnd_resource */ METHOD void set_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_region_4() compatible with bhnd_resource */ METHOD void set_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_read_region_1() compatible with bhnd_resource */ METHOD void read_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_2() compatible with bhnd_resource */ METHOD void read_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_4() compatible with bhnd_resource */ METHOD void read_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_1() compatible with * bhnd_resource */ METHOD void read_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_2() compatible with * bhnd_resource */ METHOD void read_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_4() compatible with * bhnd_resource */ METHOD void read_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_1() compatible with bhnd_resource */ METHOD void write_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_2() compatible with bhnd_resource */ METHOD void write_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_4() compatible with bhnd_resource */ METHOD void write_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_1() compatible with * bhnd_resource */ METHOD void write_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_2() compatible with * bhnd_resource */ METHOD void write_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_4() compatible with * bhnd_resource */ METHOD void write_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_barrier() compatible with bhnd_resource */ METHOD void barrier { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; bus_size_t length; int flags; } Index: head/sys/dev/bhnd/bhnd_nexus.c =================================================================== --- head/sys/dev/bhnd/bhnd_nexus.c (revision 305443) +++ head/sys/dev/bhnd/bhnd_nexus.c (revision 305444) @@ -1,136 +1,145 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); /* * bhnd(4) driver mix-in providing shared common methods for * bhnd bus devices attached via a root nexus. */ #include #include #include #include #include #include #include #include #include #include #include "bhnd_nexusvar.h" static const struct resource_spec bhnd_nexus_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* chipc registers */ { -1, 0, 0 } }; /** * Map ChipCommon's register block and read the chip identifier data. * * @param dev A bhnd_nexus device. * @param chipid On success, will be populated with the chip identifier. * @retval 0 success * @retval non-zero An error occurred reading the chip identifier.. */ int bhnd_nexus_read_chipid(device_t dev, struct bhnd_chipid *chipid) { struct resource_spec rspec[nitems(bhnd_nexus_res_spec)]; int error; memcpy(rspec, bhnd_nexus_res_spec, sizeof(rspec)); error = bhnd_read_chipid(dev, rspec, 0, chipid); if (error) device_printf(dev, "error %d reading chip ID\n", error); return (error); } static bool bhnd_nexus_is_hw_disabled(device_t dev, device_t child) { return (false); } static bhnd_attach_type bhnd_nexus_get_attach_type(device_t dev, device_t child) { return (BHND_ATTACH_NATIVE); } static int bhnd_nexus_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ if ((error = bus_activate_resource(child, type, rid, r->res))) return (error); r->direct = true; return (0); } static int bhnd_nexus_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ KASSERT(r->direct, ("indirect resource delegated to bhnd_nexus\n")); if ((error = bus_deactivate_resource(child, type, rid, r->res))) return (error); r->direct = false; return (0); } +static int +bhnd_nexus_get_intr_count(device_t dev, device_t child) +{ + // TODO: arch-specific interrupt handling. + return (0); +} + static device_method_t bhnd_nexus_methods[] = { /* bhnd interface */ DEVMETHOD(bhnd_bus_activate_resource, bhnd_nexus_activate_resource), DEVMETHOD(bhnd_bus_deactivate_resource, bhnd_nexus_deactivate_resource), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_nexus_is_hw_disabled), DEVMETHOD(bhnd_bus_get_attach_type, bhnd_nexus_get_attach_type), + + DEVMETHOD(bhnd_bus_get_intr_count, bhnd_nexus_get_intr_count), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd, bhnd_nexus_driver, bhnd_nexus_methods, sizeof(struct bhnd_softc)); Index: head/sys/dev/bhnd/bhndb/bhnd_bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhnd_bhndb.c (revision 305443) +++ head/sys/dev/bhnd/bhndb/bhnd_bhndb.c (revision 305444) @@ -1,137 +1,145 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "bhndbvar.h" /* * bhnd(4) driver mix-in providing a shared common methods for * bhnd devices attached via a bhndb bridge. */ static int bhnd_bhndb_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { int error; /* Initialize with NVRAM-derived values */ if ((error = bhnd_bus_generic_read_board_info(dev, child, info))) return (error); /* Let the bridge fill in any additional data */ return (BHNDB_POPULATE_BOARD_INFO(device_get_parent(dev), dev, info)); } static bhnd_attach_type bhnd_bhndb_get_attach_type(device_t dev, device_t child) { /* It's safe to assume that a bridged device is always an adapter */ return (BHND_ATTACH_ADAPTER); } static bool bhnd_bhndb_is_hw_disabled(device_t dev, device_t child) { struct bhnd_core_info core = bhnd_get_core_info(child); /* Delegate to parent bridge */ return (BHNDB_IS_CORE_DISABLED(device_get_parent(dev), dev, &core)); } static device_t bhnd_bhndb_find_hostb_device(device_t dev) { struct bhnd_core_info core; struct bhnd_core_match md; int error; /* Ask the bridge for the hostb core info */ if ((error = BHNDB_GET_HOSTB_CORE(device_get_parent(dev), dev, &core))) return (NULL); /* Find the corresponding bus device */ md = bhnd_core_get_match_desc(&core); return (bhnd_match_child(dev, &md)); } +static int +bhnd_bhndb_assign_intr(device_t dev, device_t child, int rid) +{ + /* Delegate to parent bridge */ + return (BHND_BUS_ASSIGN_INTR(device_get_parent(dev), child, rid)); +} + static bhnd_clksrc bhnd_bhndb_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_GET_CLKSRC(device_get_parent(dev), child, clock)); } static int bhnd_bhndb_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_GATE_CLOCK(device_get_parent(dev), child, clock)); } static int bhnd_bhndb_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_UNGATE_CLOCK(device_get_parent(dev), child, clock)); } static device_method_t bhnd_bhndb_methods[] = { /* BHND interface */ DEVMETHOD(bhnd_bus_get_attach_type, bhnd_bhndb_get_attach_type), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bhndb_is_hw_disabled), DEVMETHOD(bhnd_bus_find_hostb_device, bhnd_bhndb_find_hostb_device), DEVMETHOD(bhnd_bus_read_board_info, bhnd_bhndb_read_board_info), + DEVMETHOD(bhnd_bus_assign_intr, bhnd_bhndb_assign_intr), DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhnd_bhndb_pwrctl_get_clksrc), DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhnd_bhndb_pwrctl_gate_clock), DEVMETHOD(bhnd_bus_pwrctl_ungate_clock, bhnd_bhndb_pwrctl_ungate_clock), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd, bhnd_bhndb_driver, bhnd_bhndb_methods, 0); Index: head/sys/dev/bhnd/bhndb/bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb.c (revision 305443) +++ head/sys/dev/bhnd/bhndb/bhndb.c (revision 305444) @@ -1,2229 +1,2208 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); /* * Abstract BHND Bridge Device Driver * * Provides generic support for bridging from a parent bus (such as PCI) to * a BHND-compatible bus (e.g. bcma or siba). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhndbvar.h" #include "bhndb_bus_if.h" #include "bhndb_hwdata.h" #include "bhndb_private.h" /* Debugging flags */ static u_long bhndb_debug = 0; TUNABLE_ULONG("hw.bhndb.debug", &bhndb_debug); enum { BHNDB_DEBUG_PRIO = 1 << 0, }; #define BHNDB_DEBUG(_type) (BHNDB_DEBUG_ ## _type & bhndb_debug) static int bhndb_find_hostb_core(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhnd_core_info *core); static bhnd_erom_class_t *bhndb_probe_erom_class(struct bhndb_softc *sc, struct bhnd_chipid *cid); static int bhndb_init_full_config(struct bhndb_softc *sc, bhnd_erom_class_t *eromcls); static struct bhnd_core_info *bhndb_get_bridge_core(struct bhndb_softc *sc); static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw); static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *r, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table); static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw); bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child); static struct rman *bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type); static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size); static int bhndb_activate_static_region( struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r); static int bhndb_try_activate_resource( struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect); static inline struct bhndb_dw_alloc *bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset); /** * Default bhndb(4) implementation of DEVICE_PROBE(). * * This function provides the default bhndb implementation of DEVICE_PROBE(), * and is compatible with bhndb(4) bridges attached via bhndb_attach_bridge(). */ int bhndb_generic_probe(device_t dev) { return (BUS_PROBE_NOWILDCARD); } static void bhndb_probe_nomatch(device_t dev, device_t child) { const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> (no driver attached)\n", name); } static int bhndb_print_child(device_t dev, device_t child) { struct bhndb_softc *sc; struct resource_list *rl; int retval = 0; sc = device_get_softc(dev); retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); } retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } static int bhndb_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { *buf = '\0'; return (0); } static int bhndb_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { struct bhndb_softc *sc; sc = device_get_softc(dev); snprintf(buf, buflen, "base=0x%llx", (unsigned long long) sc->chipid.enum_addr); return (0); } /** * Return the bridge core info. Will panic if the bridge core info has not yet * been populated during full bridge configuration. * * @param sc BHNDB device state. */ static struct bhnd_core_info * bhndb_get_bridge_core(struct bhndb_softc *sc) { if (!sc->have_br_core) panic("bridge not yet fully configured; no bridge core!"); return (&sc->bridge_core); } /** * Return true if @p cores matches the @p hw specification. * * @param sc BHNDB device state. * @param cores A device table to match against. * @param ncores The number of cores in @p cores. * @param hw The hardware description to be matched against. */ static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw) { for (u_int i = 0; i < hw->num_hw_reqs; i++) { const struct bhnd_core_match *match; bool found; match = &hw->hw_reqs[i]; found = false; for (u_int d = 0; d < ncores; d++) { struct bhnd_core_info *core = &cores[d]; if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; if (!bhnd_core_matches(core, match)) continue; found = true; break; } if (!found) return (false); } return (true); } /** * Initialize the region maps and priority configuration in @p br using * the priority @p table and the set of cores enumerated by @p erom. * * @param sc The bhndb device state. * @param br The resource state to be configured. * @param erom EROM parser used to enumerate @p cores. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param table Hardware priority table to be used to determine the relative * priorities of per-core port resources. */ static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *br, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table) { const struct bhndb_hw_priority *hp; bhnd_addr_t addr; bhnd_size_t size; size_t prio_low, prio_default, prio_high; int error; /* The number of port regions per priority band that must be accessible * via dynamic register windows */ prio_low = 0; prio_default = 0; prio_high = 0; /* * Register bridge regions covering all statically mapped ports. */ for (u_int i = 0; i < ncores; i++) { const struct bhndb_regwin *regw; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); for (regw = br->cfg->register_windows; regw->win_type != BHNDB_REGWIN_T_INVALID; regw++) { /* Only core windows are supported */ if (regw->win_type != BHNDB_REGWIN_T_CORE) continue; /* Skip non-matching cores. */ if (!bhndb_regwin_match_core(regw, core)) continue; /* Fetch the base address of the mapped port */ error = bhnd_erom_lookup_core_addr(erom, &md, regw->d.core.port_type, regw->d.core.port, regw->d.core.region, NULL, &addr, &size); if (error) { /* Skip non-applicable register windows */ if (error == ENOENT) continue; return (error); } /* * Always defer to the register window's size. * * If the port size is smaller than the window size, * this ensures that we fully utilize register windows * larger than the referenced port. * * If the port size is larger than the window size, this * ensures that we do not directly map the allocations * within the region to a too-small window. */ size = regw->win_size; /* * Add to the bus region list. * * The window priority for a statically mapped * region is always HIGH. */ error = bhndb_add_resource_region(br, addr, size, BHNDB_PRIORITY_HIGH, regw); if (error) return (error); } } /* * Perform priority accounting and register bridge regions for all * ports defined in the priority table */ for (u_int i = 0; i < ncores; i++) { struct bhndb_region *region; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); /* * Skip priority accounting for cores that ... */ /* ... do not require bridge resources */ if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; /* ... do not have a priority table entry */ hp = bhndb_hw_priority_find_core(table, core); if (hp == NULL) continue; /* ... are explicitly disabled in the priority table. */ if (hp->priority == BHNDB_PRIORITY_NONE) continue; /* Determine the number of dynamic windows required and * register their bus_region entries. */ for (u_int i = 0; i < hp->num_ports; i++) { const struct bhndb_port_priority *pp; pp = &hp->ports[i]; /* Fetch the address+size of the mapped port. */ error = bhnd_erom_lookup_core_addr(erom, &md, pp->type, pp->port, pp->region, NULL, &addr, &size); if (error) { /* Skip ports not defined on this device */ if (error == ENOENT) continue; return (error); } /* Skip ports with an existing static mapping */ region = bhndb_find_resource_region(br, addr, size); if (region != NULL && region->static_regwin != NULL) continue; /* Define a dynamic region for this port */ error = bhndb_add_resource_region(br, addr, size, pp->priority, NULL); if (error) return (error); /* Update port mapping counts */ switch (pp->priority) { case BHNDB_PRIORITY_NONE: break; case BHNDB_PRIORITY_LOW: prio_low++; break; case BHNDB_PRIORITY_DEFAULT: prio_default++; break; case BHNDB_PRIORITY_HIGH: prio_high++; break; } } } /* Determine the minimum priority at which we'll allocate direct * register windows from our dynamic pool */ size_t prio_total = prio_low + prio_default + prio_high; if (prio_total <= br->dwa_count) { /* low+default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_LOW; } else if (prio_default + prio_high <= br->dwa_count) { /* default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_DEFAULT; } else { /* high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_HIGH; } if (BHNDB_DEBUG(PRIO)) { struct bhndb_region *region; const char *direct_msg, *type_msg; bhndb_priority_t prio, prio_min; prio_min = br->min_prio; device_printf(sc->dev, "min_prio: %d\n", prio_min); STAILQ_FOREACH(region, &br->bus_regions, link) { prio = region->priority; direct_msg = prio >= prio_min ? "direct" : "indirect"; type_msg = region->static_regwin ? "static" : "dynamic"; device_printf(sc->dev, "region 0x%llx+0x%llx priority " "%u %s/%s\n", (unsigned long long) region->addr, (unsigned long long) region->size, region->priority, direct_msg, type_msg); } } return (0); } /** * Find a hardware specification for @p dev. * * @param sc The bhndb device state. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param[out] hw On success, the matched hardware specification. * with @p dev. * * @retval 0 success * @retval non-zero if an error occurs fetching device info for comparison. */ static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw) { const struct bhndb_hw *next, *hw_table; /* Search for the first matching hardware config. */ hw_table = BHNDB_BUS_GET_HARDWARE_TABLE(sc->parent_dev, sc->dev); for (next = hw_table; next->hw_reqs != NULL; next++) { if (!bhndb_hw_matches(sc, cores, ncores, next)) continue; /* Found */ *hw = next; return (0); } return (ENOENT); } /** * Helper function that must be called by subclass bhndb(4) drivers * when implementing DEVICE_ATTACH() before calling any bhnd(4) or bhndb(4) * APIs on the bridge device. * * This function will add a bridged bhnd(4) child device with a device order of * BHND_PROBE_BUS. Any subclass bhndb(4) driver may use the BHND_PROBE_* * priority bands to add additional devices that will be attached in * their preferred order relative to the bridged bhnd(4) bus. * * @param dev The bridge device to attach. * @param bridge_devclass The device class of the bridging core. This is used * to automatically detect the bridge core, and to disable additional bridge * cores (e.g. PCMCIA on a PCIe device). */ int bhndb_attach(device_t dev, bhnd_devclass_t bridge_devclass) { struct bhndb_devinfo *dinfo; struct bhndb_softc *sc; const struct bhndb_hwcfg *cfg; bhnd_erom_class_t *eromcls; int error; sc = device_get_softc(dev); sc->dev = dev; sc->parent_dev = device_get_parent(dev); sc->bridge_class = bridge_devclass; BHNDB_LOCK_INIT(sc); /* Populate generic resource allocation state. */ cfg = BHNDB_BUS_GET_GENERIC_HWCFG(sc->parent_dev, sc->dev); sc->bus_res = bhndb_alloc_resources(dev, sc->parent_dev, cfg); if (sc->bus_res == NULL) { return (ENXIO); } /* Allocate our host resources */ if ((error = bhndb_alloc_host_resources(sc->bus_res))) goto failed; /* Probe for a usable EROM class for our bridged bhnd(4) bus and * populate our chip identifier. */ BHNDB_LOCK(sc); if ((eromcls = bhndb_probe_erom_class(sc, &sc->chipid)) == NULL) { BHNDB_UNLOCK(sc); device_printf(sc->dev, "device enumeration unsupported; no " "compatible driver found\n"); return (ENXIO); } BHNDB_UNLOCK(sc); /* Add our bridged bus device */ sc->bus_dev = BUS_ADD_CHILD(dev, BHND_PROBE_BUS, "bhnd", -1); if (sc->bus_dev == NULL) { error = ENXIO; goto failed; } dinfo = device_get_ivars(sc->bus_dev); dinfo->addrspace = BHNDB_ADDRSPACE_BRIDGED; /* Enumerate the bridged device and fully initialize our bridged * resource configuration */ if ((error = bhndb_init_full_config(sc, eromcls))) { device_printf(sc->dev, "initializing full bridge " "configuration failed: %d\n", error); goto failed; } return (0); failed: BHNDB_LOCK_DESTROY(sc); if (sc->bus_res != NULL) bhndb_free_resources(sc->bus_res); return (error); } /** * Return a borrowed reference to the host resource mapping at least * BHND_DEFAULT_CORE_SIZE bytes at the first bus core, for use with * bhnd_erom_probe(). * * This may return a borrowed reference to a bhndb_dw_alloc-managed * resource; any additional resource mapping requests may invalidate this * borrowed reference. * * @param sc BHNDB driver state. * @param[out] offset On success, the offset within the returned resource * at which the first bus core can be found. * * @retval non-NULL success. * @retval NULL If no usable mapping could be found. */ static struct resource * bhndb_erom_chipc_resource(struct bhndb_softc *sc, bus_size_t *offset) { const struct bhndb_hwcfg *cfg; struct bhndb_dw_alloc *dwa; struct resource *res; const struct bhndb_regwin *win; BHNDB_LOCK_ASSERT(sc, MA_OWNED); cfg = sc->bus_res->cfg; /* Find a static register window mapping ChipCommon. */ win = bhndb_regwin_find_core(cfg->register_windows, BHND_DEVCLASS_CC, 0, BHND_PORT_DEVICE, 0, 0); if (win != NULL) { if (win->win_size < BHND_DEFAULT_CORE_SIZE) { device_printf(sc->dev, "chipcommon register window too small\n"); return (NULL); } res = bhndb_find_regwin_resource(sc->bus_res, win); if (res == NULL) { device_printf(sc->dev, "chipcommon register window not allocated\n"); return (NULL); } *offset = win->win_offset; return (res); } /* We'll need to fetch and configure a dynamic window. We can assume a * device without a static ChipCommon mapping uses the default siba(4) * base address. */ dwa = bhndb_io_resource(sc, BHND_DEFAULT_CHIPC_ADDR, BHND_DEFAULT_CORE_SIZE, offset); if (dwa != NULL) return (dwa->parent_res); device_printf(sc->dev, "unable to map chipcommon registers; no usable " "register window found\n"); return (NULL); } /** * Probe all supported EROM classes, returning the best matching class * (or NULL if not found), writing the probed chip identifier to @p cid. * * @param sc BHNDB driver state. * @param cid On success, the bridged chipset's chip identifier. */ static bhnd_erom_class_t * bhndb_probe_erom_class(struct bhndb_softc *sc, struct bhnd_chipid *cid) { devclass_t bhndb_devclass; const struct bhnd_chipid *hint; struct resource *res; bus_size_t res_offset; driver_t **drivers; int drv_count; bhnd_erom_class_t *erom_cls; int prio, result; BHNDB_LOCK_ASSERT(sc, MA_OWNED); erom_cls = NULL; prio = 0; /* Let our parent device provide a chipid hint */ hint = BHNDB_BUS_GET_CHIPID(sc->parent_dev, sc->dev); /* Fetch a borrowed reference to the resource mapping ChipCommon. */ res = bhndb_erom_chipc_resource(sc, &res_offset); if (res == NULL) return (NULL); /* Fetch all available drivers */ bhndb_devclass = device_get_devclass(sc->dev); if (devclass_get_drivers(bhndb_devclass, &drivers, &drv_count) != 0) return (NULL); /* Enumerate the drivers looking for the best available EROM class */ for (int i = 0; i < drv_count; i++) { struct bhnd_chipid pcid; bhnd_erom_class_t *cls; cls = bhnd_driver_get_erom_class(drivers[i]); if (cls == NULL) continue; kobj_class_compile(cls); /* Probe the bus */ result = bhnd_erom_probe(cls, &BHND_DIRECT_RESOURCE(res), res_offset, hint, &pcid); /* The parser did not match if an error was returned */ if (result > 0) continue; /* Check for a new highest priority match */ if (erom_cls == NULL || result > prio) { prio = result; *cid = pcid; erom_cls = cls; } /* Terminate immediately on BUS_PROBE_SPECIFIC */ if (result == BUS_PROBE_SPECIFIC) break; } return (erom_cls); } /* ascending core index comparison used by bhndb_find_hostb_core() */ static int compare_core_index(const void *lhs, const void *rhs) { u_int left = ((const struct bhnd_core_info *)lhs)->core_idx; u_int right = ((const struct bhnd_core_info *)rhs)->core_idx; if (left < right) return (-1); else if (left > right) return (1); else return (0); } /** * Search @p erom for the core serving as the bhnd host bridge. * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices to determine the hostb core: * * - The core must have a Broadcom vendor ID. * - The core devclass must match the bridge type. * - The core must be the first device on the bus with the bridged device * class. * * @param sc BHNDB device state. * @param erom The device enumeration table parser to be used to fetch * core info. * @param[out] core If found, the matching core info. * * @retval 0 success * @retval ENOENT not found * @retval non-zero if an error occured fetching core info. */ static int bhndb_find_hostb_core(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhnd_core_info *core) { struct bhnd_core_match md; struct bhnd_core_info *cores; u_int ncores; int error; if ((error = bhnd_erom_get_core_table(erom, &cores, &ncores))) return (error); /* Set up a match descriptor for the required device class. */ md = (struct bhnd_core_match) { BHND_MATCH_CORE_CLASS(sc->bridge_class), BHND_MATCH_CORE_UNIT(0) }; /* Ensure the table is sorted by core index value, ascending; * the host bridge must be the absolute first matching device on the * bus. */ qsort(cores, ncores, sizeof(*cores), compare_core_index); /* Find the hostb core */ error = ENOENT; for (u_int i = 0; i < ncores; i++) { if (bhnd_core_matches(&cores[i], &md)) { /* Found! */ *core = cores[i]; error = 0; break; } } /* Clean up */ bhnd_erom_free_core_table(erom, cores); return (error); } /** * Identify the bridged device and perform final bridge resource configuration * based on capabilities of the enumerated device. * * Any bridged resources allocated using the generic brige hardware * configuration must be released prior to calling this function. */ static int bhndb_init_full_config(struct bhndb_softc *sc, bhnd_erom_class_t *eromcls) { struct bhnd_core_info *cores; struct bhndb_resources *br; const struct bhndb_hw_priority *hwprio; bhnd_erom_t *erom; const struct bhndb_hw *hw; u_int ncores; int error; erom = NULL; cores = NULL; br = NULL; /* Allocate EROM parser instance */ erom = bhnd_erom_alloc(eromcls, &sc->chipid, sc->bus_dev, 0); if (erom == NULL) { device_printf(sc->dev, "failed to allocate device enumeration " "table parser\n"); return (ENXIO); } /* Look for our host bridge core */ if ((error = bhndb_find_hostb_core(sc, erom, &sc->bridge_core))) { device_printf(sc->dev, "no host bridge core found\n"); goto cleanup; } else { sc->have_br_core = true; } /* Fetch the bridged device's core table */ if ((error = bhnd_erom_get_core_table(erom, &cores, &ncores))) { device_printf(sc->dev, "error fetching core table: %d\n", error); goto cleanup; } /* Find our full register window configuration */ if ((error = bhndb_find_hwspec(sc, cores, ncores, &hw))) { device_printf(sc->dev, "unable to identify device, " " using generic bridge resource definitions\n"); error = 0; goto cleanup; } if (bootverbose || BHNDB_DEBUG(PRIO)) device_printf(sc->dev, "%s resource configuration\n", hw->name); /* Allocate new bridge resource state using the discovered hardware * configuration */ br = bhndb_alloc_resources(sc->dev, sc->parent_dev, hw->cfg); if (br == NULL) { device_printf(sc->dev, "failed to allocate new resource state\n"); error = ENOMEM; goto cleanup; } /* Populate our resource priority configuration */ hwprio = BHNDB_BUS_GET_HARDWARE_PRIO(sc->parent_dev, sc->dev); error = bhndb_init_region_cfg(sc, erom, br, cores, ncores, hwprio); if (error) { device_printf(sc->dev, "failed to initialize resource " "priority configuration: %d\n", error); goto cleanup; } /* The EROM parser holds a reference to the resource state we're * about to invalidate */ bhnd_erom_free_core_table(erom, cores); bhnd_erom_free(erom); cores = NULL; erom = NULL; /* Replace existing resource state */ bhndb_free_resources(sc->bus_res); sc->bus_res = br; /* Pointer is now owned by sc->bus_res */ br = NULL; /* Re-allocate host resources */ if ((error = bhndb_alloc_host_resources(sc->bus_res))) { device_printf(sc->dev, "failed to reallocate bridge host " "resources: %d\n", error); goto cleanup; } return (0); cleanup: if (cores != NULL) bhnd_erom_free_core_table(erom, cores); if (erom != NULL) bhnd_erom_free(erom); if (br != NULL) bhndb_free_resources(br); return (error); } /** * Default bhndb(4) implementation of DEVICE_DETACH(). * * This function detaches any child devices, and if successful, releases all * resources held by the bridge device. */ int bhndb_generic_detach(device_t dev) { struct bhndb_softc *sc; int error; sc = device_get_softc(dev); /* Detach children */ if ((error = bus_generic_detach(dev))) return (error); /* Clean up our driver state. */ bhndb_free_resources(sc->bus_res); BHNDB_LOCK_DESTROY(sc); return (0); } /** * Default bhndb(4) implementation of DEVICE_SUSPEND(). * * This function calls bus_generic_suspend() (or implements equivalent * behavior). */ int bhndb_generic_suspend(device_t dev) { return (bus_generic_suspend(dev)); } /** * Default bhndb(4) implementation of DEVICE_RESUME(). * * This function calls bus_generic_resume() (or implements equivalent * behavior). */ int bhndb_generic_resume(device_t dev) { struct bhndb_softc *sc; struct bhndb_resources *bus_res; struct bhndb_dw_alloc *dwa; int error; sc = device_get_softc(dev); bus_res = sc->bus_res; /* Guarantee that all in-use dynamic register windows are mapped to * their previously configured target address. */ BHNDB_LOCK(sc); for (size_t i = 0; i < bus_res->dwa_count; i++) { dwa = &bus_res->dw_alloc[i]; /* Skip regions that were not previously used */ if (bhndb_dw_is_free(bus_res, dwa) && dwa->target == 0x0) continue; /* Otherwise, ensure the register window is correct before * any children attempt MMIO */ error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); if (error) break; } BHNDB_UNLOCK(sc); /* Error restoring hardware state; children cannot be safely resumed */ if (error) { device_printf(dev, "Unable to restore hardware configuration; " "cannot resume: %d\n", error); return (error); } return (bus_generic_resume(dev)); } /** * Default implementation of BHNDB_SUSPEND_RESOURCE. */ static void bhndb_suspend_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; struct bhndb_dw_alloc *dwa; sc = device_get_softc(dev); - // TODO: IRQs? + /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return; BHNDB_LOCK(sc); dwa = bhndb_dw_find_resource(sc->bus_res, r); if (dwa == NULL) { BHNDB_UNLOCK(sc); return; } if (BHNDB_DEBUG(PRIO)) device_printf(child, "suspend resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); /* Release the resource's window reference */ bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); } /** * Default implementation of BHNDB_RESUME_RESOURCE. */ static int bhndb_resume_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; sc = device_get_softc(dev); - // TODO: IRQs? + /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return (0); /* Inactive resources don't require reallocation of bridge resources */ if (!(rman_get_flags(r) & RF_ACTIVE)) return (0); if (BHNDB_DEBUG(PRIO)) device_printf(child, "resume resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); return (bhndb_try_activate_resource(sc, rman_get_device(r), type, rman_get_rid(r), r, NULL)); } - /** * Default bhndb(4) implementation of BUS_READ_IVAR(). */ static int bhndb_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { return (ENOENT); } /** * Default bhndb(4) implementation of BUS_WRITE_IVAR(). */ static int bhndb_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { return (ENOENT); } /** * Return the address space for the given @p child device. */ bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child) { struct bhndb_devinfo *dinfo; device_t imd_dev; /* Find the directly attached parent of the requesting device */ imd_dev = child; while (imd_dev != NULL && device_get_parent(imd_dev) != sc->dev) imd_dev = device_get_parent(imd_dev); if (imd_dev == NULL) panic("bhndb address space request for non-child device %s\n", device_get_nameunit(child)); dinfo = device_get_ivars(imd_dev); return (dinfo->addrspace); } /** * Return the rman instance for a given resource @p type, if any. * * @param sc The bhndb device state. * @param child The requesting child. * @param type The resource type (e.g. SYS_RES_MEMORY, SYS_RES_IRQ, ...) */ static struct rman * bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type) { switch (bhndb_get_addrspace(sc, child)) { case BHNDB_ADDRSPACE_NATIVE: switch (type) { case SYS_RES_MEMORY: return (&sc->bus_res->ht_mem_rman); case SYS_RES_IRQ: return (NULL); default: return (NULL); }; case BHNDB_ADDRSPACE_BRIDGED: switch (type) { case SYS_RES_MEMORY: return (&sc->bus_res->br_mem_rman); case SYS_RES_IRQ: - // TODO - // return &sc->irq_rman; return (NULL); default: return (NULL); }; } /* Quieten gcc */ return (NULL); } /** * Default implementation of BUS_ADD_CHILD() */ static device_t bhndb_add_child(device_t dev, u_int order, const char *name, int unit) { struct bhndb_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(struct bhndb_devinfo), M_BHND, M_NOWAIT); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; resource_list_init(&dinfo->resources); device_set_ivars(child, dinfo); return (child); } /** * Default implementation of BUS_CHILD_DELETED(). */ static void bhndb_child_deleted(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); if (dinfo != NULL) { resource_list_free(&dinfo->resources); free(dinfo, M_BHND); } device_set_ivars(child, NULL); } /** * Default implementation of BHNDB_GET_CHIPID(). */ static const struct bhnd_chipid * bhndb_get_chipid(device_t dev, device_t child) { struct bhndb_softc *sc = device_get_softc(dev); return (&sc->chipid); } /** * Default implementation of BHNDB_IS_CORE_DISABLED(). */ static bool bhndb_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc; struct bhnd_core_info *bridge_core; sc = device_get_softc(dev); /* Try to defer to the bhndb bus parent */ if (BHNDB_BUS_IS_CORE_DISABLED(sc->parent_dev, dev, core)) return (true); /* Otherwise, we treat bridge-capable cores as unpopulated if they're * not the configured host bridge */ bridge_core = bhndb_get_bridge_core(sc); if (BHND_DEVCLASS_SUPPORTS_HOSTB(bhnd_core_class(core))) return (!bhnd_cores_equal(core, bridge_core)); /* Assume the core is populated */ return (false); } /** * Default bhndb(4) implementation of BHNDB_GET_HOSTB_CORE(). * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices. */ static int bhndb_get_hostb_core(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc = device_get_softc(dev); *core = *bhndb_get_bridge_core(sc); return (0); } /** * Default bhndb(4) implementation of BUS_ALLOC_RESOURCE(). */ static struct resource * bhndb_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhndb_softc *sc; struct resource_list_entry *rle; struct resource *rv; struct rman *rm; int error; bool passthrough, isdefault; sc = device_get_softc(dev); passthrough = (device_get_parent(child) != dev); isdefault = RMAN_IS_DEFAULT_RANGE(start, end); rle = NULL; + /* Fetch the resource manager */ + rm = bhndb_get_rman(sc, child, type); + if (rm == NULL) { + /* Delegate to our parent device's bus; the requested + * resource type isn't handled locally. */ + return (BUS_ALLOC_RESOURCE(device_get_parent(sc->parent_dev), + child, type, rid, start, end, count, flags)); + } + /* Populate defaults */ if (!passthrough && isdefault) { /* Fetch the resource list entry. */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, *rid); if (rle == NULL) { device_printf(dev, "default resource %#x type %d for child %s " "not found\n", *rid, type, device_get_nameunit(child)); return (NULL); } if (rle->res != NULL) { device_printf(dev, "resource entry %#x type %d for child %s is busy\n", *rid, type, device_get_nameunit(child)); return (NULL); } start = rle->start; end = rle->end; count = ulmax(count, rle->count); } /* Validate resource addresses */ if (start > end || count > ((end - start) + 1)) return (NULL); - - /* Fetch the resource manager */ - rm = bhndb_get_rman(sc, child, type); - if (rm == NULL) - return (NULL); /* Make our reservation */ rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); /* Activate */ if (flags & RF_ACTIVE) { error = bus_activate_resource(child, type, *rid, rv); if (error) { device_printf(dev, "failed to activate entry %#x type %d for " "child %s: %d\n", *rid, type, device_get_nameunit(child), error); rman_release_resource(rv); return (NULL); } } /* Update child's resource list entry */ if (rle != NULL) { rle->res = rv; rle->start = rman_get_start(rv); rle->end = rman_get_end(rv); rle->count = rman_get_size(rv); } return (rv); } /** * Default bhndb(4) implementation of BUS_RELEASE_RESOURCE(). */ static int bhndb_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { + struct bhndb_softc *sc; struct resource_list_entry *rle; bool passthrough; int error; - + + sc = device_get_softc(dev); passthrough = (device_get_parent(child) != dev); + /* Delegate to our parent device's bus if the requested resource type + * isn't handled locally. */ + if (bhndb_get_rman(sc, child, type) == NULL) { + return (BUS_RELEASE_RESOURCE(device_get_parent(sc->parent_dev), + child, type, rid, r)); + } + /* Deactivate resources */ if (rman_get_flags(r) & RF_ACTIVE) { error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r); if (error) return (error); } if ((error = rman_release_resource(r))) return (error); if (!passthrough) { /* Clean resource list entry */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, rid); if (rle != NULL) rle->res = NULL; } return (0); } /** * Default bhndb(4) implementation of BUS_ADJUST_RESOURCE(). */ static int bhndb_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct bhndb_softc *sc; struct rman *rm; rman_res_t mstart, mend; int error; sc = device_get_softc(dev); error = 0; + /* Delegate to our parent device's bus if the requested resource type + * isn't handled locally. */ + rm = bhndb_get_rman(sc, child, type); + if (rm == NULL) { + return (BUS_ADJUST_RESOURCE(device_get_parent(sc->parent_dev), + child, type, r, start, end)); + } + /* Verify basic constraints */ if (end <= start) return (EINVAL); - /* Fetch resource manager */ - rm = bhndb_get_rman(sc, child, type); - if (rm == NULL) - return (ENXIO); - if (!rman_is_region_manager(r, rm)) return (ENXIO); BHNDB_LOCK(sc); /* If not active, allow any range permitted by the resource manager */ if (!(rman_get_flags(r) & RF_ACTIVE)) goto done; /* Otherwise, the range is limited to the existing register window * mapping */ error = bhndb_find_resource_limits(sc->bus_res, r, &mstart, &mend); if (error) goto done; if (start < mstart || end > mend) { error = EINVAL; goto done; } /* Fall through */ done: if (!error) error = rman_adjust_resource(r, start, end); BHNDB_UNLOCK(sc); return (error); } /** * Initialize child resource @p r with a virtual address, tag, and handle * copied from @p parent, adjusted to contain only the range defined by * @p offsize and @p size. * * @param r The register to be initialized. * @param parent The parent bus resource that fully contains the subregion. * @param offset The subregion offset within @p parent. * @param size The subregion size. * @p r. */ static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size) { bus_space_handle_t bh, child_bh; bus_space_tag_t bt; uintptr_t vaddr; int error; /* Fetch the parent resource's real bus values */ vaddr = (uintptr_t) rman_get_virtual(parent); bt = rman_get_bustag(parent); bh = rman_get_bushandle(parent); /* Configure child resource with window-adjusted real bus values */ vaddr += offset; error = bus_space_subregion(bt, bh, offset, size, &child_bh); if (error) return (error); rman_set_virtual(r, (void *) vaddr); rman_set_bustag(r, bt); rman_set_bushandle(r, child_bh); return (0); } /** * Attempt activation of a fixed register window mapping for @p child. * * @param sc BHNDB device state. * @param region The static region definition capable of mapping @p r. * @param child A child requesting resource activation. * @param type Resource type. * @param rid Resource identifier. * @param r Resource to be activated. * * @retval 0 if @p r was activated successfully * @retval ENOENT if no fixed register window was found. * @retval non-zero if @p r could not be activated. */ static int bhndb_activate_static_region(struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r) { struct resource *bridge_res; const struct bhndb_regwin *win; bhnd_size_t parent_offset; rman_res_t r_start, r_size; int error; win = region->static_regwin; KASSERT(win != NULL && BHNDB_REGWIN_T_IS_STATIC(win->win_type), ("can't activate non-static region")); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Find the corresponding bridge resource */ bridge_res = bhndb_find_regwin_resource(sc->bus_res, win); if (bridge_res == NULL) return (ENXIO); /* Calculate subregion offset within the parent resource */ parent_offset = r_start - region->addr; parent_offset += win->win_offset; /* Configure resource with its real bus values. */ error = bhndb_init_child_resource(r, bridge_res, parent_offset, r_size); if (error) return (error); /* Mark active */ if ((error = rman_activate_resource(r))) return (error); return (0); } /** * Attempt to allocate/retain a dynamic register window for @p r, returning * the retained window. * * @param sc The bhndb driver state. * @param r The resource for which a window will be retained. */ static struct bhndb_dw_alloc * bhndb_retain_dynamic_window(struct bhndb_softc *sc, struct resource *r) { struct bhndb_dw_alloc *dwa; rman_res_t r_start, r_size; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Look for an existing dynamic window we can reference */ dwa = bhndb_dw_find_mapping(sc->bus_res, r_start, r_size); if (dwa != NULL) { if (bhndb_dw_retain(sc->bus_res, dwa, r) == 0) return (dwa); return (NULL); } /* Otherwise, try to reserve a free window */ dwa = bhndb_dw_next_free(sc->bus_res); if (dwa == NULL) { /* No free windows */ return (NULL); } /* Window must be large enough to map the entire resource */ if (dwa->win->win_size < rman_get_size(r)) return (NULL); /* Set the window target */ error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, rman_get_start(r), rman_get_size(r)); if (error) { device_printf(sc->dev, "dynamic window initialization " "for 0x%llx-0x%llx failed: %d\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, error); return (NULL); } /* Add our reservation */ if (bhndb_dw_retain(sc->bus_res, dwa, r)) return (NULL); return (dwa); } /** * Activate a resource using any viable static or dynamic register window. * * @param sc The bhndb driver state. * @param child The child holding ownership of @p r. * @param type The type of the resource to be activated. * @param rid The resource ID of @p r. * @param r The resource to be activated * @param[out] indirect On error and if not NULL, will be set to 'true' if * the caller should instead use an indirect resource mapping. * * @retval 0 success * @retval non-zero activation failed. */ static int bhndb_try_activate_resource(struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect) { struct bhndb_region *region; struct bhndb_dw_alloc *dwa; bhndb_priority_t dw_priority; rman_res_t r_start, r_size; rman_res_t parent_offset; int error; BHNDB_LOCK_ASSERT(sc, MA_NOTOWNED); - // TODO - IRQs + /* Only MMIO resources can be mapped via register windows */ if (type != SYS_RES_MEMORY) return (ENXIO); if (indirect) *indirect = false; r_start = rman_get_start(r); r_size = rman_get_size(r); /* Activate native addrspace resources using the host address space */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_NATIVE) { struct resource *parent; /* Find the bridge resource referenced by the child */ parent = bhndb_find_resource_range(sc->bus_res, r_start, r_size); if (parent == NULL) { device_printf(sc->dev, "host resource not found " "for 0x%llx-0x%llx\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (ENOENT); } /* Initialize child resource with the real bus values */ error = bhndb_init_child_resource(r, parent, r_start - rman_get_start(parent), r_size); if (error) return (error); /* Try to activate child resource */ return (rman_activate_resource(r)); } /* Default to low priority */ dw_priority = BHNDB_PRIORITY_LOW; /* Look for a bus region matching the resource's address range */ region = bhndb_find_resource_region(sc->bus_res, r_start, r_size); if (region != NULL) dw_priority = region->priority; /* Prefer static mappings over consuming a dynamic windows. */ if (region && region->static_regwin) { error = bhndb_activate_static_region(sc, region, child, type, rid, r); if (error) device_printf(sc->dev, "static window allocation " "for 0x%llx-0x%llx failed\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (error); } /* A dynamic window will be required; is this resource high enough * priority to be reserved a dynamic window? */ if (dw_priority < sc->bus_res->min_prio) { if (indirect) *indirect = true; return (ENOMEM); } /* Find and retain a usable window */ BHNDB_LOCK(sc); { dwa = bhndb_retain_dynamic_window(sc, r); } BHNDB_UNLOCK(sc); if (dwa == NULL) { if (indirect) *indirect = true; return (ENOMEM); } /* Configure resource with its real bus values. */ parent_offset = dwa->win->win_offset; parent_offset += r_start - dwa->target; error = bhndb_init_child_resource(r, dwa->parent_res, parent_offset, dwa->win->win_size); if (error) goto failed; /* Mark active */ if ((error = rman_activate_resource(r))) goto failed; return (0); failed: /* Release our region allocation. */ BHNDB_LOCK(sc); bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); return (error); } /** * Default bhndb(4) implementation of BUS_ACTIVATE_RESOURCE(). * * Maps resource activation requests to a viable static or dynamic * register window, if any. */ static int bhndb_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_softc *sc = device_get_softc(dev); + /* Delegate directly to our parent device's bus if the requested + * resource type isn't handled locally. */ + if (bhndb_get_rman(sc, child, type) == NULL) { + return (BUS_ACTIVATE_RESOURCE(device_get_parent(sc->parent_dev), + child, type, rid, r)); + } + return (bhndb_try_activate_resource(sc, child, type, rid, r, NULL)); } /** * Default bhndb(4) implementation of BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_dw_alloc *dwa; struct bhndb_softc *sc; struct rman *rm; int error; sc = device_get_softc(dev); - if ((rm = bhndb_get_rman(sc, child, type)) == NULL) - return (EINVAL); + /* Delegate directly to our parent device's bus if the requested + * resource type isn't handled locally. */ + rm = bhndb_get_rman(sc, child, type); + if (rm == NULL) { + return (BUS_DEACTIVATE_RESOURCE( + device_get_parent(sc->parent_dev), child, type, rid, r)); + } /* Mark inactive */ if ((error = rman_deactivate_resource(r))) return (error); /* Free any dynamic window allocation. */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { BHNDB_LOCK(sc); dwa = bhndb_dw_find_resource(sc->bus_res, r); if (dwa != NULL) bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); } return (0); } /** * Default bhndb(4) implementation of BUS_GET_RESOURCE_LIST(). */ static struct resource_list * bhndb_get_resource_list(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } /** * Default bhndb(4) implementation of BHND_BUS_ACTIVATE_RESOURCE(). * * For BHNDB_ADDRSPACE_NATIVE children, all resources may be assumed to * be activated by the bridge. * * For BHNDB_ADDRSPACE_BRIDGED children, attempts to activate a static register * window, a dynamic register window, or configures @p r as an indirect * resource -- in that order. */ static int bhndb_activate_bhnd_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { struct bhndb_softc *sc; struct bhndb_region *region; rman_res_t r_start, r_size; int error; bool indirect; KASSERT(!r->direct, ("direct flag set on inactive resource")); KASSERT(!(rman_get_flags(r->res) & RF_ACTIVE), ("RF_ACTIVE set on inactive resource")); sc = device_get_softc(dev); + /* Delegate directly to BUS_ACTIVATE_RESOURCE() if the requested + * resource type isn't handled locally. */ + if (bhndb_get_rman(sc, child, type) == NULL) { + error = BUS_ACTIVATE_RESOURCE(dev, child, type, rid, r->res); + if (error == 0) + r->direct = true; + return (error); + } + r_start = rman_get_start(r->res); r_size = rman_get_size(r->res); /* Verify bridged address range's resource priority, and skip direct * allocation if the priority is too low. */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { bhndb_priority_t r_prio; region = bhndb_find_resource_region(sc->bus_res, r_start, r_size); if (region != NULL) r_prio = region->priority; else r_prio = BHNDB_PRIORITY_NONE; /* If less than the minimum dynamic window priority, this * resource should always be indirect. */ if (r_prio < sc->bus_res->min_prio) return (0); } /* Attempt direct activation */ error = bhndb_try_activate_resource(sc, child, type, rid, r->res, &indirect); if (!error) { r->direct = true; } else if (indirect) { /* The request was valid, but no viable register window is * available; indirection must be employed. */ error = 0; r->direct = false; } if (BHNDB_DEBUG(PRIO) && bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { device_printf(child, "activated 0x%llx-0x%llx as %s " "resource\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, r->direct ? "direct" : "indirect"); } return (error); }; /** * Default bhndb(4) implementation of BHND_BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_bhnd_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Indirect resources don't require activation */ if (!r->direct) return (0); KASSERT(rman_get_flags(r->res) & RF_ACTIVE, ("RF_ACTIVE not set on direct resource")); /* Perform deactivation */ - error = bus_deactivate_resource(child, type, rid, r->res); + error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r->res); if (!error) r->direct = false; return (error); }; /** * Slow path for bhndb_io_resource(). * * Iterates over the existing allocated dynamic windows looking for a viable * in-use region; the first matching region is returned. */ static struct bhndb_dw_alloc * bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Search for an existing dynamic mapping of this address range. * Static regions are not searched, as a statically mapped * region would never be allocated as an indirect resource. */ for (size_t i = 0; i < br->dwa_count; i++) { const struct bhndb_regwin *win; dwa = &br->dw_alloc[i]; win = dwa->win; KASSERT(win->win_type == BHNDB_REGWIN_T_DYN, ("invalid register window type")); /* Verify the range */ if (addr < dwa->target) continue; if (addr + size > dwa->target + win->win_size) continue; /* Found */ *offset = dwa->win->win_offset; *offset += addr - dwa->target; return (dwa); } /* not found */ return (NULL); } /** * Return a borrowed reference to a bridge resource allocation record capable * of handling bus I/O requests of @p size at @p addr. * * This will either return a reference to an existing allocation * record mapping the requested space, or will configure and return a free * allocation record. * * Will panic if a usable record cannot be found. * * @param sc Bridge driver state. * @param addr The I/O target address. * @param size The size of the I/O operation to be performed at @p addr. * @param[out] offset The offset within the returned resource at which * to perform the I/O request. */ static inline struct bhndb_dw_alloc * bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Try to fetch a free window */ dwa = bhndb_dw_next_free(br); /* * If no dynamic windows are available, look for an existing * region that maps the target range. * * If none are found, this is a child driver bug -- our window * over-commit should only fail in the case where a child driver leaks * resources, or perform operations out-of-order. * * Broadcom HND chipsets are designed to not require register window * swapping during execution; as long as the child devices are * attached/detached correctly, using the hardware's required order * of operations, there should always be a window available for the * current operation. */ if (dwa == NULL) { dwa = bhndb_io_resource_slow(sc, addr, size, offset); if (dwa == NULL) { panic("register windows exhausted attempting to map " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } return (dwa); } /* Adjust the window if the I/O request won't fit in the current * target range. */ if (addr < dwa->target || addr > dwa->target + dwa->win->win_size || (dwa->target + dwa->win->win_size) - addr < size) { error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, addr, size); if (error) { panic("failed to set register window target mapping " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } } /* Calculate the offset and return */ *offset = (addr - dwa->target) + dwa->win->win_offset; return (dwa); } /* * BHND_BUS_(READ|WRITE_* implementations */ /* bhndb_bus_(read|write) common implementation */ #define BHNDB_IO_COMMON_SETUP(_io_size) \ struct bhndb_softc *sc; \ struct bhndb_dw_alloc *dwa; \ struct resource *io_res; \ bus_size_t io_offset; \ \ sc = device_get_softc(dev); \ \ BHNDB_LOCK(sc); \ dwa = bhndb_io_resource(sc, rman_get_start(r->res) + \ offset, _io_size, &io_offset); \ io_res = dwa->parent_res; \ \ KASSERT(!r->direct, \ ("bhnd_bus slow path used for direct resource")); \ \ KASSERT(rman_get_flags(io_res) & RF_ACTIVE, \ ("i/o resource is not active")); #define BHNDB_IO_COMMON_TEARDOWN() \ BHNDB_UNLOCK(sc); /* Defines a bhndb_bus_read_* method implementation */ #define BHNDB_IO_READ(_type, _name) \ static _type \ bhndb_bus_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ _type v; \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ v = bus_read_ ## _name (io_res, io_offset); \ BHNDB_IO_COMMON_TEARDOWN(); \ \ return (v); \ } /* Defines a bhndb_bus_write_* method implementation */ #define BHNDB_IO_WRITE(_type, _name) \ static void \ bhndb_bus_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ bus_write_ ## _name (io_res, io_offset, value); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a bhndb_bus_(read|write|set)_(multi|region)_* method */ #define BHNDB_IO_MISC(_type, _ptr, _op, _size) \ static void \ bhndb_bus_ ## _op ## _ ## _size (device_t dev, \ device_t child, struct bhnd_resource *r, bus_size_t offset, \ _type _ptr datap, bus_size_t count) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type) * count); \ bus_ ## _op ## _ ## _size (io_res, io_offset, \ datap, count); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a complete set of read/write methods */ #define BHNDB_IO_METHODS(_type, _size) \ BHNDB_IO_READ(_type, _size) \ BHNDB_IO_WRITE(_type, _size) \ \ BHNDB_IO_READ(_type, stream_ ## _size) \ BHNDB_IO_WRITE(_type, stream_ ## _size) \ \ BHNDB_IO_MISC(_type, *, read_multi, _size) \ BHNDB_IO_MISC(_type, *, write_multi, _size) \ \ BHNDB_IO_MISC(_type, *, read_multi_stream, _size) \ BHNDB_IO_MISC(_type, *, write_multi_stream, _size) \ \ BHNDB_IO_MISC(_type, , set_multi, _size) \ BHNDB_IO_MISC(_type, , set_region, _size) \ BHNDB_IO_MISC(_type, *, read_region, _size) \ BHNDB_IO_MISC(_type, *, write_region, _size) \ \ BHNDB_IO_MISC(_type, *, read_region_stream, _size) \ BHNDB_IO_MISC(_type, *, write_region_stream, _size) BHNDB_IO_METHODS(uint8_t, 1); BHNDB_IO_METHODS(uint16_t, 2); BHNDB_IO_METHODS(uint32_t, 4); /** * Default bhndb(4) implementation of BHND_BUS_BARRIER(). */ static void bhndb_bus_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHNDB_IO_COMMON_SETUP(length); bus_barrier(io_res, io_offset + offset, length, flags); BHNDB_IO_COMMON_TEARDOWN(); } /** - * Default bhndb(4) implementation of BUS_SETUP_INTR(). - */ -static int -bhndb_setup_intr(device_t dev, device_t child, struct resource *r, - int flags, driver_filter_t filter, driver_intr_t handler, void *arg, - void **cookiep) -{ - // TODO - return (EOPNOTSUPP); -} - -/** - * Default bhndb(4) implementation of BUS_TEARDOWN_INTR(). - */ -static int -bhndb_teardown_intr(device_t dev, device_t child, struct resource *r, - void *cookie) -{ - // TODO - return (EOPNOTSUPP); -} - -/** - * Default bhndb(4) implementation of BUS_CONFIG_INTR(). - */ -static int -bhndb_config_intr(device_t dev, int irq, enum intr_trigger trig, - enum intr_polarity pol) -{ - // TODO - return (EOPNOTSUPP); -} - -/** - * Default bhndb(4) implementation of BUS_BIND_INTR(). - */ -static int -bhndb_bind_intr(device_t dev, device_t child, struct resource *r, int cpu) -{ - // TODO - return (EOPNOTSUPP); -} - -/** - * Default bhndb(4) implementation of BUS_DESCRIBE_INTR(). - */ -static int -bhndb_describe_intr(device_t dev, device_t child, struct resource *irq, void *cookie, - const char *descr) -{ - // TODO - return (EOPNOTSUPP); -} - -/** * Default bhndb(4) implementation of BUS_GET_DMA_TAG(). */ static bus_dma_tag_t bhndb_get_dma_tag(device_t dev, device_t child) { // TODO return (NULL); } static device_method_t bhndb_methods[] = { /* Device interface */ \ DEVMETHOD(device_probe, bhndb_generic_probe), DEVMETHOD(device_detach, bhndb_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bhndb_generic_suspend), DEVMETHOD(device_resume, bhndb_generic_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, bhndb_probe_nomatch), DEVMETHOD(bus_print_child, bhndb_print_child), DEVMETHOD(bus_child_pnpinfo_str, bhndb_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, bhndb_child_location_str), DEVMETHOD(bus_add_child, bhndb_add_child), DEVMETHOD(bus_child_deleted, bhndb_child_deleted), DEVMETHOD(bus_alloc_resource, bhndb_alloc_resource), DEVMETHOD(bus_release_resource, bhndb_release_resource), DEVMETHOD(bus_activate_resource, bhndb_activate_resource), DEVMETHOD(bus_deactivate_resource, bhndb_deactivate_resource), - DEVMETHOD(bus_setup_intr, bhndb_setup_intr), - DEVMETHOD(bus_teardown_intr, bhndb_teardown_intr), - DEVMETHOD(bus_config_intr, bhndb_config_intr), - DEVMETHOD(bus_bind_intr, bhndb_bind_intr), - DEVMETHOD(bus_describe_intr, bhndb_describe_intr), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_config_intr, bus_generic_config_intr), + DEVMETHOD(bus_bind_intr, bus_generic_bind_intr), + DEVMETHOD(bus_describe_intr, bus_generic_describe_intr), DEVMETHOD(bus_get_dma_tag, bhndb_get_dma_tag), DEVMETHOD(bus_adjust_resource, bhndb_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_get_resource_list, bhndb_get_resource_list), DEVMETHOD(bus_read_ivar, bhndb_read_ivar), DEVMETHOD(bus_write_ivar, bhndb_write_ivar), /* BHNDB interface */ DEVMETHOD(bhndb_get_chipid, bhndb_get_chipid), DEVMETHOD(bhndb_is_core_disabled, bhndb_is_core_disabled), DEVMETHOD(bhndb_get_hostb_core, bhndb_get_hostb_core), DEVMETHOD(bhndb_suspend_resource, bhndb_suspend_resource), DEVMETHOD(bhndb_resume_resource, bhndb_resume_resource), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhndb_get_chipid), DEVMETHOD(bhnd_bus_activate_resource, bhndb_activate_bhnd_resource), DEVMETHOD(bhnd_bus_deactivate_resource, bhndb_deactivate_bhnd_resource), DEVMETHOD(bhnd_bus_get_nvram_var, bhnd_bus_generic_get_nvram_var), DEVMETHOD(bhnd_bus_read_1, bhndb_bus_read_1), DEVMETHOD(bhnd_bus_read_2, bhndb_bus_read_2), DEVMETHOD(bhnd_bus_read_4, bhndb_bus_read_4), DEVMETHOD(bhnd_bus_write_1, bhndb_bus_write_1), DEVMETHOD(bhnd_bus_write_2, bhndb_bus_write_2), DEVMETHOD(bhnd_bus_write_4, bhndb_bus_write_4), DEVMETHOD(bhnd_bus_read_stream_1, bhndb_bus_read_stream_1), DEVMETHOD(bhnd_bus_read_stream_2, bhndb_bus_read_stream_2), DEVMETHOD(bhnd_bus_read_stream_4, bhndb_bus_read_stream_4), DEVMETHOD(bhnd_bus_write_stream_1, bhndb_bus_write_stream_1), DEVMETHOD(bhnd_bus_write_stream_2, bhndb_bus_write_stream_2), DEVMETHOD(bhnd_bus_write_stream_4, bhndb_bus_write_stream_4), DEVMETHOD(bhnd_bus_read_multi_1, bhndb_bus_read_multi_1), DEVMETHOD(bhnd_bus_read_multi_2, bhndb_bus_read_multi_2), DEVMETHOD(bhnd_bus_read_multi_4, bhndb_bus_read_multi_4), DEVMETHOD(bhnd_bus_write_multi_1, bhndb_bus_write_multi_1), DEVMETHOD(bhnd_bus_write_multi_2, bhndb_bus_write_multi_2), DEVMETHOD(bhnd_bus_write_multi_4, bhndb_bus_write_multi_4), DEVMETHOD(bhnd_bus_read_multi_stream_1, bhndb_bus_read_multi_stream_1), DEVMETHOD(bhnd_bus_read_multi_stream_2, bhndb_bus_read_multi_stream_2), DEVMETHOD(bhnd_bus_read_multi_stream_4, bhndb_bus_read_multi_stream_4), DEVMETHOD(bhnd_bus_write_multi_stream_1,bhndb_bus_write_multi_stream_1), DEVMETHOD(bhnd_bus_write_multi_stream_2,bhndb_bus_write_multi_stream_2), DEVMETHOD(bhnd_bus_write_multi_stream_4,bhndb_bus_write_multi_stream_4), DEVMETHOD(bhnd_bus_set_multi_1, bhndb_bus_set_multi_1), DEVMETHOD(bhnd_bus_set_multi_2, bhndb_bus_set_multi_2), DEVMETHOD(bhnd_bus_set_multi_4, bhndb_bus_set_multi_4), DEVMETHOD(bhnd_bus_set_region_1, bhndb_bus_set_region_1), DEVMETHOD(bhnd_bus_set_region_2, bhndb_bus_set_region_2), DEVMETHOD(bhnd_bus_set_region_4, bhndb_bus_set_region_4), DEVMETHOD(bhnd_bus_read_region_1, bhndb_bus_read_region_1), DEVMETHOD(bhnd_bus_read_region_2, bhndb_bus_read_region_2), DEVMETHOD(bhnd_bus_read_region_4, bhndb_bus_read_region_4), DEVMETHOD(bhnd_bus_write_region_1, bhndb_bus_write_region_1), DEVMETHOD(bhnd_bus_write_region_2, bhndb_bus_write_region_2), DEVMETHOD(bhnd_bus_write_region_4, bhndb_bus_write_region_4), DEVMETHOD(bhnd_bus_read_region_stream_1,bhndb_bus_read_region_stream_1), DEVMETHOD(bhnd_bus_read_region_stream_2,bhndb_bus_read_region_stream_2), DEVMETHOD(bhnd_bus_read_region_stream_4,bhndb_bus_read_region_stream_4), DEVMETHOD(bhnd_bus_write_region_stream_1,bhndb_bus_write_region_stream_1), DEVMETHOD(bhnd_bus_write_region_stream_2,bhndb_bus_write_region_stream_2), DEVMETHOD(bhnd_bus_write_region_stream_4,bhndb_bus_write_region_stream_4), DEVMETHOD(bhnd_bus_barrier, bhndb_bus_barrier), DEVMETHOD_END }; devclass_t bhndb_devclass; DEFINE_CLASS_0(bhndb, bhndb_driver, bhndb_methods, sizeof(struct bhndb_softc)); MODULE_VERSION(bhndb, 1); MODULE_DEPEND(bhndb, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb_pci.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 305443) +++ head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 305444) @@ -1,709 +1,786 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); /* * PCI-specific implementation for the BHNDB bridge driver. * * Provides support for bridging from a PCI parent bus to a BHND-compatible * bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point * mode. * * This driver handles all initial generic host-level PCI interactions with a * PCI/PCIe bridge core operating in endpoint mode. Once the bridged bhnd(4) * bus has been enumerated, this driver works in tandem with a core-specific * bhnd_pci_hostb driver to manage the PCI core. */ #include #include #include #include #include #include #include #include #include #include #include #include "bhndb_pcireg.h" #include "bhndb_pcivar.h" #include "bhndb_private.h" +static int bhndb_pci_init_msi(struct bhndb_pci_softc *sc); static int bhndb_pci_add_children(struct bhndb_pci_softc *sc); static int bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc); static int bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc); static int bhndb_pci_compat_setregwin(struct bhndb_pci_softc *, const struct bhndb_regwin *, bhnd_addr_t); static int bhndb_pci_fast_setregwin(struct bhndb_pci_softc *, const struct bhndb_regwin *, bhnd_addr_t); static void bhndb_init_sromless_pci_config( struct bhndb_pci_softc *sc); 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); +#define BHNDB_PCI_MSI_COUNT 1 + /** * Default bhndb_pci implementation of device_probe(). * * Verifies that the parent is a PCI/PCIe device. */ static int bhndb_pci_probe(device_t dev) { device_t parent; devclass_t parent_bus; devclass_t pci; /* Our parent must be a PCI/PCIe device. */ pci = devclass_find("pci"); parent = device_get_parent(dev); parent_bus = device_get_devclass(device_get_parent(parent)); if (parent_bus != pci) return (ENXIO); device_set_desc(dev, "PCI-BHND bridge"); return (BUS_PROBE_DEFAULT); } +/* Configure MSI interrupts */ static int +bhndb_pci_init_msi(struct bhndb_pci_softc *sc) +{ + int error; + + /* Is MSI available? */ + if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT) + return (ENXIO); + + /* Allocate expected message count */ + sc->intr.msi_count = BHNDB_PCI_MSI_COUNT; + if ((error = pci_alloc_msi(sc->parent, &sc->intr.msi_count))) { + device_printf(sc->dev, "failed to allocate MSI interrupts: " + "%d\n", error); + return (error); + } + + if (sc->intr.msi_count < BHNDB_PCI_MSI_COUNT) + return (ENXIO); + + /* MSI uses resource IDs starting at 1 */ + sc->intr.intr_rid = 1; + + return (0); +} + +static int bhndb_pci_attach(device_t dev) { struct bhndb_pci_softc *sc; int error, reg; sc = device_get_softc(dev); sc->dev = dev; sc->parent = device_get_parent(dev); sc->set_regwin = bhndb_pci_compat_setregwin; + /* Enable PCI bus mastering */ + pci_enable_busmaster(sc->parent); + + /* Set up interrupt handling */ + if (bhndb_pci_init_msi(sc) == 0) { + device_printf(dev, "Using MSI interrupts on %s\n", + device_get_nameunit(sc->parent)); + } else { + device_printf(dev, "Using INTx interrupts on %s\n", + device_get_nameunit(sc->parent)); + sc->intr.intr_rid = 0; + } + + /* Determine our bridge device class */ + sc->pci_devclass = BHND_DEVCLASS_PCI; if (pci_find_cap(sc->parent, PCIY_EXPRESS, ®) == 0) sc->pci_devclass = BHND_DEVCLASS_PCIE; else sc->pci_devclass = BHND_DEVCLASS_PCI; /* Enable clocks (if required by this hardware) */ if ((error = bhndb_enable_pci_clocks(sc))) goto cleanup; /* Perform bridge attach, fully initializing the bridge * configuration. */ if ((error = bhndb_attach(dev, sc->pci_devclass))) goto cleanup; /* If supported, switch to faster regwin handling */ if (sc->bhndb.chipid.chip_type != BHND_CHIPTYPE_SIBA) { atomic_store_rel_ptr((volatile void *) &sc->set_regwin, (uintptr_t) &bhndb_pci_fast_setregwin); } /* Enable PCI bus mastering */ pci_enable_busmaster(sc->parent); /* Fix-up power on defaults for SROM-less devices. */ bhndb_init_sromless_pci_config(sc); /* Add any additional child devices */ if ((error = bhndb_pci_add_children(sc))) goto cleanup; /* Probe and attach our children */ if ((error = bus_generic_attach(dev))) goto cleanup; return (0); cleanup: device_delete_children(dev); bhndb_disable_pci_clocks(sc); + if (sc->intr.msi_count > 0) + pci_release_msi(dev); + pci_disable_busmaster(sc->parent); return (error); } static int bhndb_pci_detach(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Attempt to detach our children */ if ((error = bus_generic_detach(dev))) return (error); /* Perform generic bridge detach */ if ((error = bhndb_generic_detach(dev))) return (error); /* Disable clocks (if required by this hardware) */ if ((error = bhndb_disable_pci_clocks(sc))) return (error); + /* Release MSI interrupts */ + if (sc->intr.msi_count > 0) + pci_release_msi(dev); + /* Disable PCI bus mastering */ pci_disable_busmaster(sc->parent); return (0); } static int bhndb_pci_add_children(struct bhndb_pci_softc *sc) { bus_size_t nv_sz; int error; /** * If SPROM is mapped directly into BAR0, add child NVRAM * device. */ nv_sz = bhndb_pci_sprom_size(sc); if (nv_sz > 0) { struct bhndb_devinfo *dinfo; device_t child; if (bootverbose) { device_printf(sc->dev, "found SPROM (%ju bytes)\n", (uintmax_t)nv_sz); } /* Add sprom device, ordered early enough to be available * before the bridged bhnd(4) bus is attached. */ child = BUS_ADD_CHILD(sc->dev, BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY, "bhnd_nvram", -1); if (child == NULL) { device_printf(sc->dev, "failed to add sprom device\n"); return (ENXIO); } /* Initialize device address space and resource covering the * BAR0 SPROM shadow. */ dinfo = device_get_ivars(child); dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; error = bus_set_resource(child, SYS_RES_MEMORY, 0, bhndb_pci_sprom_addr(sc), nv_sz); if (error) { device_printf(sc->dev, "failed to register sprom resources\n"); return (error); } } return (0); } static const struct bhndb_regwin * bhndb_pci_sprom_regwin(struct bhndb_pci_softc *sc) { struct bhndb_resources *bres; const struct bhndb_hwcfg *cfg; const struct bhndb_regwin *sprom_win; bres = sc->bhndb.bus_res; cfg = bres->cfg; sprom_win = bhndb_regwin_find_type(cfg->register_windows, BHNDB_REGWIN_T_SPROM, BHNDB_PCI_V0_BAR0_SPROM_SIZE); return (sprom_win); } static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; struct resource *r; /* Fetch the SPROM register window */ sprom_win = bhndb_pci_sprom_regwin(sc); KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+")); /* Fetch the associated resource */ r = bhndb_find_regwin_resource(sc->bhndb.bus_res, sprom_win); KASSERT(r != NULL, ("missing resource for sprom window\n")); return (rman_get_start(r) + sprom_win->win_offset); } static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; uint32_t sctl; bus_size_t sprom_sz; sprom_win = bhndb_pci_sprom_regwin(sc); /* PCI_V2 and later devices map SPROM/OTP via ChipCommon */ if (sprom_win == NULL) return (0); /* Determine SPROM size */ sctl = pci_read_config(sc->parent, BHNDB_PCI_SPROM_CONTROL, 4); if (sctl & BHNDB_PCI_SPROM_BLANK) return (0); switch (sctl & BHNDB_PCI_SPROM_SZ_MASK) { case BHNDB_PCI_SPROM_SZ_1KB: sprom_sz = (1 * 1024); break; case BHNDB_PCI_SPROM_SZ_4KB: sprom_sz = (4 * 1024); break; case BHNDB_PCI_SPROM_SZ_16KB: sprom_sz = (16 * 1024); break; case BHNDB_PCI_SPROM_SZ_RESERVED: default: device_printf(sc->dev, "invalid PCI sprom size 0x%x\n", sctl); return (0); } if (sprom_sz > sprom_win->win_size) { device_printf(sc->dev, "PCI sprom size (0x%x) overruns defined register window\n", sctl); return (0); } return (sprom_sz); } /* * On devices without a SROM, the PCI(e) cores will be initialized with * their Power-on-Reset defaults; this can leave two of the BAR0 PCI windows * mapped to the wrong core. * * This function updates the SROM shadow to point the BAR0 windows at the * current PCI core. * * Applies to all PCI/PCIe revisions. */ static void bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc) { struct bhndb_resources *bres; const struct bhndb_hwcfg *cfg; const struct bhndb_regwin *win; struct bhnd_core_info hostb_core; struct resource *core_regs; bus_size_t srom_offset; u_int pci_cidx, sprom_cidx; uint16_t val; int error; bres = sc->bhndb.bus_res; cfg = bres->cfg; /* Find our hostb core */ error = BHNDB_GET_HOSTB_CORE(sc->dev, sc->bhndb.bus_dev, &hostb_core); if (error) { device_printf(sc->dev, "no host bridge device found\n"); return; } if (hostb_core.vendor != BHND_MFGID_BCM) return; switch (hostb_core.device) { case BHND_COREID_PCI: srom_offset = BHND_PCI_SRSH_PI_OFFSET; break; case BHND_COREID_PCIE: srom_offset = BHND_PCIE_SRSH_PI_OFFSET; break; default: device_printf(sc->dev, "unsupported PCI host bridge device\n"); return; } /* Locate the static register window mapping the PCI core */ win = bhndb_regwin_find_core(cfg->register_windows, sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0); if (win == NULL) { device_printf(sc->dev, "missing PCI core register window\n"); return; } /* Fetch the resource containing the register window */ core_regs = bhndb_find_regwin_resource(bres, win); if (core_regs == NULL) { device_printf(sc->dev, "missing PCI core register resource\n"); return; } /* Fetch the SPROM's configured core index */ val = bus_read_2(core_regs, win->win_offset + srom_offset); sprom_cidx = (val & BHND_PCI_SRSH_PI_MASK) >> BHND_PCI_SRSH_PI_SHIFT; /* If it doesn't match host bridge's core index, update the index * value */ pci_cidx = hostb_core.core_idx; if (sprom_cidx != pci_cidx) { val &= ~BHND_PCI_SRSH_PI_MASK; val |= (pci_cidx << BHND_PCI_SRSH_PI_SHIFT); bus_write_2(core_regs, win->win_offset + srom_offset, val); } } static int bhndb_pci_resume(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Enable clocks (if supported by this hardware) */ if ((error = bhndb_enable_pci_clocks(sc))) return (error); /* Perform resume */ return (bhndb_generic_resume(dev)); } static int bhndb_pci_suspend(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Disable clocks (if supported by this hardware) */ if ((error = bhndb_disable_pci_clocks(sc))) return (error); /* Perform suspend */ return (bhndb_generic_suspend(dev)); } static int bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { struct bhndb_pci_softc *sc = device_get_softc(dev); return (sc->set_regwin(sc, rw, addr)); } /** * A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation. * * On siba(4) devices, it's possible that writing a PCI window register may * not succeed; it's necessary to immediately read the configuration register * and retry if not set to the desired value. * * This is not necessary on bcma(4) devices, but other than the overhead of * validating the register, there's no harm in performing the verification. */ static int bhndb_pci_compat_setregwin(struct bhndb_pci_softc *sc, 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(sc, rw, addr))) return (error); if (pci_read_config(sc->parent, reg, 4) == addr) return (0); DELAY(10); } /* Unable to set window */ return (ENODEV); } /** * A bcma(4)-only bhndb_set_window_addr implementation. */ static int bhndb_pci_fast_setregwin(struct bhndb_pci_softc *sc, const struct bhndb_regwin *rw, bhnd_addr_t addr) { /* The PCI bridge core only supports 32-bit addressing, regardless * of the bus' support for 64-bit addressing */ if (addr > UINT32_MAX) return (ERANGE); switch (rw->win_type) { case BHNDB_REGWIN_T_DYN: /* Addresses must be page aligned */ if (addr % rw->win_size != 0) return (EINVAL); pci_write_config(sc->parent, rw->d.dyn.cfg_offset, addr, 4); break; default: return (ENODEV); } return (0); } static int bhndb_pci_populate_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { struct bhndb_pci_softc *sc; sc = device_get_softc(dev); /* * On a subset of Apple BCM4360 modules, always prefer the * PCI subdevice to the SPROM-supplied boardtype. * * TODO: * * Broadcom's own drivers implement this override, and then later use * the remapped BCM4360 board type to determine the required * board-specific workarounds. * * Without access to this hardware, it's unclear why this mapping * is done, and we must do the same. If we can survey the hardware * in question, it may be possible to replace this behavior with * explicit references to the SPROM-supplied boardtype(s) in our * quirk definitions. */ if (pci_get_subvendor(sc->parent) == PCI_VENDOR_APPLE) { switch (info->board_type) { case BHND_BOARD_BCM94360X29C: case BHND_BOARD_BCM94360X29CP2: case BHND_BOARD_BCM94360X51: case BHND_BOARD_BCM94360X51P2: info->board_type = 0; /* allow override below */ break; default: break; } } /* If NVRAM did not supply vendor/type info, provide the PCI * subvendor/subdevice values. */ if (info->board_vendor == 0) info->board_vendor = pci_get_subvendor(sc->parent); if (info->board_type == 0) info->board_type = pci_get_subdevice(sc->parent); return (0); } /** * Enable externally managed clocks, if required. * * Some PCI chipsets (BCM4306, possibly others) chips do not support * the idle low-power clock. Clocking must be bootstrapped at * attach/resume by directly adjusting GPIO registers exposed in the * PCI config space, and correspondingly, explicitly shutdown at * detach/suspend. * * @param sc Bridge driver state. */ static int bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc) { uint32_t gpio_in, gpio_out, gpio_en; uint32_t gpio_flags; uint16_t pci_status; /* Only supported and required on PCI devices */ if (sc->pci_devclass != BHND_DEVCLASS_PCI) return (0); /* Read state of XTAL pin */ gpio_in = pci_read_config(sc->parent, BHNDB_PCI_GPIO_IN, 4); if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON) return (0); /* already enabled */ /* Fetch current config */ gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */ gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); gpio_out |= gpio_flags; gpio_en |= gpio_flags; pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4); pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); DELAY(1000); /* Reset PLL_OFF */ gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4); DELAY(5000); /* Clear any PCI 'sent target-abort' flag. */ pci_status = pci_read_config(sc->parent, PCIR_STATUS, 2); pci_status &= ~PCIM_STATUS_STABORT; pci_write_config(sc->parent, PCIR_STATUS, pci_status, 2); return (0); } /** * Disable externally managed clocks, if required. * * @param sc Bridge driver state. */ static int bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc) { uint32_t gpio_out, gpio_en; /* Only supported and required on PCI devices */ if (sc->pci_devclass != BHND_DEVCLASS_PCI) return (0); /* Fetch current config */ gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF to HIGH, XTAL_ON to LOW. */ gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON; gpio_out |= BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4); /* Enable both output pins */ gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); pci_write_config(sc->parent, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); return (0); } static bhnd_clksrc bhndb_pci_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc; uint32_t gpio_out; sc = device_get_softc(dev); /* Only supported on PCI devices */ if (sc->pci_devclass != BHND_DEVCLASS_PCI) return (ENODEV); /* Only ILP is supported */ if (clock != BHND_CLOCK_ILP) return (ENXIO); gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4); if (gpio_out & BHNDB_PCI_GPIO_SCS) return (BHND_CLKSRC_PCI); else return (BHND_CLKSRC_XTAL); } static int bhndb_pci_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (sc->pci_devclass != BHND_DEVCLASS_PCI) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); return (bhndb_disable_pci_clocks(sc)); } static int bhndb_pci_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (sc->pci_devclass != BHND_DEVCLASS_PCI) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); return (bhndb_enable_pci_clocks(sc)); } +static int +bhndb_pci_assign_intr(device_t dev, device_t child, int rid) +{ + struct bhndb_pci_softc *sc; + rman_res_t start, count; + int error; + + sc = device_get_softc(dev); + + /* Is the rid valid? */ + if (rid >= bhnd_get_intr_count(child)) + return (EINVAL); + + /* Fetch our common PCI interrupt's start/count. */ + error = bus_get_resource(sc->parent, SYS_RES_IRQ, sc->intr.intr_rid, + &start, &count); + if (error) + return (error); + + /* Add to child's resource list */ + return (bus_set_resource(child, SYS_RES_IRQ, rid, start, count)); +} + static device_method_t bhndb_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhndb_pci_probe), DEVMETHOD(device_attach, bhndb_pci_attach), DEVMETHOD(device_resume, bhndb_pci_resume), DEVMETHOD(device_suspend, bhndb_pci_suspend), DEVMETHOD(device_detach, bhndb_pci_detach), /* BHND interface */ + DEVMETHOD(bhnd_bus_assign_intr, bhndb_pci_assign_intr), + DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhndb_pci_pwrctl_get_clksrc), DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhndb_pci_pwrctl_gate_clock), DEVMETHOD(bhnd_bus_pwrctl_ungate_clock, bhndb_pci_pwrctl_ungate_clock), /* BHNDB interface */ DEVMETHOD(bhndb_set_window_addr, bhndb_pci_set_window_addr), DEVMETHOD(bhndb_populate_board_info, bhndb_pci_populate_board_info), 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); Index: head/sys/dev/bhnd/bhndb/bhndb_pcivar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 305443) +++ head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 305444) @@ -1,59 +1,67 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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. * * $FreeBSD$ */ #ifndef _BHND_BHNDB_PCIVAR_H_ #define _BHND_BHNDB_PCIVAR_H_ #include "bhndbvar.h" /* * bhndb(4) PCI driver subclass. */ DECLARE_CLASS(bhndb_pci_driver); struct bhndb_pci_softc; /* * An interconnect-specific function implementing BHNDB_SET_WINDOW_ADDR */ typedef int (*bhndb_pci_set_regwin_t)(struct bhndb_pci_softc *sc, const struct bhndb_regwin *rw, bhnd_addr_t addr); +/* bhndb_pci interrupt state */ +struct bhndb_pci_intr { + int msi_count; /**< MSI count, or 0 */ + int intr_rid; /**< interrupt resource ID.*/ +}; + struct bhndb_pci_softc { struct bhndb_softc bhndb; /**< parent softc */ device_t dev; /**< bridge device */ device_t parent; /**< parent PCI device */ bhnd_devclass_t pci_devclass; /**< PCI core's devclass */ + struct bhndb_pci_intr intr; /**< PCI interrupt config */ + bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */ }; #endif /* _BHND_BHNDB_PCIVAR_H_ */ Index: head/sys/dev/bhnd/cores/pmu/bhnd_pmu_subr.c =================================================================== --- head/sys/dev/bhnd/cores/pmu/bhnd_pmu_subr.c (revision 305443) +++ head/sys/dev/bhnd/cores/pmu/bhnd_pmu_subr.c (revision 305444) @@ -1,3508 +1,3508 @@ /*- * Copyright (c) 2016 Landon Fuller * Copyright (C) 2010, Broadcom Corporation. * All rights reserved. * * This file is derived from the hndpmu.c source contributed by Broadcom * to to the Linux staging repository, as well as later revisions of hndpmu.c * distributed with the Asus RT-N16 firmware source code release. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "bhnd_nvram_map.h" #include "bhnd_pmureg.h" #include "bhnd_pmuvar.h" #include "bhnd_pmu_private.h" #define PMU_LOG(_sc, _fmt, ...) do { \ if (_sc->dev != NULL) \ device_printf(_sc->dev, _fmt, ##__VA_ARGS__); \ else \ printf(_fmt, ##__VA_ARGS__); \ } while (0) #ifdef BCMDBG #define PMU_DEBUG(_sc, _fmt, ...) PMU_LOG(_sc, _fmt, ##__VA_ARGS__) #else #define PMU_DEBUG(_sc, _fmt, ...) #endif typedef struct pmu0_xtaltab0 pmu0_xtaltab0_t; typedef struct pmu1_xtaltab0 pmu1_xtaltab0_t; /* PLL controls/clocks */ static const pmu1_xtaltab0_t *bhnd_pmu1_xtaltab0(struct bhnd_pmu_query *sc); static const pmu1_xtaltab0_t *bhnd_pmu1_xtaldef0(struct bhnd_pmu_query *sc); static void bhnd_pmu0_pllinit0(struct bhnd_pmu_softc *sc, uint32_t xtal); static uint32_t bhnd_pmu0_cpuclk0(struct bhnd_pmu_query *sc); static uint32_t bhnd_pmu0_alpclk0(struct bhnd_pmu_query *sc); static void bhnd_pmu1_pllinit0(struct bhnd_pmu_softc *sc, uint32_t xtal); static uint32_t bhnd_pmu1_pllfvco0(struct bhnd_pmu_query *sc); static uint32_t bhnd_pmu1_cpuclk0(struct bhnd_pmu_query *sc); static uint32_t bhnd_pmu1_alpclk0(struct bhnd_pmu_query *sc); static uint32_t bhnd_pmu5_clock(struct bhnd_pmu_query *sc, u_int pll0, u_int m); /* PMU resources */ static bool bhnd_pmu_res_depfltr_bb(struct bhnd_pmu_softc *sc); static bool bhnd_pmu_res_depfltr_ncb(struct bhnd_pmu_softc *sc); static bool bhnd_pmu_res_depfltr_paldo(struct bhnd_pmu_softc *sc); static bool bhnd_pmu_res_depfltr_npaldo(struct bhnd_pmu_softc *sc); static uint32_t bhnd_pmu_res_deps(struct bhnd_pmu_softc *sc, uint32_t rsrcs, bool all); static int bhnd_pmu_res_uptime(struct bhnd_pmu_softc *sc, uint8_t rsrc, uint32_t *uptime); static int bhnd_pmu_res_masks(struct bhnd_pmu_softc *sc, uint32_t *pmin, uint32_t *pmax); static void bhnd_pmu_spuravoid_pllupdate(struct bhnd_pmu_softc *sc, uint8_t spuravoid); static void bhnd_pmu_set_4330_plldivs(struct bhnd_pmu_softc *sc); #define BHND_PMU_REV(_sc) \ ((uint8_t)BHND_PMU_GET_BITS((_sc)->caps, BHND_PMU_CAP_REV)) #define PMU_WAIT_CLKST(_sc, _val, _mask) \ bhnd_pmu_wait_clkst((_sc), (_sc)->dev, (_sc)->res, \ BHND_CLK_CTL_ST, (_val), (_mask)) #define PMURES_BIT(_bit) \ (1 << (BHND_PMU_ ## _bit)) #define PMU_CST4330_SDIOD_CHIPMODE(_sc) \ CHIPC_CST4330_CHIPMODE_SDIOD((_sc)->io->rd_chipst((_sc)->io_ctx)) /** * Initialize @p query state. * * @param[out] query On success, will be populated with a valid query instance * state. * @param dev The device owning @p query, or NULL. * @param id The bhnd chip identification. * @param io I/O callback functions. * @param ctx I/O callback context. * * @retval 0 success * @retval non-zero if the query state could not be initialized. */ int bhnd_pmu_query_init(struct bhnd_pmu_query *query, device_t dev, struct bhnd_chipid id, const struct bhnd_pmu_io *io, void *ctx) { query->dev = dev; query->io = io; query->io_ctx = ctx; query->cid = id; query->caps = BHND_PMU_READ_4(query, BHND_PMU_CAP); return (0); } /** * Release any resources held by @p query. * * @param query A query instance previously initialized via * bhnd_pmu_query_init(). */ void bhnd_pmu_query_fini(struct bhnd_pmu_query *query) { /* nothing to do */ } /** * Perform an indirect register read. * * @param addr Offset of the address register. * @param data Offset of the data register. * @param reg Indirect register to be read. */ uint32_t bhnd_pmu_ind_read(const struct bhnd_pmu_io *io, void *io_ctx, bus_size_t addr, bus_size_t data, uint32_t reg) { io->wr4(addr, reg, io_ctx); return (io->rd4(data, io_ctx)); } /** * Perform an indirect register write. * * @param addr Offset of the address register. * @param data Offset of the data register. * @param reg Indirect register to be written. * @param val Value to be written to @p reg. * @param mask Only the bits defined by @p mask will be updated from @p val. */ void bhnd_pmu_ind_write(const struct bhnd_pmu_io *io, void *io_ctx, bus_size_t addr, bus_size_t data, uint32_t reg, uint32_t val, uint32_t mask) { uint32_t rval; io->wr4(addr, reg, io_ctx); if (mask != UINT32_MAX) { rval = io->rd4(data, io_ctx); rval &= ~mask | (val & mask); } else { rval = val; } io->wr4(data, rval, io_ctx); } /** * Wait for up to BHND_PMU_MAX_TRANSITION_DLY microseconds for the per-core * clock status to be equal to @p value after applying @p mask. * * @param sc PMU driver state. * @param dev Requesting device. * @param r An active resource mapping the clock status register. * @param clkst_reg Offset to the CLK_CTL_ST register. * @param value Value to wait for. * @param mask Mask to apply prior to value comparison. */ bool bhnd_pmu_wait_clkst(struct bhnd_pmu_softc *sc, device_t dev, struct bhnd_resource *r, bus_size_t clkst_reg, uint32_t value, uint32_t mask) { uint32_t clkst; /* Bitswapped HTAVAIL/ALPAVAIL work-around */ if (sc->quirks & BPMU_QUIRK_CLKCTL_CCS0) { uint32_t fmask, fval; fmask = mask & ~(BHND_CCS_HTAVAIL | BHND_CCS_ALPAVAIL); fval = value & ~(BHND_CCS_HTAVAIL | BHND_CCS_ALPAVAIL); if (mask & BHND_CCS_HTAVAIL) fmask |= BHND_CCS0_HTAVAIL; if (value & BHND_CCS_HTAVAIL) fval |= BHND_CCS0_HTAVAIL; if (mask & BHND_CCS_ALPAVAIL) fmask |= BHND_CCS0_ALPAVAIL; if (value & BHND_CCS_ALPAVAIL) fval |= BHND_CCS0_ALPAVAIL; mask = fmask; value = fval; } for (uint32_t i = 0; i < BHND_PMU_MAX_TRANSITION_DLY; i += 10) { clkst = bhnd_bus_read_4(r, clkst_reg); if ((clkst & mask) == (value & mask)) return (true); DELAY(10); } device_printf(dev, "clkst wait timeout (value=%#x, " "mask=%#x)\n", value, mask); return (false); } /* Setup switcher voltage */ void bhnd_pmu_set_switcher_voltage(struct bhnd_pmu_softc *sc, uint8_t bb_voltage, uint8_t rf_voltage) { BHND_PMU_REGCTRL_WRITE(sc, 0x01, (bb_voltage & 0x1f) << 22, ~0); BHND_PMU_REGCTRL_WRITE(sc, 0x00, (rf_voltage & 0x1f) << 14, ~0); } void bhnd_pmu_set_ldo_voltage(struct bhnd_pmu_softc *sc, uint8_t ldo, uint8_t voltage) { uint32_t chipst; uint32_t regctrl; uint8_t shift; uint8_t mask; uint8_t addr; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4328: case BHND_CHIPID_BCM5354: switch (ldo) { case SET_LDO_VOLTAGE_LDO1: addr = 2; shift = 17 + 8; mask = 0xf; break; case SET_LDO_VOLTAGE_LDO2: addr = 3; shift = 1; mask = 0xf; break; case SET_LDO_VOLTAGE_LDO3: addr = 3; shift = 9; mask = 0xf; break; case SET_LDO_VOLTAGE_PAREF: addr = 3; shift = 17; mask = 0x3f; break; default: panic("unknown BCM4328/BCM5354 LDO %hhu\n", ldo); } break; case BHND_CHIPID_BCM4312: switch (ldo) { case SET_LDO_VOLTAGE_PAREF: addr = 0; shift = 21; mask = 0x3f; break; default: panic("unknown BCM4312 LDO %hhu\n", ldo); } break; case BHND_CHIPID_BCM4325: switch (ldo) { case SET_LDO_VOLTAGE_CLDO_PWM: addr = 5; shift = 9; mask = 0xf; break; case SET_LDO_VOLTAGE_CLDO_BURST: addr = 5; shift = 13; mask = 0xf; break; case SET_LDO_VOLTAGE_CBUCK_PWM: addr = 3; shift = 20; mask = 0x1f; /* Bit 116 & 119 are inverted in CLB for opt 2b */ chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_PMUTOP_2B)) voltage ^= 0x9; break; case SET_LDO_VOLTAGE_CBUCK_BURST: addr = 3; shift = 25; mask = 0x1f; /* Bit 121 & 124 are inverted in CLB for opt 2b */ chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_PMUTOP_2B)) voltage ^= 0x9; break; case SET_LDO_VOLTAGE_LNLDO1: addr = 5; shift = 17; mask = 0x1f; break; case SET_LDO_VOLTAGE_LNLDO2_SEL: addr = 6; shift = 0; mask = 0x1; break; default: panic("unknown BCM4325 LDO %hhu\n", ldo); } break; case BHND_CHIPID_BCM4336: switch (ldo) { case SET_LDO_VOLTAGE_CLDO_PWM: addr = 4; shift = 1; mask = 0xf; break; case SET_LDO_VOLTAGE_CLDO_BURST: addr = 4; shift = 5; mask = 0xf; break; case SET_LDO_VOLTAGE_LNLDO1: addr = 4; shift = 17; mask = 0xf; break; default: panic("unknown BCM4336 LDO %hhu\n", ldo); } break; case BHND_CHIPID_BCM4330: switch (ldo) { case SET_LDO_VOLTAGE_CBUCK_PWM: addr = 3; shift = 0; mask = 0x1f; break; default: panic("unknown BCM4330 LDO %hhu\n", ldo); } break; case BHND_CHIPID_BCM4331: switch (ldo) { case SET_LDO_VOLTAGE_PAREF: addr = 1; shift = 0; mask = 0xf; break; default: panic("unknown BCM4331 LDO %hhu\n", ldo); } break; default: panic("cannot set LDO voltage on unsupported chip %hu\n", sc->cid.chip_id); return; } regctrl = (voltage & mask) << shift; BHND_PMU_REGCTRL_WRITE(sc, addr, regctrl, mask << shift); } /* d11 slow to fast clock transition time in slow clock cycles */ #define D11SCC_SLOW2FAST_TRANSITION 2 int bhnd_pmu_fast_pwrup_delay(struct bhnd_pmu_softc *sc, uint16_t *pwrup_delay) { uint32_t ilp; uint32_t uptime; u_int delay; int error; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: case BHND_CHIPID_BCM4331: case BHND_CHIPID_BCM6362: case BHND_CHIPID_BCM4313: delay = 3700; break; case BHND_CHIPID_BCM4325: error = bhnd_pmu_res_uptime(sc, BHND_PMU_RES4325_HT_AVAIL, &uptime); if (error) return (error); ilp = bhnd_pmu_ilp_clock(&sc->query); delay = (uptime + D11SCC_SLOW2FAST_TRANSITION) * ((1000000 + ilp - 1) / ilp); delay = (11 * delay) / 10; break; case BHND_CHIPID_BCM4329: error = bhnd_pmu_res_uptime(sc, BHND_PMU_RES4329_HT_AVAIL, &uptime); if (error) return (error); ilp = bhnd_pmu_ilp_clock(&sc->query); delay = (uptime + D11SCC_SLOW2FAST_TRANSITION) * ((1000000 + ilp - 1) / ilp); delay = (11 * delay) / 10; break; case BHND_CHIPID_BCM4319: delay = 3700; break; case BHND_CHIPID_BCM4336: error = bhnd_pmu_res_uptime(sc, BHND_PMU_RES4336_HT_AVAIL, &uptime); if (error) return (error); ilp = bhnd_pmu_ilp_clock(&sc->query); delay = (uptime + D11SCC_SLOW2FAST_TRANSITION) * ((1000000 + ilp - 1) / ilp); delay = (11 * delay) / 10; break; case BHND_CHIPID_BCM4330: error = bhnd_pmu_res_uptime(sc, BHND_PMU_RES4330_HT_AVAIL, &uptime); if (error) return (error); ilp = bhnd_pmu_ilp_clock(&sc->query); delay = (uptime + D11SCC_SLOW2FAST_TRANSITION) * ((1000000 + ilp - 1) / ilp); delay = (11 * delay) / 10; break; default: delay = BHND_PMU_MAX_TRANSITION_DLY; break; } *pwrup_delay = (uint16_t)delay; return (0); } uint32_t bhnd_pmu_force_ilp(struct bhnd_pmu_softc *sc, bool force) { uint32_t orig; uint32_t pctrl; pctrl = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); orig = pctrl; if (force) pctrl &= ~(BHND_PMU_CTRL_HT_REQ_EN | BHND_PMU_CTRL_ALP_REQ_EN); else pctrl |= (BHND_PMU_CTRL_HT_REQ_EN | BHND_PMU_CTRL_ALP_REQ_EN); BHND_PMU_WRITE_4(sc, BHND_PMU_CTRL, pctrl); return (orig); } /* Setup resource up/down timers */ typedef struct { uint8_t resnum; uint16_t updown; } pmu_res_updown_t; typedef bool (*pmu_res_filter) (struct bhnd_pmu_softc *sc); /* Change resource dependencies masks */ typedef struct { uint32_t res_mask; /* resources (chip specific) */ int8_t action; /* action */ uint32_t depend_mask; /* changes to the dependencies mask */ pmu_res_filter filter; /* action is taken when filter is NULL or returns true */ } pmu_res_depend_t; /* Resource dependencies mask change action */ #define RES_DEPEND_SET 0 /* Override the dependencies mask */ #define RES_DEPEND_ADD 1 /* Add to the dependencies mask */ #define RES_DEPEND_REMOVE -1 /* Remove from the dependencies mask */ static const pmu_res_updown_t bcm4328a0_res_updown[] = { { BHND_PMU_RES4328_EXT_SWITCHER_PWM, 0x0101}, { BHND_PMU_RES4328_BB_SWITCHER_PWM, 0x1f01}, { BHND_PMU_RES4328_BB_SWITCHER_BURST, 0x010f}, { BHND_PMU_RES4328_BB_EXT_SWITCHER_BURST, 0x0101}, { BHND_PMU_RES4328_ILP_REQUEST, 0x0202}, { BHND_PMU_RES4328_RADIO_SWITCHER_PWM, 0x0f01}, { BHND_PMU_RES4328_RADIO_SWITCHER_BURST, 0x0f01}, { BHND_PMU_RES4328_ROM_SWITCH, 0x0101}, { BHND_PMU_RES4328_PA_REF_LDO, 0x0f01}, { BHND_PMU_RES4328_RADIO_LDO, 0x0f01}, { BHND_PMU_RES4328_AFE_LDO, 0x0f01}, { BHND_PMU_RES4328_PLL_LDO, 0x0f01}, { BHND_PMU_RES4328_BG_FILTBYP, 0x0101}, { BHND_PMU_RES4328_TX_FILTBYP, 0x0101}, { BHND_PMU_RES4328_RX_FILTBYP, 0x0101}, { BHND_PMU_RES4328_XTAL_PU, 0x0101}, { BHND_PMU_RES4328_XTAL_EN, 0xa001}, { BHND_PMU_RES4328_BB_PLL_FILTBYP, 0x0101}, { BHND_PMU_RES4328_RF_PLL_FILTBYP, 0x0101}, { BHND_PMU_RES4328_BB_PLL_PU, 0x0701} }; static const pmu_res_depend_t bcm4328a0_res_depend[] = { /* Adjust ILP request resource not to force ext/BB switchers into burst mode */ { PMURES_BIT(RES4328_ILP_REQUEST), RES_DEPEND_SET, PMURES_BIT(RES4328_EXT_SWITCHER_PWM) | PMURES_BIT(RES4328_BB_SWITCHER_PWM), NULL} }; static const pmu_res_updown_t bcm4325a0_res_updown[] = { { BHND_PMU_RES4325_XTAL_PU, 0x1501} }; static const pmu_res_depend_t bcm4325a0_res_depend[] = { /* Adjust OTP PU resource dependencies - remove BB BURST */ { PMURES_BIT(RES4325_OTP_PU), RES_DEPEND_REMOVE, PMURES_BIT(RES4325_BUCK_BOOST_BURST), NULL}, /* Adjust ALP/HT Avail resource dependencies - bring up BB along if it is used. */ { PMURES_BIT(RES4325_ALP_AVAIL) | PMURES_BIT(RES4325_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4325_BUCK_BOOST_BURST) | PMURES_BIT(RES4325_BUCK_BOOST_PWM), bhnd_pmu_res_depfltr_bb}, /* Adjust HT Avail resource dependencies - bring up RF switches along with HT. */ { PMURES_BIT(RES4325_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4325_RX_PWRSW_PU) | PMURES_BIT(RES4325_TX_PWRSW_PU) | PMURES_BIT(RES4325_LOGEN_PWRSW_PU) | PMURES_BIT(RES4325_AFE_PWRSW_PU), NULL}, /* Adjust ALL resource dependencies - remove CBUCK dependencies if it is not used. */ { PMURES_BIT(RES4325_ILP_REQUEST) | PMURES_BIT(RES4325_ABUCK_BURST) | PMURES_BIT(RES4325_ABUCK_PWM) | PMURES_BIT(RES4325_LNLDO1_PU) | PMURES_BIT(RES4325C1_LNLDO2_PU) | PMURES_BIT(RES4325_XTAL_PU) | PMURES_BIT(RES4325_ALP_AVAIL) | PMURES_BIT(RES4325_RX_PWRSW_PU) | PMURES_BIT(RES4325_TX_PWRSW_PU) | PMURES_BIT(RES4325_RFPLL_PWRSW_PU) | PMURES_BIT(RES4325_LOGEN_PWRSW_PU) | PMURES_BIT(RES4325_AFE_PWRSW_PU) | PMURES_BIT(RES4325_BBPLL_PWRSW_PU) | PMURES_BIT(RES4325_HT_AVAIL), RES_DEPEND_REMOVE, PMURES_BIT(RES4325B0_CBUCK_LPOM) | PMURES_BIT(RES4325B0_CBUCK_BURST) | PMURES_BIT(RES4325B0_CBUCK_PWM), bhnd_pmu_res_depfltr_ncb} }; static const pmu_res_updown_t bcm4315a0_res_updown[] = { { BHND_PMU_RES4315_XTAL_PU, 0x2501} }; static const pmu_res_depend_t bcm4315a0_res_depend[] = { /* Adjust OTP PU resource dependencies - not need PALDO unless write */ { PMURES_BIT(RES4315_OTP_PU), RES_DEPEND_REMOVE, PMURES_BIT(RES4315_PALDO_PU), bhnd_pmu_res_depfltr_npaldo}, /* Adjust ALP/HT Avail resource dependencies - bring up PALDO along if it is used. */ { PMURES_BIT(RES4315_ALP_AVAIL) | PMURES_BIT(RES4315_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4315_PALDO_PU), bhnd_pmu_res_depfltr_paldo}, /* Adjust HT Avail resource dependencies - bring up RF switches along with HT. */ { PMURES_BIT(RES4315_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4315_RX_PWRSW_PU) | PMURES_BIT(RES4315_TX_PWRSW_PU) | PMURES_BIT(RES4315_LOGEN_PWRSW_PU) | PMURES_BIT(RES4315_AFE_PWRSW_PU), NULL}, /* Adjust ALL resource dependencies - remove CBUCK dependencies if it is not used. */ { PMURES_BIT(RES4315_CLDO_PU) | PMURES_BIT(RES4315_ILP_REQUEST) | PMURES_BIT(RES4315_LNLDO1_PU) | PMURES_BIT(RES4315_OTP_PU) | PMURES_BIT(RES4315_LNLDO2_PU) | PMURES_BIT(RES4315_XTAL_PU) | PMURES_BIT(RES4315_ALP_AVAIL) | PMURES_BIT(RES4315_RX_PWRSW_PU) | PMURES_BIT(RES4315_TX_PWRSW_PU) | PMURES_BIT(RES4315_RFPLL_PWRSW_PU) | PMURES_BIT(RES4315_LOGEN_PWRSW_PU) | PMURES_BIT(RES4315_AFE_PWRSW_PU) | PMURES_BIT(RES4315_BBPLL_PWRSW_PU) | PMURES_BIT(RES4315_HT_AVAIL), RES_DEPEND_REMOVE, PMURES_BIT(RES4315_CBUCK_LPOM) | PMURES_BIT(RES4315_CBUCK_BURST) | PMURES_BIT(RES4315_CBUCK_PWM), bhnd_pmu_res_depfltr_ncb} }; /* 4329 specific. needs to come back this issue later */ static const pmu_res_updown_t bcm4329_res_updown[] = { { BHND_PMU_RES4329_XTAL_PU, 0x1501} }; static const pmu_res_depend_t bcm4329_res_depend[] = { /* Adjust HT Avail resource dependencies */ { PMURES_BIT(RES4329_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4329_CBUCK_LPOM) | PMURES_BIT(RES4329_CBUCK_BURST) | PMURES_BIT(RES4329_CBUCK_PWM) | PMURES_BIT(RES4329_CLDO_PU) | PMURES_BIT(RES4329_PALDO_PU) | PMURES_BIT(RES4329_LNLDO1_PU) | PMURES_BIT(RES4329_XTAL_PU) | PMURES_BIT(RES4329_ALP_AVAIL) | PMURES_BIT(RES4329_RX_PWRSW_PU) | PMURES_BIT(RES4329_TX_PWRSW_PU) | PMURES_BIT(RES4329_RFPLL_PWRSW_PU) | PMURES_BIT(RES4329_LOGEN_PWRSW_PU) | PMURES_BIT(RES4329_AFE_PWRSW_PU) | PMURES_BIT(RES4329_BBPLL_PWRSW_PU), NULL} }; static const pmu_res_updown_t bcm4319a0_res_updown[] = { { BHND_PMU_RES4319_XTAL_PU, 0x3f01} }; static const pmu_res_depend_t bcm4319a0_res_depend[] = { /* Adjust OTP PU resource dependencies - not need PALDO unless write */ { PMURES_BIT(RES4319_OTP_PU), RES_DEPEND_REMOVE, PMURES_BIT(RES4319_PALDO_PU), bhnd_pmu_res_depfltr_npaldo}, /* Adjust HT Avail resource dependencies - bring up PALDO along if it is used. */ { PMURES_BIT(RES4319_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4319_PALDO_PU), bhnd_pmu_res_depfltr_paldo}, /* Adjust HT Avail resource dependencies - bring up RF switches along with HT. */ { PMURES_BIT(RES4319_HT_AVAIL), RES_DEPEND_ADD, PMURES_BIT(RES4319_RX_PWRSW_PU) | PMURES_BIT(RES4319_TX_PWRSW_PU) | PMURES_BIT(RES4319_RFPLL_PWRSW_PU) | PMURES_BIT(RES4319_LOGEN_PWRSW_PU) | PMURES_BIT(RES4319_AFE_PWRSW_PU), NULL} }; static const pmu_res_updown_t bcm4336a0_res_updown[] = { { BHND_PMU_RES4336_HT_AVAIL, 0x0D01} }; static const pmu_res_depend_t bcm4336a0_res_depend[] = { /* Just a dummy entry for now */ { PMURES_BIT(RES4336_RSVD), RES_DEPEND_ADD, 0, NULL} }; static const pmu_res_updown_t bcm4330a0_res_updown[] = { { BHND_PMU_RES4330_HT_AVAIL, 0x0e02} }; static const pmu_res_depend_t bcm4330a0_res_depend[] = { /* Just a dummy entry for now */ { PMURES_BIT(RES4330_HT_AVAIL), RES_DEPEND_ADD, 0, NULL} }; /* true if the power topology uses the buck boost to provide 3.3V to VDDIO_RF * and WLAN PA */ static bool bhnd_pmu_res_depfltr_bb(struct bhnd_pmu_softc *sc) { return (BHND_PMU_GET_FLAG(sc->board.board_flags, BHND_BFL_BUCKBOOST)); } /* true if the power topology doesn't use the cbuck. Key on chiprev also if * the chip is BCM4325. */ static bool bhnd_pmu_res_depfltr_ncb(struct bhnd_pmu_softc *sc) { if (sc->cid.chip_id == BHND_CHIPID_BCM4325 && sc->cid.chip_rev <= 1) return (false); return (BHND_PMU_GET_FLAG(sc->board.board_flags, BHND_BFL_NOCBUCK)); } /* true if the power topology uses the PALDO */ static bool bhnd_pmu_res_depfltr_paldo(struct bhnd_pmu_softc *sc) { return (BHND_PMU_GET_FLAG(sc->board.board_flags, BHND_BFL_PALDO)); } /* true if the power topology doesn't use the PALDO */ static bool bhnd_pmu_res_depfltr_npaldo(struct bhnd_pmu_softc *sc) { return (!BHND_PMU_GET_FLAG(sc->board.board_flags, BHND_BFL_PALDO)); } /* Determine min/max rsrc masks. Value 0 leaves hardware at default. */ static int bhnd_pmu_res_masks(struct bhnd_pmu_softc *sc, uint32_t *pmin, uint32_t *pmax) { uint32_t max_mask, min_mask; uint32_t chipst, otpsel; uint32_t nval; uint8_t rsrcs; int error; max_mask = 0; min_mask = 0; /* # resources */ rsrcs = BHND_PMU_GET_BITS(sc->caps, BHND_PMU_CAP_RC); /* determine min/max rsrc masks */ switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4325: /* If used by this device, enable the CBUCK */ if (!bhnd_pmu_res_depfltr_ncb(sc)) min_mask |= PMURES_BIT(RES4325B0_CBUCK_LPOM); chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_PMUTOP_2B)) min_mask |= PMURES_BIT(RES4325B0_CLDO_PU); /* Is OTP required? */ otpsel = BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_SPROM_OTP_SEL); if (otpsel != CHIPC_CST_OTP_PWRDN) min_mask |= PMURES_BIT(RES4325_OTP_PU); /* Leave buck boost on in burst mode for certain boards */ if (sc->board.board_flags & BHND_BFL_BUCKBOOST) { switch (sc->board.board_type) { case BHND_BOARD_BCM94325DEVBU: case BHND_BOARD_BCM94325BGABU: min_mask |= PMURES_BIT( RES4325_BUCK_BOOST_BURST); break; } } /* Allow all resources to be turned on upon requests */ max_mask = ~(~0 << rsrcs); break; case BHND_CHIPID_BCM4312: /* default min_mask = 0x80000cbb is wrong */ min_mask = 0xcbb; /* * max_mask = 0x7fff; * pmu_res_updown_table_sz = 0; * pmu_res_depend_table_sz = 0; */ break; case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM4342: if (sc->cid.chip_rev >= 2) break; /* request ALP(can skip for A1) */ min_mask = PMURES_BIT(RES4322_RF_LDO) | PMURES_BIT(RES4322_XTAL_PU) | PMURES_BIT(RES4322_ALP_AVAIL); if (bhnd_get_attach_type(sc->chipc_dev) == BHND_ATTACH_NATIVE) { min_mask |= PMURES_BIT(RES4322_SI_PLL_ON) | PMURES_BIT(RES4322_HT_SI_AVAIL) | PMURES_BIT(RES4322_PHY_PLL_ON) | PMURES_BIT(RES4322_OTP_PU) | PMURES_BIT(RES4322_HT_PHY_AVAIL); max_mask = 0x1ff; } break; case BHND_CHIPID_BCM43222: case BHND_CHIPID_BCM43111: case BHND_CHIPID_BCM43112: case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43226: case BHND_CHIPID_BCM43420: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: case BHND_CHIPID_BCM43234: case BHND_CHIPID_BCM43237: case BHND_CHIPID_BCM4331: case BHND_CHIPID_BCM43431: case BHND_CHIPID_BCM6362: /* use chip default */ break; case BHND_CHIPID_BCM4328: min_mask = PMURES_BIT(RES4328_BB_SWITCHER_PWM) | PMURES_BIT(RES4328_EXT_SWITCHER_PWM) | PMURES_BIT(RES4328_XTAL_EN); max_mask = 0xfffffff; break; case BHND_CHIPID_BCM5354: /* Allow (but don't require) PLL to turn on */ max_mask = 0xfffffff; break; case BHND_CHIPID_BCM4329: /* Down to save the power. */ if (sc->cid.chip_rev >= 0x2) { min_mask = PMURES_BIT(RES4329_CBUCK_LPOM) | PMURES_BIT(RES4329_LNLDO1_PU) | PMURES_BIT(RES4329_CLDO_PU); } else { min_mask = PMURES_BIT(RES4329_CBUCK_LPOM) | PMURES_BIT(RES4329_CLDO_PU); } /* Is OTP required? */ chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); otpsel = BHND_PMU_GET_BITS(chipst, CHIPC_CST4329_SPROM_OTP_SEL); if (otpsel != CHIPC_CST_OTP_PWRDN) min_mask |= PMURES_BIT(RES4329_OTP_PU); /* Allow (but don't require) PLL to turn on */ max_mask = 0x3ff63e; break; case BHND_CHIPID_BCM4319: /* We only need a few resources to be kept on all the time */ min_mask = PMURES_BIT(RES4319_CBUCK_LPOM) | PMURES_BIT(RES4319_CLDO_PU); /* Allow everything else to be turned on upon requests */ max_mask = ~(~0 << rsrcs); break; case BHND_CHIPID_BCM4336: /* Down to save the power. */ min_mask = PMURES_BIT(RES4336_CBUCK_LPOM) | PMURES_BIT(RES4336_CLDO_PU) | PMURES_BIT(RES4336_LDO3P3_PU) | PMURES_BIT(RES4336_OTP_PU) | PMURES_BIT(RES4336_DIS_INT_RESET_PD); /* Allow (but don't require) PLL to turn on */ max_mask = 0x1ffffff; break; case BHND_CHIPID_BCM4330: /* Down to save the power. */ min_mask = PMURES_BIT(RES4330_CBUCK_LPOM) | PMURES_BIT(RES4330_CLDO_PU) | PMURES_BIT(RES4330_DIS_INT_RESET_PD) | PMURES_BIT(RES4330_LDO3P3_PU) | PMURES_BIT(RES4330_OTP_PU); /* Allow (but don't require) PLL to turn on */ max_mask = 0xfffffff; break; case BHND_CHIPID_BCM4313: min_mask = PMURES_BIT(RES4313_BB_PU_RSRC) | PMURES_BIT(RES4313_XTAL_PU_RSRC) | PMURES_BIT(RES4313_ALP_AVAIL_RSRC) | PMURES_BIT(RES4313_BB_PLL_PWRSW_RSRC); max_mask = 0xffff; break; default: break; } /* Apply nvram override to min mask */ error = bhnd_nvram_getvar_uint32(sc->chipc_dev, BHND_NVAR_RMIN, &nval); if (error && error != ENOENT) { PMU_LOG(sc, "NVRAM error reading %s: %d\n", BHND_NVAR_RMIN, error); return (error); } else if (!error) { PMU_DEBUG(sc, "Applying rmin=%#x to min_mask\n", nval); min_mask = nval; } /* Apply nvram override to max mask */ error = bhnd_nvram_getvar_uint32(sc->chipc_dev, BHND_NVAR_RMAX, &nval); if (error && error != ENOENT) { PMU_LOG(sc, "NVRAM error reading %s: %d\n", BHND_NVAR_RMAX, error); return (error); } else if (!error) { PMU_DEBUG(sc, "Applying rmax=%#x to max_mask\n", nval); min_mask = nval; } if (pmin != NULL) *pmin = min_mask; if (pmax != NULL) *pmax = max_mask; return (0); } /* initialize PMU resources */ int bhnd_pmu_res_init(struct bhnd_pmu_softc *sc) { const pmu_res_updown_t *pmu_res_updown_table; const pmu_res_depend_t *pmu_res_depend_table; size_t pmu_res_updown_table_sz; size_t pmu_res_depend_table_sz; uint32_t max_mask, min_mask; uint8_t rsrcs; int error; pmu_res_depend_table = NULL; pmu_res_depend_table_sz = 0; pmu_res_updown_table = NULL; pmu_res_updown_table_sz = 0; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4315: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4315a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4315a0_res_updown); /* Optimize resources dependencies */ pmu_res_depend_table = bcm4315a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4315a0_res_depend); break; case BHND_CHIPID_BCM4325: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4325a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4325a0_res_updown); /* Optimize resources dependencies */ pmu_res_depend_table = bcm4325a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4325a0_res_depend); break; case BHND_CHIPID_BCM4328: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4328a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4328a0_res_updown); /* Optimize resources dependencies */ pmu_res_depend_table = bcm4328a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4328a0_res_depend); break; case BHND_CHIPID_BCM4329: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4329_res_updown; pmu_res_updown_table_sz = nitems(bcm4329_res_updown); /* Optimize resources dependencies */ pmu_res_depend_table = bcm4329_res_depend; pmu_res_depend_table_sz = nitems(bcm4329_res_depend); break; case BHND_CHIPID_BCM4319: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4319a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4319a0_res_updown); /* Optimize resources dependencies masks */ pmu_res_depend_table = bcm4319a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4319a0_res_depend); break; case BHND_CHIPID_BCM4336: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4336a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4336a0_res_updown); /* Optimize resources dependencies masks */ pmu_res_depend_table = bcm4336a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4336a0_res_depend); break; case BHND_CHIPID_BCM4330: /* Optimize resources up/down timers */ pmu_res_updown_table = bcm4330a0_res_updown; pmu_res_updown_table_sz = nitems(bcm4330a0_res_updown); /* Optimize resources dependencies masks */ pmu_res_depend_table = bcm4330a0_res_depend; pmu_res_depend_table_sz = nitems(bcm4330a0_res_depend); break; default: break; } /* # resources */ rsrcs = BHND_PMU_GET_BITS(sc->caps, BHND_PMU_CAP_RC); /* Program up/down timers */ for (size_t i = 0; i < pmu_res_updown_table_sz; i++) { const pmu_res_updown_t *updt; KASSERT(pmu_res_updown_table != NULL, ("no updown tables")); updt = &pmu_res_updown_table[pmu_res_updown_table_sz - i - 1]; PMU_DEBUG(sc, "Changing rsrc %d res_updn_timer to %#x\n", updt->resnum, updt->updown); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, updt->resnum); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_UPDN_TIMER, updt->updown); } /* Apply nvram overrides to up/down timers */ for (uint8_t i = 0; i < rsrcs; i++) { char name[6]; uint32_t val; snprintf(name, sizeof(name), "r%dt", i); error = bhnd_nvram_getvar_uint32(sc->chipc_dev, name, &val); if (error == ENOENT) { continue; } else if (error) { PMU_LOG(sc, "NVRAM error reading %s: %d\n", name, error); return (error); } PMU_DEBUG(sc, "Applying %s=%s to rsrc %d res_updn_timer\n", name, val, i); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, i); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_UPDN_TIMER, val); } /* Program resource dependencies table */ for (size_t i = 0; i < pmu_res_depend_table_sz; i++) { const pmu_res_depend_t *rdep; pmu_res_filter filter; uint32_t depend_mask; KASSERT(pmu_res_depend_table != NULL, ("no depend tables")); rdep = &pmu_res_depend_table[pmu_res_depend_table_sz - i - 1]; filter = rdep->filter; if (filter != NULL && !filter(sc)) continue; for (uint8_t i = 0; i < rsrcs; i++) { if ((rdep->res_mask & BHND_PMURES_BIT(i)) == 0) continue; BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, i); depend_mask = BHND_PMU_READ_4(sc, BHND_PMU_RES_DEP_MASK); switch (rdep->action) { case RES_DEPEND_SET: PMU_DEBUG(sc, "Changing rsrc %hhu res_dep_mask to " "%#x\n", i, table->depend_mask); depend_mask = rdep->depend_mask; break; case RES_DEPEND_ADD: PMU_DEBUG(sc, "Adding %#x to rsrc %hhu " "res_dep_mask\n", table->depend_mask, i); depend_mask |= rdep->depend_mask; break; case RES_DEPEND_REMOVE: PMU_DEBUG(sc, "Removing %#x from rsrc %hhu " "res_dep_mask\n", table->depend_mask, i); depend_mask &= ~(rdep->depend_mask); break; default: panic("unknown RES_DEPEND action: %d\n", rdep->action); break; } } } /* Apply nvram overrides to dependencies masks */ for (uint8_t i = 0; i < rsrcs; i++) { char name[6]; uint32_t val; snprintf(name, sizeof(name), "r%dd", i); error = bhnd_nvram_getvar_uint32(sc->chipc_dev, name, &val); if (error == ENOENT) { continue; } else if (error) { PMU_LOG(sc, "NVRAM error reading %s: %d\n", name, error); return (error); } PMU_DEBUG(sc, "Applying %s=%s to rsrc %d res_dep_mask\n", name, val, i); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, i); BHND_PMU_WRITE_4(sc, BHND_PMU_RES_DEP_MASK, val); } /* Determine min/max rsrc masks */ if ((error = bhnd_pmu_res_masks(sc, &min_mask, &max_mask))) return (error); /* It is required to program max_mask first and then min_mask */ /* Program max resource mask */ if (max_mask != 0) { PMU_DEBUG(sc, "Changing max_res_mask to 0x%x\n", max_mask); BHND_PMU_WRITE_4(sc, BHND_PMU_MAX_RES_MASK, max_mask); } /* Program min resource mask */ if (min_mask != 0) { PMU_DEBUG(sc, "Changing min_res_mask to 0x%x\n", min_mask); BHND_PMU_WRITE_4(sc, BHND_PMU_MIN_RES_MASK, min_mask); } /* Add some delay; allow resources to come up and settle. */ DELAY(2000); return (0); } /* setup pll and query clock speed */ struct pmu0_xtaltab0 { uint16_t freq; uint8_t xf; uint8_t wbint; uint32_t wbfrac; }; /* the following table is based on 880Mhz fvco */ static const pmu0_xtaltab0_t pmu0_xtaltab0[] = { { 12000, 1, 73, 349525}, { 13000, 2, 67, 725937}, { 14400, 3, 61, 116508}, { 15360, 4, 57, 305834}, { 16200, 5, 54, 336579}, { 16800, 6, 52, 399457}, { 19200, 7, 45, 873813}, { 19800, 8, 44, 466033}, { 20000, 9, 44, 0}, { 25000, 10, 70, 419430}, { 26000, 11, 67, 725937}, { 30000, 12, 58, 699050}, { 38400, 13, 45, 873813}, { 40000, 14, 45, 0}, { 0, 0, 0, 0} }; #define PMU0_XTAL0_DEFAULT 8 /* setup pll and query clock speed */ struct pmu1_xtaltab0 { uint16_t fref; uint8_t xf; uint8_t p1div; uint8_t p2div; uint8_t ndiv_int; uint32_t ndiv_frac; }; static const pmu1_xtaltab0_t pmu1_xtaltab0_880_4329[] = { { 12000, 1, 3, 22, 0x9, 0xFFFFEF}, { 13000, 2, 1, 6, 0xb, 0x483483}, { 14400, 3, 1, 10, 0xa, 0x1C71C7}, { 15360, 4, 1, 5, 0xb, 0x755555}, { 16200, 5, 1, 10, 0x5, 0x6E9E06}, { 16800, 6, 1, 10, 0x5, 0x3Cf3Cf}, { 19200, 7, 1, 4, 0xb, 0x755555}, { 19800, 8, 1, 11, 0x4, 0xA57EB}, { 20000, 9, 1, 11, 0x4, 0x0}, { 24000, 10, 3, 11, 0xa, 0x0}, { 25000, 11, 5, 16, 0xb, 0x0}, { 26000, 12, 1, 1, 0x21, 0xD89D89}, { 30000, 13, 3, 8, 0xb, 0x0}, { 37400, 14, 3, 1, 0x46, 0x969696}, { 38400, 15, 1, 1, 0x16, 0xEAAAAA}, { 40000, 16, 1, 2, 0xb, 0}, { 0, 0, 0, 0, 0, 0} }; /* the following table is based on 880Mhz fvco */ static const pmu1_xtaltab0_t pmu1_xtaltab0_880[] = { { 12000, 1, 3, 22, 0x9, 0xFFFFEF}, { 13000, 2, 1, 6, 0xb, 0x483483}, { 14400, 3, 1, 10, 0xa, 0x1C71C7}, { 15360, 4, 1, 5, 0xb, 0x755555}, { 16200, 5, 1, 10, 0x5, 0x6E9E06}, { 16800, 6, 1, 10, 0x5, 0x3Cf3Cf}, { 19200, 7, 1, 4, 0xb, 0x755555}, { 19800, 8, 1, 11, 0x4, 0xA57EB}, { 20000, 9, 1, 11, 0x4, 0x0}, { 24000, 10, 3, 11, 0xa, 0x0}, { 25000, 11, 5, 16, 0xb, 0x0}, { 26000, 12, 1, 2, 0x10, 0xEC4EC4}, { 30000, 13, 3, 8, 0xb, 0x0}, { 33600, 14, 1, 2, 0xd, 0x186186}, { 38400, 15, 1, 2, 0xb, 0x755555}, { 40000, 16, 1, 2, 0xb, 0}, { 0, 0, 0, 0, 0, 0} }; #define PMU1_XTALTAB0_880_12000K 0 #define PMU1_XTALTAB0_880_13000K 1 #define PMU1_XTALTAB0_880_14400K 2 #define PMU1_XTALTAB0_880_15360K 3 #define PMU1_XTALTAB0_880_16200K 4 #define PMU1_XTALTAB0_880_16800K 5 #define PMU1_XTALTAB0_880_19200K 6 #define PMU1_XTALTAB0_880_19800K 7 #define PMU1_XTALTAB0_880_20000K 8 #define PMU1_XTALTAB0_880_24000K 9 #define PMU1_XTALTAB0_880_25000K 10 #define PMU1_XTALTAB0_880_26000K 11 #define PMU1_XTALTAB0_880_30000K 12 #define PMU1_XTALTAB0_880_37400K 13 #define PMU1_XTALTAB0_880_38400K 14 #define PMU1_XTALTAB0_880_40000K 15 /* the following table is based on 1760Mhz fvco */ static const pmu1_xtaltab0_t pmu1_xtaltab0_1760[] = { { 12000, 1, 3, 44, 0x9, 0xFFFFEF}, { 13000, 2, 1, 12, 0xb, 0x483483}, { 14400, 3, 1, 20, 0xa, 0x1C71C7}, { 15360, 4, 1, 10, 0xb, 0x755555}, { 16200, 5, 1, 20, 0x5, 0x6E9E06}, { 16800, 6, 1, 20, 0x5, 0x3Cf3Cf}, { 19200, 7, 1, 18, 0x5, 0x17B425}, { 19800, 8, 1, 22, 0x4, 0xA57EB}, { 20000, 9, 1, 22, 0x4, 0x0}, { 24000, 10, 3, 22, 0xa, 0x0}, { 25000, 11, 5, 32, 0xb, 0x0}, { 26000, 12, 1, 4, 0x10, 0xEC4EC4}, { 30000, 13, 3, 16, 0xb, 0x0}, { 38400, 14, 1, 10, 0x4, 0x955555}, { 40000, 15, 1, 4, 0xb, 0}, { 0, 0, 0, 0, 0, 0} }; /* table index */ #define PMU1_XTALTAB0_1760_12000K 0 #define PMU1_XTALTAB0_1760_13000K 1 #define PMU1_XTALTAB0_1760_14400K 2 #define PMU1_XTALTAB0_1760_15360K 3 #define PMU1_XTALTAB0_1760_16200K 4 #define PMU1_XTALTAB0_1760_16800K 5 #define PMU1_XTALTAB0_1760_19200K 6 #define PMU1_XTALTAB0_1760_19800K 7 #define PMU1_XTALTAB0_1760_20000K 8 #define PMU1_XTALTAB0_1760_24000K 9 #define PMU1_XTALTAB0_1760_25000K 10 #define PMU1_XTALTAB0_1760_26000K 11 #define PMU1_XTALTAB0_1760_30000K 12 #define PMU1_XTALTAB0_1760_38400K 13 #define PMU1_XTALTAB0_1760_40000K 14 /* the following table is based on 1440Mhz fvco */ static const pmu1_xtaltab0_t pmu1_xtaltab0_1440[] = { { 12000, 1, 1, 1, 0x78, 0x0}, { 13000, 2, 1, 1, 0x6E, 0xC4EC4E}, { 14400, 3, 1, 1, 0x64, 0x0}, { 15360, 4, 1, 1, 0x5D, 0xC00000}, { 16200, 5, 1, 1, 0x58, 0xE38E38}, { 16800, 6, 1, 1, 0x55, 0xB6DB6D}, { 19200, 7, 1, 1, 0x4B, 0}, { 19800, 8, 1, 1, 0x48, 0xBA2E8B}, { 20000, 9, 1, 1, 0x48, 0x0}, { 25000, 10, 1, 1, 0x39, 0x999999}, { 26000, 11, 1, 1, 0x37, 0x627627}, { 30000, 12, 1, 1, 0x30, 0x0}, { 37400, 13, 2, 1, 0x4D, 0x15E76}, { 38400, 13, 2, 1, 0x4B, 0x0}, { 40000, 14, 2, 1, 0x48, 0x0}, { 48000, 15, 2, 1, 0x3c, 0x0}, { 0, 0, 0, 0, 0, 0} }; /* table index */ #define PMU1_XTALTAB0_1440_12000K 0 #define PMU1_XTALTAB0_1440_13000K 1 #define PMU1_XTALTAB0_1440_14400K 2 #define PMU1_XTALTAB0_1440_15360K 3 #define PMU1_XTALTAB0_1440_16200K 4 #define PMU1_XTALTAB0_1440_16800K 5 #define PMU1_XTALTAB0_1440_19200K 6 #define PMU1_XTALTAB0_1440_19800K 7 #define PMU1_XTALTAB0_1440_20000K 8 #define PMU1_XTALTAB0_1440_25000K 9 #define PMU1_XTALTAB0_1440_26000K 10 #define PMU1_XTALTAB0_1440_30000K 11 #define PMU1_XTALTAB0_1440_37400K 12 #define PMU1_XTALTAB0_1440_38400K 13 #define PMU1_XTALTAB0_1440_40000K 14 #define PMU1_XTALTAB0_1440_48000K 15 #define XTAL_FREQ_24000MHZ 24000 #define XTAL_FREQ_30000MHZ 30000 #define XTAL_FREQ_37400MHZ 37400 #define XTAL_FREQ_48000MHZ 48000 static const pmu1_xtaltab0_t pmu1_xtaltab0_960[] = { { 12000, 1, 1, 1, 0x50, 0x0}, { 13000, 2, 1, 1, 0x49, 0xD89D89}, { 14400, 3, 1, 1, 0x42, 0xAAAAAA}, { 15360, 4, 1, 1, 0x3E, 0x800000}, { 16200, 5, 1, 1, 0x39, 0x425ED0}, { 16800, 6, 1, 1, 0x39, 0x249249}, { 19200, 7, 1, 1, 0x32, 0x0}, { 19800, 8, 1, 1, 0x30, 0x7C1F07}, { 20000, 9, 1, 1, 0x30, 0x0}, { 25000, 10, 1, 1, 0x26, 0x666666}, { 26000, 11, 1, 1, 0x24, 0xEC4EC4}, { 30000, 12, 1, 1, 0x20, 0x0}, { 37400, 13, 2, 1, 0x33, 0x563EF9}, { 38400, 14, 2, 1, 0x32, 0x0}, { 40000, 15, 2, 1, 0x30, 0x0}, { 48000, 16, 2, 1, 0x28, 0x0}, { 0, 0, 0, 0, 0, 0} }; /* table index */ #define PMU1_XTALTAB0_960_12000K 0 #define PMU1_XTALTAB0_960_13000K 1 #define PMU1_XTALTAB0_960_14400K 2 #define PMU1_XTALTAB0_960_15360K 3 #define PMU1_XTALTAB0_960_16200K 4 #define PMU1_XTALTAB0_960_16800K 5 #define PMU1_XTALTAB0_960_19200K 6 #define PMU1_XTALTAB0_960_19800K 7 #define PMU1_XTALTAB0_960_20000K 8 #define PMU1_XTALTAB0_960_25000K 9 #define PMU1_XTALTAB0_960_26000K 10 #define PMU1_XTALTAB0_960_30000K 11 #define PMU1_XTALTAB0_960_37400K 12 #define PMU1_XTALTAB0_960_38400K 13 #define PMU1_XTALTAB0_960_40000K 14 #define PMU1_XTALTAB0_960_48000K 15 /* select xtal table for each chip */ static const pmu1_xtaltab0_t * bhnd_pmu1_xtaltab0(struct bhnd_pmu_query *sc) { switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4315: return (pmu1_xtaltab0_1760); case BHND_CHIPID_BCM4319: return (pmu1_xtaltab0_1440); case BHND_CHIPID_BCM4325: return (pmu1_xtaltab0_880); case BHND_CHIPID_BCM4329: return (pmu1_xtaltab0_880_4329); case BHND_CHIPID_BCM4336: return (pmu1_xtaltab0_960); case BHND_CHIPID_BCM4330: if (PMU_CST4330_SDIOD_CHIPMODE(sc)) return (pmu1_xtaltab0_960); else return (pmu1_xtaltab0_1440); default: PMU_DEBUG(sc, "bhnd_pmu1_xtaltab0: Unknown chipid %#hx\n", sc->cid.chip_id); return (NULL); } } /* select default xtal frequency for each chip */ static const pmu1_xtaltab0_t * bhnd_pmu1_xtaldef0(struct bhnd_pmu_query *sc) { switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4315: /* Default to 26000Khz */ return (&pmu1_xtaltab0_1760[PMU1_XTALTAB0_1760_26000K]); case BHND_CHIPID_BCM4319: /* Default to 30000Khz */ return (&pmu1_xtaltab0_1440[PMU1_XTALTAB0_1440_30000K]); case BHND_CHIPID_BCM4325: /* Default to 26000Khz */ return (&pmu1_xtaltab0_880[PMU1_XTALTAB0_880_26000K]); case BHND_CHIPID_BCM4329: /* Default to 38400Khz */ return (&pmu1_xtaltab0_880_4329[PMU1_XTALTAB0_880_38400K]); case BHND_CHIPID_BCM4336: /* Default to 26000Khz */ return (&pmu1_xtaltab0_960[PMU1_XTALTAB0_960_26000K]); case BHND_CHIPID_BCM4330: /* Default to 37400Khz */ if (PMU_CST4330_SDIOD_CHIPMODE(sc)) return (&pmu1_xtaltab0_960[PMU1_XTALTAB0_960_37400K]); else return (&pmu1_xtaltab0_1440[PMU1_XTALTAB0_1440_37400K]); default: PMU_DEBUG(sc, "bhnd_pmu1_xtaldef0: Unknown chipid %#hx\n", sc->cid.chip_id); return (NULL); } } /* select default pll fvco for each chip */ static uint32_t bhnd_pmu1_pllfvco0(struct bhnd_pmu_query *sc) { switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4329: return (FVCO_880); case BHND_CHIPID_BCM4319: return (FVCO_1440); case BHND_CHIPID_BCM4336: return (FVCO_960); case BHND_CHIPID_BCM4330: if (PMU_CST4330_SDIOD_CHIPMODE(sc)) return (FVCO_960); else return (FVCO_1440); default: PMU_DEBUG(sc, "bhnd_pmu1_pllfvco0: Unknown chipid %#hx\n", sc->cid.chip_id); return (0); } } /* query alp/xtal clock frequency */ static uint32_t bhnd_pmu1_alpclk0(struct bhnd_pmu_query *sc) { const pmu1_xtaltab0_t *xt; uint32_t xf; /* Find the frequency in the table */ xf = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); xf = BHND_PMU_GET_BITS(xf, BHND_PMU_CTRL_XTALFREQ); for (xt = bhnd_pmu1_xtaltab0(sc); xt != NULL && xt->fref != 0; xt++) { if (xt->xf == xf) break; } /* Could not find it so assign a default value */ if (xt == NULL || xt->fref == 0) xt = bhnd_pmu1_xtaldef0(sc); if (xt == NULL || xt->fref == 0) { PMU_LOG(sc, "no matching ALP/XTAL frequency found\n"); return (0); } return (xt->fref * 1000); } /* Set up PLL registers in the PMU as per the crystal speed. */ static void bhnd_pmu0_pllinit0(struct bhnd_pmu_softc *sc, uint32_t xtal) { const pmu0_xtaltab0_t *xt; uint32_t pll_data, pll_mask; uint32_t pll_res; uint32_t pmu_ctrl; uint32_t xf; /* Use h/w default PLL config */ if (xtal == 0) { PMU_DEBUG(sc, "Unspecified xtal frequency, skipping PLL " "configuration\n"); return; } /* Find the frequency in the table */ for (xt = pmu0_xtaltab0; xt->freq; xt ++) { if (xt->freq == xtal) break; } if (xt->freq == 0) xt = &pmu0_xtaltab0[PMU0_XTAL0_DEFAULT]; PMU_DEBUG(sc, "XTAL %d.%d MHz (%d)\n", xtal / 1000, xtal % 1000, xt->xf); /* Check current PLL state */ pmu_ctrl = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); xf = BHND_PMU_GET_BITS(pmu_ctrl, BHND_PMU_CTRL_XTALFREQ); if (xf == xt->xf) { #ifdef BCMUSBDEV if (sc->cid.chip_id == BHND_CHIPID_BCM4328) { bhnd_pmu0_sbclk4328(sc, BHND_PMU0_PLL0_PC0_DIV_ARM_88MHZ); return; } #endif /* BCMUSBDEV */ PMU_DEBUG(sc, "PLL already programmed for %d.%d MHz\n", xt->freq / 1000, xt->freq % 1000); return; } if (xf != 0) { PMU_DEBUG(sc, "Reprogramming PLL for %d.%d MHz (was %d.%dMHz)\n", xt->freq / 1000, xt->freq % 1000, pmu0_xtaltab0[tmp-1].freq / 1000, pmu0_xtaltab0[tmp-1].freq % 1000); } else { PMU_DEBUG(sc, "Programming PLL for %d.%d MHz\n", xt->freq / 1000, xt->freq % 1000); } /* Make sure the PLL is off */ switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4328: pll_res = PMURES_BIT(RES4328_BB_PLL_PU); break; case BHND_CHIPID_BCM5354: pll_res = PMURES_BIT(RES5354_BB_PLL_PU); break; default: panic("unsupported chipid %#hx\n", sc->cid.chip_id); } BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~pll_res); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~pll_res); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); PMU_DEBUG(sc, "Done masking\n"); /* Write PDIV in pllcontrol[0] */ if (xt->freq >= BHND_PMU0_PLL0_PC0_PDIV_FREQ) { BHND_PMU_PLL_WRITE(sc, BHND_PMU0_PLL0_PLLCTL0, BHND_PMU0_PLL0_PC0_PDIV_MASK, BHND_PMU0_PLL0_PC0_PDIV_MASK); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU0_PLL0_PLLCTL0, 0, BHND_PMU0_PLL0_PC0_PDIV_MASK); } /* Write WILD in pllcontrol[1] */ pll_data = BHND_PMU_SET_BITS(xt->wbint, BHND_PMU0_PLL0_PC1_WILD_INT) | BHND_PMU_SET_BITS(xt->wbfrac, BHND_PMU0_PLL0_PC1_WILD_FRAC); if (xt->wbfrac == 0) { pll_data |= BHND_PMU0_PLL0_PC1_STOP_MOD; } else { pll_data &= ~BHND_PMU0_PLL0_PC1_STOP_MOD; } pll_mask = BHND_PMU0_PLL0_PC1_WILD_INT_MASK | BHND_PMU0_PLL0_PC1_WILD_FRAC_MASK; BHND_PMU_PLL_WRITE(sc, BHND_PMU0_PLL0_PLLCTL1, pll_data, pll_mask); /* Write WILD in pllcontrol[2] */ pll_data = BHND_PMU_SET_BITS(xt->wbint, BHND_PMU0_PLL0_PC2_WILD_INT); pll_mask = BHND_PMU0_PLL0_PC2_WILD_INT_MASK; BHND_PMU_PLL_WRITE(sc, BHND_PMU0_PLL0_PLLCTL2, pll_data, pll_mask); PMU_DEBUG(sc, "Done pll\n"); /* Write XtalFreq. Set the divisor also. */ pmu_ctrl = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); pmu_ctrl &= ~(BHND_PMU_CTRL_ILP_DIV_MASK|BHND_PMU_CTRL_XTALFREQ_MASK); pmu_ctrl |= BHND_PMU_SET_BITS(((xt->freq + 127) / 128) - 1, BHND_PMU_CTRL_ILP_DIV); pmu_ctrl |= BHND_PMU_SET_BITS(xt->xf, BHND_PMU_CTRL_XTALFREQ); BHND_PMU_WRITE_4(sc, BHND_PMU_CTRL, pmu_ctrl); } /* query alp/xtal clock frequency */ static uint32_t bhnd_pmu0_alpclk0(struct bhnd_pmu_query *sc) { const pmu0_xtaltab0_t *xt; uint32_t xf; /* Find the frequency in the table */ xf = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); xf = BHND_PMU_GET_BITS(xf, BHND_PMU_CTRL_XTALFREQ); for (xt = pmu0_xtaltab0; xt->freq; xt++) if (xt->xf == xf) break; /* PLL must be configured before */ if (xt == NULL || xt->freq == 0) panic("unsupported frequency: %u", xf); return (xt->freq * 1000); } /* query CPU clock frequency */ static uint32_t bhnd_pmu0_cpuclk0(struct bhnd_pmu_query *sc) { uint32_t tmp, divarm; uint32_t FVCO; #ifdef BCMDBG uint32_t pdiv, wbint, wbfrac, fvco; uint32_t freq; #endif FVCO = FVCO_880; /* Read divarm from pllcontrol[0] */ tmp = BHND_PMU_PLL_READ(sc, BHND_PMU0_PLL0_PLLCTL0); divarm = BHND_PMU_GET_BITS(tmp, BHND_PMU0_PLL0_PC0_DIV_ARM); #ifdef BCMDBG /* Calculate fvco based on xtal freq, pdiv, and wild */ pdiv = tmp & BHND_PMU0_PLL0_PC0_PDIV_MASK; tmp = BHND_PMU_PLL_READ(sc, BHND_PMU0_PLL0_PLLCTL1); wbfrac = BHND_PMU_GET_BITS(tmp, BHND_PMU0_PLL0_PC1_WILD_FRAC); wbint = BHND_PMU_GET_BITS(tmp, PMU0_PLL0_PC1_WILD_INT); tmp = BHND_PMU_PLL_READ(sc, BHND_PMU0_PLL0_PLLCTL2); wbint += BHND_PMU_GET_BITS(tmp, BHND_PMU0_PLL0_PC2_WILD_INT); freq = bhnd_pmu0_alpclk0(sih, osh, cc) / 1000; fvco = (freq * wbint) << 8; fvco += (freq * (wbfrac >> 10)) >> 2; fvco += (freq * (wbfrac & 0x3ff)) >> 10; fvco >>= 8; fvco >>= pdiv; fvco /= 1000; fvco *= 1000; PMU_DEBUG(sc, "bhnd_pmu0_cpuclk0: wbint %u wbfrac %u fvco %u\n", wbint, wbfrac, fvco); FVCO = fvco; #endif /* BCMDBG */ /* Return ARM/SB clock */ return FVCO / (divarm + BHND_PMU0_PLL0_PC0_DIV_ARM_BASE) * 1000; } /* Set up PLL registers in the PMU as per the crystal speed. */ static void bhnd_pmu1_pllinit0(struct bhnd_pmu_softc *sc, uint32_t xtal) { const pmu1_xtaltab0_t *xt; uint32_t buf_strength; uint32_t plladdr, plldata, pllmask; uint32_t pmuctrl; uint32_t FVCO; uint8_t ndiv_mode; FVCO = bhnd_pmu1_pllfvco0(&sc->query) / 1000; buf_strength = 0; ndiv_mode = 1; /* Use h/w default PLL config */ if (xtal == 0) { PMU_DEBUG(sc, "Unspecified xtal frequency, skipping PLL " "configuration\n"); return; } /* Find the frequency in the table */ for (xt = bhnd_pmu1_xtaltab0(&sc->query); xt != NULL && xt->fref != 0; xt++) { if (xt->fref == xtal) break; } /* Check current PLL state, bail out if it has been programmed or * we don't know how to program it. */ if (xt == NULL || xt->fref == 0) { PMU_LOG(sc, "Unsupported XTAL frequency %d.%dMHz, skipping PLL " "configuration\n", xtal / 1000, xtal % 1000); return; } /* For 4319 bootloader already programs the PLL but bootloader does not * program the PLL4 and PLL5. So Skip this check for 4319. */ pmuctrl = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); if (BHND_PMU_GET_BITS(pmuctrl, BHND_PMU_CTRL_XTALFREQ) == xt->xf && sc->cid.chip_id != BHND_CHIPID_BCM4319 && sc->cid.chip_id != BHND_CHIPID_BCM4330) { PMU_DEBUG(sc, "PLL already programmed for %d.%dMHz\n", xt->fref / 1000, xt->fref % 1000); return; } PMU_DEBUG(sc, "XTAL %d.%dMHz (%d)\n", xtal / 1000, xtal % 1000, xt->xf); PMU_DEBUG(sc, "Programming PLL for %d.%dMHz\n", xt->fref / 1000, xt->fref % 1000); switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4325: /* Change the BBPLL drive strength to 2 for all channels */ buf_strength = 0x222222; BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4325_BBPLL_PWRSW_PU) | PMURES_BIT(RES4325_HT_AVAIL))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4325_BBPLL_PWRSW_PU) | PMURES_BIT(RES4325_HT_AVAIL))); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); break; case BHND_CHIPID_BCM4329: /* Change the BBPLL drive strength to 8 for all channels */ buf_strength = 0x888888; BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4329_BBPLL_PWRSW_PU) | PMURES_BIT(RES4329_HT_AVAIL))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4329_BBPLL_PWRSW_PU) | PMURES_BIT(RES4329_HT_AVAIL))); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); /* Initialize PLL4 */ plladdr = BHND_PMU1_PLL0_PLLCTL4; if (xt->fref == 38400) plldata = 0x200024C0; else if (xt->fref == 37400) plldata = 0x20004500; else if (xt->fref == 26000) plldata = 0x200024C0; else plldata = 0x200005C0; /* Chip Dflt Settings */ BHND_PMU_PLL_WRITE(sc, plladdr, plldata, ~0); /* Initialize PLL5 */ plladdr = BHND_PMU1_PLL0_PLLCTL5; plldata = BHND_PMU_PLL_READ(sc, plladdr); plldata &= BHND_PMU1_PLL0_PC5_CLK_DRV_MASK; if (xt->fref == 38400 || xt->fref == 37400 || xt->fref == 26000) { plldata |= 0x15; } else { plldata |= 0x25; /* Chip Dflt Settings */ } BHND_PMU_PLL_WRITE(sc, plladdr, plldata, ~0); break; case BHND_CHIPID_BCM4319: /* Change the BBPLL drive strength to 2 for all channels */ buf_strength = 0x222222; /* Make sure the PLL is off */ /* WAR65104: Disable the HT_AVAIL resource first and then * after a delay (more than downtime for HT_AVAIL) remove the * BBPLL resource; backplane clock moves to ALP from HT. */ BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4319_HT_AVAIL))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4319_HT_AVAIL))); DELAY(100); BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4319_BBPLL_PWRSW_PU))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4319_BBPLL_PWRSW_PU))); DELAY(100); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); plldata = 0x200005c0; BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, plldata, ~0); break; case BHND_CHIPID_BCM4336: BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4336_HT_AVAIL) | PMURES_BIT(RES4336_MACPHY_CLKAVAIL))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4336_HT_AVAIL) | PMURES_BIT(RES4336_MACPHY_CLKAVAIL))); DELAY(100); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); break; case BHND_CHIPID_BCM4330: BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(PMURES_BIT(RES4330_HT_AVAIL) | PMURES_BIT(RES4330_MACPHY_CLKAVAIL))); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~(PMURES_BIT(RES4330_HT_AVAIL) | PMURES_BIT(RES4330_MACPHY_CLKAVAIL))); DELAY(100); /* Wait for HT clock to shutdown. */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); break; default: panic("unsupported chipid %#hx\n", sc->cid.chip_id); } PMU_DEBUG(sc, "Done masking\n"); /* Write p1div and p2div to pllcontrol[0] */ plldata = BHND_PMU_SET_BITS(xt->p1div, BHND_PMU1_PLL0_PC0_P1DIV) | BHND_PMU_SET_BITS(xt->p2div, BHND_PMU1_PLL0_PC0_P2DIV); pllmask = BHND_PMU1_PLL0_PC0_P1DIV_MASK|BHND_PMU1_PLL0_PC0_P2DIV_MASK; if (sc->cid.chip_id == BHND_CHIPID_BCM4319) { plldata &= ~(BHND_PMU1_PLL0_PC0_BYPASS_SDMOD_MASK); pllmask |= BHND_PMU1_PLL0_PC0_BYPASS_SDMOD_MASK; if (!xt->ndiv_frac) { plldata |= BHND_PMU_SET_BITS(1, BHND_PMU1_PLL0_PC0_BYPASS_SDMOD); } } BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, plldata, pllmask); if (sc->cid.chip_id == BHND_CHIPID_BCM4330) bhnd_pmu_set_4330_plldivs(sc); if (sc->cid.chip_id == BHND_CHIPID_BCM4329 && sc->cid.chip_rev == 0) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, BHND_PMU_DOT11MAC_880MHZ_CLK_DIVISOR_VAL, BHND_PMU_DOT11MAC_880MHZ_CLK_DIVISOR_MASK); } /* Write ndiv_int and ndiv_mode to pllcontrol[2] */ if (sc->cid.chip_id == BHND_CHIPID_BCM4336 || sc->cid.chip_id == BHND_CHIPID_BCM4330) { ndiv_mode = BHND_PMU1_PLL0_PC2_NDIV_MODE_MFB; } else if (sc->cid.chip_id == BHND_CHIPID_BCM4319) { if (!(xt->ndiv_frac)) ndiv_mode = BHND_PMU1_PLL0_PC2_NDIV_MODE_INT; else ndiv_mode = BHND_PMU1_PLL0_PC2_NDIV_MODE_MFB; } else { ndiv_mode = BHND_PMU1_PLL0_PC2_NDIV_MODE_MASH; } BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, BHND_PMU_SET_BITS(xt->ndiv_int, BHND_PMU1_PLL0_PC2_NDIV_INT) | BHND_PMU_SET_BITS(ndiv_mode, BHND_PMU1_PLL0_PC2_NDIV_MODE), BHND_PMU1_PLL0_PC2_NDIV_INT_MASK | BHND_PMU1_PLL0_PC2_NDIV_MODE_MASK); /* Write ndiv_frac to pllcontrol[3] */ BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, BHND_PMU_SET_BITS(xt->ndiv_frac, BHND_PMU1_PLL0_PC3_NDIV_FRAC), BHND_PMU1_PLL0_PC3_NDIV_FRAC_MASK); /* Writing to pllcontrol[4] */ if (sc->cid.chip_id == BHND_CHIPID_BCM4319) { uint8_t xs; if (!xt->ndiv_frac) plldata = 0x200005c0; else plldata = 0x202C2820; if (FVCO < 1600) xs = 4; else xs = 7; plldata &= ~(BHND_PMU1_PLL0_PC4_KVCO_XS_MASK); plldata |= BHND_PMU_SET_BITS(xs, BHND_PMU1_PLL0_PC4_KVCO_XS); BHND_PMU_WRITE_4(sc, BHND_PMU1_PLL0_PLLCTL4, plldata); } /* Write clock driving strength to pllcontrol[5] */ if (buf_strength) { PMU_DEBUG(sc, "Adjusting PLL buffer drive strength: %x\n", buf_strength); plldata = BHND_PMU_SET_BITS(buf_strength, BHND_PMU1_PLL0_PC5_CLK_DRV); pllmask = BHND_PMU1_PLL0_PC5_CLK_DRV_MASK; if (sc->cid.chip_id == BHND_CHIPID_BCM4319) { pllmask |= BHND_PMU1_PLL0_PC5_VCO_RNG_MASK | BHND_PMU1_PLL0_PC5_PLL_CTRL_37_32_MASK; if (!xt->ndiv_frac) { plldata |= BHND_PMU_SET_BITS(0x25, BHND_PMU1_PLL0_PC5_PLL_CTRL_37_32); } else { plldata |= BHND_PMU_SET_BITS(0x15, BHND_PMU1_PLL0_PC5_PLL_CTRL_37_32); } if (FVCO >= 1600) { plldata |= BHND_PMU_SET_BITS(0x1, BHND_PMU1_PLL0_PC5_VCO_RNG); } } BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, plldata, pllmask); } PMU_DEBUG(sc, "Done pll\n"); /* to operate the 4319 usb in 24MHz/48MHz; chipcontrol[2][84:83] needs * to be updated. */ if (sc->cid.chip_id == BHND_CHIPID_BCM4319 && xt->fref != XTAL_FREQ_30000MHZ) { uint32_t pll_sel; switch (xt->fref) { case XTAL_FREQ_24000MHZ: pll_sel = BHND_PMU_CCTL_4319USB_24MHZ_PLL_SEL; break; case XTAL_FREQ_48000MHZ: pll_sel = BHND_PMU_CCTL_4319USB_48MHZ_PLL_SEL; break; default: panic("unsupported 4319USB XTAL frequency: %hu\n", xt->fref); } BHND_PMU_CCTRL_WRITE(sc, BHND_PMU1_PLL0_CHIPCTL2, BHND_PMU_SET_BITS(pll_sel, BHND_PMU_CCTL_4319USB_XTAL_SEL), BHND_PMU_CCTL_4319USB_XTAL_SEL_MASK); } /* Flush deferred pll control registers writes */ if (BHND_PMU_REV(sc) >= 2) BHND_PMU_OR_4(sc, BHND_PMU_CTRL, BHND_PMU_CTRL_PLL_PLLCTL_UPD); /* Write XtalFreq. Set the divisor also. */ pmuctrl = BHND_PMU_READ_4(sc, BHND_PMU_CTRL); pmuctrl = ~(BHND_PMU_CTRL_ILP_DIV_MASK | BHND_PMU_CTRL_XTALFREQ_MASK); pmuctrl |= BHND_PMU_SET_BITS(((xt->fref + 127) / 128) - 1, BHND_PMU_CTRL_ILP_DIV); pmuctrl |= BHND_PMU_SET_BITS(xt->xf, BHND_PMU_CTRL_XTALFREQ); if (sc->cid.chip_id == BHND_CHIPID_BCM4329 && sc->cid.chip_rev == 0) { /* clear the htstretch before clearing HTReqEn */ BHND_PMU_AND_4(sc, BHND_PMU_CLKSTRETCH, ~BHND_PMU_CLKSTRETCH); pmuctrl &= ~BHND_PMU_CTRL_HT_REQ_EN; } BHND_PMU_WRITE_4(sc, BHND_PMU_CTRL, pmuctrl); } /* query the CPU clock frequency */ static uint32_t bhnd_pmu1_cpuclk0(struct bhnd_pmu_query *sc) { uint32_t tmp, m1div; #ifdef BCMDBG uint32_t ndiv_int, ndiv_frac, p2div, p1div, fvco; uint32_t fref; #endif uint32_t FVCO = bhnd_pmu1_pllfvco0(sc); /* Read m1div from pllcontrol[1] */ tmp = BHND_PMU_PLL_READ(sc, BHND_PMU1_PLL0_PLLCTL1); m1div = BHND_PMU_GET_BITS(tmp, BHND_PMU1_PLL0_PC1_M1DIV); #ifdef BCMDBG /* Read p2div/p1div from pllcontrol[0] */ tmp = BHND_PMU_PLL_READ(sc, BHND_PMU1_PLL0_PLLCTL0); p2div = BHND_PMU_GET_BITS(tmp, BHND_PMU1_PLL0_PC0_P2DIV); p1div = BHND_PMU_GET_BITS(tmp, BHND_PMU1_PLL0_PC0_P1DIV); /* Calculate fvco based on xtal freq and ndiv and pdiv */ tmp = BHND_PMU_PLL_READ(sc, BHND_PMU1_PLL0_PLLCTL2); ndiv_int = BHND_PMU_GET_BITS(tmp, BHND_PMU1_PLL0_PC2_NDIV_INT); tmp = BHND_PMU_PLL_READ(sc, BHND_PMU1_PLL0_PLLCTL3); ndiv_frac = BHND_PMU_GET_BITS(tmp, BHND_PMU1_PLL0_PC3_NDIV_FRAC); fref = bhnd_pmu1_alpclk0(sc) / 1000; fvco = (fref * ndiv_int) << 8; fvco += (fref * (ndiv_frac >> 12)) >> 4; fvco += (fref * (ndiv_frac & 0xfff)) >> 12; fvco >>= 8; fvco *= p2div; fvco /= p1div; fvco /= 1000; fvco *= 1000; PMU_DEBUG(sc, "bhnd_pmu1_cpuclk0: ndiv_int %u ndiv_frac %u p2div %u " "p1div %u fvco %u\n", ndiv_int, ndiv_frac, p2div, p1div, fvco); FVCO = fvco; #endif /* BCMDBG */ /* Return ARM/SB clock */ return (FVCO / m1div * 1000); } /* initialize PLL */ void bhnd_pmu_pll_init(struct bhnd_pmu_softc *sc, u_int xtalfreq) { uint32_t max_mask, min_mask; uint32_t res_ht, res_pll; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4312: /* assume default works */ break; case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM4342: if (sc->cid.chip_rev != 0) break; min_mask = BHND_PMU_READ_4(sc, BHND_PMU_MIN_RES_MASK); max_mask = BHND_PMU_READ_4(sc, BHND_PMU_MIN_RES_MASK); res_ht = PMURES_BIT(RES4322_HT_SI_AVAIL); res_pll = PMURES_BIT(RES4322_SI_PLL_ON); /* Have to remove HT Avail request before powering off PLL */ BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~res_ht); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~res_ht); PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); /* Make sure the PLL is off */ BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~res_pll); BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~res_pll); PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); DELAY(1000); BHND_PMU_PLL_WRITE(sc, BHND_PMU2_SI_PLL_PLLCTL, 0x380005c0, ~0); DELAY(100); BHND_PMU_WRITE_4(sc, BHND_PMU_MAX_RES_MASK, max_mask); DELAY(100); BHND_PMU_WRITE_4(sc, BHND_PMU_MIN_RES_MASK, min_mask); DELAY(100); break; case BHND_CHIPID_BCM4325: bhnd_pmu1_pllinit0(sc, xtalfreq); break; case BHND_CHIPID_BCM4328: bhnd_pmu0_pllinit0(sc, xtalfreq); break; case BHND_CHIPID_BCM5354: if (xtalfreq == 0) xtalfreq = 25000; bhnd_pmu0_pllinit0(sc, xtalfreq); break; case BHND_CHIPID_BCM4329: if (xtalfreq == 0) xtalfreq = 38400; bhnd_pmu1_pllinit0(sc, xtalfreq); break; case BHND_CHIPID_BCM4313: case BHND_CHIPID_BCM43222: case BHND_CHIPID_BCM43111: case BHND_CHIPID_BCM43112: case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43420: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43226: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: case BHND_CHIPID_BCM43234: case BHND_CHIPID_BCM43237: case BHND_CHIPID_BCM4331: case BHND_CHIPID_BCM43431: case BHND_CHIPID_BCM43131: case BHND_CHIPID_BCM43227: case BHND_CHIPID_BCM43228: case BHND_CHIPID_BCM43428: case BHND_CHIPID_BCM6362: /* assume default works */ break; case BHND_CHIPID_BCM4315: case BHND_CHIPID_BCM4319: case BHND_CHIPID_BCM4336: case BHND_CHIPID_BCM4330: bhnd_pmu1_pllinit0(sc, xtalfreq); break; default: PMU_DEBUG("No PLL init done for chip %#hx rev %d pmurev %d\n", sc->cid.chip_id, sc->cid.chip_rev, BHND_PMU_REV(sc)); break; } } /** * Return the ALP/XTAL clock frequency, in Hz. * * @param sc PMU query instance. */ uint32_t bhnd_pmu_alp_clock(struct bhnd_pmu_query *sc) { uint32_t clock; clock = BHND_PMU_ALP_CLOCK; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4328: case BHND_CHIPID_BCM5354: clock = bhnd_pmu0_alpclk0(sc); break; case BHND_CHIPID_BCM4315: case BHND_CHIPID_BCM4319: case BHND_CHIPID_BCM4325: case BHND_CHIPID_BCM4329: case BHND_CHIPID_BCM4330: case BHND_CHIPID_BCM4336: clock = bhnd_pmu1_alpclk0(sc); break; case BHND_CHIPID_BCM4312: case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM43222: case BHND_CHIPID_BCM43111: case BHND_CHIPID_BCM43112: case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43420: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43226: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: case BHND_CHIPID_BCM43234: case BHND_CHIPID_BCM43237: case BHND_CHIPID_BCM4331: case BHND_CHIPID_BCM43431: case BHND_CHIPID_BCM43131: case BHND_CHIPID_BCM43227: case BHND_CHIPID_BCM43228: case BHND_CHIPID_BCM43428: case BHND_CHIPID_BCM6362: case BHND_CHIPID_BCM4342: case BHND_CHIPID_BCM4716: case BHND_CHIPID_BCM4748: case BHND_CHIPID_BCM47162: case BHND_CHIPID_BCM4313: case BHND_CHIPID_BCM5357: case BHND_CHIPID_BCM4749: case BHND_CHIPID_BCM53572: /* always 20Mhz */ clock = 20000 * 1000; break; case BHND_CHIPID_BCM5356: case BHND_CHIPID_BCM4706: /* always 25Mhz */ clock = 25000 * 1000; break; default: PMU_DEBUG("No ALP clock specified " "for chip %s rev %d pmurev %d, using default %d Hz\n", bcm_chipname(sih->chip, chn, 8), sih->chiprev, sih->pmurev, clock); break; } return (clock); } /* Find the output of the "m" pll divider given pll controls that start with * pllreg "pll0" i.e. 12 for main 6 for phy, 0 for misc. */ static uint32_t bhnd_pmu5_clock(struct bhnd_pmu_query *sc, u_int pll0, u_int m) { uint32_t div; uint32_t fc; uint32_t ndiv; uint32_t p1, p2; uint32_t tmp; if ((pll0 & 3) || (pll0 > BHND_PMU4716_MAINPLL_PLL0)) { PMU_LOG(sc, "%s: Bad pll0: %d", __func__, pll0); return (0); } /* Strictly there is an m5 divider, but I'm not sure we use it */ if ((m == 0) || (m > 4)) { PMU_LOG(sc, "%s: Bad m divider: %d", __func__, m); return (0); } if (sc->cid.chip_id == BHND_CHIPID_BCM5357 || sc->cid.chip_id == BHND_CHIPID_BCM4749) { /* Detect failure in clock setting */ tmp = sc->io->rd_chipst(sc->io_ctx); if ((tmp & 0x40000) != 0) return (133 * 1000000); } /* Fetch p1 and p2 */ BHND_PMU_WRITE_4(sc, BHND_PMU_PLL_CONTROL_ADDR, pll0 + BHND_PMU5_PLL_P1P2_OFF); BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_ADDR); tmp = BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_DATA); p1 = BHND_PMU_GET_BITS(tmp, BHND_PMU5_PLL_P1); p2 = BHND_PMU_GET_BITS(tmp, BHND_PMU5_PLL_P2); /* Fetch div */ BHND_PMU_WRITE_4(sc, BHND_PMU_PLL_CONTROL_ADDR, pll0 + BHND_PMU5_PLL_M14_OFF); BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_ADDR); tmp = BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_DATA); div = (tmp >> ((m - 1) * BHND_PMU5_PLL_MDIV_WIDTH)); div &= BHND_PMU5_PLL_MDIV_MASK; /* Fetch ndiv */ BHND_PMU_WRITE_4(sc, BHND_PMU_PLL_CONTROL_ADDR, pll0 + BHND_PMU5_PLL_NM5_OFF); BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_ADDR); tmp = BHND_PMU_READ_4(sc, BHND_PMU_PLL_CONTROL_DATA); ndiv = BHND_PMU_GET_BITS(tmp, BHND_PMU5_PLL_NDIV); /* Do calculation in Mhz */ fc = bhnd_pmu_alp_clock(sc) / 1000000; fc = (p1 * ndiv * fc) / p2; PMU_DEBUG(sc, "%s: p1=%d, p2=%d, ndiv=%d(0x%x), m%d=%d; fc=%d, " "clock=%d\n", __func__, p1, p2, ndiv, ndiv, m, div, fc, fc / div); /* Return clock in Hertz */ return ((fc / div) * 1000000); } /** * Return the backplane clock frequency, in Hz. * * On designs that feed the same clock to both backplane * and CPU, this returns the CPU clock speed. * * @param sc PMU query instance. */ uint32_t bhnd_pmu_si_clock(struct bhnd_pmu_query *sc) { uint32_t chipst; uint32_t clock; clock = BHND_PMU_HT_CLOCK; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM43222: case BHND_CHIPID_BCM43111: case BHND_CHIPID_BCM43112: case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43420: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43226: case BHND_CHIPID_BCM4331: case BHND_CHIPID_BCM43431: case BHND_CHIPID_BCM6362: case BHND_CHIPID_BCM4342: /* 96MHz backplane clock */ clock = 96000 * 1000; break; case BHND_CHIPID_BCM4716: case BHND_CHIPID_BCM4748: case BHND_CHIPID_BCM47162: clock = bhnd_pmu5_clock(sc, BHND_PMU4716_MAINPLL_PLL0, BHND_PMU5_MAINPLL_SI); break; case BHND_CHIPID_BCM4325: clock = bhnd_pmu1_cpuclk0(sc); break; case BHND_CHIPID_BCM4328: clock = bhnd_pmu0_cpuclk0(sc); break; case BHND_CHIPID_BCM4329: if (sc->cid.chip_rev == 0) clock = 38400 * 1000; else clock = bhnd_pmu1_cpuclk0(sc); break; case BHND_CHIPID_BCM4315: case BHND_CHIPID_BCM4319: case BHND_CHIPID_BCM4336: case BHND_CHIPID_BCM4330: clock = bhnd_pmu1_cpuclk0(sc); break; case BHND_CHIPID_BCM4313: /* 80MHz backplane clock */ clock = 80000 * 1000; break; case BHND_CHIPID_BCM43234: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: chipst = sc->io->rd_chipst(sc->io_ctx); if (chipst & CHIPC_CST43236_BP_CLK) clock = 120000 * 1000; else clock = 96000 * 1000; break; case BHND_CHIPID_BCM43237: chipst = sc->io->rd_chipst(sc->io_ctx); if (chipst & CHIPC_CST43237_BP_CLK) clock = 96000 * 1000; else clock = 80000 * 1000; break; case BHND_CHIPID_BCM5356: clock = bhnd_pmu5_clock(sc, BHND_PMU5356_MAINPLL_PLL0, BHND_PMU5_MAINPLL_SI); break; case BHND_CHIPID_BCM5357: case BHND_CHIPID_BCM4749: clock = bhnd_pmu5_clock(sc, BHND_PMU5357_MAINPLL_PLL0, BHND_PMU5_MAINPLL_SI); break; case BHND_CHIPID_BCM53572: clock = 75000000; break; default: PMU_LOG(sc, "No backplane clock specified for chip %#hx rev " "%hhd pmurev %hhd, using default %dHz\n", sc->cid.chip_id, sc->cid.chip_rev, BHND_PMU_REV(sc), clock); break; } return (clock); } /** * Return the CPU clock frequency, in Hz. * * @param sc PMU query instance. */ uint32_t bhnd_pmu_cpu_clock(struct bhnd_pmu_query *sc) { uint32_t clock; /* 5354 chip uses a non programmable PLL of frequency 240MHz */ if (sc->cid.chip_id == BHND_CHIPID_BCM5354) return (240 * 1000 * 1000); /* 240MHz */ if (sc->cid.chip_id == BHND_CHIPID_BCM53572) return (300000000); if (BHND_PMU_REV(sc) >= 5 && sc->cid.chip_id != BHND_CHIPID_BCM4329 && sc->cid.chip_id != BHND_CHIPID_BCM4319 && sc->cid.chip_id != BHND_CHIPID_BCM43234 && sc->cid.chip_id != BHND_CHIPID_BCM43235 && sc->cid.chip_id != BHND_CHIPID_BCM43236 && sc->cid.chip_id != BHND_CHIPID_BCM43237 && sc->cid.chip_id != BHND_CHIPID_BCM43238 && sc->cid.chip_id != BHND_CHIPID_BCM4336 && sc->cid.chip_id != BHND_CHIPID_BCM4330) { u_int pll; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM5356: pll = BHND_PMU5356_MAINPLL_PLL0; break; case BHND_CHIPID_BCM5357: case BHND_CHIPID_BCM4749: pll = BHND_PMU5357_MAINPLL_PLL0; break; default: pll = BHND_PMU4716_MAINPLL_PLL0; break; } clock = bhnd_pmu5_clock(sc, pll, BHND_PMU5_MAINPLL_CPU); } else { clock = bhnd_pmu_si_clock(sc); } return (clock); } /** * Return the memory clock frequency, in Hz. * * @param sc PMU query instance. */ uint32_t bhnd_pmu_mem_clock(struct bhnd_pmu_query *sc) { uint32_t clock; if (BHND_PMU_REV(sc) >= 5 && sc->cid.chip_id != BHND_CHIPID_BCM4329 && sc->cid.chip_id != BHND_CHIPID_BCM4319 && sc->cid.chip_id != BHND_CHIPID_BCM43234 && sc->cid.chip_id != BHND_CHIPID_BCM43235 && sc->cid.chip_id != BHND_CHIPID_BCM43236 && sc->cid.chip_id != BHND_CHIPID_BCM43237 && sc->cid.chip_id != BHND_CHIPID_BCM43238 && sc->cid.chip_id != BHND_CHIPID_BCM4336 && sc->cid.chip_id != BHND_CHIPID_BCM4330) { u_int pll; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM5356: pll = BHND_PMU5356_MAINPLL_PLL0; break; case BHND_CHIPID_BCM5357: case BHND_CHIPID_BCM4749: pll = BHND_PMU5357_MAINPLL_PLL0; break; default: pll = BHND_PMU4716_MAINPLL_PLL0; break; } clock = bhnd_pmu5_clock(sc, pll, BHND_PMU5_MAINPLL_MEM); } else { clock = bhnd_pmu_si_clock(sc); } return (clock); } /* Measure ILP clock frequency */ #define ILP_CALC_DUR 10 /* ms, make sure 1000 can be divided by it. */ /** * Measure and return the ILP clock frequency, in Hz. * * @param sc PMU query instance. */ uint32_t bhnd_pmu_ilp_clock(struct bhnd_pmu_query *sc) { uint32_t start, end, delta; if (sc->ilp_cps == 0) { start = BHND_PMU_READ_4(sc, BHND_PMU_TIMER); DELAY(ILP_CALC_DUR); end = BHND_PMU_READ_4(sc, BHND_PMU_TIMER); delta = end - start; sc->ilp_cps = delta * (1000 / ILP_CALC_DUR); } return (sc->ilp_cps); } /* SDIO Pad drive strength to select value mappings */ typedef struct { uint8_t strength; /* Pad Drive Strength in mA */ uint8_t sel; /* Chip-specific select value */ } sdiod_drive_str_t; /* SDIO Drive Strength to sel value table for PMU Rev 1 */ static const sdiod_drive_str_t sdiod_drive_strength_tab1[] = { { 4, 0x2}, { 2, 0x3}, { 1, 0x0}, { 0, 0x0} }; /* SDIO Drive Strength to sel value table for PMU Rev 2, 3 */ static const sdiod_drive_str_t sdiod_drive_strength_tab2[] = { { 12, 0x7}, { 10, 0x6}, { 8, 0x5}, { 6, 0x4}, { 4, 0x2}, { 2, 0x1}, { 0, 0x0} }; /* SDIO Drive Strength to sel value table for PMU Rev 8 (1.8V) */ static const sdiod_drive_str_t sdiod_drive_strength_tab3[] = { { 32, 0x7}, { 26, 0x6}, { 22, 0x5}, { 16, 0x4}, { 12, 0x3}, { 8, 0x2}, { 4, 0x1}, { 0, 0x0} }; #define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) void bhnd_pmu_sdiod_drive_strength_init(struct bhnd_pmu_softc *sc, uint32_t drivestrength) { const sdiod_drive_str_t *str_tab; uint32_t str_mask; uint32_t str_shift; u_int intr_val; str_tab = NULL; str_mask = 0; str_shift = 0; intr_val = 0; switch (SDIOD_DRVSTR_KEY(sc->cid.chip_id, BHND_PMU_REV(sc))) { case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4325, 1): str_tab = sdiod_drive_strength_tab1; str_mask = 0x30000000; str_shift = 28; break; case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4325, 2): case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4325, 3): case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4315, 4): case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4319, 7): str_tab = sdiod_drive_strength_tab2; str_mask = 0x00003800; str_shift = 11; break; case SDIOD_DRVSTR_KEY(BHND_CHIPID_BCM4336, 8): str_tab = sdiod_drive_strength_tab3; str_mask = 0x00003800; str_shift = 11; break; default: PMU_LOG(sc, "No SDIO Drive strength init done for chip %#x " "rev %hhd pmurev %hhd\n", sc->cid.chip_id, sc->cid.chip_rev, BHND_PMU_REV(sc)); break; } if (str_tab != NULL) { uint32_t drivestrength_sel = 0; uint32_t cc_data_temp; for (u_int i = 0; str_tab[i].strength != 0; i++) { if (drivestrength >= str_tab[i].strength) { drivestrength_sel = str_tab[i].sel; break; } } cc_data_temp = BHND_PMU_CCTRL_READ(sc, 1); cc_data_temp &= ~str_mask; drivestrength_sel <<= str_shift; cc_data_temp |= drivestrength_sel; BHND_PMU_CCTRL_WRITE(sc, 1, cc_data_temp, ~0); PMU_DEBUG(sc, "SDIO: %dmA drive strength selected, set to " "0x%08x\n", drivestrength, cc_data_temp); } } /** * Initialize the PMU. */ int bhnd_pmu_init(struct bhnd_pmu_softc *sc) { uint32_t xtalfreq; int error; if (BHND_PMU_REV(sc) == 1) { BHND_PMU_AND_4(sc, BHND_PMU_CTRL, ~BHND_PMU_CTRL_NOILP_ON_WAIT); } else if (BHND_PMU_REV(sc) >= 2) { BHND_PMU_OR_4(sc, BHND_PMU_CTRL, BHND_PMU_CTRL_NOILP_ON_WAIT); } if (sc->cid.chip_id == BHND_CHIPID_BCM4329 && sc->cid.chip_rev == 2) { /* Fix for 4329b0 bad LPOM state. */ BHND_PMU_REGCTRL_WRITE(sc, 2, 0x100, ~0); BHND_PMU_REGCTRL_WRITE(sc, 3, 0x4, ~0); } if (sc->cid.chip_id == BHND_CHIPID_BCM4319) { /* Limiting the PALDO spike during init time */ BHND_PMU_REGCTRL_WRITE(sc, 2, 0x00000005, 0x00000007); } /* Fetch target xtalfreq, in KHz */ error = bhnd_nvram_getvar_uint32(sc->chipc_dev, BHND_NVAR_XTALFREQ, &xtalfreq); /* If not available, log any real errors, and then try to measure it */ if (error) { if (error != ENOENT) PMU_LOG(sc, "error fetching xtalfreq: %d\n", error); xtalfreq = bhnd_pmu_measure_alpclk(sc); } /* Perform PLL initialization */ bhnd_pmu_pll_init(sc, xtalfreq); if ((error = bhnd_pmu_res_init(sc))) return (error); bhnd_pmu_swreg_init(sc); return (0); } /* Return up time in ILP cycles for the given resource. */ static int bhnd_pmu_res_uptime(struct bhnd_pmu_softc *sc, uint8_t rsrc, uint32_t *uptime) { uint32_t deps; uint32_t up, dup, dmax; uint32_t min_mask; int error; /* uptime of resource 'rsrc' */ BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, rsrc); up = BHND_PMU_READ_4(sc, BHND_PMU_RES_UPDN_TIMER); up = BHND_PMU_GET_BITS(up, BHND_PMU_RES_UPDN_UPTME); /* Find direct dependencies of resource 'rsrc' */ deps = bhnd_pmu_res_deps(sc, BHND_PMURES_BIT(rsrc), false); for (uint8_t i = 0; i <= BHND_PMU_RESNUM_MAX; i++) { if (!(deps & BHND_PMURES_BIT(i))) continue; deps &= ~bhnd_pmu_res_deps(sc, BHND_PMURES_BIT(i), true); } /* Exclude the minimum resource set */ if ((error = bhnd_pmu_res_masks(sc, &min_mask, NULL))) return (error); deps &= ~min_mask; /* max uptime of direct dependencies */ dmax = 0; for (uint8_t i = 0; i <= BHND_PMU_RESNUM_MAX; i++) { if (!(deps & BHND_PMURES_BIT(i))) continue; if ((error = bhnd_pmu_res_uptime(sc, i, &dup))) return (error); if (dmax < dup) dmax = dup; } PMU_DEBUG(sc, "bhnd_pmu_res_uptime: rsrc %hhu uptime %u(deps 0x%08x " "uptime %u)\n", rsrc, up, deps, dmax); *uptime = (up + dmax + BHND_PMURES_UP_TRANSITION); return (0); } /* Return dependencies (direct or all/indirect) for the given resources */ static uint32_t bhnd_pmu_res_deps(struct bhnd_pmu_softc *sc, uint32_t rsrcs, bool all) { uint32_t deps; deps = 0; for (uint8_t i = 0; i <= BHND_PMU_RESNUM_MAX; i++) { if (!(rsrcs & BHND_PMURES_BIT(i))) continue; BHND_PMU_WRITE_4(sc, BHND_PMU_RES_TABLE_SEL, i); deps |= BHND_PMU_READ_4(sc, BHND_PMU_RES_DEP_MASK); } /* None found? */ if (deps == 0) return (0); /* Recurse dependencies */ if (all) deps |= bhnd_pmu_res_deps(sc, deps, true); return (deps); } /* power up/down OTP through PMU resources */ int bhnd_pmu_otp_power(struct bhnd_pmu_softc *sc, bool on) { uint32_t deps; uint32_t min_mask; uint32_t rsrcs; int error; /* Determine rsrcs to turn on/off OTP power */ switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM4342: rsrcs = PMURES_BIT(RES4322_OTP_PU); break; case BHND_CHIPID_BCM4315: rsrcs = PMURES_BIT(RES4315_OTP_PU); break; case BHND_CHIPID_BCM4325: rsrcs = PMURES_BIT(RES4325_OTP_PU); break; case BHND_CHIPID_BCM4329: rsrcs = PMURES_BIT(RES4329_OTP_PU); break; case BHND_CHIPID_BCM4319: rsrcs = PMURES_BIT(RES4319_OTP_PU); break; case BHND_CHIPID_BCM4336: rsrcs = PMURES_BIT(RES4336_OTP_PU); break; case BHND_CHIPID_BCM4330: rsrcs = PMURES_BIT(RES4330_OTP_PU); break; default: /* Not required? */ return (0); } /* Fetch all dependencies */ deps = bhnd_pmu_res_deps(sc, rsrcs, true); /* Exclude the minimum resource set */ if ((error = bhnd_pmu_res_masks(sc, &min_mask, NULL))) return (error); deps &= ~min_mask; /* Turn on/off the power */ if (on) { uint32_t state; PMU_DEBUG(sc, "Adding rsrc 0x%x to min_res_mask\n", rsrcs | deps); BHND_PMU_OR_4(sc, BHND_PMU_MIN_RES_MASK, (rsrcs|deps)); /* Wait for all resources to become available */ for (int i = 0; i < BHND_PMU_MAX_TRANSITION_DLY; i += 10) { state = BHND_PMU_READ_4(sc, BHND_PMU_RES_STATE); if ((state & rsrcs) == rsrcs) break; DELAY(10); } if ((state & rsrcs) != rsrcs) { PMU_LOG(sc, "timeout waiting for OTP resource " "enable\n"); return (ENXIO); } } else { PMU_DEBUG(sc, "Removing rsrc 0x%x from min_res_mask\n", rsrcs | deps); BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~(rsrcs|deps)); } return (0); } void bhnd_pmu_rcal(struct bhnd_pmu_softc *sc) { uint32_t chipst; uint32_t val; uint8_t rcal_code; bool bluetooth_rcal; bluetooth_rcal = false; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4325: case BHND_CHIPID_BCM4329: /* Kick RCal */ BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_ADDR, 1); /* Power Down RCAL Block */ BHND_PMU_AND_4(sc, BHND_PMU_CHIPCTL_DATA, ~0x04); if (sc->cid.chip_id == BHND_CHIPID_BCM4325) { chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_RCAL_VALID)) bluetooth_rcal = true; } /* Power Up RCAL block */ BHND_PMU_AND_4(sc, BHND_PMU_CHIPCTL_DATA, 0x04); /* Wait for completion */ for (int i = 0; i < (10 * 1000 * 1000); i++) { chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (chipst & 0x08) break; DELAY(10); } KASSERT((chipst & 0x08) != 0, ("rcal completion timeout")); if (bluetooth_rcal) { rcal_code = 0x6; } else { /* Drop LSB to convert from 5 bit code to 4 bit code */ rcal_code = (uint8_t) (chipst >> 5) & 0x0f; } PMU_DEBUG("RCal completed, status 0x%x, code 0x%x\n", R_REG(&cc->chipstatus), rcal_code); /* Write RCal code into pmu_vreg_ctrl[32:29] */ BHND_PMU_WRITE_4(sc, BHND_PMU_REG_CONTROL_ADDR, 0); val = BHND_PMU_READ_4(sc, BHND_PMU_REG_CONTROL_DATA); val &= ~((uint32_t) 0x07 << 29); val |= (uint32_t) (rcal_code & 0x07) << 29; BHND_PMU_WRITE_4(sc, BHND_PMU_REG_CONTROL_DATA, val); BHND_PMU_WRITE_4(sc, BHND_PMU_REG_CONTROL_ADDR, 1); val = BHND_PMU_READ_4(sc, BHND_PMU_REG_CONTROL_DATA); val &= ~(uint32_t) 0x01; val |= (uint32_t) ((rcal_code >> 3) & 0x01); BHND_PMU_WRITE_4(sc, BHND_PMU_REG_CONTROL_DATA, val); /* Write RCal code into pmu_chip_ctrl[33:30] */ BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_ADDR, 0); val = BHND_PMU_READ_4(sc, BHND_PMU_CHIPCTL_DATA); val &= ~((uint32_t) 0x03 << 30); val |= (uint32_t) (rcal_code & 0x03) << 30; BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_DATA, val); BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_ADDR, 1); val = BHND_PMU_READ_4(sc, BHND_PMU_CHIPCTL_DATA); val &= ~(uint32_t) 0x03; val |= (uint32_t) ((rcal_code >> 2) & 0x03); BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_DATA, val); /* Set override in pmu_chip_ctrl[29] */ BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_ADDR, 0); BHND_PMU_OR_4(sc, BHND_PMU_CHIPCTL_DATA, (0x01 << 29)); /* Power off RCal block */ BHND_PMU_WRITE_4(sc, BHND_PMU_CHIPCTL_ADDR, 1); BHND_PMU_AND_4(sc, BHND_PMU_CHIPCTL_DATA, ~0x04); break; default: break; } } void bhnd_pmu_spuravoid(struct bhnd_pmu_softc *sc, uint8_t spuravoid) { /* force the HT off */ if (sc->cid.chip_id == BHND_CHIPID_BCM4336) { BHND_PMU_AND_4(sc, BHND_PMU_MAX_RES_MASK, ~BHND_PMU_RES4336_HT_AVAIL); /* wait for the ht to really go away */ PMU_WAIT_CLKST(sc, 0, BHND_CCS_HTAVAIL); } /* update the pll changes */ bhnd_pmu_spuravoid_pllupdate(sc, spuravoid); /* enable HT back on */ if (sc->cid.chip_id == BHND_CHIPID_BCM4336) { BHND_PMU_OR_4(sc, BHND_PMU_MAX_RES_MASK, BHND_PMU_RES4336_HT_AVAIL); } } static void bhnd_pmu_spuravoid_pllupdate(struct bhnd_pmu_softc *sc, uint8_t spuravoid) { uint16_t chip_id; uint32_t tmp; uint32_t pmuctrl; uint8_t phypll_offset; uint8_t bcm5357_bcm43236_p1div[] = { 0x1, 0x5, 0x5 }; uint8_t bcm5357_bcm43236_ndiv[] = { 0x30, 0xf6, 0xfc }; /* 6362a0 has same clks as 4322[4-6] */ chip_id = sc->cid.chip_id; if (chip_id == BHND_CHIPID_BCM6362 && sc->cid.chip_rev == 0) { chip_id = BHND_CHIPID_BCM43224; } switch (chip_id) { case BHND_CHIPID_BCM6362: KASSERT(sc->cid.chip_rev != 0, ("invalid clock config")); /* fallthrough */ case BHND_CHIPID_BCM5357: case BHND_CHIPID_BCM4749: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43238: case BHND_CHIPID_BCM43234: case BHND_CHIPID_BCM43237: case BHND_CHIPID_BCM53572: KASSERT(spuravoid < nitems(bcm5357_bcm43236_p1div), ("spuravoid %hhu outside p1div table\n", spuravoid)); KASSERT(spuravoid < nitems(bcm5357_bcm43236_ndiv), ("spuravoid %hhu outside ndiv table\n", spuravoid)); /* BCM5357 needs to touch PLL1_PLLCTL[02], so offset * PLL0_PLLCTL[02] by 6 */ phypll_offset = 0; if (sc->cid.chip_id == BHND_CHIPID_BCM5357) phypll_offset = 6; /* RMW only the P1 divider */ tmp = BHND_PMU_SET_BITS(bcm5357_bcm43236_p1div[spuravoid], BHND_PMU1_PLL0_PC0_P1DIV); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0 + phypll_offset, tmp, BHND_PMU1_PLL0_PC0_P1DIV_MASK); /* RMW only the int feedback divider */ tmp = BHND_PMU_SET_BITS(bcm5357_bcm43236_ndiv[spuravoid], BHND_PMU1_PLL0_PC2_NDIV_INT); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2 + phypll_offset, tmp, BHND_PMU1_PLL0_PC0_P1DIV_MASK); pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM4331: if (spuravoid == 2) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11500014, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x0FC00a08, ~0); } else if (spuravoid == 1) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11500014, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x0F600a08, ~0); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100014, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03000a08, ~0); } pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43226: case BHND_CHIPID_BCM43421: if (spuravoid == 1) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11500010, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x000C0C06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x0F600a08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x2001E920, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100010, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x000c0c06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03000a08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x200005c0, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM43111: case BHND_CHIPID_BCM43112: case BHND_CHIPID_BCM43222: case BHND_CHIPID_BCM43420: if (spuravoid == 1) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11500008, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x0c000c06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x0f600a08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x2001e920, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100008, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x0c000c06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03000a08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x200005c0, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888855, ~0); } pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM4716: case BHND_CHIPID_BCM4748: case BHND_CHIPID_BCM47162: if (spuravoid == 1) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11500060, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x080C0C06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x0F600000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x2001E924, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100060, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x080c0c06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x200005c0, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } pmuctrl = BHND_PMU_CTRL_NOILP_ON_WAIT | BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM4319: pmuctrl = 0; break; case BHND_CHIPID_BCM4322: case BHND_CHIPID_BCM43221: case BHND_CHIPID_BCM43231: case BHND_CHIPID_BCM4342: BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100070, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x1014140a, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888854, ~0); if (spuravoid == 1) { /* spur_avoid ON, enable 41/82/164Mhz clock mode */ BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x05201828, ~0); } else { /* enable 40/80/160Mhz clock mode */ BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x05001828, ~0); } pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM4336: /* Looks like these are only for default xtal freq 26MHz */ BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x02100020, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x0C0C0C0C, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x01240C0C, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x202C2820, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888825, ~0); if (spuravoid == 1) { tmp = 0x00EC4EC4; } else { tmp = 0x00762762; } BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, tmp, ~0); pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; case BHND_CHIPID_BCM43131: case BHND_CHIPID_BCM43227: case BHND_CHIPID_BCM43228: case BHND_CHIPID_BCM43428: /* LCNXN */ /* PLL Settings for spur avoidance on/off mode, no on2 support * for 43228A0 */ if (spuravoid == 1) { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x01100014, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x040C0C06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03140A08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00333333, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x202C2820, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } else { BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL0, 0x11100014, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, 0x040c0c06, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, 0x03000a08, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL3, 0x00000000, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL4, 0x200005c0, ~0); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL5, 0x88888815, ~0); } pmuctrl = BHND_PMU_CTRL_PLL_PLLCTL_UPD; break; default: PMU_LOG(sc, "%s: unknown spuravoidance settings for chip %#hx, " "not changing PLL", __func__, sc->cid.chip_id); pmuctrl = 0; break; } if (pmuctrl != 0) BHND_PMU_OR_4(sc, BHND_PMU_CTRL, pmuctrl); } bool bhnd_pmu_is_otp_powered(struct bhnd_pmu_softc *sc) { uint32_t otp_res; /* Determine per-chip OTP resource */ switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4329: otp_res = PMURES_BIT(RES4329_OTP_PU); break; case BHND_CHIPID_BCM4319: otp_res = PMURES_BIT(RES4319_OTP_PU); break; case BHND_CHIPID_BCM4336: otp_res = PMURES_BIT(RES4336_OTP_PU); break; case BHND_CHIPID_BCM4330: otp_res = PMURES_BIT(RES4330_OTP_PU); break; /* These chips don't use PMU bit to power up/down OTP. OTP always on. * Use OTP_INIT command to reset/refresh state. */ case BHND_CHIPID_BCM43224: case BHND_CHIPID_BCM43225: case BHND_CHIPID_BCM43421: case BHND_CHIPID_BCM43236: case BHND_CHIPID_BCM43235: case BHND_CHIPID_BCM43238: return (true); default: return (true); } /* Check resource state */ if ((BHND_PMU_READ_4(sc, BHND_PMU_RES_STATE) & otp_res) == 0) return (false); return (true); } void bhnd_pmu_paref_ldo_enable(struct bhnd_pmu_softc *sc, bool enable) { uint32_t ldo; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4328: ldo = PMURES_BIT(RES4328_PA_REF_LDO); break; case BHND_CHIPID_BCM5354: ldo = PMURES_BIT(RES5354_PA_REF_LDO); break; case BHND_CHIPID_BCM4312: ldo = PMURES_BIT(RES4312_PA_REF_LDO); break; default: return; } if (enable) { BHND_PMU_OR_4(sc, BHND_PMU_MIN_RES_MASK, ldo); } else { BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~ldo); } } /* initialize PMU switch/regulators */ void bhnd_pmu_swreg_init(struct bhnd_pmu_softc *sc) { uint32_t chipst; switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4325: if (sc->cid.chip_rev <= 2) break; chipst = BHND_CHIPC_READ_CHIPST(sc->chipc_dev); if (BHND_PMU_GET_BITS(chipst, CHIPC_CST4325_PMUTOP_2B)) { bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CLDO_PWM, 0xf); bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CLDO_BURST, 0xf); } bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CBUCK_PWM, 0xb); bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CBUCK_BURST, 0xb); bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_LNLDO1, 0x1); if (sc->board.board_flags & BHND_BFL_LNLDO2_2P5) { bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_LNLDO2_SEL, 0x1); } break; case BHND_CHIPID_BCM4336: /* Reduce CLDO PWM output voltage to 1.2V */ bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CLDO_PWM, 0xe); /* Reduce CLDO BURST output voltage to 1.2V */ bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CLDO_BURST, 0xe); /* Reduce LNLDO1 output voltage to 1.2V */ bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_LNLDO1, 0xe); if (sc->cid.chip_rev == 0) BHND_PMU_REGCTRL_WRITE(sc, 2, 0x400000, 0x400000); break; case BHND_CHIPID_BCM4330: /* CBUCK Voltage is 1.8 by default and set that to 1.5 */ bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_CBUCK_PWM, 0); break; default: break; } } void bhnd_pmu_radio_enable(struct bhnd_pmu_softc *sc, device_t d11core, bool enable) { uint32_t oobsel; uint32_t rsrcs; if (bhnd_get_device(d11core) != BHND_COREID_D11) panic("bhnd_pmu_radio_enable() called on non-D11 core"); switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4325: if (sc->board.board_flags & BHND_BFL_FASTPWR) break; if ((sc->board.board_flags & BHND_BFL_BUCKBOOST) == 0) break; rsrcs = PMURES_BIT(RES4325_BUCK_BOOST_BURST); if (enable) { BHND_PMU_OR_4(sc, BHND_PMU_MIN_RES_MASK, rsrcs); DELAY(100 * 1000); /* 100ms */ } else { BHND_PMU_AND_4(sc, BHND_PMU_MIN_RES_MASK, ~rsrcs); } break; case BHND_CHIPID_BCM4319: oobsel = bhnd_read_config(d11core, BCMA_DMP_OOBSELOUTB74, 4); if (enable) { oobsel |= BHND_PMU_SET_BITS(BCMA_DMP_OOBSEL_EN, - BCMA_DMP_OOBSEL_1); + BCMA_DMP_OOBSEL_5); oobsel |= BHND_PMU_SET_BITS(BCMA_DMP_OOBSEL_EN, - BCMA_DMP_OOBSEL_2); + BCMA_DMP_OOBSEL_6); } else { oobsel &= ~BHND_PMU_SET_BITS(BCMA_DMP_OOBSEL_EN, - BCMA_DMP_OOBSEL_1); + BCMA_DMP_OOBSEL_5); oobsel &= ~BHND_PMU_SET_BITS(BCMA_DMP_OOBSEL_EN, - BCMA_DMP_OOBSEL_2); + BCMA_DMP_OOBSEL_6); } bhnd_write_config(d11core, BCMA_DMP_OOBSELOUTB74, oobsel, 4); break; } } /* Wait for a particular clock level to be on the backplane */ uint32_t bhnd_pmu_waitforclk_on_backplane(struct bhnd_pmu_softc *sc, uint32_t clk, uint32_t delay) { uint32_t pmu_st; for (uint32_t i = 0; i < delay; i += 10) { pmu_st = BHND_PMU_READ_4(sc, BHND_PMU_ST); if ((pmu_st & clk) == clk) return (clk); DELAY(10); } pmu_st = BHND_PMU_READ_4(sc, BHND_PMU_ST); return (pmu_st & clk); } /* * Measures the ALP clock frequency in KHz. Returns 0 if not possible. * Possible only if PMU rev >= 10 and there is an external LPO 32768Hz crystal. */ #define EXT_ILP_HZ 32768 uint32_t bhnd_pmu_measure_alpclk(struct bhnd_pmu_softc *sc) { uint32_t alp_khz; uint32_t pmu_st; if (BHND_PMU_REV(sc) < 10) return (0); pmu_st = BHND_PMU_READ_4(sc, BHND_PMU_ST); if (pmu_st & BHND_PMU_ST_EXTLPOAVAIL) { uint32_t alp_hz, ilp_ctr; /* Enable frequency measurement */ BHND_PMU_WRITE_4(sc, BHND_PMU_XTALFREQ, 1U << BHND_PMU_XTALFREQ_REG_MEASURE_SHIFT); /* Delay for well over 4 ILP clocks */ DELAY(1000); /* Read the latched number of ALP ticks per 4 ILP ticks */ ilp_ctr = BHND_PMU_READ_4(sc, BHND_PMU_XTALFREQ); ilp_ctr = BHND_PMU_GET_BITS(ilp_ctr, BHND_PMU_XTALFREQ_REG_ILPCTR); /* Turn off PMU_XTALFREQ_REG_MEASURE to save power */ BHND_PMU_WRITE_4(sc, BHND_PMU_XTALFREQ, 0); /* Calculate ALP frequency */ alp_hz = (ilp_ctr * EXT_ILP_HZ) / 4; /* Round to nearest 100KHz and convert to KHz */ alp_khz = (alp_hz + 50000) / 100000 * 100; } else { alp_khz = 0; } return (alp_khz); } static void bhnd_pmu_set_4330_plldivs(struct bhnd_pmu_softc *sc) { uint32_t FVCO = bhnd_pmu1_pllfvco0(&sc->query) / 1000; uint32_t m1div, m2div, m3div, m4div, m5div, m6div; uint32_t pllc1, pllc2; m2div = m3div = m4div = m6div = FVCO / 80; m5div = FVCO / 160; if (PMU_CST4330_SDIOD_CHIPMODE(sc)) m1div = FVCO / 80; else m1div = FVCO / 90; pllc1 = 0; pllc1 |= BHND_PMU_SET_BITS(m1div, BHND_PMU1_PLL0_PC1_M1DIV); pllc1 |= BHND_PMU_SET_BITS(m2div, BHND_PMU1_PLL0_PC1_M2DIV); pllc1 |= BHND_PMU_SET_BITS(m3div, BHND_PMU1_PLL0_PC1_M3DIV); pllc1 |= BHND_PMU_SET_BITS(m4div, BHND_PMU1_PLL0_PC1_M4DIV); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL1, pllc1, ~0); pllc2 = 0; pllc2 |= BHND_PMU_SET_BITS(m5div, BHND_PMU1_PLL0_PC2_M5DIV); pllc2 |= BHND_PMU_SET_BITS(m6div, BHND_PMU1_PLL0_PC2_M6DIV); BHND_PMU_PLL_WRITE(sc, BHND_PMU1_PLL0_PLLCTL2, pllc2, BHND_PMU1_PLL0_PC2_M5DIV_MASK | BHND_PMU1_PLL0_PC2_M6DIV_MASK); } Index: head/sys/dev/bhnd/siba/siba.c =================================================================== --- head/sys/dev/bhnd/siba/siba.c (revision 305443) +++ head/sys/dev/bhnd/siba/siba.c (revision 305444) @@ -1,649 +1,716 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" 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; /* Enumerate children */ if ((error = siba_add_children(dev))) { device_delete_children(dev); return (error); } return (0); } int siba_detach(device_t dev) { return (bhnd_generic_detach(dev)); } 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) { const struct siba_devinfo *dinfo; const struct bhnd_core_info *cfg; 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); default: return (ENOENT); } } static int siba_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { 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); 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); } static int siba_reset_core(device_t dev, device_t child, uint16_t flags) { struct siba_devinfo *dinfo; if (device_get_parent(child) != dev) BHND_BUS_RESET_CORE(device_get_parent(dev), child, flags); dinfo = device_get_ivars(child); /* Can't reset the core without access to the CFG0 registers */ if (dinfo->cfg[0] == NULL) return (ENODEV); // TODO - perform reset return (ENXIO); } static int siba_suspend_core(device_t dev, device_t child) { struct siba_devinfo *dinfo; if (device_get_parent(child) != dev) BHND_BUS_SUSPEND_CORE(device_get_parent(dev), child); dinfo = device_get_ivars(child); /* Can't suspend the core without access to the CFG0 registers */ if (dinfo->cfg[0] == NULL) return (ENODEV); // TODO - perform suspend return (ENXIO); } static uint32_t siba_read_config(device_t dev, device_t child, bus_size_t offset, u_int width) { struct siba_devinfo *dinfo; rman_res_t r_size; /* Must be directly attached */ if (device_get_parent(child) != dev) return (UINT32_MAX); /* CFG0 registers must be available */ dinfo = device_get_ivars(child); if (dinfo->cfg[0] == NULL) return (UINT32_MAX); /* Offset must fall within CFG0 */ r_size = rman_get_size(dinfo->cfg[0]->res); if (r_size < offset || r_size - offset < width) return (UINT32_MAX); switch (width) { case 1: return (bhnd_bus_read_1(dinfo->cfg[0], offset)); case 2: return (bhnd_bus_read_2(dinfo->cfg[0], offset)); case 4: return (bhnd_bus_read_4(dinfo->cfg[0], offset)); } /* Unsuported */ return (UINT32_MAX); } static void siba_write_config(device_t dev, device_t child, bus_size_t offset, uint32_t val, u_int width) { struct siba_devinfo *dinfo; rman_res_t r_size; /* Must be directly attached */ if (device_get_parent(child) != dev) return; /* CFG0 registers must be available */ dinfo = device_get_ivars(child); if (dinfo->cfg[0] == NULL) return; /* Offset must fall within CFG0 */ r_size = rman_get_size(dinfo->cfg[0]->res); if (r_size < offset || r_size - offset < width) return; switch (width) { case 1: bhnd_bus_write_1(dinfo->cfg[0], offset, val); case 2: bhnd_bus_write_2(dinfo->cfg[0], offset, val); case 4: bhnd_bus_write_4(dinfo->cfg[0], offset, val); } } 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_addrspace_port_count(dinfo->core_id.num_addrspace)); } 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); if (!siba_is_port_valid(dinfo->core_id.num_addrspace, type, port)) return (0); return (siba_addrspace_region_count(dinfo->core_id.num_addrspace, 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; /* 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); addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num); if (addrspace == NULL) return (-1); return (addrspace->sa_rid); } 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); for (int i = 0; i < dinfo->core_id.num_addrspace; i++) { if (dinfo->addrspace[i].sa_rid != rid) continue; *port_type = BHND_PORT_DEVICE; *port_num = siba_addrspace_port(i); *region_num = siba_addrspace_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; /* 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); addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num); if (addrspace == NULL) return (ENOENT); *addr = addrspace->sa_base; *size = addrspace->sa_size - addrspace->sa_bus_reserved; return (0); } +/** + * Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT(). + * + * This implementation consults @p child's configuration block mapping, + * returning SIBA_CORE_NUM_INTR if a valid CFG0 block is mapped. + */ +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); + + /* We can get/set interrupt sbflags on any core with a valid cfg0 + * block; whether the core actually makes use of it is another matter + * entirely */ + if (dinfo->cfg[0] == NULL) + return (0); + + return (SIBA_CORE_NUM_INTR); +} + /** + * Default siba(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC(). + * + * This implementation consults @p child's CFG0 register block, + * returning the interrupt flag assigned to @p child. + */ +int +siba_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec) +{ + struct siba_devinfo *dinfo; + uint32_t tpsflag; + + /* delegate non-bus-attached devices to our parent */ + if (device_get_parent(child) != dev) + return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), child, + intr, ivec)); + + /* Must be a valid interrupt ID */ + if (intr >= siba_get_intr_count(dev, child)) + return (ENXIO); + + /* Fetch sbflag number */ + dinfo = device_get_ivars(child); + tpsflag = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_TPSFLAG); + *ivec = SIBA_REG_GET(tpsflag, TPS_NUM0); + + return (0); +} + +/** * Register all address space mappings for @p di. * * @param dev The siba bus device. * @param di The device info instance on which to register all address * space entries. * @param r A resource mapping the enumeration table block for @p di. */ static int siba_register_addrspaces(device_t dev, struct siba_devinfo *di, struct bhnd_resource *r) { struct siba_core_id *cid; uint32_t addr; uint32_t size; int error; cid = &di->core_id; /* Register the device address space entries */ for (uint8_t i = 0; i < di->core_id.num_addrspace; i++) { uint32_t adm; u_int adm_offset; uint32_t bus_reserved; /* Determine the register offset */ adm_offset = siba_admatch_offset(i); if (adm_offset == 0) { device_printf(dev, "addrspace %hhu is unsupported", i); return (ENODEV); } /* Fetch the address match register value */ adm = bhnd_bus_read_4(r, adm_offset); /* Parse the value */ if ((error = siba_parse_admatch(adm, &addr, &size))) { device_printf(dev, "failed to decode address " " match register value 0x%x\n", adm); return (error); } /* If this is the device's core/enumeration addrespace, * reserve the Sonics configuration register blocks for the * use of our bus. */ bus_reserved = 0; if (i == SIBA_CORE_ADDRSPACE) bus_reserved = cid->num_cfg_blocks * SIBA_CFG_SIZE; /* Append the region info */ error = siba_append_dinfo_region(di, i, addr, size, bus_reserved); if (error) return (error); } 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; 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++) { /* Determine the config block's address range; configuration * blocks are allocated starting at SIBA_CFG0_OFFSET, * growing downwards. */ r_start = addrspace->sa_base + SIBA_CFG0_OFFSET; r_start -= i * SIBA_CFG_SIZE; r_count = SIBA_CFG_SIZE; r_end = r_start + r_count - 1; /* Allocate the config resource */ dinfo->cfg_rid[i] = SIBA_CFG_RID(dinfo, i); dinfo->cfg[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev, SYS_RES_MEMORY, &dinfo->cfg_rid[i], r_start, r_end, r_count, RF_ACTIVE); if (dinfo->cfg[i] == NULL) { device_printf(dev, "failed to allocate SIBA_CFG%hhu\n", i); return (ENXIO); } } return (0); } static struct bhnd_devinfo * siba_alloc_bhnd_dinfo(device_t dev) { struct siba_devinfo *dinfo = siba_alloc_dinfo(dev); return ((struct bhnd_devinfo *)dinfo); } static void siba_free_bhnd_dinfo(device_t dev, struct bhnd_devinfo *dinfo) { siba_free_dinfo(dev, (struct siba_devinfo *)dinfo); } /** * 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) { const struct bhnd_chipid *chipid; struct bhnd_core_info *cores; struct siba_devinfo *dinfo; struct bhnd_resource *r; int rid; int error; dinfo = NULL; cores = NULL; r = NULL; chipid = BHND_BUS_GET_CHIPID(dev, dev); /* Allocate our temporary core table and enumerate all cores */ cores = malloc(sizeof(*cores) * chipid->ncores, M_BHND, M_NOWAIT); if (cores == NULL) return (ENOMEM); /* Add all cores. */ for (u_int i = 0; i < chipid->ncores; i++) { struct siba_core_id cid; device_t child; uint32_t idhigh, idlow; rman_res_t r_count, r_end, r_start; + int nintr; /* Map the core's register block */ rid = 0; r_start = SIBA_CORE_ADDR(i); r_count = SIBA_CORE_SIZE; r_end = r_start + SIBA_CORE_SIZE - 1; r = bhnd_alloc_resource(dev, SYS_RES_MEMORY, &rid, r_start, r_end, r_count, RF_ACTIVE); if (r == NULL) { error = ENXIO; goto cleanup; } /* Add the child device */ child = BUS_ADD_CHILD(dev, 0, NULL, -1); if (child == NULL) { error = ENXIO; goto cleanup; } /* Read the core info */ idhigh = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); idlow = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_IDLOW)); cid = siba_parse_core_id(idhigh, idlow, i, 0); cores[i] = cid.core_info; /* Determine unit number */ for (u_int j = 0; j < i; j++) { if (cores[j].vendor == cores[i].vendor && cores[j].device == cores[i].device) cores[i].unit++; } /* Initialize per-device bus info */ if ((dinfo = device_get_ivars(child)) == NULL) { error = ENXIO; goto cleanup; } if ((error = siba_init_dinfo(dev, dinfo, &cid))) goto cleanup; /* Register the core's address space(s). */ if ((error = siba_register_addrspaces(dev, dinfo, r))) goto cleanup; /* Release our resource covering the register blocks * we're about to map */ bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); r = NULL; /* Map the core's config blocks */ if ((error = siba_map_cfg_resources(dev, dinfo))) goto cleanup; + /* Assign interrupts */ + nintr = bhnd_get_intr_count(child); + for (int rid = 0; rid < nintr; rid++) { + error = BHND_BUS_ASSIGN_INTR(dev, child, rid); + if (error) { + device_printf(dev, "failed to assign interrupt " + "%d to core %u: %d\n", rid, i, error); + } + } + /* 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); /* Issue bus callback for fully initialized child. */ BHND_BUS_CHILD_ADDED(dev, child); } cleanup: if (cores != NULL) free(cores, M_BHND); if (r != NULL) bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); 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_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_devinfo, siba_alloc_bhnd_dinfo), DEVMETHOD(bhnd_bus_free_devinfo, siba_free_bhnd_dinfo), DEVMETHOD(bhnd_bus_reset_core, siba_reset_core), DEVMETHOD(bhnd_bus_suspend_core, siba_suspend_core), 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_core_ivec, siba_get_core_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); Index: head/sys/dev/bhnd/siba/siba_bhndb.c =================================================================== --- head/sys/dev/bhnd/siba/siba_bhndb.c (revision 305443) +++ head/sys/dev/bhnd/siba/siba_bhndb.c (revision 305444) @@ -1,298 +1,297 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" /* * Supports attachment of siba(4) bus devices via a bhndb bridge. */ // // TODO: PCI rev < 6 interrupt handling // // On early PCI cores (rev < 6) interrupt masking is handled via interconnect // configuration registers (SBINTVEC), rather than the PCI_INT_MASK // config register. // // On those devices, we should handle interrupts locally using SBINTVEC, rather // than delegating to our parent bhndb device. // static int siba_bhndb_wars_hwup(struct siba_softc *sc); /* Bridge-specific core device quirks */ enum { /** When PCIe-bridged, the D11 core's initiator request * timeout must be disabled to prevent D11 from entering a * RESP_TIMEOUT error state. */ SIBA_QUIRK_PCIE_D11_SB_TIMEOUT = (1<<0) }; static struct bhnd_device_quirk bridge_quirks[] = { BHND_CHIP_QUIRK(4311, HWREV_EQ(2), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), BHND_CHIP_QUIRK(4312, HWREV_EQ(0), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), BHND_DEVICE_QUIRK_END }; static struct bhnd_device bridge_devs[] = { BHND_DEVICE(BCM, PCI, NULL, bridge_quirks), BHND_DEVICE_END }; static int siba_bhndb_probe(device_t dev) { const struct bhnd_chipid *cid; int error; /* Defer to default probe implementation */ if ((error = siba_probe(dev)) > 0) return (error); /* Check bus type */ cid = BHNDB_GET_CHIPID(device_get_parent(dev), dev); if (cid->chip_type != BHND_CHIPTYPE_SIBA) return (ENXIO); /* Set device description */ bhnd_set_default_bus_desc(dev, cid); return (error); } static int siba_bhndb_attach(device_t dev) { struct siba_softc *sc; int error; sc = device_get_softc(dev); /* Perform initial attach and enumerate our children. */ if ((error = siba_attach(dev))) goto failed; /* Apply attach/resume workarounds before any child drivers attach */ if ((error = siba_bhndb_wars_hwup(sc))) goto failed; /* Delegate remainder to standard bhnd method implementation */ if ((error = bhnd_generic_attach(dev))) goto failed; return (0); failed: device_delete_children(dev); return (error); } static int siba_bhndb_resume(device_t dev) { struct siba_softc *sc; int error; sc = device_get_softc(dev); /* Apply attach/resume work-arounds */ if ((error = siba_bhndb_wars_hwup(sc))) return (error); /* Call our superclass' implementation */ return (siba_resume(dev)); } /* Suspend all references to the device's cfg register blocks */ static void siba_bhndb_suspend_cfgblocks(device_t dev, struct siba_devinfo *dinfo) { for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) { if (dinfo->cfg[i] == NULL) continue; BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, SYS_RES_MEMORY, dinfo->cfg[i]->res); } } static int siba_bhndb_suspend_child(device_t dev, device_t child) { struct siba_devinfo *dinfo; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); dinfo = device_get_ivars(child); /* Suspend the child */ if ((error = bhnd_generic_br_suspend_child(dev, child))) return (error); /* Suspend resource references to the child's config registers */ siba_bhndb_suspend_cfgblocks(dev, dinfo); return (0); } static int siba_bhndb_resume_child(device_t dev, device_t child) { struct siba_devinfo *dinfo; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); if (!device_is_suspended(child)) return (EBUSY); dinfo = device_get_ivars(child); /* Resume all resource references to the child's config registers */ for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) { if (dinfo->cfg[i] == NULL) continue; error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev, SYS_RES_MEMORY, dinfo->cfg[i]->res); if (error) { siba_bhndb_suspend_cfgblocks(dev, dinfo); return (error); } } /* Resume the child */ if ((error = bhnd_generic_br_resume_child(dev, child))) { siba_bhndb_suspend_cfgblocks(dev, dinfo); return (error); } return (0); } /* Work-around implementation for SIBA_QUIRK_PCIE_D11_SB_TIMEOUT */ static int siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_softc *sc) { struct siba_devinfo *dinfo; device_t hostb_dev; device_t d11; uint32_t imcfg; /* Only applies when bridged by PCIe */ if ((hostb_dev = bhnd_find_hostb_device(sc->dev)) == NULL) return (ENXIO); if (bhnd_get_class(hostb_dev) != BHND_DEVCLASS_PCIE) return (0); /* Only applies if there's a D11 core */ d11 = bhnd_match_child(sc->dev, &(struct bhnd_core_match) { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_D11), BHND_MATCH_CORE_UNIT(0) }); if (d11 == NULL) return (0); /* Clear initiator timeout in D11's CFG0 block */ dinfo = device_get_ivars(d11); KASSERT(dinfo->cfg[0] != NULL, ("missing core config mapping")); imcfg = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW); imcfg &= ~SIBA_IMCL_RTO_MASK; bhnd_bus_write_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW, imcfg); return (0); } /** * Apply any hardware workarounds that are required upon attach or resume * of the bus. */ static int siba_bhndb_wars_hwup(struct siba_softc *sc) { device_t hostb_dev; uint32_t quirks; int error; if ((hostb_dev = bhnd_find_hostb_device(sc->dev)) == NULL) return (ENXIO); quirks = bhnd_device_quirks(hostb_dev, bridge_devs, sizeof(bridge_devs[0])); if (quirks & SIBA_QUIRK_PCIE_D11_SB_TIMEOUT) { if ((error = siba_bhndb_wars_pcie_clear_d11_timeout(sc))) return (error); } return (0); } - static device_method_t siba_bhndb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, siba_bhndb_probe), DEVMETHOD(device_attach, siba_bhndb_attach), DEVMETHOD(device_resume, siba_bhndb_resume), /* Bus interface */ DEVMETHOD(bus_suspend_child, siba_bhndb_suspend_child), DEVMETHOD(bus_resume_child, siba_bhndb_resume_child), DEVMETHOD_END }; DEFINE_CLASS_2(bhnd, siba_bhndb_driver, siba_bhndb_methods, sizeof(struct siba_softc), bhnd_bhndb_driver, siba_driver); DRIVER_MODULE(siba_bhndb, bhndb, siba_bhndb_driver, bhnd_devclass, NULL, NULL); MODULE_VERSION(siba_bhndb, 1); MODULE_DEPEND(siba_bhndb, siba, 1, 1, 1); MODULE_DEPEND(siba_bhndb, bhnd, 1, 1, 1); MODULE_DEPEND(siba_bhndb, bhndb, 1, 1, 1); Index: head/sys/dev/bhnd/siba/sibareg.h =================================================================== --- head/sys/dev/bhnd/siba/sibareg.h (revision 305443) +++ head/sys/dev/bhnd/siba/sibareg.h (revision 305444) @@ -1,265 +1,267 @@ /*- * Copyright (c) 2015 Landon Fuller * Copyright (c) 2010 Broadcom Corporation * * This file was derived from the sbconfig.h header distributed with * Broadcom's initial brcm80211 Linux driver release, as * contributed to the Linux staging repository. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_SIBA_SIBAREG_ #define _BHND_SIBA_SIBAREG_ #include /* * Broadcom SIBA Configuration Space Registers. * * Backplane configuration registers common to siba(4) core register * blocks. */ /** * Extract a config attribute by applying _MASK and _SHIFT defines. * * @param _reg The register value containing the desired attribute * @param _attr The BCMA EROM attribute name (e.g. ENTRY_ISVALID), to be * concatenated with the `SB` prefix and `_MASK`/`_SHIFT` suffixes. */ #define SIBA_REG_GET(_entry, _attr) \ ((_entry & SIBA_ ## _attr ## _MASK) \ >> SIBA_ ## _attr ## _SHIFT) #define SIBA_ENUM_ADDR BHND_DEFAULT_CHIPC_ADDR /**< enumeration space */ #define SIBA_ENUM_SIZE 0x00100000 /**< size of the enumeration space */ #define SIBA_CORE_SIZE BHND_DEFAULT_CORE_SIZE /**< per-core register block size */ +#define SIBA_CORE_NUM_INTR 1 /**< number of per-core interrupt lines */ #define SIBA_MAX_CORES \ (SIBA_ENUM_SIZE/SIBA_CORE_SIZE) /**< Maximum number of cores */ /** Evaluates to the bus address offset of the @p idx core register block */ #define SIBA_CORE_OFFSET(idx) ((idx) * SIBA_CORE_SIZE) /** Evaluates to the bus address of the @p idx core register block */ #define SIBA_CORE_ADDR(idx) (SIBA_ENUM_ADDR + SIBA_CORE_OFFSET(idx)) /* * Sonics configuration registers are mapped to each core's enumeration * space, at the end of the 4kb device register block, in reverse * order: * * [0x0000-0x0dff] core registers * [0x0e00-0x0eff] SIBA_R1 registers (sonics >= 2.3) * [0x0f00-0x0fff] SIBA_R0 registers */ #define SIBA_CFG0_OFFSET 0xf00 /**< first configuration block */ #define SIBA_CFG1_OFFSET 0xe00 /**< second configuration block (sonics >= 2.3) */ #define SIBA_CFG_SIZE 0x100 /**< cfg register block size */ /* Return the SIBA_CORE_ADDR-relative offset for a SIBA_CFG* register. */ #define SB0_REG_ABS(off) ((off) + SIBA_CFG0_OFFSET) #define SB1_REG_ABS(off) ((off) + SIBA_CFG1_OFFSET) /* SIBA_CFG0 registers */ #define SIBA_CFG0_IPSFLAG 0x08 /**< initiator port ocp slave flag */ #define SIBA_CFG0_TPSFLAG 0x18 /**< target port ocp slave flag */ #define SIBA_CFG0_TMERRLOGA 0x48 /**< sonics >= 2.3 */ #define SIBA_CFG0_TMERRLOG 0x50 /**< sonics >= 2.3 */ #define SIBA_CFG0_ADMATCH3 0x60 /**< address match3 */ #define SIBA_CFG0_ADMATCH2 0x68 /**< address match2 */ #define SIBA_CFG0_ADMATCH1 0x70 /**< address match1 */ #define SIBA_CFG0_IMSTATE 0x90 /**< initiator agent state */ #define SIBA_CFG0_INTVEC 0x94 /**< interrupt mask */ #define SIBA_CFG0_TMSTATELOW 0x98 /**< target state */ #define SIBA_CFG0_TMSTATEHIGH 0x9c /**< target state */ #define SIBA_CFG0_BWA0 0xa0 /**< bandwidth allocation table0 */ #define SIBA_CFG0_IMCONFIGLOW 0xa8 /**< initiator configuration */ #define SIBA_CFG0_IMCONFIGHIGH 0xac /**< initiator configuration */ #define SIBA_CFG0_ADMATCH0 0xb0 /**< address match0 */ #define SIBA_CFG0_TMCONFIGLOW 0xb8 /**< target configuration */ #define SIBA_CFG0_TMCONFIGHIGH 0xbc /**< target configuration */ #define SIBA_CFG0_BCONFIG 0xc0 /**< broadcast configuration */ #define SIBA_CFG0_BSTATE 0xc8 /**< broadcast state */ #define SIBA_CFG0_ACTCNFG 0xd8 /**< activate configuration */ #define SIBA_CFG0_FLAGST 0xe8 /**< current sbflags */ #define SIBA_CFG0_IDLOW 0xf8 /**< identification */ #define SIBA_CFG0_IDHIGH 0xfc /**< identification */ /* SIBA_CFG1 registers (sonics >= 2.3) */ #define SIBA_CFG1_IMERRLOGA 0xa8 /**< (sonics >= 2.3) */ #define SIBA_CFG1_IMERRLOG 0xb0 /**< sbtmerrlog (sonics >= 2.3) */ #define SIBA_CFG1_TMPORTCONNID0 0xd8 /**< sonics >= 2.3 */ #define SIBA_CFG1_TMPORTLOCK0 0xf8 /**< sonics >= 2.3 */ /* sbipsflag */ #define SIBA_IPS_INT1_MASK 0x3f /* which sbflags get routed to mips interrupt 1 */ #define SIBA_IPS_INT1_SHIFT 0 #define SIBA_IPS_INT2_MASK 0x3f00 /* which sbflags get routed to mips interrupt 2 */ #define SIBA_IPS_INT2_SHIFT 8 #define SIBA_IPS_INT3_MASK 0x3f0000 /* which sbflags get routed to mips interrupt 3 */ #define SIBA_IPS_INT3_SHIFT 16 #define SIBA_IPS_INT4_MASK 0x3f000000 /* which sbflags get routed to mips interrupt 4 */ #define SIBA_IPS_INT4_SHIFT 24 /* sbtpsflag */ #define SIBA_TPS_NUM0_MASK 0x3f /* interrupt sbFlag # generated by this core */ +#define SIBA_TPS_NUM0_SHIFT 0 #define SIBA_TPS_F0EN0 0x40 /* interrupt is always sent on the backplane */ /* sbtmerrlog */ #define SIBA_TMEL_CM 0x00000007 /* command */ #define SIBA_TMEL_CI 0x0000ff00 /* connection id */ #define SIBA_TMEL_EC 0x0f000000 /* error code */ #define SIBA_TMEL_ME 0x80000000 /* multiple error */ /* sbimstate */ #define SIBA_IM_PC 0xf /* pipecount */ #define SIBA_IM_AP_MASK 0x30 /* arbitration policy */ #define SIBA_IM_AP_BOTH 0x00 /* use both timeslaces and token */ #define SIBA_IM_AP_TS 0x10 /* use timesliaces only */ #define SIBA_IM_AP_TK 0x20 /* use token only */ #define SIBA_IM_AP_RSV 0x30 /* reserved */ #define SIBA_IM_IBE 0x20000 /* inbanderror */ #define SIBA_IM_TO 0x40000 /* timeout */ #define SIBA_IM_BY 0x01800000 /* busy (sonics >= 2.3) */ #define SIBA_IM_RJ 0x02000000 /* reject (sonics >= 2.3) */ /* sbtmstatelow */ #define SIBA_TML_RESET 0x0001 /* reset */ #define SIBA_TML_REJ_MASK 0x0006 /* reject field */ #define SIBA_TML_REJ 0x0002 /* reject */ #define SIBA_TML_TMPREJ 0x0004 /* temporary reject, for error recovery */ #define SIBA_TML_SICF_SHIFT 16 /* Shift to locate the SI control flags in sbtml */ /* sbtmstatehigh */ #define SIBA_TMH_SERR 0x0001 /* serror */ #define SIBA_TMH_INT 0x0002 /* interrupt */ #define SIBA_TMH_BUSY 0x0004 /* busy */ #define SIBA_TMH_TO 0x0020 /* timeout (sonics >= 2.3) */ #define SIBA_TMH_SISF_SHIFT 16 /* Shift to locate the SI status flags in sbtmh */ /* sbbwa0 */ #define SIBA_BWA_TAB0_MASK 0xffff /* lookup table 0 */ #define SIBA_BWA_TAB1_MASK 0xffff /* lookup table 1 */ #define SIBA_BWA_TAB1_SHIFT 16 /* sbimconfiglow */ #define SIBA_IMCL_STO_MASK 0x7 /* service timeout */ #define SIBA_IMCL_RTO_MASK 0x70 /* request timeout */ #define SIBA_IMCL_RTO_SHIFT 4 #define SIBA_IMCL_CID_MASK 0xff0000 /* connection id */ #define SIBA_IMCL_CID_SHIFT 16 /* sbimconfighigh */ #define SIBA_IMCH_IEM_MASK 0xc /* inband error mode */ #define SIBA_IMCH_TEM_MASK 0x30 /* timeout error mode */ #define SIBA_IMCH_TEM_SHIFT 4 #define SIBA_IMCH_BEM_MASK 0xc0 /* bus error mode */ #define SIBA_IMCH_BEM_SHIFT 6 /* sbadmatch0-4 */ #define SIBA_AM_TYPE_MASK 0x3 /* address type */ #define SIBA_AM_TYPE_SHIFT 0x0 #define SIBA_AM_AD64 0x4 /* reserved */ #define SIBA_AM_ADINT0_MASK 0xf8 /* type0 size */ #define SIBA_AM_ADINT0_SHIFT 3 #define SIBA_AM_ADINT1_MASK 0x1f8 /* type1 size */ #define SIBA_AM_ADINT1_SHIFT 3 #define SIBA_AM_ADINT2_MASK 0x1f8 /* type2 size */ #define SIBA_AM_ADINT2_SHIFT 3 #define SIBA_AM_ADEN 0x400 /* enable */ #define SIBA_AM_ADNEG 0x800 /* negative decode */ #define SIBA_AM_BASE0_MASK 0xffffff00 /* type0 base address */ #define SIBA_AM_BASE0_SHIFT 8 #define SIBA_AM_BASE1_MASK 0xfffff000 /* type1 base address for the core */ #define SIBA_AM_BASE1_SHIFT 12 #define SIBA_AM_BASE2_MASK 0xffff0000 /* type2 base address for the core */ #define SIBA_AM_BASE2_SHIFT 16 /* sbtmconfiglow */ #define SIBA_TMCL_CD_MASK 0xff /* clock divide */ #define SIBA_TMCL_CO_MASK 0xf800 /* clock offset */ #define SIBA_TMCL_CO_SHIFT 11 #define SIBA_TMCL_IF_MASK 0xfc0000 /* interrupt flags */ #define SIBA_TMCL_IF_SHIFT 18 #define SIBA_TMCL_IM_MASK 0x3000000 /* interrupt mode */ #define SIBA_TMCL_IM_SHIFT 24 /* sbtmconfighigh */ #define SIBA_TMCH_BM_MASK 0x3 /* busy mode */ #define SIBA_TMCH_RM_MASK 0x3 /* retry mode */ #define SIBA_TMCH_RM_SHIFT 2 #define SIBA_TMCH_SM_MASK 0x30 /* stop mode */ #define SIBA_TMCH_SM_SHIFT 4 #define SIBA_TMCH_EM_MASK 0x300 /* sb error mode */ #define SIBA_TMCH_EM_SHIFT 8 #define SIBA_TMCH_IM_MASK 0xc00 /* int mode */ #define SIBA_TMCH_IM_SHIFT 10 /* sbbconfig */ #define SIBA_BC_LAT_MASK 0x3 /* sb latency */ #define SIBA_BC_MAX0_MASK 0xf0000 /* maxccntr0 */ #define SIBA_BC_MAX0_SHIFT 16 #define SIBA_BC_MAX1_MASK 0xf00000 /* maxccntr1 */ #define SIBA_BC_MAX1_SHIFT 20 /* sbbstate */ #define SIBA_BS_SRD 0x1 /* st reg disable */ #define SIBA_BS_HRD 0x2 /* hold reg disable */ /* sbidlow */ #define SIBA_IDL_CS_MASK 0x3 /* config space */ #define SIBA_IDL_CS_SHIFT 0 #define SIBA_IDL_NRADDR_MASK 0x38 /* # address ranges supported */ #define SIBA_IDL_NRADDR_SHIFT 3 #define SIBA_IDL_SYNCH 0x40 /* sync */ #define SIBA_IDL_INIT 0x80 /* initiator */ #define SIBA_IDL_MINLAT_MASK 0xf00 /* minimum backplane latency */ #define SIBA_IDL_MINLAT_SHIFT 8 #define SIBA_IDL_MAXLAT_MASK 0xf000 /* maximum backplane latency */ #define SIBA_IDL_MAXLAT_SHIFT 12 #define SIBA_IDL_FIRST_MASK 0x10000 /* this initiator is first */ #define SIBA_IDL_FIRST_SHIFT 16 #define SIBA_IDL_CW_MASK 0xc0000 /* cycle counter width */ #define SIBA_IDL_CW_SHIFT 18 #define SIBA_IDL_TP_MASK 0xf00000 /* target ports */ #define SIBA_IDL_TP_SHIFT 20 #define SIBA_IDL_IP_MASK 0xf000000 /* initiator ports */ #define SIBA_IDL_IP_SHIFT 24 #define SIBA_IDL_SBREV_MASK 0xf0000000 /* sonics backplane revision code */ #define SIBA_IDL_SBREV_SHIFT 28 #define SIBA_IDL_SBREV_2_2 0x0 /* version 2.2 or earlier */ #define SIBA_IDL_SBREV_2_3 0x1 /* version 2.3 */ /* sbidhigh */ #define SIBA_IDH_RC_MASK 0x000f /* revision code */ #define SIBA_IDH_RCE_MASK 0x7000 /* revision code extension field */ #define SIBA_IDH_RCE_SHIFT 8 #define SIBA_IDH_DEVICE_MASK 0x8ff0 /* core code */ #define SIBA_IDH_DEVICE_SHIFT 4 #define SIBA_IDH_VENDOR_MASK 0xffff0000 /* vendor code */ #define SIBA_IDH_VENDOR_SHIFT 16 #define SIBA_IDH_CORE_REV(sbidh) \ (SIBA_REG_GET((sbidh), IDH_RCE) | ((sbidh) & SIBA_IDH_RC_MASK)) #define SIBA_COMMIT 0xfd8 /* update buffered registers value */ #endif /* _BHND_SIBA_SIBAREG_ */ Index: head/sys/dev/bhnd/siba/sibavar.h =================================================================== --- head/sys/dev/bhnd/siba/sibavar.h (revision 305443) +++ head/sys/dev/bhnd/siba/sibavar.h (revision 305444) @@ -1,164 +1,167 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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. * * $FreeBSD$ */ #ifndef _SIBA_SIBAVAR_H_ #define _SIBA_SIBAVAR_H_ #include #include #include #include #include #include "siba.h" /* * Internal definitions shared by siba(4) driver implementations. */ struct siba_addrspace; struct siba_devinfo; struct siba_core_id; int siba_probe(device_t dev); int siba_attach(device_t dev); int siba_detach(device_t dev); int siba_resume(device_t dev); int siba_suspend(device_t dev); +int siba_get_intr_count(device_t dev, device_t child); +int siba_get_core_ivec(device_t dev, device_t child, + u_int intr, uint32_t *ivec); uint16_t siba_get_bhnd_mfgid(uint16_t ocp_vendor); struct siba_core_id siba_parse_core_id(uint32_t idhigh, uint32_t idlow, u_int core_idx, int unit); int siba_add_children(device_t bus); struct siba_devinfo *siba_alloc_dinfo(device_t dev); int siba_init_dinfo(device_t dev, struct siba_devinfo *dinfo, const struct siba_core_id *core_id); void siba_free_dinfo(device_t dev, struct siba_devinfo *dinfo); u_int siba_addrspace_port_count(u_int num_addrspace); u_int siba_addrspace_region_count(u_int num_addrspace, u_int port); u_int siba_addrspace_port(u_int addrspace); u_int siba_addrspace_region(u_int addrspace); int siba_addrspace_index(u_int num_addrspace, bhnd_port_type type, u_int port, u_int region, u_int *addridx); bool siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port); struct siba_addrspace *siba_find_addrspace(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port, u_int region); int siba_append_dinfo_region(struct siba_devinfo *dinfo, uint8_t sid, uint32_t base, uint32_t size, uint32_t bus_reserved); u_int siba_admatch_offset(uint8_t addrspace); int siba_parse_admatch(uint32_t am, uint32_t *addr, uint32_t *size); /* Sonics configuration register blocks */ #define SIBA_CFG_NUM_2_2 1 /**< sonics <= 2.2 maps SIBA_CFG0. */ #define SIBA_CFG_NUM_2_3 2 /**< sonics <= 2.3 maps SIBA_CFG0 and SIBA_CFG1 */ #define SIBA_MAX_CFG SIBA_CFG_NUM_2_3 /**< maximum number of supported config register blocks */ #define SIBA_CFG_RID_BASE 100 /**< base resource ID for SIBA_CFG* register allocations */ #define SIBA_CFG_RID(_dinfo, _cfg) \ (SIBA_CFG_RID_BASE + (_cfg) + \ (_dinfo->core_id.core_info.core_idx * SIBA_MAX_CFG)) /* Sonics/OCP address space mappings */ #define SIBA_CORE_ADDRSPACE 0 /**< Address space mapping the primary device registers */ #define SIBA_MAX_ADDRSPACE 4 /**< Maximum number of Sonics/OCP * address space mappings for a * single core. */ /* bhnd(4) (port,region) representation of siba address space mappings */ #define SIBA_MAX_PORT 2 /**< maximum number of advertised * bhnd(4) ports */ /** siba(4) address space descriptor */ struct siba_addrspace { uint32_t sa_base; /**< base address */ uint32_t sa_size; /**< size */ int sa_rid; /**< bus resource id */ uint32_t sa_bus_reserved;/**< number of bytes at high end of * address space reserved for the bus */ }; /** * siba(4) per-core identification info. */ struct siba_core_id { struct bhnd_core_info core_info; /**< standard bhnd(4) core info */ uint16_t sonics_vendor; /**< OCP vendor identifier used to generate * the JEDEC-106 bhnd(4) vendor identifier. */ uint8_t sonics_rev; /**< sonics backplane revision code */ uint8_t num_addrspace; /**< number of address ranges mapped to this core. */ uint8_t num_cfg_blocks; /**< number of Sonics configuration register blocks mapped to the core's enumeration space */ }; /** * siba(4) per-device info */ struct siba_devinfo { struct bhnd_devinfo bhnd_dinfo; /**< superclass device info. */ struct resource_list resources; /**< per-core memory regions. */ struct siba_core_id core_id; /**< core identification info */ struct siba_addrspace addrspace[SIBA_MAX_ADDRSPACE]; /**< memory map descriptors */ struct bhnd_resource *cfg[SIBA_MAX_CFG]; /**< SIBA_CFG_* registers */ int cfg_rid[SIBA_MAX_CFG]; /**< SIBA_CFG_* resource IDs */ }; /** siba(4) per-instance state */ struct siba_softc { struct bhnd_softc bhnd_sc; /**< bhnd state */ device_t dev; /**< siba device */ }; #endif /* _SIBA_SIBAVAR_H_ */ Index: head/sys/dev/bwn/bwn_mac.c =================================================================== --- head/sys/dev/bwn/bwn_mac.c (revision 305443) +++ head/sys/dev/bwn/bwn_mac.c (revision 305444) @@ -1,154 +1,174 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include "opt_bwn.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include "bhnd_nvram_map.h" -static const struct resource_spec bwn_rspec[] = { - { SYS_RES_MEMORY, 0, RF_ACTIVE }, - { -1, -1, 0 } -}; - -#define RSPEC_LEN (sizeof(bwn_rspec)/sizeof(bwn_rspec[0])) - struct bwn_softc { - struct resource_spec rspec[RSPEC_LEN]; - struct bhnd_resource *res[RSPEC_LEN-1]; + int mem_rid; + struct bhnd_resource *mem_res; + + int intr_rid; + struct resource *intr_res; }; static const struct bwn_device { uint16_t vendor; uint16_t device; } bwn_devices[] = { { BHND_MFGID_BCM, BHND_COREID_D11 }, { BHND_MFGID_INVALID, BHND_COREID_INVALID } }; static int bwn_probe(device_t dev) { const struct bwn_device *id; for (id = bwn_devices; id->device != BHND_COREID_INVALID; id++) { if (bhnd_get_vendor(dev) == id->vendor && bhnd_get_device(dev) == id->device) { device_set_desc(dev, bhnd_get_device_name(dev)); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int bwn_attach(device_t dev) { struct bwn_softc *sc; - struct bhnd_resource *r; int error; sc = device_get_softc(dev); - memcpy(sc->rspec, bwn_rspec, sizeof(bwn_rspec)); - if ((error = bhnd_alloc_resources(dev, sc->rspec, sc->res))) - return (error); + /* Allocate device resources */ + sc->mem_rid = 0; + sc->mem_res = bhnd_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->mem_rid, RF_ACTIVE); + if (sc->mem_res == NULL) { + device_printf(dev, "failed to allocate device registers\n"); + error = ENXIO; + goto cleanup; + } - // XXX TODO - r = sc->res[0]; - device_printf(dev, "got rid=%d res=%p\n", sc->rspec[0].rid, r); + sc->intr_rid = 0; + sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, + RF_ACTIVE|RF_SHAREABLE); + if (sc->intr_res == NULL) { + device_printf(dev, "failed to allocate device interrupt\n"); + error = ENXIO; + goto cleanup; + } + // TODO uint8_t macaddr[6]; error = bhnd_nvram_getvar_array(dev, BHND_NVAR_MACADDR, macaddr, sizeof(macaddr), BHND_NVRAM_TYPE_UINT8); if (error) - return (error); + device_printf(dev, "error fetching macaddr: %d\n", error); + else + device_printf(dev, "got macaddr %6D\n", macaddr, ":"); - device_printf(dev, "got macaddr %6D\n", macaddr, ":"); - return (0); + +cleanup: + if (sc->mem_res != NULL) + bhnd_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, + sc->mem_res); + + if (sc->intr_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, + sc->intr_res); + + return (error); } static int bwn_detach(device_t dev) { struct bwn_softc *sc; sc = device_get_softc(dev); - bhnd_release_resources(dev, sc->rspec, sc->res); + + bhnd_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res); + bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); return (0); } static int bwn_suspend(device_t dev) { return (0); } static int bwn_resume(device_t dev) { return (0); } static device_method_t bwn_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bwn_probe), DEVMETHOD(device_attach, bwn_attach), DEVMETHOD(device_detach, bwn_detach), DEVMETHOD(device_suspend, bwn_suspend), DEVMETHOD(device_resume, bwn_resume), DEVMETHOD_END }; static devclass_t bwn_devclass; DEFINE_CLASS_0(bwn, bwn_driver, bwn_methods, sizeof(struct bwn_softc)); DRIVER_MODULE(bwn_mac, bhnd, bwn_driver, bwn_devclass, 0, 0); MODULE_DEPEND(bwn_mac, bhnd, 1, 1, 1); MODULE_VERSION(bwn_mac, 1);