xref: /freebsd/usr.bin/sdiotool/sdiotool.c (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
1 /*-
2  * Copyright (c) 2016-2017 Ilya Bakulin
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * Copyright (c) 2010 Broadcom Corporation
27  *
28  * Permission to use, copy, modify, and/or distribute this software for any
29  * purpose with or without fee is hereby granted, provided that the above
30  * copyright notice and this permission notice appear in all copies.
31  *
32  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
33  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
34  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
35  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
36  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
37  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
38  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
39  */
40 
41 #include <sys/cdefs.h>
42 __FBSDID("$FreeBSD$");
43 
44 #include <sys/ioctl.h>
45 #include <sys/stdint.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/endian.h>
49 #include <sys/sbuf.h>
50 #include <sys/mman.h>
51 
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56 #include <inttypes.h>
57 #include <limits.h>
58 #include <fcntl.h>
59 #include <ctype.h>
60 #include <err.h>
61 #include <libutil.h>
62 #include <unistd.h>
63 
64 #include <cam/cam.h>
65 #include <cam/cam_debug.h>
66 #include <cam/cam_ccb.h>
67 #include <cam/mmc/mmc_all.h>
68 #include <camlib.h>
69 
70 #include "linux_compat.h"
71 #include "linux_sdio_compat.h"
72 #include "cam_sdio.h"
73 #include "brcmfmac_sdio.h"
74 #include "brcmfmac_bus.h"
75 
76 static void probe_bcrm(struct cam_device *dev);
77 
78 /*
79  * How Linux driver works
80  *
81  * The probing begins by calling brcmf_ops_sdio_probe() which is defined as probe function in struct sdio_driver. http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L1126
82  *
83  * The driver does black magic by copying func struct for F2 and setting func number to zero there, to create an F0 func structure :)
84  * Driver state changes to BRCMF_SDIOD_DOWN.
85  * ops_sdio_probe() then calls brcmf_sdio_probe() -- at this point it has filled in sdiodev struct with the pointers to all three functions (F0, F1, F2).
86  *
87  * brcmf_sdiod_probe() sets block sizes for F1 and F2. It sets F1 block size to 64 and F2 to 512, not consulting the values stored in SDIO CCCR  / FBR registers!
88  * Then it increases timeout for F2 (what is this?!)
89  * Then it enables F1
90  * Then it attaches "freezer" (without PM this is NOP)
91  * Finally it calls brcmf_sdio_probe() http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L4082
92  *
93  * Here high-level workqueues and sg tables are allocated.
94  * It then calls brcmf_sdio_probe_attach()
95  *
96  * Here at the beginning there is a pr_debug() call with brcmf_sdiod_regrl() inside to addr #define SI_ENUM_BASE            0x18000000.
97  * Return value is 0x16044330.
98  * Then turns off PLL:  byte-write BRCMF_INIT_CLKCTL1 (0x28) ->  SBSDIO_FUNC1_CHIPCLKCSR (0x1000E)
99  * Then it reads value back, should be 0xe8.
100  * Then calls brcmf_chip_attach()
101  *
102  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L1054
103  * This func enumerates and resets all the cores on the dongle.
104  *  - brcmf_sdio_buscoreprep(): force clock to ALPAvail req only:
105  *    SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ -> SBSDIO_FUNC1_CHIPCLKCSR
106  * Wait up to 15ms to !SBSDIO_ALPAV(clkval) of the value from CLKCSR.
107  * Force ALP:
108  *    SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP (0x21)-> SBSDIO_FUNC1_CHIPCLKCSR
109  * Disaable SDIO pullups:
110  * byte 0 -> SBSDIO_FUNC1_SDIOPULLUP (0x0001000f)
111  *
112  *  Calls brcmf_chip_recognition()
113  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L908
114  * Read 0x18000000. Get 0x16044330: chip 4330 rev 4
115  * AXI chip, call  brcmf_chip_dmp_erom_scan() to get info about all cores.
116  * Then  brcmf_chip_cores_check() to check that CPU and RAM are found,
117  *
118  * Setting cores to passive: not clear which of CR4/CA7/CM3 our chip has.
119  *  Quite a few r/w calls to different parts of the chip to reset cores....
120  * Finally get_raminfo() called to fill in RAM info:
121  * brcmf_chip_get_raminfo: RAM: base=0x0 size=294912 (0x48000) sr=0 (0x0)
122  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L700
123  *
124  * Then brcmf_chip_setup() is called, this prints and fills in chipcommon rev and PMU caps:
125  *   brcmf_chip_setup: ccrev=39, pmurev=12, pmucaps=0x19583c0c
126  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L1015
127  *  Bus-specific setup code is NOP for SDIO.
128  *
129  * brcmf_sdio_kso_init() is called.
130  * Here it first reads 0x1 from SBSDIO_FUNC1_SLEEPCSR 0x18000650 and then writes it back... WTF?
131  *
132  * brcmf_sdio_drivestrengthinit() is called
133  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L3630
134  *
135  * Set card control so an SDIO card reset does a WLAN backplane reset
136  * set PMUControl so a backplane reset does PMU state reload
137  * === end of brcmf_sdio_probe_attach ===
138 
139  **** Finished reading at http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L4152, line 2025 in the dump
140 
141  * === How register reading works ===
142  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L357
143  * The address to read from is written to three byte-sized registers of F1:
144  *  - SBSDIO_FUNC1_SBADDRLOW  0x1000A
145  *  - SBSDIO_FUNC1_SBADDRMID  0x1000B
146  *  - SBSDIO_FUNC1_SBADDRHIGH 0x1000C
147  * If this is 32-bit read , a flag is set. The address is ANDed with SBSDIO_SB_OFT_ADDR_MASK which is 0x07FFF.
148  * Then brcmf_sdiod_regrw_helper() is called to read the reply.
149  * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L306
150  * Based on the address it figures out where to read it from (CCCR / FBR in F0, or somewhere in F1).
151  * Reads are retried three times.
152  * 1-byte IO is done with CMD52, more is read with CMD53 with address increment (not FIFO mode).
153  * http://lxr.free-electrons.com/source/drivers/mmc/core/sdio_io.c#L458
154  * ==================================
155  *
156  *
157  */
158 
159 /* BRCM-specific functions */
160 #define SDIOH_API_ACCESS_RETRY_LIMIT	2
161 #define SI_ENUM_BASE            0x18000000
162 #define REPLY_MAGIC             0x16044330
163 #define brcmf_err(fmt, ...) brcmf_dbg(0, fmt, ##__VA_ARGS__)
164 #define brcmf_dbg(level, fmt, ...) printf(fmt, ##__VA_ARGS__)
165 
166 struct brcmf_sdio_dev {
167 	struct cam_device *cam_dev;
168 	u32 sbwad;			/* Save backplane window address */
169 	struct brcmf_bus *bus_if;
170 	enum brcmf_sdiod_state state;
171 	struct sdio_func *func[8];
172 };
173 
174 void brcmf_bus_change_state(struct brcmf_bus *bus, enum brcmf_bus_state state);
175 void brcmf_sdiod_change_state(struct brcmf_sdio_dev *sdiodev,
176 			      enum brcmf_sdiod_state state);
177 static int brcmf_sdiod_request_data(struct brcmf_sdio_dev *sdiodev, u8 fn, u32 addr,
178 				    u8 regsz, void *data, bool write);
179 static int brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address);
180 static int brcmf_sdiod_addrprep(struct brcmf_sdio_dev *sdiodev, uint width, u32 *addr);
181 u32 brcmf_sdiod_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret);
182 
183 static void bailout(int ret);
184 
185 static void
186 bailout(int ret) {
187 	if (ret == 0)
188 		return;
189 	errx(1, "Operation returned error %d", ret);
190 }
191 
192 void
193 brcmf_bus_change_state(struct brcmf_bus *bus, enum brcmf_bus_state state)
194 {
195 	bus->state = state;
196 }
197 
198 void brcmf_sdiod_change_state(struct brcmf_sdio_dev *sdiodev,
199 			      enum brcmf_sdiod_state state)
200 {
201 	if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM ||
202 	    state == sdiodev->state)
203 		return;
204 
205 	//brcmf_dbg(TRACE, "%d -> %d\n", sdiodev->state, state);
206 	switch (sdiodev->state) {
207 	case BRCMF_SDIOD_DATA:
208 		/* any other state means bus interface is down */
209 		brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_DOWN);
210 		break;
211 	case BRCMF_SDIOD_DOWN:
212 		/* transition from DOWN to DATA means bus interface is up */
213 		if (state == BRCMF_SDIOD_DATA)
214 			brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_UP);
215 		break;
216 	default:
217 		break;
218 	}
219 	sdiodev->state = state;
220 }
221 
222 static inline int brcmf_sdiod_f0_writeb(struct sdio_func *func,
223 					uint regaddr, u8 byte) {
224 	int err_ret;
225 
226 	/*
227 	 * Can only directly write to some F0 registers.
228 	 * Handle CCCR_IENx and CCCR_ABORT command
229 	 * as a special case.
230 	 */
231 	if ((regaddr == SDIO_CCCR_ABORT) ||
232 	    (regaddr == SDIO_CCCR_IENx))
233 		sdio_writeb(func, byte, regaddr, &err_ret);
234 	else
235 		sdio_f0_writeb(func, byte, regaddr, &err_ret);
236 
237 	return err_ret;
238 }
239 
240 static int brcmf_sdiod_request_data(struct brcmf_sdio_dev *sdiodev, u8 fn, u32 addr, u8 regsz, void *data, bool write)
241 {
242 	struct sdio_func *func;
243 	int ret = -EINVAL;
244 
245 	brcmf_dbg(SDIO, "rw=%d, func=%d, addr=0x%05x, nbytes=%d\n",
246 		  write, fn, addr, regsz);
247 
248 	/* only allow byte access on F0 */
249 	if (WARN_ON(regsz > 1 && !fn))
250 		return -EINVAL;
251 	func = sdiodev->func[fn];
252 
253 	switch (regsz) {
254 	case sizeof(u8):
255 		if (write) {
256 			if (fn)
257 				sdio_writeb(func, *(u8 *)data, addr, &ret);
258 			else
259 				ret = brcmf_sdiod_f0_writeb(func, addr,
260 							    *(u8 *)data);
261 		} else {
262 			if (fn)
263 				*(u8 *)data = sdio_readb(func, addr, &ret);
264 			else
265 				*(u8 *)data = sdio_f0_readb(func, addr, &ret);
266 		}
267 		break;
268 	case sizeof(u16):
269 		if (write)
270 			sdio_writew(func, *(u16 *)data, addr, &ret);
271 		else
272 			*(u16 *)data = sdio_readw(func, addr, &ret);
273 		break;
274 	case sizeof(u32):
275 		if (write)
276 			sdio_writel(func, *(u32 *)data, addr, &ret);
277 		else
278 			*(u32 *)data = sdio_readl(func, addr, &ret);
279 		break;
280 	default:
281 		brcmf_err("invalid size: %d\n", regsz);
282 		break;
283 	}
284 
285 	if (ret)
286 		brcmf_dbg(SDIO, "failed to %s data F%d@0x%05x, err: %d\n",
287 			  write ? "write" : "read", fn, addr, ret);
288 
289 	return ret;
290 }
291 
292 static int
293 brcmf_sdiod_addrprep(struct brcmf_sdio_dev *sdiodev, uint width, u32 *addr)
294 {
295 	uint bar0 = *addr & ~SBSDIO_SB_OFT_ADDR_MASK;
296 	int err = 0;
297 
298 	if (bar0 != sdiodev->sbwad) {
299 		err = brcmf_sdiod_set_sbaddr_window(sdiodev, bar0);
300 		if (err)
301 			return err;
302 
303 		sdiodev->sbwad = bar0;
304 	}
305 
306 	*addr &= SBSDIO_SB_OFT_ADDR_MASK;
307 
308 	if (width == 4)
309 		*addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
310 
311 	return 0;
312 }
313 
314 static int brcmf_sdiod_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr, u8 regsz, void *data, bool write) {
315 	u8 func;
316 	s32 retry = 0;
317 	int ret;
318 
319 	if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM)
320 		return -ENOMEDIUM;
321 
322 	/*
323 	 * figure out how to read the register based on address range
324 	 * 0x00 ~ 0x7FF: function 0 CCCR and FBR
325 	 * 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
326 	 * The rest: function 1 silicon backplane core registers
327 	 */
328 	if ((addr & ~REG_F0_REG_MASK) == 0)
329 		func = SDIO_FUNC_0;
330 	else
331 		func = SDIO_FUNC_1;
332 
333 	do {
334 		if (!write)
335 			memset(data, 0, regsz);
336 		/* for retry wait for 1 ms till bus get settled down */
337 		if (retry)
338 			usleep_range(1000, 2000);
339 		ret = brcmf_sdiod_request_data(sdiodev, func, addr, regsz,
340 					       data, write);
341 	} while (ret != 0 && ret != -ENOMEDIUM &&
342 		 retry++ < SDIOH_API_ACCESS_RETRY_LIMIT);
343 
344 	if (ret == -ENOMEDIUM)
345 		brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM);
346 	else if (ret != 0) {
347 		/*
348 		 * SleepCSR register access can fail when
349 		 * waking up the device so reduce this noise
350 		 * in the logs.
351 		 */
352 		if (addr != SBSDIO_FUNC1_SLEEPCSR)
353 			brcmf_err("failed to %s data F%d@0x%05x, err: %d\n",
354 				  write ? "write" : "read", func, addr, ret);
355 		else
356 			brcmf_dbg(SDIO, "failed to %s data F%d@0x%05x, err: %d\n",
357 				  write ? "write" : "read", func, addr, ret);
358 	}
359 	return ret;
360 }
361 
362 static int
363 brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address)
364 {
365 	int err = 0, i;
366 	u8 addr[3];
367 
368 	if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM)
369 		return -ENOMEDIUM;
370 
371 	addr[0] = (address >> 8) & SBSDIO_SBADDRLOW_MASK;
372 	addr[1] = (address >> 16) & SBSDIO_SBADDRMID_MASK;
373 	addr[2] = (address >> 24) & SBSDIO_SBADDRHIGH_MASK;
374 
375 	for (i = 0; i < 3; i++) {
376 		err = brcmf_sdiod_regrw_helper(sdiodev,
377 					       SBSDIO_FUNC1_SBADDRLOW + i,
378 					       sizeof(u8), &addr[i], true);
379 		if (err) {
380 			brcmf_err("failed at addr: 0x%0x\n",
381 				  SBSDIO_FUNC1_SBADDRLOW + i);
382 			break;
383 		}
384 	}
385 
386 	return err;
387 }
388 
389 u32 brcmf_sdiod_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret)
390 {
391 	u32 data = 0;
392 	int retval;
393 
394 	brcmf_dbg(SDIO, "addr:0x%08x\n", addr);
395 	retval = brcmf_sdiod_addrprep(sdiodev, sizeof(data), &addr);
396 	if (retval)
397 		goto done;
398 	retval = brcmf_sdiod_regrw_helper(sdiodev, addr, sizeof(data), &data,
399 					  false);
400 	brcmf_dbg(SDIO, "data:0x%08x\n", data);
401 
402 done:
403 	if (ret)
404 		*ret = retval;
405 
406 	return data;
407 }
408 
409 /********************************************************/
410 __unused
411 static void
412 probe_bcrm(struct cam_device *dev) {
413 	uint32_t cis_addr;
414 	struct cis_info info;
415 
416 	sdio_card_set_bus_width(dev, bus_width_4);
417 	cis_addr = sdio_get_common_cis_addr(dev);
418 	printf("CIS address: %04X\n", cis_addr);
419 
420 	memset(&info, 0, sizeof(info));
421 	sdio_func_read_cis(dev, 0, cis_addr, &info);
422 	printf("Vendor 0x%04X product 0x%04X\n", info.man_id, info.prod_id);
423 }
424 
425 __unused static uint8_t*
426 mmap_fw() {
427 	const char fw_path[] = "/home/kibab/repos/fbsd-bbb/brcm-firmware/brcmfmac4330-sdio.bin";
428 	struct stat sb;
429 	uint8_t *fw_ptr;
430 
431 	int fd = open(fw_path, O_RDONLY);
432 	if (fd < 0)
433 		errx(1, "Cannot open firmware file");
434 	if (fstat(fd, &sb) < 0)
435 		errx(1, "Cannot get file stat");
436 	fw_ptr = mmap(NULL, sb.st_size, PROT_READ, 0, fd, 0);
437 	if (fw_ptr == MAP_FAILED)
438 		errx(1, "Cannot map the file");
439 
440 	return fw_ptr;
441 }
442 
443 static void
444 usage() {
445 	printf("sdiotool -u <pass_dev_unit>\n");
446 	exit(0);
447 }
448 
449 struct card_info {
450 	uint8_t num_funcs;
451 	struct cis_info f[8];
452 };
453 
454 /*
455  * TODO: We should add SDIO card info about at least number of
456  * available functions to struct cam_device and use it instead
457  * of checking for man_id = 0x00 for detecting number of functions
458  */
459 static void
460 get_sdio_card_info(struct cam_device *dev, struct card_info *ci) {
461 	uint32_t cis_addr;
462 	uint32_t fbr_addr;
463 	int ret;
464 
465 	cis_addr = sdio_get_common_cis_addr(dev);
466 
467 	memset(ci, 0, sizeof(struct card_info));
468 	sdio_func_read_cis(dev, 0, cis_addr, &ci->f[0]);
469 	printf("F0: Vendor 0x%04X product 0x%04X max block size %d bytes\n",
470 	       ci->f[0].man_id, ci->f[0].prod_id, ci->f[0].max_block_size);
471 	for (int i = 1; i <= 7; i++) {
472 		fbr_addr = SD_IO_FBR_START * i + 0x9;
473 		cis_addr =  sdio_read_1(dev, 0, fbr_addr++, &ret);bailout(ret);
474 		cis_addr |= sdio_read_1(dev, 0, fbr_addr++, &ret) << 8;
475 		cis_addr |= sdio_read_1(dev, 0, fbr_addr++, &ret) << 16;
476 		sdio_func_read_cis(dev, i, cis_addr, &ci->f[i]);
477 		printf("F%d: Vendor 0x%04X product 0x%04X max block size %d bytes\n",
478 		       i, ci->f[i].man_id, ci->f[i].prod_id, ci->f[i].max_block_size);
479 		if (ci->f[i].man_id == 0) {
480 			printf("F%d doesn't exist\n", i);
481 			break;
482 		}
483 		ci->num_funcs++;
484 	}
485 }
486 
487 int
488 main(int argc, char **argv) {
489 	char device[] = "pass";
490 	int unit = 0;
491 	int func = 0;
492 	__unused uint8_t *fw_ptr;
493 	int ch;
494 	struct cam_device *cam_dev;
495 	int ret;
496 	struct card_info ci;
497 
498 	//fw_ptr = mmap_fw();
499 
500 	while ((ch = getopt(argc, argv, "fu:")) != -1) {
501 		switch (ch) {
502 		case 'u':
503 			unit = (int) strtol(optarg, NULL, 10);
504 			break;
505 		case 'f':
506 			func = (int) strtol(optarg, NULL, 10);
507 			break;
508 		case '?':
509 		default:
510 			usage();
511 		}
512 	}
513 	argc -= optind;
514 	argv += optind;
515 
516 	if ((cam_dev = cam_open_spec_device(device, unit, O_RDWR, NULL)) == NULL)
517 		errx(1, "Cannot open device");
518 
519 	get_sdio_card_info(cam_dev, &ci);
520 
521 	/* For now, everything non-broadcom is out of the question */
522 	if (ci.f[0].man_id != 0x02D0) {
523 		printf("The card is not a Broadcom device\n");
524 		exit(1);
525 	}
526 	/* Init structures */
527 	struct brcmf_sdio_dev brcmf_dev;
528 	struct brcmf_bus bus_if;
529 	struct sdio_func f0, f1, f2;
530 	bus_if.state = BRCMF_BUS_DOWN;
531 	brcmf_dev.cam_dev = cam_dev;
532 	brcmf_dev.bus_if = &bus_if;
533 	brcmf_dev.state = BRCMF_SDIOD_DOWN;
534 
535 	/* Fill in functions */
536 	brcmf_dev.func[0] = &f0;
537 	brcmf_dev.func[1] = &f1;
538 	brcmf_dev.func[2] = &f2;
539 
540 	brcmf_dev.func[0]->dev = brcmf_dev.func[1]->dev
541 		= brcmf_dev.func[2]->dev = cam_dev;
542 	brcmf_dev.func[0]->num = 0;
543 	brcmf_dev.func[1]->num = 1;
544 	brcmf_dev.func[2]->num = 2;
545 
546 	ret = sdio_func_enable(cam_dev, 1, 1);bailout(ret);
547 	uint32_t magic = brcmf_sdiod_regrl(&brcmf_dev, 0x18000000, &ret);
548 	printf("Magic = %08x\n", magic);
549 	if (magic != REPLY_MAGIC) {
550 		errx(1, "Reply magic is incorrect: expected %08x, got %08x",
551 		     REPLY_MAGIC, magic);
552 	}
553 	cam_close_spec_device(cam_dev);
554 }
555