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