#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <dev/bhnd/bhndvar.h>
#include "bcma_dmp.h"
#include "bcmavar.h"
#define BCMA_AGENT_RID(_dinfo) \
(BCMA_AGENT_RID_BASE + BCMA_DINFO_COREIDX(_dinfo))
struct bcma_corecfg *
bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor,
uint16_t device, uint8_t hwrev)
{
struct bcma_corecfg *cfg;
cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT);
if (cfg == NULL)
return NULL;
cfg->core_info = (struct bhnd_core_info) {
.vendor = vendor,
.device = device,
.hwrev = hwrev,
.core_idx = core_index,
.unit = core_unit
};
STAILQ_INIT(&cfg->master_ports);
cfg->num_master_ports = 0;
STAILQ_INIT(&cfg->dev_ports);
cfg->num_dev_ports = 0;
STAILQ_INIT(&cfg->bridge_ports);
cfg->num_bridge_ports = 0;
STAILQ_INIT(&cfg->wrapper_ports);
cfg->num_wrapper_ports = 0;
return (cfg);
}
void
bcma_free_corecfg(struct bcma_corecfg *corecfg)
{
struct bcma_mport *mport, *mnext;
struct bcma_sport *sport, *snext;
STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) {
free(mport, M_BHND);
}
STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) {
bcma_free_sport(sport);
}
STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) {
bcma_free_sport(sport);
}
STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) {
bcma_free_sport(sport);
}
free(corecfg, M_BHND);
}
struct bcma_sport_list *
bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type)
{
switch (type) {
case BHND_PORT_DEVICE:
return (&cfg->dev_ports);
break;
case BHND_PORT_BRIDGE:
return (&cfg->bridge_ports);
break;
case BHND_PORT_AGENT:
return (&cfg->wrapper_ports);
break;
default:
return (NULL);
}
}
static void
bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo,
struct bcma_sport_list *ports)
{
struct bcma_map *map;
struct bcma_sport *port;
bhnd_addr_t end;
STAILQ_FOREACH(port, ports, sp_link) {
STAILQ_FOREACH(map, &port->sp_maps, m_link) {
end = map->m_base + map->m_size;
if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) {
map->m_rid = resource_list_add_next(
&dinfo->resources, SYS_RES_MEMORY,
map->m_base, end, map->m_size);
} else if (bootverbose) {
device_printf(bus,
"core%u %s%u.%u: region %llx-%llx extends "
"beyond supported addressable range\n",
dinfo->corecfg->core_info.core_idx,
bhnd_port_type_name(port->sp_type),
port->sp_num, map->m_region_num,
(unsigned long long) map->m_base,
(unsigned long long) end);
}
}
}
}
static int
bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
{
bhnd_addr_t addr;
bhnd_size_t size;
rman_res_t r_start, r_count, r_end;
int error, rid_agent;
KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));
if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)
return (0);
error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,
&addr, &size);
if (error) {
device_printf(bus, "failed fetching agent register block "
"address for core %u\n", BCMA_DINFO_COREIDX(dinfo));
return (error);
}
r_start = addr;
r_count = size;
r_end = r_start + r_count - 1;
rid_agent = BCMA_AGENT_RID(dinfo);
dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY,
rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE);
if (dinfo->res_agent == NULL) {
device_printf(bus, "failed allocating agent register block for "
"core %u\n", BCMA_DINFO_COREIDX(dinfo));
return (ENXIO);
}
return (0);
}
static int
bcma_dinfo_init_intrs(device_t bus, device_t child,
struct bcma_devinfo *dinfo)
{
uint32_t dmpcfg, oobw;
if (dinfo->res_agent == NULL)
return (0);
dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);
if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))
return (0);
oobw = bhnd_bus_read_4(dinfo->res_agent,
BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));
if (oobw >= BCMA_OOB_NUM_SEL) {
device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: "
"%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);
return (0);
}
for (uint32_t sel = 0; sel < oobw; sel++) {
struct bcma_intr *intr;
uint32_t selout;
uint8_t line;
if (dinfo->num_intrs == UINT_MAX)
return (ENOMEM);
selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(
BCMA_OOB_BANK_INTR, sel));
line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) &
BCMA_DMP_OOBSEL_BUSLINE_MASK;
intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line);
if (intr == NULL) {
device_printf(bus, "failed allocating interrupt "
"descriptor %#x for core %u\n", sel,
BCMA_DINFO_COREIDX(dinfo));
return (ENOMEM);
}
STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link);
dinfo->num_intrs++;
}
return (0);
}
struct bcma_devinfo *
bcma_alloc_dinfo(device_t bus)
{
struct bcma_devinfo *dinfo;
dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);
if (dinfo == NULL)
return (NULL);
dinfo->corecfg = NULL;
dinfo->res_agent = NULL;
STAILQ_INIT(&dinfo->intrs);
dinfo->num_intrs = 0;
resource_list_init(&dinfo->resources);
return (dinfo);
}
int
bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo,
struct bcma_corecfg *corecfg)
{
struct bcma_intr *intr;
int error;
KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));
dinfo->corecfg = corecfg;
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports);
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports);
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports);
if ((error = bcma_dinfo_init_agent(bus, child, dinfo)))
goto failed;
if ((error = bcma_dinfo_init_intrs(bus, child, dinfo)))
goto failed;
STAILQ_FOREACH(intr, &dinfo->intrs, i_link) {
if (intr->i_mapped)
continue;
error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel,
&intr->i_irq);
if (error) {
device_printf(bus, "failed mapping interrupt line %u "
"for core %u: %d\n", intr->i_sel,
BCMA_DINFO_COREIDX(dinfo), error);
goto failed;
}
intr->i_mapped = true;
intr->i_rid = resource_list_add_next(&dinfo->resources,
SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);
}
return (0);
failed:
dinfo->corecfg = NULL;
return (error);
}
void
bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo)
{
struct bcma_intr *intr, *inext;
resource_list_free(&dinfo->resources);
if (dinfo->corecfg != NULL)
bcma_free_corecfg(dinfo->corecfg);
if (dinfo->res_agent != NULL) {
bhnd_release_resource(bus, dinfo->res_agent);
}
STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) {
STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link);
if (intr->i_mapped) {
BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq);
intr->i_mapped = false;
}
bcma_free_intr(intr);
}
free(dinfo, M_BHND);
}
struct bcma_intr *
bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line)
{
struct bcma_intr *intr;
if (bank >= BCMA_OOB_NUM_BANKS)
return (NULL);
if (sel >= BCMA_OOB_NUM_SEL)
return (NULL);
if (line >= BCMA_OOB_NUM_BUSLINES)
return (NULL);
intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT);
if (intr == NULL)
return (NULL);
intr->i_bank = bank;
intr->i_sel = sel;
intr->i_busline = line;
intr->i_mapped = false;
intr->i_irq = 0;
return (intr);
}
void
bcma_free_intr(struct bcma_intr *intr)
{
KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel));
free(intr, M_BHND);
}
struct bcma_sport *
bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type)
{
struct bcma_sport *sport;
sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT);
if (sport == NULL)
return NULL;
sport->sp_num = port_num;
sport->sp_type = port_type;
sport->sp_num_maps = 0;
STAILQ_INIT(&sport->sp_maps);
return sport;
}
void
bcma_free_sport(struct bcma_sport *sport) {
struct bcma_map *map, *mapnext;
STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) {
free(map, M_BHND);
}
free(sport, M_BHND);
}
int
bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo)
{
uint32_t rst;
if (dinfo->res_agent == NULL)
return (ENODEV);
for (int i = 0; i < 10000; i += 10) {
rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETSTATUS);
if (rst == 0)
return (0);
DELAY(10);
}
device_printf(child, "BCMA_DMP_RESETSTATUS timeout\n");
return (ETIMEDOUT);
}
int
bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value)
{
uint32_t rst;
if (dinfo->res_agent == NULL)
return (ENODEV);
rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);
if (rst == value)
return (0);
bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value);
bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);
DELAY(10);
return (bcma_dmp_wait_reset(child, dinfo));
}