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