1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
5 * Copyright (c) 2017 The FreeBSD Foundation
6 * All rights reserved.
7 *
8 * Portions of this software were developed by Landon Fuller
9 * under sponsorship from the FreeBSD Foundation.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer,
16 *    without modification.
17 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
18 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
19 *    redistribution must be conditioned upon including a substantially
20 *    similar Disclaimer requirement for further binary redistribution.
21 *
22 * NO WARRANTY
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
26 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
27 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
28 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
31 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33 * THE POSSIBILITY OF SUCH DAMAGES.
34 */
35
36#include <sys/cdefs.h>
37__FBSDID("$FreeBSD$");
38
39#include <sys/param.h>
40#include <sys/bus.h>
41#include <sys/kernel.h>
42#include <sys/limits.h>
43#include <sys/systm.h>
44
45#include <machine/bus.h>
46#include <machine/resource.h>
47
48#include <dev/bhnd/bhndvar.h>
49
50#include "bcma_dmp.h"
51
52#include "bcmavar.h"
53
54/* Return the resource ID for a device's agent register allocation */
55#define	BCMA_AGENT_RID(_dinfo)	\
56    (BCMA_AGENT_RID_BASE + BCMA_DINFO_COREIDX(_dinfo))
57
58 /**
59 * Allocate and initialize new core config structure.
60 *
61 * @param core_index Core index on the bus.
62 * @param core_unit Core unit number.
63 * @param vendor Core designer.
64 * @param device Core identifier (e.g. part number).
65 * @param hwrev Core revision.
66 */
67struct bcma_corecfg *
68bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor,
69    uint16_t device, uint8_t hwrev)
70{
71	struct bcma_corecfg *cfg;
72
73	cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT);
74	if (cfg == NULL)
75		return NULL;
76
77	cfg->core_info = (struct bhnd_core_info) {
78		.vendor = vendor,
79		.device = device,
80		.hwrev = hwrev,
81		.core_idx = core_index,
82		.unit = core_unit
83	};
84
85	STAILQ_INIT(&cfg->master_ports);
86	cfg->num_master_ports = 0;
87
88	STAILQ_INIT(&cfg->dev_ports);
89	cfg->num_dev_ports = 0;
90
91	STAILQ_INIT(&cfg->bridge_ports);
92	cfg->num_bridge_ports = 0;
93
94	STAILQ_INIT(&cfg->wrapper_ports);
95	cfg->num_wrapper_ports = 0;
96
97	return (cfg);
98}
99
100/**
101 * Deallocate the given core config and any associated resources.
102 *
103 * @param corecfg Core info to be deallocated.
104 */
105void
106bcma_free_corecfg(struct bcma_corecfg *corecfg)
107{
108	struct bcma_mport *mport, *mnext;
109	struct bcma_sport *sport, *snext;
110
111	STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) {
112		free(mport, M_BHND);
113	}
114
115	STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) {
116		bcma_free_sport(sport);
117	}
118
119	STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) {
120		bcma_free_sport(sport);
121	}
122
123	STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) {
124		bcma_free_sport(sport);
125	}
126
127	free(corecfg, M_BHND);
128}
129
130/**
131 * Return the @p cfg port list for @p type.
132 *
133 * @param cfg The core configuration.
134 * @param type The requested port type.
135 */
136struct bcma_sport_list *
137bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type)
138{
139	switch (type) {
140	case BHND_PORT_DEVICE:
141		return (&cfg->dev_ports);
142		break;
143	case BHND_PORT_BRIDGE:
144		return (&cfg->bridge_ports);
145		break;
146	case BHND_PORT_AGENT:
147		return (&cfg->wrapper_ports);
148		break;
149	default:
150		return (NULL);
151	}
152}
153
154/**
155 * Populate the resource list and bcma_map RIDs using the maps defined on
156 * @p ports.
157 *
158 * @param bus The requesting bus device.
159 * @param dinfo The device info instance to be initialized.
160 * @param ports The set of ports to be enumerated
161 */
162static void
163bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo,
164    struct bcma_sport_list *ports)
165{
166	struct bcma_map		*map;
167	struct bcma_sport	*port;
168	bhnd_addr_t		 end;
169
170	STAILQ_FOREACH(port, ports, sp_link) {
171		STAILQ_FOREACH(map, &port->sp_maps, m_link) {
172			/*
173			 * Create the corresponding device resource list entry.
174			 *
175			 * We necessarily skip registration if the region's
176			 * device memory range is not representable via
177			 * rman_res_t.
178			 *
179			 * When rman_res_t is migrated to uintmax_t, any
180			 * range should be representable.
181			 */
182			end = map->m_base + map->m_size;
183			if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) {
184				map->m_rid = resource_list_add_next(
185				    &dinfo->resources, SYS_RES_MEMORY,
186				    map->m_base, end, map->m_size);
187			} else if (bootverbose) {
188				device_printf(bus,
189				    "core%u %s%u.%u: region %llx-%llx extends "
190				        "beyond supported addressable range\n",
191				    dinfo->corecfg->core_info.core_idx,
192				    bhnd_port_type_name(port->sp_type),
193				    port->sp_num, map->m_region_num,
194				    (unsigned long long) map->m_base,
195				    (unsigned long long) end);
196			}
197		}
198	}
199}
200
201
202
203/**
204 * Allocate the per-core agent register block for a device info structure.
205 *
206 * If an agent0.0 region is not defined on @p dinfo, the device info
207 * agent resource is set to NULL and 0 is returned.
208 *
209 * @param bus The requesting bus device.
210 * @param child The bcma child device.
211 * @param dinfo The device info associated with @p child
212 *
213 * @retval 0 success
214 * @retval non-zero resource allocation failed.
215 */
216static int
217bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
218{
219	bhnd_addr_t	addr;
220	bhnd_size_t	size;
221	rman_res_t	r_start, r_count, r_end;
222	int		error;
223
224	KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));
225
226	/* Verify that the agent register block exists and is
227	 * mappable */
228	if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)
229		return (0);	/* nothing to do */
230
231	/* Fetch the address of the agent register block */
232	error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,
233	    &addr, &size);
234	if (error) {
235		device_printf(bus, "failed fetching agent register block "
236		    "address for core %u\n", BCMA_DINFO_COREIDX(dinfo));
237		return (error);
238	}
239
240	/* Allocate the resource */
241	r_start = addr;
242	r_count = size;
243	r_end = r_start + r_count - 1;
244
245	dinfo->rid_agent = BCMA_AGENT_RID(dinfo);
246	dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY,
247	    &dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE);
248	if (dinfo->res_agent == NULL) {
249		device_printf(bus, "failed allocating agent register block for "
250		    "core %u\n", BCMA_DINFO_COREIDX(dinfo));
251		return (ENXIO);
252	}
253
254	return (0);
255}
256
257/**
258 * Populate the list of interrupts for a device info structure
259 * previously initialized via bcma_dinfo_alloc_agent().
260 *
261 * If an agent0.0 region is not mapped on @p dinfo, the OOB interrupt bank is
262 * assumed to be unavailable and 0 is returned.
263 *
264 * @param bus The requesting bus device.
265 * @param dinfo The device info instance to be initialized.
266 */
267static int
268bcma_dinfo_init_intrs(device_t bus, device_t child,
269    struct bcma_devinfo *dinfo)
270{
271	uint32_t dmpcfg, oobw;
272
273	/* Agent block must be mapped */
274	if (dinfo->res_agent == NULL)
275		return (0);
276
277	/* Agent must support OOB */
278	dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);
279	if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))
280		return (0);
281
282	/* Fetch width of the OOB interrupt bank */
283	oobw = bhnd_bus_read_4(dinfo->res_agent,
284	     BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));
285	if (oobw >= BCMA_OOB_NUM_SEL) {
286		device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: "
287		    "%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);
288		return (0);
289	}
290
291	/* Fetch OOBSEL busline values and populate list of interrupt
292	 * descriptors */
293	for (uint32_t sel = 0; sel < oobw; sel++) {
294		struct bcma_intr	*intr;
295		uint32_t		 selout;
296		uint8_t			 line;
297
298		if (dinfo->num_intrs == UINT_MAX)
299			return (ENOMEM);
300
301		selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(
302		    BCMA_OOB_BANK_INTR, sel));
303
304		line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) &
305		    BCMA_DMP_OOBSEL_BUSLINE_MASK;
306
307		intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line);
308		if (intr == NULL) {
309			device_printf(bus, "failed allocating interrupt "
310			    "descriptor %#x for core %u\n", sel,
311			    BCMA_DINFO_COREIDX(dinfo));
312			return (ENOMEM);
313		}
314
315		STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link);
316		dinfo->num_intrs++;
317	}
318
319	return (0);
320}
321
322/**
323 * Allocate and return a new empty device info structure.
324 *
325 * @param bus The requesting bus device.
326 *
327 * @retval NULL if allocation failed.
328 */
329struct bcma_devinfo *
330bcma_alloc_dinfo(device_t bus)
331{
332	struct bcma_devinfo *dinfo;
333
334	dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);
335	if (dinfo == NULL)
336		return (NULL);
337
338	dinfo->corecfg = NULL;
339	dinfo->res_agent = NULL;
340	dinfo->rid_agent = -1;
341
342	STAILQ_INIT(&dinfo->intrs);
343	dinfo->num_intrs = 0;
344
345	resource_list_init(&dinfo->resources);
346
347	return (dinfo);
348}
349
350/**
351 * Initialize a device info structure previously allocated via
352 * bcma_alloc_dinfo, assuming ownership of the provided core
353 * configuration.
354 *
355 * @param bus The requesting bus device.
356 * @param child The bcma child device.
357 * @param dinfo The device info associated with @p child
358 * @param corecfg Device core configuration; ownership of this value
359 * will be assumed by @p dinfo.
360 *
361 * @retval 0 success
362 * @retval non-zero initialization failed.
363 */
364int
365bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo,
366    struct bcma_corecfg *corecfg)
367{
368	struct bcma_intr	*intr;
369	int			 error;
370
371	KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));
372
373	/* Save core configuration value */
374	dinfo->corecfg = corecfg;
375
376	/* The device ports must always be initialized first to ensure that
377	 * rid 0 maps to the first device port */
378	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports);
379	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports);
380	bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports);
381
382	/* Now that we've defined the port resources, we can map the device's
383	 * agent registers (if any) */
384	if ((error = bcma_dinfo_init_agent(bus, child, dinfo)))
385		goto failed;
386
387	/* With agent registers mapped, we can populate the device's interrupt
388	 * descriptors */
389	if ((error = bcma_dinfo_init_intrs(bus, child, dinfo)))
390		goto failed;
391
392	/* Finally, map the interrupt descriptors */
393	STAILQ_FOREACH(intr, &dinfo->intrs, i_link) {
394		/* Already mapped? */
395		if (intr->i_mapped)
396			continue;
397
398		/* Map the interrupt */
399		error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel,
400		    &intr->i_irq);
401		if (error) {
402			device_printf(bus, "failed mapping interrupt line %u "
403			    "for core %u: %d\n", intr->i_sel,
404			    BCMA_DINFO_COREIDX(dinfo), error);
405			goto failed;
406		}
407
408		intr->i_mapped = true;
409
410		/* Add to resource list */
411		intr->i_rid = resource_list_add_next(&dinfo->resources,
412		    SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);
413	}
414
415	return (0);
416
417failed:
418	/* Owned by the caller on failure */
419	dinfo->corecfg = NULL;
420
421	return (error);
422}
423
424/**
425 * Deallocate the given device info structure and any associated resources.
426 *
427 * @param bus The requesting bus device.
428 * @param dinfo Device info to be deallocated.
429 */
430void
431bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo)
432{
433	struct bcma_intr *intr, *inext;
434
435	resource_list_free(&dinfo->resources);
436
437	if (dinfo->corecfg != NULL)
438		bcma_free_corecfg(dinfo->corecfg);
439
440	/* Release agent resource, if any */
441	if (dinfo->res_agent != NULL) {
442		bhnd_release_resource(bus, SYS_RES_MEMORY, dinfo->rid_agent,
443		    dinfo->res_agent);
444	}
445
446	/* Clean up interrupt descriptors */
447	STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) {
448		STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link);
449
450		/* Release our IRQ mapping */
451		if (intr->i_mapped) {
452			BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq);
453			intr->i_mapped = false;
454		}
455
456		bcma_free_intr(intr);
457	}
458
459	free(dinfo, M_BHND);
460}
461
462
463/**
464 * Allocate and initialize a new interrupt descriptor.
465 *
466 * @param bank OOB bank.
467 * @param sel OOB selector.
468 * @param line OOB bus line.
469 */
470struct bcma_intr *
471bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line)
472{
473	struct bcma_intr *intr;
474
475	if (bank >= BCMA_OOB_NUM_BANKS)
476		return (NULL);
477
478	if (sel >= BCMA_OOB_NUM_SEL)
479		return (NULL);
480
481	if (line >= BCMA_OOB_NUM_BUSLINES)
482		return (NULL);
483
484	intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT);
485	if (intr == NULL)
486		return (NULL);
487
488	intr->i_bank = bank;
489	intr->i_sel = sel;
490	intr->i_busline = line;
491	intr->i_mapped = false;
492	intr->i_irq = 0;
493
494	return (intr);
495}
496
497/**
498 * Deallocate all resources associated with the given interrupt descriptor.
499 *
500 * @param intr Interrupt descriptor to be deallocated.
501 */
502void
503bcma_free_intr(struct bcma_intr *intr)
504{
505	KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel));
506
507	free(intr, M_BHND);
508}
509
510/**
511 * Allocate and initialize new slave port descriptor.
512 *
513 * @param port_num Per-core port number.
514 * @param port_type Port type.
515 */
516struct bcma_sport *
517bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type)
518{
519	struct bcma_sport *sport;
520
521	sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT);
522	if (sport == NULL)
523		return NULL;
524
525	sport->sp_num = port_num;
526	sport->sp_type = port_type;
527	sport->sp_num_maps = 0;
528	STAILQ_INIT(&sport->sp_maps);
529
530	return sport;
531}
532
533/**
534 * Deallocate all resources associated with the given port descriptor.
535 *
536 * @param sport Port descriptor to be deallocated.
537 */
538void
539bcma_free_sport(struct bcma_sport *sport) {
540	struct bcma_map *map, *mapnext;
541
542	STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) {
543		free(map, M_BHND);
544	}
545
546	free(sport, M_BHND);
547}
548
549
550/**
551 * Given a bcma(4) child's device info, spin waiting for the device's DMP
552 * resetstatus register to clear.
553 *
554 * @param child The bcma(4) child device.
555 * @param dinfo The @p child device info.
556 *
557 * @retval 0 success
558 * @retval ENODEV if @p dinfo does not map an agent register resource.
559 * @retval ETIMEDOUT if timeout occurs
560 */
561int
562bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo)
563{
564	uint32_t rst;
565
566	if (dinfo->res_agent == NULL)
567		return (ENODEV);
568
569	/* 300us should be long enough, but there are references to this
570	 * requiring up to 10ms when performing reset of an 80211 core
571	 * after a MAC PSM microcode watchdog event. */
572	for (int i = 0; i < 10000; i += 10) {
573		rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETSTATUS);
574		if (rst == 0)
575			return (0);
576
577		DELAY(10);
578	}
579
580	device_printf(child, "BCMA_DMP_RESETSTATUS timeout\n");
581	return (ETIMEDOUT);
582}
583
584/**
585 * Set the bcma(4) child's DMP resetctrl register value, and then wait
586 * for all backplane operations to complete.
587 *
588 * @param child The bcma(4) child device.
589 * @param dinfo The @p child device info.
590 * @param value The new ioctrl value to set.
591 *
592 * @retval 0 success
593 * @retval ENODEV if @p dinfo does not map an agent register resource.
594 * @retval ETIMEDOUT if timeout occurs waiting for reset completion
595 */
596int
597bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value)
598{
599	uint32_t rst;
600
601	if (dinfo->res_agent == NULL)
602		return (ENODEV);
603
604	/* Already in requested reset state? */
605	rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);
606	if (rst == value)
607		return (0);
608
609	bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value);
610	bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */
611	DELAY(10);
612
613	return (bcma_dmp_wait_reset(child, dinfo));
614}
615