aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net
diff options
context:
space:
mode:
authorFranky Lin <frankyl@broadcom.com>2012-04-27 21:56:59 -0400
committerJohn W. Linville <linville@tuxdriver.com>2012-05-08 21:53:55 -0400
commitba89bf1961bb991a5c6415bd8408a8cb61ee46dc (patch)
tree073ad2e1ae876eb9a37eb20d649bd02d3d0310f1 /drivers/net
parente2f93cc3218853a3c00bd7c9f923bec65aaf9103 (diff)
brcmfmac: add out of band interrupt support
Some sdio host controllers do not support real in band interrupt. Software polling mode as a replacement is not fast enough for high throughput and new features. Also some in band interrupts do not support host wake up on embedded platform even when they are real physical interrupts. Therefore out of band (oob) interrupt mechanism is implemented for these scenarios. To provide oob irq number and flags used for irq registration in brcmfmac, a platform device contains irq resource must be registered in board specific code. Here is an example of platform device structure: struct resource brcmf_sdio_res[] = { { .start = GPIO_BRCMF_SDIO_OOB_NUM, .end = GPIO_BRCMF_SDIO_OOB_NUM, .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL, } }; struct platform_device brcmf_sdio_device = { .name = "brcmf_sdio_pd", .id = -1, .num_resources = ARRAY_SIZE(brcmf_sdio_res), .resource = brcmf_sdio_res, }; Reviewed-by: pieter-paul giesberts <pieterpg@broadcom.com> Reviewed-by: arend van spriel <arend@broadcom.com> Signed-off-by: franky lin <frankyl@broadcom.com> Signed-off-by: Franky Lin <frankyl@broadcom.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/wireless/brcm80211/Kconfig9
-rw-r--r--drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c95
-rw-r--r--drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c105
-rw-r--r--drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c24
-rw-r--r--drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h22
5 files changed, 240 insertions, 15 deletions
diff --git a/drivers/net/wireless/brcm80211/Kconfig b/drivers/net/wireless/brcm80211/Kconfig
index c5104533e24e..b480088b3dbe 100644
--- a/drivers/net/wireless/brcm80211/Kconfig
+++ b/drivers/net/wireless/brcm80211/Kconfig
@@ -36,6 +36,15 @@ config BRCMFMAC_SDIO
36 IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to 36 IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to
37 use the driver for a SDIO wireless card. 37 use the driver for a SDIO wireless card.
38 38
39config BRCMFMAC_SDIO_OOB
40 bool "Out of band interrupt support for SDIO interface chipset"
41 depends on BRCMFMAC_SDIO
42 ---help---
43 This option enables out-of-band interrupt support for Broadcom
44 SDIO Wifi chipset using fullmac in order to gain better
45 performance and deep sleep wake up capability on certain
46 platforms. Say N if you are unsure.
47
39config BRCMFMAC_USB 48config BRCMFMAC_USB
40 bool "USB bus interface support for FullMAC driver" 49 bool "USB bus interface support for FullMAC driver"
41 depends on USB 50 depends on USB
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c
index ea350200a7db..4add7da24681 100644
--- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c
+++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c
@@ -39,33 +39,113 @@
39 39
40#define SDIOH_API_ACCESS_RETRY_LIMIT 2 40#define SDIOH_API_ACCESS_RETRY_LIMIT 2
41 41
42static void brcmf_sdioh_irqhandler(struct sdio_func *func) 42#ifdef CONFIG_BRCMFMAC_SDIO_OOB
43static irqreturn_t brcmf_sdio_irqhandler(int irq, void *dev_id)
44{
45 struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(dev_id);
46
47 brcmf_dbg(INTR, "oob intr triggered\n");
48
49 /*
50 * out-of-band interrupt is level-triggered which won't
51 * be cleared until dpc
52 */
53 if (sdiodev->irq_en) {
54 disable_irq_nosync(irq);
55 sdiodev->irq_en = false;
56 }
57
58 brcmf_sdbrcm_isr(sdiodev->bus);
59
60 return IRQ_HANDLED;
61}
62
63int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev)
64{
65 int ret = 0;
66 u8 data;
67 unsigned long flags;
68
69 brcmf_dbg(TRACE, "Entering\n");
70
71 brcmf_dbg(ERROR, "requesting irq %d\n", sdiodev->irq);
72 ret = request_irq(sdiodev->irq, brcmf_sdio_irqhandler,
73 sdiodev->irq_flags, "brcmf_oob_intr",
74 &sdiodev->func[1]->card->dev);
75 if (ret != 0)
76 return ret;
77 spin_lock_init(&sdiodev->irq_en_lock);
78 spin_lock_irqsave(&sdiodev->irq_en_lock, flags);
79 sdiodev->irq_en = true;
80 spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags);
81
82 ret = enable_irq_wake(sdiodev->irq);
83 if (ret != 0)
84 return ret;
85 sdiodev->irq_wake = true;
86
87 /* must configure SDIO_CCCR_IENx to enable irq */
88 data = brcmf_sdcard_cfg_read(sdiodev, SDIO_FUNC_0,
89 SDIO_CCCR_IENx, &ret);
90 data |= 1 << SDIO_FUNC_1 | 1 << SDIO_FUNC_2 | 1;
91 brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_IENx,
92 data, &ret);
93
94 /* redirect, configure ane enable io for interrupt signal */
95 data = SDIO_SEPINT_MASK | SDIO_SEPINT_OE;
96 if (sdiodev->irq_flags | IRQF_TRIGGER_HIGH)
97 data |= SDIO_SEPINT_ACT_HI;
98 brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_BRCM_SEPINT,
99 data, &ret);
100
101 return 0;
102}
103
104int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev)
105{
106 brcmf_dbg(TRACE, "Entering\n");
107
108 brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_BRCM_SEPINT,
109 0, NULL);
110 brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_IENx, 0, NULL);
111
112 if (sdiodev->irq_wake) {
113 disable_irq_wake(sdiodev->irq);
114 sdiodev->irq_wake = false;
115 }
116 free_irq(sdiodev->irq, &sdiodev->func[1]->card->dev);
117 sdiodev->irq_en = false;
118
119 return 0;
120}
121#else /* CONFIG_BRCMFMAC_SDIO_OOB */
122static void brcmf_sdio_irqhandler(struct sdio_func *func)
43{ 123{
44 struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(&func->card->dev); 124 struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(&func->card->dev);
45 125
46 brcmf_dbg(TRACE, "***IRQHandler\n"); 126 brcmf_dbg(INTR, "ib intr triggered\n");
47 127
48 brcmf_sdbrcm_isr(sdiodev->bus); 128 brcmf_sdbrcm_isr(sdiodev->bus);
49} 129}
50 130
51/* dummy handler for SDIO function 2 interrupt */ 131/* dummy handler for SDIO function 2 interrupt */
52static void brcmf_sdioh_dummy_irq_handler(struct sdio_func *func) 132static void brcmf_sdio_dummy_irqhandler(struct sdio_func *func)
53{ 133{
54} 134}
55 135
56int brcmf_sdcard_intr_reg(struct brcmf_sdio_dev *sdiodev) 136int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev)
57{ 137{
58 brcmf_dbg(TRACE, "Entering\n"); 138 brcmf_dbg(TRACE, "Entering\n");
59 139
60 sdio_claim_host(sdiodev->func[1]); 140 sdio_claim_host(sdiodev->func[1]);
61 sdio_claim_irq(sdiodev->func[1], brcmf_sdioh_irqhandler); 141 sdio_claim_irq(sdiodev->func[1], brcmf_sdio_irqhandler);
62 sdio_claim_irq(sdiodev->func[2], brcmf_sdioh_dummy_irq_handler); 142 sdio_claim_irq(sdiodev->func[2], brcmf_sdio_dummy_irqhandler);
63 sdio_release_host(sdiodev->func[1]); 143 sdio_release_host(sdiodev->func[1]);
64 144
65 return 0; 145 return 0;
66} 146}
67 147
68int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev) 148int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev)
69{ 149{
70 brcmf_dbg(TRACE, "Entering\n"); 150 brcmf_dbg(TRACE, "Entering\n");
71 151
@@ -76,6 +156,7 @@ int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev)
76 156
77 return 0; 157 return 0;
78} 158}
159#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
79 160
80u8 brcmf_sdcard_cfg_read(struct brcmf_sdio_dev *sdiodev, uint fnc_num, u32 addr, 161u8 brcmf_sdcard_cfg_read(struct brcmf_sdio_dev *sdiodev, uint fnc_num, u32 addr,
81 int *err) 162 int *err)
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c
index 758c115b556e..dd07d33a927c 100644
--- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c
+++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c
@@ -27,6 +27,7 @@
27#include <linux/errno.h> 27#include <linux/errno.h>
28#include <linux/sched.h> /* request_irq() */ 28#include <linux/sched.h> /* request_irq() */
29#include <linux/module.h> 29#include <linux/module.h>
30#include <linux/platform_device.h>
30#include <net/cfg80211.h> 31#include <net/cfg80211.h>
31 32
32#include <defs.h> 33#include <defs.h>
@@ -55,6 +56,15 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = {
55}; 56};
56MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); 57MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids);
57 58
59#ifdef CONFIG_BRCMFMAC_SDIO_OOB
60static struct list_head oobirq_lh;
61struct brcmf_sdio_oobirq {
62 unsigned int irq;
63 unsigned long flags;
64 struct list_head list;
65};
66#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
67
58static bool 68static bool
59brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev) 69brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev)
60{ 70{
@@ -107,7 +117,8 @@ static inline int brcmf_sdioh_f0_write_byte(struct brcmf_sdio_dev *sdiodev,
107 } 117 }
108 sdio_release_host(sdfunc); 118 sdio_release_host(sdfunc);
109 } 119 }
110 } else if (regaddr == SDIO_CCCR_ABORT) { 120 } else if ((regaddr == SDIO_CCCR_ABORT) ||
121 (regaddr == SDIO_CCCR_IENx)) {
111 sdfunc = kmemdup(sdiodev->func[0], sizeof(struct sdio_func), 122 sdfunc = kmemdup(sdiodev->func[0], sizeof(struct sdio_func),
112 GFP_KERNEL); 123 GFP_KERNEL);
113 if (!sdfunc) 124 if (!sdfunc)
@@ -467,12 +478,40 @@ void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev)
467 478
468} 479}
469 480
481#ifdef CONFIG_BRCMFMAC_SDIO_OOB
482static int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev)
483{
484 struct brcmf_sdio_oobirq *oobirq_entry;
485
486 if (list_empty(&oobirq_lh)) {
487 brcmf_dbg(ERROR, "no valid oob irq resource\n");
488 return -ENXIO;
489 }
490
491 oobirq_entry = list_first_entry(&oobirq_lh, struct brcmf_sdio_oobirq,
492 list);
493
494 sdiodev->irq = oobirq_entry->irq;
495 sdiodev->irq_flags = oobirq_entry->flags;
496 list_del(&oobirq_entry->list);
497 kfree(oobirq_entry);
498
499 return 0;
500}
501#else
502static inline int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev)
503{
504 return 0;
505}
506#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
507
470static int brcmf_ops_sdio_probe(struct sdio_func *func, 508static int brcmf_ops_sdio_probe(struct sdio_func *func,
471 const struct sdio_device_id *id) 509 const struct sdio_device_id *id)
472{ 510{
473 int ret = 0; 511 int ret = 0;
474 struct brcmf_sdio_dev *sdiodev; 512 struct brcmf_sdio_dev *sdiodev;
475 struct brcmf_bus *bus_if; 513 struct brcmf_bus *bus_if;
514
476 brcmf_dbg(TRACE, "Enter\n"); 515 brcmf_dbg(TRACE, "Enter\n");
477 brcmf_dbg(TRACE, "func->class=%x\n", func->class); 516 brcmf_dbg(TRACE, "func->class=%x\n", func->class);
478 brcmf_dbg(TRACE, "sdio_vendor: 0x%04x\n", func->vendor); 517 brcmf_dbg(TRACE, "sdio_vendor: 0x%04x\n", func->vendor);
@@ -511,6 +550,10 @@ static int brcmf_ops_sdio_probe(struct sdio_func *func,
511 sdiodev = dev_get_drvdata(&func->card->dev); 550 sdiodev = dev_get_drvdata(&func->card->dev);
512 if ((!sdiodev) || (sdiodev->func[1]->card != func->card)) 551 if ((!sdiodev) || (sdiodev->func[1]->card != func->card))
513 return -ENODEV; 552 return -ENODEV;
553
554 ret = brcmf_sdio_getintrcfg(sdiodev);
555 if (ret)
556 return ret;
514 sdiodev->func[2] = func; 557 sdiodev->func[2] = func;
515 558
516 bus_if = sdiodev->bus_if; 559 bus_if = sdiodev->bus_if;
@@ -603,6 +646,65 @@ static struct sdio_driver brcmf_sdmmc_driver = {
603#endif /* CONFIG_PM_SLEEP */ 646#endif /* CONFIG_PM_SLEEP */
604}; 647};
605 648
649#ifdef CONFIG_BRCMFMAC_SDIO_OOB
650static int brcmf_sdio_pd_probe(struct platform_device *pdev)
651{
652 struct resource *res;
653 struct brcmf_sdio_oobirq *oobirq_entry;
654 int i, ret;
655
656 INIT_LIST_HEAD(&oobirq_lh);
657
658 for (i = 0; ; i++) {
659 res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
660 if (!res)
661 break;
662
663 oobirq_entry = kzalloc(sizeof(struct brcmf_sdio_oobirq),
664 GFP_KERNEL);
665 oobirq_entry->irq = res->start;
666 oobirq_entry->flags = res->flags & IRQF_TRIGGER_MASK;
667 list_add_tail(&oobirq_entry->list, &oobirq_lh);
668 }
669 if (i == 0)
670 return -ENXIO;
671
672 ret = sdio_register_driver(&brcmf_sdmmc_driver);
673
674 if (ret)
675 brcmf_dbg(ERROR, "sdio_register_driver failed: %d\n", ret);
676
677 return ret;
678}
679
680static struct platform_driver brcmf_sdio_pd = {
681 .probe = brcmf_sdio_pd_probe,
682 .driver = {
683 .name = "brcmf_sdio_pd"
684 }
685};
686
687void brcmf_sdio_exit(void)
688{
689 brcmf_dbg(TRACE, "Enter\n");
690
691 sdio_unregister_driver(&brcmf_sdmmc_driver);
692
693 platform_driver_unregister(&brcmf_sdio_pd);
694}
695
696void brcmf_sdio_init(void)
697{
698 int ret;
699
700 brcmf_dbg(TRACE, "Enter\n");
701
702 ret = platform_driver_register(&brcmf_sdio_pd);
703
704 if (ret)
705 brcmf_dbg(ERROR, "platform_driver_register failed: %d\n", ret);
706}
707#else
606void brcmf_sdio_exit(void) 708void brcmf_sdio_exit(void)
607{ 709{
608 brcmf_dbg(TRACE, "Enter\n"); 710 brcmf_dbg(TRACE, "Enter\n");
@@ -621,3 +723,4 @@ void brcmf_sdio_init(void)
621 if (ret) 723 if (ret)
622 brcmf_dbg(ERROR, "sdio_register_driver failed: %d\n", ret); 724 brcmf_dbg(ERROR, "sdio_register_driver failed: %d\n", ret);
623} 725}
726#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c
index e3b1c32c83bb..149ee67beb2e 100644
--- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c
+++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c
@@ -2352,6 +2352,24 @@ static void brcmf_sdbrcm_bus_stop(struct device *dev)
2352 up(&bus->sdsem); 2352 up(&bus->sdsem);
2353} 2353}
2354 2354
2355#ifdef CONFIG_BRCMFMAC_SDIO_OOB
2356static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus)
2357{
2358 unsigned long flags;
2359
2360 spin_lock_irqsave(&bus->sdiodev->irq_en_lock, flags);
2361 if (!bus->sdiodev->irq_en && !bus->ipend) {
2362 enable_irq(bus->sdiodev->irq);
2363 bus->sdiodev->irq_en = true;
2364 }
2365 spin_unlock_irqrestore(&bus->sdiodev->irq_en_lock, flags);
2366}
2367#else
2368static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus)
2369{
2370}
2371#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
2372
2355static bool brcmf_sdbrcm_dpc(struct brcmf_sdio *bus) 2373static bool brcmf_sdbrcm_dpc(struct brcmf_sdio *bus)
2356{ 2374{
2357 u32 intstatus, newstatus = 0; 2375 u32 intstatus, newstatus = 0;
@@ -2509,6 +2527,8 @@ static bool brcmf_sdbrcm_dpc(struct brcmf_sdio *bus)
2509 bus->intstatus = intstatus; 2527 bus->intstatus = intstatus;
2510 2528
2511clkwait: 2529clkwait:
2530 brcmf_sdbrcm_clrintr(bus);
2531
2512 if (data_ok(bus) && bus->ctrl_frame_stat && 2532 if (data_ok(bus) && bus->ctrl_frame_stat &&
2513 (bus->clkstate == CLK_AVAIL)) { 2533 (bus->clkstate == CLK_AVAIL)) {
2514 int ret, i; 2534 int ret, i;
@@ -3509,7 +3529,7 @@ static int brcmf_sdbrcm_bus_init(struct device *dev)
3509 SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err); 3529 SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err);
3510 3530
3511 if (ret == 0) { 3531 if (ret == 0) {
3512 ret = brcmf_sdcard_intr_reg(bus->sdiodev); 3532 ret = brcmf_sdio_intr_register(bus->sdiodev);
3513 if (ret != 0) 3533 if (ret != 0)
3514 brcmf_dbg(ERROR, "intr register failed:%d\n", ret); 3534 brcmf_dbg(ERROR, "intr register failed:%d\n", ret);
3515 } 3535 }
@@ -3873,7 +3893,7 @@ static void brcmf_sdbrcm_release(struct brcmf_sdio *bus)
3873 3893
3874 if (bus) { 3894 if (bus) {
3875 /* De-register interrupt handler */ 3895 /* De-register interrupt handler */
3876 brcmf_sdcard_intr_dereg(bus->sdiodev); 3896 brcmf_sdio_intr_unregister(bus->sdiodev);
3877 3897
3878 if (bus->sdiodev->bus_if->drvr) { 3898 if (bus->sdiodev->bus_if->drvr) {
3879 brcmf_detach(bus->sdiodev->dev); 3899 brcmf_detach(bus->sdiodev->dev);
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h
index 0281d207d998..7010eaf71f99 100644
--- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h
+++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h
@@ -43,6 +43,13 @@
43/* as of sdiod rev 0, supports 3 functions */ 43/* as of sdiod rev 0, supports 3 functions */
44#define SBSDIO_NUM_FUNCTION 3 44#define SBSDIO_NUM_FUNCTION 3
45 45
46/* function 0 vendor specific CCCR registers */
47#define SDIO_CCCR_BRCM_SEPINT 0xf2
48
49#define SDIO_SEPINT_MASK 0x01
50#define SDIO_SEPINT_OE 0x02
51#define SDIO_SEPINT_ACT_HI 0x04
52
46/* function 1 miscellaneous registers */ 53/* function 1 miscellaneous registers */
47 54
48/* sprom command and status */ 55/* sprom command and status */
@@ -144,13 +151,18 @@ struct brcmf_sdio_dev {
144 wait_queue_head_t request_buffer_wait; 151 wait_queue_head_t request_buffer_wait;
145 struct device *dev; 152 struct device *dev;
146 struct brcmf_bus *bus_if; 153 struct brcmf_bus *bus_if;
154#ifdef CONFIG_BRCMFMAC_SDIO_OOB
155 unsigned int irq; /* oob interrupt number */
156 unsigned long irq_flags; /* board specific oob flags */
157 bool irq_en; /* irq enable flags */
158 spinlock_t irq_en_lock;
159 bool irq_wake; /* irq wake enable flags */
160#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
147}; 161};
148 162
149/* Register/deregister device interrupt handler. */ 163/* Register/deregister interrupt handler. */
150extern int 164extern int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev);
151brcmf_sdcard_intr_reg(struct brcmf_sdio_dev *sdiodev); 165extern int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev);
152
153extern int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev);
154 166
155/* Access SDIO address space (e.g. CCCR) using CMD52 (single-byte interface). 167/* Access SDIO address space (e.g. CCCR) using CMD52 (single-byte interface).
156 * fn: function number 168 * fn: function number