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