Commit ba89bf19 authored by Franky Lin's avatar Franky Lin Committed by John W. Linville

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: default avatarpieter-paul giesberts <pieterpg@broadcom.com>
Reviewed-by: default avatararend van spriel <arend@broadcom.com>
Signed-off-by: default avatarfranky lin <frankyl@broadcom.com>
Signed-off-by: default avatarFranky Lin <frankyl@broadcom.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent e2f93cc3
......@@ -36,6 +36,15 @@ config BRCMFMAC_SDIO
IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to
use the driver for a SDIO wireless card.
config BRCMFMAC_SDIO_OOB
bool "Out of band interrupt support for SDIO interface chipset"
depends on BRCMFMAC_SDIO
---help---
This option enables out-of-band interrupt support for Broadcom
SDIO Wifi chipset using fullmac in order to gain better
performance and deep sleep wake up capability on certain
platforms. Say N if you are unsure.
config BRCMFMAC_USB
bool "USB bus interface support for FullMAC driver"
depends on USB
......
......@@ -39,33 +39,113 @@
#define SDIOH_API_ACCESS_RETRY_LIMIT 2
static void brcmf_sdioh_irqhandler(struct sdio_func *func)
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
static irqreturn_t brcmf_sdio_irqhandler(int irq, void *dev_id)
{
struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(dev_id);
brcmf_dbg(INTR, "oob intr triggered\n");
/*
* out-of-band interrupt is level-triggered which won't
* be cleared until dpc
*/
if (sdiodev->irq_en) {
disable_irq_nosync(irq);
sdiodev->irq_en = false;
}
brcmf_sdbrcm_isr(sdiodev->bus);
return IRQ_HANDLED;
}
int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev)
{
int ret = 0;
u8 data;
unsigned long flags;
brcmf_dbg(TRACE, "Entering\n");
brcmf_dbg(ERROR, "requesting irq %d\n", sdiodev->irq);
ret = request_irq(sdiodev->irq, brcmf_sdio_irqhandler,
sdiodev->irq_flags, "brcmf_oob_intr",
&sdiodev->func[1]->card->dev);
if (ret != 0)
return ret;
spin_lock_init(&sdiodev->irq_en_lock);
spin_lock_irqsave(&sdiodev->irq_en_lock, flags);
sdiodev->irq_en = true;
spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags);
ret = enable_irq_wake(sdiodev->irq);
if (ret != 0)
return ret;
sdiodev->irq_wake = true;
/* must configure SDIO_CCCR_IENx to enable irq */
data = brcmf_sdcard_cfg_read(sdiodev, SDIO_FUNC_0,
SDIO_CCCR_IENx, &ret);
data |= 1 << SDIO_FUNC_1 | 1 << SDIO_FUNC_2 | 1;
brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_IENx,
data, &ret);
/* redirect, configure ane enable io for interrupt signal */
data = SDIO_SEPINT_MASK | SDIO_SEPINT_OE;
if (sdiodev->irq_flags | IRQF_TRIGGER_HIGH)
data |= SDIO_SEPINT_ACT_HI;
brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_BRCM_SEPINT,
data, &ret);
return 0;
}
int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev)
{
brcmf_dbg(TRACE, "Entering\n");
brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_BRCM_SEPINT,
0, NULL);
brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_0, SDIO_CCCR_IENx, 0, NULL);
if (sdiodev->irq_wake) {
disable_irq_wake(sdiodev->irq);
sdiodev->irq_wake = false;
}
free_irq(sdiodev->irq, &sdiodev->func[1]->card->dev);
sdiodev->irq_en = false;
return 0;
}
#else /* CONFIG_BRCMFMAC_SDIO_OOB */
static void brcmf_sdio_irqhandler(struct sdio_func *func)
{
struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(&func->card->dev);
brcmf_dbg(TRACE, "***IRQHandler\n");
brcmf_dbg(INTR, "ib intr triggered\n");
brcmf_sdbrcm_isr(sdiodev->bus);
}
/* dummy handler for SDIO function 2 interrupt */
static void brcmf_sdioh_dummy_irq_handler(struct sdio_func *func)
static void brcmf_sdio_dummy_irqhandler(struct sdio_func *func)
{
}
int brcmf_sdcard_intr_reg(struct brcmf_sdio_dev *sdiodev)
int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev)
{
brcmf_dbg(TRACE, "Entering\n");
sdio_claim_host(sdiodev->func[1]);
sdio_claim_irq(sdiodev->func[1], brcmf_sdioh_irqhandler);
sdio_claim_irq(sdiodev->func[2], brcmf_sdioh_dummy_irq_handler);
sdio_claim_irq(sdiodev->func[1], brcmf_sdio_irqhandler);
sdio_claim_irq(sdiodev->func[2], brcmf_sdio_dummy_irqhandler);
sdio_release_host(sdiodev->func[1]);
return 0;
}
int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev)
int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev)
{
brcmf_dbg(TRACE, "Entering\n");
......@@ -76,6 +156,7 @@ int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev)
return 0;
}
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
u8 brcmf_sdcard_cfg_read(struct brcmf_sdio_dev *sdiodev, uint fnc_num, u32 addr,
int *err)
......
......@@ -27,6 +27,7 @@
#include <linux/errno.h>
#include <linux/sched.h> /* request_irq() */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <net/cfg80211.h>
#include <defs.h>
......@@ -55,6 +56,15 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = {
};
MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids);
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
static struct list_head oobirq_lh;
struct brcmf_sdio_oobirq {
unsigned int irq;
unsigned long flags;
struct list_head list;
};
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
static bool
brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev)
{
......@@ -107,7 +117,8 @@ static inline int brcmf_sdioh_f0_write_byte(struct brcmf_sdio_dev *sdiodev,
}
sdio_release_host(sdfunc);
}
} else if (regaddr == SDIO_CCCR_ABORT) {
} else if ((regaddr == SDIO_CCCR_ABORT) ||
(regaddr == SDIO_CCCR_IENx)) {
sdfunc = kmemdup(sdiodev->func[0], sizeof(struct sdio_func),
GFP_KERNEL);
if (!sdfunc)
......@@ -467,12 +478,40 @@ void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev)
}
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
static int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev)
{
struct brcmf_sdio_oobirq *oobirq_entry;
if (list_empty(&oobirq_lh)) {
brcmf_dbg(ERROR, "no valid oob irq resource\n");
return -ENXIO;
}
oobirq_entry = list_first_entry(&oobirq_lh, struct brcmf_sdio_oobirq,
list);
sdiodev->irq = oobirq_entry->irq;
sdiodev->irq_flags = oobirq_entry->flags;
list_del(&oobirq_entry->list);
kfree(oobirq_entry);
return 0;
}
#else
static inline int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev)
{
return 0;
}
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
static int brcmf_ops_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
int ret = 0;
struct brcmf_sdio_dev *sdiodev;
struct brcmf_bus *bus_if;
brcmf_dbg(TRACE, "Enter\n");
brcmf_dbg(TRACE, "func->class=%x\n", func->class);
brcmf_dbg(TRACE, "sdio_vendor: 0x%04x\n", func->vendor);
......@@ -511,6 +550,10 @@ static int brcmf_ops_sdio_probe(struct sdio_func *func,
sdiodev = dev_get_drvdata(&func->card->dev);
if ((!sdiodev) || (sdiodev->func[1]->card != func->card))
return -ENODEV;
ret = brcmf_sdio_getintrcfg(sdiodev);
if (ret)
return ret;
sdiodev->func[2] = func;
bus_if = sdiodev->bus_if;
......@@ -603,6 +646,65 @@ static struct sdio_driver brcmf_sdmmc_driver = {
#endif /* CONFIG_PM_SLEEP */
};
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
static int brcmf_sdio_pd_probe(struct platform_device *pdev)
{
struct resource *res;
struct brcmf_sdio_oobirq *oobirq_entry;
int i, ret;
INIT_LIST_HEAD(&oobirq_lh);
for (i = 0; ; i++) {
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!res)
break;
oobirq_entry = kzalloc(sizeof(struct brcmf_sdio_oobirq),
GFP_KERNEL);
oobirq_entry->irq = res->start;
oobirq_entry->flags = res->flags & IRQF_TRIGGER_MASK;
list_add_tail(&oobirq_entry->list, &oobirq_lh);
}
if (i == 0)
return -ENXIO;
ret = sdio_register_driver(&brcmf_sdmmc_driver);
if (ret)
brcmf_dbg(ERROR, "sdio_register_driver failed: %d\n", ret);
return ret;
}
static struct platform_driver brcmf_sdio_pd = {
.probe = brcmf_sdio_pd_probe,
.driver = {
.name = "brcmf_sdio_pd"
}
};
void brcmf_sdio_exit(void)
{
brcmf_dbg(TRACE, "Enter\n");
sdio_unregister_driver(&brcmf_sdmmc_driver);
platform_driver_unregister(&brcmf_sdio_pd);
}
void brcmf_sdio_init(void)
{
int ret;
brcmf_dbg(TRACE, "Enter\n");
ret = platform_driver_register(&brcmf_sdio_pd);
if (ret)
brcmf_dbg(ERROR, "platform_driver_register failed: %d\n", ret);
}
#else
void brcmf_sdio_exit(void)
{
brcmf_dbg(TRACE, "Enter\n");
......@@ -621,3 +723,4 @@ void brcmf_sdio_init(void)
if (ret)
brcmf_dbg(ERROR, "sdio_register_driver failed: %d\n", ret);
}
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
......@@ -2352,6 +2352,24 @@ static void brcmf_sdbrcm_bus_stop(struct device *dev)
up(&bus->sdsem);
}
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus)
{
unsigned long flags;
spin_lock_irqsave(&bus->sdiodev->irq_en_lock, flags);
if (!bus->sdiodev->irq_en && !bus->ipend) {
enable_irq(bus->sdiodev->irq);
bus->sdiodev->irq_en = true;
}
spin_unlock_irqrestore(&bus->sdiodev->irq_en_lock, flags);
}
#else
static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus)
{
}
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
static bool brcmf_sdbrcm_dpc(struct brcmf_sdio *bus)
{
u32 intstatus, newstatus = 0;
......@@ -2509,6 +2527,8 @@ static bool brcmf_sdbrcm_dpc(struct brcmf_sdio *bus)
bus->intstatus = intstatus;
clkwait:
brcmf_sdbrcm_clrintr(bus);
if (data_ok(bus) && bus->ctrl_frame_stat &&
(bus->clkstate == CLK_AVAIL)) {
int ret, i;
......@@ -3509,7 +3529,7 @@ static int brcmf_sdbrcm_bus_init(struct device *dev)
SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err);
if (ret == 0) {
ret = brcmf_sdcard_intr_reg(bus->sdiodev);
ret = brcmf_sdio_intr_register(bus->sdiodev);
if (ret != 0)
brcmf_dbg(ERROR, "intr register failed:%d\n", ret);
}
......@@ -3873,7 +3893,7 @@ static void brcmf_sdbrcm_release(struct brcmf_sdio *bus)
if (bus) {
/* De-register interrupt handler */
brcmf_sdcard_intr_dereg(bus->sdiodev);
brcmf_sdio_intr_unregister(bus->sdiodev);
if (bus->sdiodev->bus_if->drvr) {
brcmf_detach(bus->sdiodev->dev);
......
......@@ -43,6 +43,13 @@
/* as of sdiod rev 0, supports 3 functions */
#define SBSDIO_NUM_FUNCTION 3
/* function 0 vendor specific CCCR registers */
#define SDIO_CCCR_BRCM_SEPINT 0xf2
#define SDIO_SEPINT_MASK 0x01
#define SDIO_SEPINT_OE 0x02
#define SDIO_SEPINT_ACT_HI 0x04
/* function 1 miscellaneous registers */
/* sprom command and status */
......@@ -144,13 +151,18 @@ struct brcmf_sdio_dev {
wait_queue_head_t request_buffer_wait;
struct device *dev;
struct brcmf_bus *bus_if;
#ifdef CONFIG_BRCMFMAC_SDIO_OOB
unsigned int irq; /* oob interrupt number */
unsigned long irq_flags; /* board specific oob flags */
bool irq_en; /* irq enable flags */
spinlock_t irq_en_lock;
bool irq_wake; /* irq wake enable flags */
#endif /* CONFIG_BRCMFMAC_SDIO_OOB */
};
/* Register/deregister device interrupt handler. */
extern int
brcmf_sdcard_intr_reg(struct brcmf_sdio_dev *sdiodev);
extern int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev);
/* Register/deregister interrupt handler. */
extern int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev);
extern int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev);
/* Access SDIO address space (e.g. CCCR) using CMD52 (single-byte interface).
* fn: function number
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment