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