]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/bhnd/siba/siba_subr.c
Upgrade to OpenSSH 7.8p1.
[FreeBSD/FreeBSD.git] / sys / dev / bhnd / siba / siba_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 "sibareg.h"
49 #include "sibavar.h"
50
51 static int      siba_register_interrupts(device_t dev, device_t child,
52                     struct siba_devinfo *dinfo);
53 static int      siba_append_dinfo_region(struct siba_devinfo *dinfo,
54                      uint8_t addridx, uint32_t base, uint32_t size,
55                      uint32_t bus_reserved);
56
57 /**
58  * Map a siba(4) OCP vendor code to its corresponding JEDEC JEP-106 vendor
59  * code.
60  * 
61  * @param ocp_vendor An OCP vendor code.
62  * @return The BHND_MFGID constant corresponding to @p ocp_vendor, or
63  * BHND_MFGID_INVALID if the OCP vendor is unknown.
64  */
65 uint16_t
66 siba_get_bhnd_mfgid(uint16_t ocp_vendor)
67 {
68         switch (ocp_vendor) {
69         case OCP_VENDOR_BCM:
70                 return (BHND_MFGID_BCM);
71         default:
72                 return (BHND_MFGID_INVALID);
73         }
74 }
75
76 /**
77  * Allocate and return a new empty device info structure.
78  * 
79  * @param bus The requesting bus device.
80  * 
81  * @retval NULL if allocation failed.
82  */
83 struct siba_devinfo *
84 siba_alloc_dinfo(device_t bus)
85 {
86         struct siba_devinfo *dinfo;
87         
88         dinfo = malloc(sizeof(struct siba_devinfo), M_BHND, M_NOWAIT|M_ZERO);
89         if (dinfo == NULL)
90                 return NULL;
91
92         for (u_int i = 0; i < nitems(dinfo->cfg); i++) {
93                 dinfo->cfg[i] = ((struct siba_cfg_block){
94                         .cb_base = 0,
95                         .cb_size = 0,
96                         .cb_rid = -1,
97                 });
98                 dinfo->cfg_res[i] = NULL;
99                 dinfo->cfg_rid[i] = -1;
100         }
101
102         resource_list_init(&dinfo->resources);
103
104         dinfo->pmu_state = SIBA_PMU_NONE;
105
106         dinfo->intr = (struct siba_intr) {
107                 .mapped = false,
108                 .rid = -1
109         };
110
111         return dinfo;
112 }
113
114 /**
115  * Initialize a device info structure previously allocated via
116  * siba_alloc_dinfo, copying the provided core id.
117  * 
118  * @param dev The requesting bus device.
119  * @param child The siba child device.
120  * @param dinfo The device info instance.
121  * @param core Device core info.
122  * 
123  * @retval 0 success
124  * @retval non-zero initialization failed.
125  */
126 int
127 siba_init_dinfo(device_t dev, device_t child, struct siba_devinfo *dinfo,
128     const struct siba_core_id *core_id)
129 {
130         int error;
131
132         dinfo->core_id = *core_id;
133
134         /* Register all address space mappings */
135         for (uint8_t i = 0; i < core_id->num_admatch; i++) {
136                 uint32_t bus_reserved;
137
138                 /* If this is the device's core/enumeration addrespace,
139                  * reserve the Sonics configuration register blocks for the
140                  * use of our bus. */
141                 bus_reserved = 0;
142                 if (i == SIBA_CORE_ADDRSPACE)
143                         bus_reserved = core_id->num_cfg_blocks * SIBA_CFG_SIZE;
144
145                 /* Append the region info */
146                 error = siba_append_dinfo_region(dinfo, i,
147                     core_id->admatch[i].am_base, core_id->admatch[i].am_size,
148                     bus_reserved);
149                 if (error)
150                         return (error);
151         }
152
153         /* Register all interrupt(s) */
154         if ((error = siba_register_interrupts(dev, child, dinfo)))
155                 return (error);
156
157         return (0);
158 }
159
160
161 /**
162  * Register and map all interrupts for @p dinfo.
163  *
164  * @param dev The siba bus device.
165  * @param child The siba child device.
166  * @param dinfo The device info instance on which to register all interrupt
167  * entries.
168  */
169 static int
170 siba_register_interrupts(device_t dev, device_t child,
171      struct siba_devinfo *dinfo)
172 {
173         int error;
174
175         /* Is backplane interrupt distribution enabled for this core? */
176         if (!dinfo->core_id.intr_en)
177                 return (0);
178
179         /* Have one interrupt */
180         dinfo->intr.mapped = false;
181         dinfo->intr.irq = 0;
182         dinfo->intr.rid = -1;
183
184         /* Map the interrupt */
185         error = BHND_BUS_MAP_INTR(dev, child, 0 /* single intr is always 0 */,
186             &dinfo->intr.irq);
187         if (error) {
188                 device_printf(dev, "failed mapping interrupt line for core %u: "
189                     "%d\n", dinfo->core_id.core_info.core_idx, error);
190                 return (error);
191         }
192         dinfo->intr.mapped = true;
193
194         /* Update the resource list */
195         dinfo->intr.rid = resource_list_add_next(&dinfo->resources, SYS_RES_IRQ,
196             dinfo->intr.irq, dinfo->intr.irq, 1);
197
198         return (0);
199 }
200
201 /**
202  * Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port
203  * number.
204  * 
205  * @param addrspace Address space index.
206  */
207 u_int
208 siba_addrspace_device_port(u_int addrspace)
209 {
210         /* The first addrspace is always mapped to device0; the remainder
211          * are mapped to device1 */
212         if (addrspace == 0)
213                 return (0);
214         else
215                 return (1);
216 }
217
218 /**
219  * Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port
220  * region number.
221  * 
222  * @param addrspace Address space index.
223  */
224 u_int
225 siba_addrspace_device_region(u_int addrspace)
226 {
227         /* The first addrspace is always mapped to device0.0; the remainder
228          * are mapped to device1.0 + (n - 1) */
229         if (addrspace == 0)
230                 return (0);
231         else
232                 return (addrspace - 1);
233 }
234
235 /**
236  * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
237  * number.
238  * 
239  * @param cfg Config block index.
240  */
241 u_int
242 siba_cfg_agent_port(u_int cfg)
243 {
244         /* Always agent0 */
245         return (0);
246 }
247
248 /**
249  * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
250  * region number.
251  * 
252  * @param cfg Config block index.
253  */
254 u_int
255 siba_cfg_agent_region(u_int cfg)
256 {
257         /* Always agent0.<idx> */
258         return (cfg);
259 }
260
261 /**
262  * Return the number of bhnd(4) ports to advertise for the given
263  * @p core_id and @p port_type.
264  * 
265  * Refer to the siba_addrspace_index() and siba_cfg_index() functions for
266  * information on siba's mapping of bhnd(4) port and region identifiers.
267  * 
268  * @param core_id The siba core info.
269  * @param port_type The bhnd(4) port type.
270  */
271 u_int
272 siba_port_count(struct siba_core_id *core_id, bhnd_port_type port_type)
273 {
274         switch (port_type) {
275         case BHND_PORT_DEVICE:
276                 /* 0, 1, or 2 ports */
277                 return (min(core_id->num_admatch, 2));
278
279         case BHND_PORT_AGENT:
280                 /* One agent port maps all configuration blocks */
281                 if (core_id->num_cfg_blocks > 0)
282                         return (1);
283
284                 /* Do not advertise an agent port if there are no configuration
285                  * register blocks */
286                 return (0);
287
288         default:
289                 return (0);
290         }
291 }
292
293 /**
294  * Return true if @p port of @p port_type is defined by @p core_id, false
295  * otherwise.
296  * 
297  * @param core_id The siba core info.
298  * @param port_type The bhnd(4) port type.
299  * @param port The bhnd(4) port number.
300  */
301 bool
302 siba_is_port_valid(struct siba_core_id *core_id, bhnd_port_type port_type,
303     u_int port)
304 {
305         /* Verify the index against the port count */
306         if (siba_port_count(core_id, port_type) <= port)
307                 return (false);
308
309         return (true);
310 }
311
312 /**
313  * Return the number of bhnd(4) regions to advertise for @p core_id on the
314  * @p port of @p port_type.
315  * 
316  * @param core_id The siba core info.
317  * @param port_type The bhnd(4) port type.
318  */
319 u_int
320 siba_port_region_count(struct siba_core_id *core_id, bhnd_port_type port_type,
321     u_int port)
322 {
323         /* The port must exist */
324         if (!siba_is_port_valid(core_id, port_type, port))
325                 return (0);
326
327         switch (port_type) {
328         case BHND_PORT_DEVICE:
329                 /* The first address space, if any, is mapped to device0.0 */
330                 if (port == 0)
331                         return (min(core_id->num_admatch, 1));
332
333                 /* All remaining address spaces are mapped to device0.(n - 1) */
334                 if (port == 1 && core_id->num_admatch >= 2)
335                         return (core_id->num_admatch - 1);
336
337                 break;
338
339         case BHND_PORT_AGENT:
340                 /* All config blocks are mapped to a single port */
341                 if (port == 0)
342                         return (core_id->num_cfg_blocks);
343
344                 break;
345
346         default:
347                 break;
348         }
349
350         /* Validated above */
351         panic("siba_is_port_valid() returned true for unknown %s.%u port",
352             bhnd_port_type_name(port_type), port);
353
354 }
355
356 /**
357  * Map a bhnd(4) type/port/region triplet to its associated config block index,
358  * if any.
359  * 
360  * We map config registers to port/region identifiers as follows:
361  * 
362  *      [port].[region] [cfg register block]
363  *      agent0.0        0
364  *      agent0.1        1
365  * 
366  * @param port_type The bhnd(4) port type.
367  * @param port The bhnd(4) port number.
368  * @param region The bhnd(4) port region.
369  * @param addridx On success, the corresponding addrspace index.
370  * 
371  * @retval 0 success
372  * @retval ENOENT if the given type/port/region cannot be mapped to a
373  * siba config register block.
374  */
375 int
376 siba_cfg_index(struct siba_core_id *core_id, bhnd_port_type port_type,
377     u_int port, u_int region, u_int *cfgidx)
378 {
379         /* Config blocks are mapped to agent ports */
380         if (port_type != BHND_PORT_AGENT)
381                 return (ENOENT);
382
383         /* Port must be valid */
384         if (!siba_is_port_valid(core_id, port_type, port))
385                 return (ENOENT);
386
387         if (region >= core_id->num_cfg_blocks)
388                 return (ENOENT);
389
390         if (region >= SIBA_MAX_CFG)
391                 return (ENOENT);
392
393         /* Found */
394         *cfgidx = region;
395         return (0);
396 }
397
398 /**
399  * Map an bhnd(4) type/port/region triplet to its associated config block
400  * entry, if any.
401  *
402  * The only supported port type is BHND_PORT_DEVICE.
403  * 
404  * @param dinfo The device info to search for a matching address space.
405  * @param type The bhnd(4) port type.
406  * @param port The bhnd(4) port number.
407  * @param region The bhnd(4) port region.
408  */
409 struct siba_cfg_block *
410 siba_find_cfg_block(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port,
411     u_int region)
412 {
413         u_int   cfgidx;
414         int     error;
415
416         /* Map to addrspace index */
417         error = siba_cfg_index(&dinfo->core_id, type, port, region, &cfgidx);
418         if (error)
419                 return (NULL);
420
421         /* Found */
422         return (&dinfo->cfg[cfgidx]);
423 }
424
425 /**
426  * Map a bhnd(4) type/port/region triplet to its associated address space
427  * index, if any.
428  * 
429  * For compatibility with bcma(4), we map address spaces to port/region
430  * identifiers as follows:
431  * 
432  *      [port.region]   [admatch index]
433  *      device0.0       0
434  *      device1.0       1
435  *      device1.1       2
436  *      device1.2       3
437  * 
438  * @param core_id The siba core info.
439  * @param port_type The bhnd(4) port type.
440  * @param port The bhnd(4) port number.
441  * @param region The bhnd(4) port region.
442  * @param addridx On success, the corresponding addrspace index.
443  * 
444  * @retval 0 success
445  * @retval ENOENT if the given type/port/region cannot be mapped to a
446  * siba address space.
447  */
448 int
449 siba_addrspace_index(struct siba_core_id *core_id, bhnd_port_type port_type,
450     u_int port, u_int region, u_int *addridx)
451 {
452         u_int idx;
453
454         /* Address spaces are always device ports */
455         if (port_type != BHND_PORT_DEVICE)
456                 return (ENOENT);
457
458         /* Port must be valid */
459         if (!siba_is_port_valid(core_id, port_type, port))
460                 return (ENOENT);
461         
462         if (port == 0)
463                 idx = region;
464         else if (port == 1)
465                 idx = region + 1;
466         else
467                 return (ENOENT);
468
469         if (idx >= core_id->num_admatch)
470                 return (ENOENT);
471
472         /* Found */
473         *addridx = idx;
474         return (0);
475 }
476
477 /**
478  * Map an bhnd(4) type/port/region triplet to its associated address space
479  * entry, if any.
480  *
481  * The only supported port type is BHND_PORT_DEVICE.
482  * 
483  * @param dinfo The device info to search for a matching address space.
484  * @param type The bhnd(4) port type.
485  * @param port The bhnd(4) port number.
486  * @param region The bhnd(4) port region.
487  */
488 struct siba_addrspace *
489 siba_find_addrspace(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port,
490     u_int region)
491 {
492         u_int   addridx;
493         int     error;
494
495         /* Map to addrspace index */
496         error = siba_addrspace_index(&dinfo->core_id, type, port, region,
497             &addridx);
498         if (error)
499                 return (NULL);
500
501         /* Found */
502         if (addridx >= SIBA_MAX_ADDRSPACE)
503                 return (NULL);
504
505         return (&dinfo->addrspace[addridx]);
506 }
507
508 /**
509  * Append an address space entry to @p dinfo.
510  * 
511  * @param dinfo The device info entry to update.
512  * @param addridx The address space index.
513  * @param base The mapping's base address.
514  * @param size The mapping size.
515  * @param bus_reserved Number of bytes to reserve in @p size for bus use
516  * when registering the resource list entry. This is used to reserve bus
517  * access to the core's SIBA_CFG* register blocks.
518  * 
519  * @retval 0 success
520  * @retval non-zero An error occurred appending the entry.
521  */
522 static int
523 siba_append_dinfo_region(struct siba_devinfo *dinfo, uint8_t addridx,
524     uint32_t base, uint32_t size, uint32_t bus_reserved)
525 {
526         struct siba_addrspace   *sa;
527         rman_res_t               r_size;
528
529         /* Verify that base + size will not overflow */
530         if (size > 0 && UINT32_MAX - (size - 1) < base)
531                 return (ERANGE);
532
533         /* Verify that size - bus_reserved will not underflow */
534         if (size < bus_reserved)
535                 return (ERANGE);
536
537         /* Must not be 0-length */
538         if (size == 0)
539                 return (EINVAL);
540
541         /* Must not exceed addrspace array size */
542         if (addridx >= nitems(dinfo->addrspace))
543                 return (EINVAL);
544
545         /* Initialize new addrspace entry */
546         sa = &dinfo->addrspace[addridx];
547         sa->sa_base = base;
548         sa->sa_size = size;
549         sa->sa_bus_reserved = bus_reserved;
550
551         /* Populate the resource list */
552         r_size = size - bus_reserved;
553         sa->sa_rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY,
554             base, base + (r_size - 1), r_size);
555
556         return (0);
557 }
558
559 /**
560  * Deallocate the given device info structure and any associated resources.
561  * 
562  * @param dev The requesting bus device.
563  * @param child The siba child device.
564  * @param dinfo Device info associated with @p child to be deallocated.
565  */
566 void
567 siba_free_dinfo(device_t dev, device_t child, struct siba_devinfo *dinfo)
568 {
569         resource_list_free(&dinfo->resources);
570
571         /* Free all mapped configuration blocks */
572         for (u_int i = 0; i < nitems(dinfo->cfg); i++) {
573                 if (dinfo->cfg_res[i] == NULL)
574                         continue;
575
576                 bhnd_release_resource(dev, SYS_RES_MEMORY, dinfo->cfg_rid[i],
577                     dinfo->cfg_res[i]);
578
579                 dinfo->cfg_res[i] = NULL;
580                 dinfo->cfg_rid[i] = -1;
581         }
582
583         /* Unmap the core's interrupt */
584         if (dinfo->core_id.intr_en && dinfo->intr.mapped) {
585                 BHND_BUS_UNMAP_INTR(dev, child, dinfo->intr.irq);
586                 dinfo->intr.mapped = false;
587         }
588
589         free(dinfo, M_BHND);
590 }
591
592 /**
593  * Return the core-enumeration-relative offset for the @p addrspace
594  * SIBA_R0_ADMATCH* register.
595  * 
596  * @param addrspace The address space index.
597  * 
598  * @retval non-zero success
599  * @retval 0 the given @p addrspace index is not supported.
600  */
601 u_int
602 siba_admatch_offset(uint8_t addrspace)
603 {
604         switch (addrspace) {
605         case 0:
606                 return SB0_REG_ABS(SIBA_CFG0_ADMATCH0);
607         case 1:
608                 return SB0_REG_ABS(SIBA_CFG0_ADMATCH1);
609         case 2:
610                 return SB0_REG_ABS(SIBA_CFG0_ADMATCH2);
611         case 3:
612                 return SB0_REG_ABS(SIBA_CFG0_ADMATCH3);
613         default:
614                 return (0);
615         }
616 }
617
618 /**
619  * Parse a SIBA_R0_ADMATCH* register.
620  * 
621  * @param addrspace The address space index.
622  * @param am The address match register value to be parsed.
623  * @param[out] admatch The parsed address match descriptor
624  * 
625  * @retval 0 success
626  * @retval non-zero a parse error occurred.
627  */
628 int
629 siba_parse_admatch(uint32_t am, struct siba_admatch *admatch)
630 {
631         u_int am_type;
632         
633         /* Extract the base address and size */
634         am_type = SIBA_REG_GET(am, AM_TYPE);
635         switch (am_type) {
636         case 0:
637                 /* Type 0 entries are always enabled, and do not support
638                  * negative matching */
639                 admatch->am_base = am & SIBA_AM_BASE0_MASK;
640                 admatch->am_size = 1 << (SIBA_REG_GET(am, AM_ADINT0) + 1);
641                 admatch->am_enabled = true;
642                 admatch->am_negative = false;
643                 break;
644         case 1:
645                 admatch->am_base = am & SIBA_AM_BASE1_MASK;
646                 admatch->am_size = 1 << (SIBA_REG_GET(am, AM_ADINT1) + 1);
647                 admatch->am_enabled = ((am & SIBA_AM_ADEN) != 0);
648                 admatch->am_negative = ((am & SIBA_AM_ADNEG) != 0);
649                 break;
650         case 2:
651                 admatch->am_base = am & SIBA_AM_BASE2_MASK;
652                 admatch->am_size = 1 << (SIBA_REG_GET(am, AM_ADINT2) + 1);
653                 admatch->am_enabled = ((am & SIBA_AM_ADEN) != 0);
654                 admatch->am_negative = ((am & SIBA_AM_ADNEG) != 0);
655                 break;
656         default:
657                 return (EINVAL);
658         }
659
660         return (0);
661 }
662
663 /**
664  * Write @p value to @p dev's CFG0 target/initiator state register, performing
665  * required read-back and waiting for completion.
666  * 
667  * @param dev The siba(4) child device.
668  * @param reg The CFG0 state register to write (e.g. SIBA_CFG0_TMSTATELOW,
669  * SIBA_CFG0_IMSTATE)
670  * @param value The value to write to @p reg.
671  * @param mask The mask of bits to be included from @p value.
672  */
673 void
674 siba_write_target_state(device_t dev, struct siba_devinfo *dinfo,
675     bus_size_t reg, uint32_t value, uint32_t mask)
676 {
677         struct bhnd_resource    *r;
678         uint32_t                 rval;
679
680         r = dinfo->cfg_res[0];
681
682         KASSERT(r != NULL, ("%s missing CFG0 mapping",
683             device_get_nameunit(dev)));
684         KASSERT(reg <= SIBA_CFG_SIZE-4, ("%s invalid CFG0 register offset %#jx",
685             device_get_nameunit(dev), (uintmax_t)reg));
686
687         rval = bhnd_bus_read_4(r, reg);
688         rval &= ~mask;
689         rval |= (value & mask);
690
691         bhnd_bus_write_4(r, reg, rval);
692         bhnd_bus_read_4(r, reg); /* read-back */
693         DELAY(1);
694 }
695
696 /**
697  * Spin for up to @p usec waiting for @p dev's CFG0 target/initiator state
698  * register value to be equal to @p value after applying @p mask bits to both
699  * values.
700  * 
701  * @param dev The siba(4) child device to wait on.
702  * @param dinfo The @p dev's device info
703  * @param reg The state register to read (e.g. SIBA_CFG0_TMSTATEHIGH,
704  * SIBA_CFG0_IMSTATE)
705  * @param value The value against which @p reg will be compared.
706  * @param mask The mask to be applied when comparing @p value with @p reg.
707  * @param usec The maximum number of microseconds to wait for completion.
708  * 
709  * @retval 0 if SIBA_TMH_BUSY is cleared prior to the @p usec timeout.
710  * @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo.
711  * @retval ETIMEDOUT if a timeout occurs.
712  */
713 int
714 siba_wait_target_state(device_t dev, struct siba_devinfo *dinfo, bus_size_t reg,
715     uint32_t value, uint32_t mask, u_int usec)
716 {
717         struct bhnd_resource    *r;
718         uint32_t                 rval;
719
720         if ((r = dinfo->cfg_res[0]) == NULL)
721                 return (ENODEV);
722
723         value &= mask;
724         for (int i = 0; i < usec; i += 10) {
725                 rval = bhnd_bus_read_4(r, reg);
726                 if ((rval & mask) == value)
727                         return (0);
728
729                 DELAY(10);
730         }
731
732         return (ETIMEDOUT);
733 }