/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2015-2016 Landon Fuller <[email protected]>4* Copyright (c) 2017 The FreeBSD Foundation5* All rights reserved.6*7* Portions of this software were developed by Landon Fuller8* under sponsorship from the FreeBSD Foundation.9*10* Redistribution and use in source and binary forms, with or without11* modification, are permitted provided that the following conditions12* are met:13* 1. Redistributions of source code must retain the above copyright14* notice, this list of conditions and the following disclaimer,15* without modification.16* 2. Redistributions in binary form must reproduce at minimum a disclaimer17* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any18* redistribution must be conditioned upon including a substantially19* similar Disclaimer requirement for further binary redistribution.20*21* NO WARRANTY22* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS23* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT24* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY25* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL26* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,27* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF28* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS29* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER30* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)31* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF32* THE POSSIBILITY OF SUCH DAMAGES.33*/3435#include <sys/param.h>36#include <sys/bus.h>37#include <sys/kernel.h>38#include <sys/limits.h>39#include <sys/systm.h>4041#include <machine/bus.h>42#include <machine/resource.h>4344#include <dev/bhnd/bhndvar.h>4546#include "bcma_dmp.h"4748#include "bcmavar.h"4950/* Return the resource ID for a device's agent register allocation */51#define BCMA_AGENT_RID(_dinfo) \52(BCMA_AGENT_RID_BASE + BCMA_DINFO_COREIDX(_dinfo))5354/**55* Allocate and initialize new core config structure.56*57* @param core_index Core index on the bus.58* @param core_unit Core unit number.59* @param vendor Core designer.60* @param device Core identifier (e.g. part number).61* @param hwrev Core revision.62*/63struct bcma_corecfg *64bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor,65uint16_t device, uint8_t hwrev)66{67struct bcma_corecfg *cfg;6869cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT);70if (cfg == NULL)71return NULL;7273cfg->core_info = (struct bhnd_core_info) {74.vendor = vendor,75.device = device,76.hwrev = hwrev,77.core_idx = core_index,78.unit = core_unit79};8081STAILQ_INIT(&cfg->master_ports);82cfg->num_master_ports = 0;8384STAILQ_INIT(&cfg->dev_ports);85cfg->num_dev_ports = 0;8687STAILQ_INIT(&cfg->bridge_ports);88cfg->num_bridge_ports = 0;8990STAILQ_INIT(&cfg->wrapper_ports);91cfg->num_wrapper_ports = 0;9293return (cfg);94}9596/**97* Deallocate the given core config and any associated resources.98*99* @param corecfg Core info to be deallocated.100*/101void102bcma_free_corecfg(struct bcma_corecfg *corecfg)103{104struct bcma_mport *mport, *mnext;105struct bcma_sport *sport, *snext;106107STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) {108free(mport, M_BHND);109}110111STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) {112bcma_free_sport(sport);113}114115STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) {116bcma_free_sport(sport);117}118119STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) {120bcma_free_sport(sport);121}122123free(corecfg, M_BHND);124}125126/**127* Return the @p cfg port list for @p type.128*129* @param cfg The core configuration.130* @param type The requested port type.131*/132struct bcma_sport_list *133bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type)134{135switch (type) {136case BHND_PORT_DEVICE:137return (&cfg->dev_ports);138break;139case BHND_PORT_BRIDGE:140return (&cfg->bridge_ports);141break;142case BHND_PORT_AGENT:143return (&cfg->wrapper_ports);144break;145default:146return (NULL);147}148}149150/**151* Populate the resource list and bcma_map RIDs using the maps defined on152* @p ports.153*154* @param bus The requesting bus device.155* @param dinfo The device info instance to be initialized.156* @param ports The set of ports to be enumerated157*/158static void159bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo,160struct bcma_sport_list *ports)161{162struct bcma_map *map;163struct bcma_sport *port;164bhnd_addr_t end;165166STAILQ_FOREACH(port, ports, sp_link) {167STAILQ_FOREACH(map, &port->sp_maps, m_link) {168/*169* Create the corresponding device resource list entry.170*171* We necessarily skip registration if the region's172* device memory range is not representable via173* rman_res_t.174*175* When rman_res_t is migrated to uintmax_t, any176* range should be representable.177*/178end = map->m_base + map->m_size;179if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) {180map->m_rid = resource_list_add_next(181&dinfo->resources, SYS_RES_MEMORY,182map->m_base, end, map->m_size);183} else if (bootverbose) {184device_printf(bus,185"core%u %s%u.%u: region %llx-%llx extends "186"beyond supported addressable range\n",187dinfo->corecfg->core_info.core_idx,188bhnd_port_type_name(port->sp_type),189port->sp_num, map->m_region_num,190(unsigned long long) map->m_base,191(unsigned long long) end);192}193}194}195}196197/**198* Allocate the per-core agent register block for a device info structure.199*200* If an agent0.0 region is not defined on @p dinfo, the device info201* agent resource is set to NULL and 0 is returned.202*203* @param bus The requesting bus device.204* @param child The bcma child device.205* @param dinfo The device info associated with @p child206*207* @retval 0 success208* @retval non-zero resource allocation failed.209*/210static int211bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)212{213bhnd_addr_t addr;214bhnd_size_t size;215rman_res_t r_start, r_count, r_end;216int error;217218KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));219220/* Verify that the agent register block exists and is221* mappable */222if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)223return (0); /* nothing to do */224225/* Fetch the address of the agent register block */226error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,227&addr, &size);228if (error) {229device_printf(bus, "failed fetching agent register block "230"address for core %u\n", BCMA_DINFO_COREIDX(dinfo));231return (error);232}233234/* Allocate the resource */235r_start = addr;236r_count = size;237r_end = r_start + r_count - 1;238239dinfo->rid_agent = BCMA_AGENT_RID(dinfo);240dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY,241&dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE);242if (dinfo->res_agent == NULL) {243device_printf(bus, "failed allocating agent register block for "244"core %u\n", BCMA_DINFO_COREIDX(dinfo));245return (ENXIO);246}247248return (0);249}250251/**252* Populate the list of interrupts for a device info structure253* previously initialized via bcma_dinfo_alloc_agent().254*255* If an agent0.0 region is not mapped on @p dinfo, the OOB interrupt bank is256* assumed to be unavailable and 0 is returned.257*258* @param bus The requesting bus device.259* @param dinfo The device info instance to be initialized.260*/261static int262bcma_dinfo_init_intrs(device_t bus, device_t child,263struct bcma_devinfo *dinfo)264{265uint32_t dmpcfg, oobw;266267/* Agent block must be mapped */268if (dinfo->res_agent == NULL)269return (0);270271/* Agent must support OOB */272dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);273if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))274return (0);275276/* Fetch width of the OOB interrupt bank */277oobw = bhnd_bus_read_4(dinfo->res_agent,278BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));279if (oobw >= BCMA_OOB_NUM_SEL) {280device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: "281"%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);282return (0);283}284285/* Fetch OOBSEL busline values and populate list of interrupt286* descriptors */287for (uint32_t sel = 0; sel < oobw; sel++) {288struct bcma_intr *intr;289uint32_t selout;290uint8_t line;291292if (dinfo->num_intrs == UINT_MAX)293return (ENOMEM);294295selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(296BCMA_OOB_BANK_INTR, sel));297298line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) &299BCMA_DMP_OOBSEL_BUSLINE_MASK;300301intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line);302if (intr == NULL) {303device_printf(bus, "failed allocating interrupt "304"descriptor %#x for core %u\n", sel,305BCMA_DINFO_COREIDX(dinfo));306return (ENOMEM);307}308309STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link);310dinfo->num_intrs++;311}312313return (0);314}315316/**317* Allocate and return a new empty device info structure.318*319* @param bus The requesting bus device.320*321* @retval NULL if allocation failed.322*/323struct bcma_devinfo *324bcma_alloc_dinfo(device_t bus)325{326struct bcma_devinfo *dinfo;327328dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);329if (dinfo == NULL)330return (NULL);331332dinfo->corecfg = NULL;333dinfo->res_agent = NULL;334dinfo->rid_agent = -1;335336STAILQ_INIT(&dinfo->intrs);337dinfo->num_intrs = 0;338339resource_list_init(&dinfo->resources);340341return (dinfo);342}343344/**345* Initialize a device info structure previously allocated via346* bcma_alloc_dinfo, assuming ownership of the provided core347* configuration.348*349* @param bus The requesting bus device.350* @param child The bcma child device.351* @param dinfo The device info associated with @p child352* @param corecfg Device core configuration; ownership of this value353* will be assumed by @p dinfo.354*355* @retval 0 success356* @retval non-zero initialization failed.357*/358int359bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo,360struct bcma_corecfg *corecfg)361{362struct bcma_intr *intr;363int error;364365KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));366367/* Save core configuration value */368dinfo->corecfg = corecfg;369370/* The device ports must always be initialized first to ensure that371* rid 0 maps to the first device port */372bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports);373bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports);374bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports);375376/* Now that we've defined the port resources, we can map the device's377* agent registers (if any) */378if ((error = bcma_dinfo_init_agent(bus, child, dinfo)))379goto failed;380381/* With agent registers mapped, we can populate the device's interrupt382* descriptors */383if ((error = bcma_dinfo_init_intrs(bus, child, dinfo)))384goto failed;385386/* Finally, map the interrupt descriptors */387STAILQ_FOREACH(intr, &dinfo->intrs, i_link) {388/* Already mapped? */389if (intr->i_mapped)390continue;391392/* Map the interrupt */393error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel,394&intr->i_irq);395if (error) {396device_printf(bus, "failed mapping interrupt line %u "397"for core %u: %d\n", intr->i_sel,398BCMA_DINFO_COREIDX(dinfo), error);399goto failed;400}401402intr->i_mapped = true;403404/* Add to resource list */405intr->i_rid = resource_list_add_next(&dinfo->resources,406SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);407}408409return (0);410411failed:412/* Owned by the caller on failure */413dinfo->corecfg = NULL;414415return (error);416}417418/**419* Deallocate the given device info structure and any associated resources.420*421* @param bus The requesting bus device.422* @param dinfo Device info to be deallocated.423*/424void425bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo)426{427struct bcma_intr *intr, *inext;428429resource_list_free(&dinfo->resources);430431if (dinfo->corecfg != NULL)432bcma_free_corecfg(dinfo->corecfg);433434/* Release agent resource, if any */435if (dinfo->res_agent != NULL) {436bhnd_release_resource(bus, SYS_RES_MEMORY, dinfo->rid_agent,437dinfo->res_agent);438}439440/* Clean up interrupt descriptors */441STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) {442STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link);443444/* Release our IRQ mapping */445if (intr->i_mapped) {446BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq);447intr->i_mapped = false;448}449450bcma_free_intr(intr);451}452453free(dinfo, M_BHND);454}455456/**457* Allocate and initialize a new interrupt descriptor.458*459* @param bank OOB bank.460* @param sel OOB selector.461* @param line OOB bus line.462*/463struct bcma_intr *464bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line)465{466struct bcma_intr *intr;467468if (bank >= BCMA_OOB_NUM_BANKS)469return (NULL);470471if (sel >= BCMA_OOB_NUM_SEL)472return (NULL);473474if (line >= BCMA_OOB_NUM_BUSLINES)475return (NULL);476477intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT);478if (intr == NULL)479return (NULL);480481intr->i_bank = bank;482intr->i_sel = sel;483intr->i_busline = line;484intr->i_mapped = false;485intr->i_irq = 0;486487return (intr);488}489490/**491* Deallocate all resources associated with the given interrupt descriptor.492*493* @param intr Interrupt descriptor to be deallocated.494*/495void496bcma_free_intr(struct bcma_intr *intr)497{498KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel));499500free(intr, M_BHND);501}502503/**504* Allocate and initialize new slave port descriptor.505*506* @param port_num Per-core port number.507* @param port_type Port type.508*/509struct bcma_sport *510bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type)511{512struct bcma_sport *sport;513514sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT);515if (sport == NULL)516return NULL;517518sport->sp_num = port_num;519sport->sp_type = port_type;520sport->sp_num_maps = 0;521STAILQ_INIT(&sport->sp_maps);522523return sport;524}525526/**527* Deallocate all resources associated with the given port descriptor.528*529* @param sport Port descriptor to be deallocated.530*/531void532bcma_free_sport(struct bcma_sport *sport) {533struct bcma_map *map, *mapnext;534535STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) {536free(map, M_BHND);537}538539free(sport, M_BHND);540}541542/**543* Given a bcma(4) child's device info, spin waiting for the device's DMP544* resetstatus register to clear.545*546* @param child The bcma(4) child device.547* @param dinfo The @p child device info.548*549* @retval 0 success550* @retval ENODEV if @p dinfo does not map an agent register resource.551* @retval ETIMEDOUT if timeout occurs552*/553int554bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo)555{556uint32_t rst;557558if (dinfo->res_agent == NULL)559return (ENODEV);560561/* 300us should be long enough, but there are references to this562* requiring up to 10ms when performing reset of an 80211 core563* after a MAC PSM microcode watchdog event. */564for (int i = 0; i < 10000; i += 10) {565rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETSTATUS);566if (rst == 0)567return (0);568569DELAY(10);570}571572device_printf(child, "BCMA_DMP_RESETSTATUS timeout\n");573return (ETIMEDOUT);574}575576/**577* Set the bcma(4) child's DMP resetctrl register value, and then wait578* for all backplane operations to complete.579*580* @param child The bcma(4) child device.581* @param dinfo The @p child device info.582* @param value The new ioctrl value to set.583*584* @retval 0 success585* @retval ENODEV if @p dinfo does not map an agent register resource.586* @retval ETIMEDOUT if timeout occurs waiting for reset completion587*/588int589bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value)590{591uint32_t rst;592593if (dinfo->res_agent == NULL)594return (ENODEV);595596/* Already in requested reset state? */597rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);598if (rst == value)599return (0);600601bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value);602bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */603DELAY(10);604605return (bcma_dmp_wait_reset(child, dinfo));606}607608609