diff options
author | Arend van Spriel <arend@broadcom.com> | 2011-10-05 13:19:03 +0200 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2011-10-11 15:55:30 -0400 |
commit | 5b435de0d786869c95d1962121af0d7df2542009 (patch) | |
tree | 9b7cfbc4aa9f1ec0e719e3a0c677bd9f4e56540d | |
parent | 5f68a2b0a890d086e40fc7b55f4a0c32c28bc0d2 (diff) | |
download | linux-5b435de0d786869c95d1962121af0d7df2542009.tar.gz |
net: wireless: add brcm80211 drivers
Add the brcm80211 tree to drivers/net/wireless, and disable the version that's
in drivers/staging. This version includes the sources currently in staging,
plus any changes that have been sent out for review.
Sources in staging will be deleted in a followup patch.
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
81 files changed, 97056 insertions, 3 deletions
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index f354bd4e121e..abd3b71cd4ab 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -271,6 +271,7 @@ config MWL8K source "drivers/net/wireless/ath/Kconfig" source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" +source "drivers/net/wireless/brcm80211/Kconfig" source "drivers/net/wireless/hostap/Kconfig" source "drivers/net/wireless/ipw2x00/Kconfig" source "drivers/net/wireless/iwlwifi/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 4cf0ad312da1..0a304b060b6c 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -58,3 +58,6 @@ obj-$(CONFIG_WL12XX_PLATFORM_DATA) += wl12xx/ obj-$(CONFIG_IWM) += iwmc3200wifi/ obj-$(CONFIG_MWIFIEX) += mwifiex/ +obj-$(CONFIG_BRCMFMAC) += brcm80211/ +obj-$(CONFIG_BRCMUMAC) += brcm80211/ +obj-$(CONFIG_BRCMSMAC) += brcm80211/ diff --git a/drivers/net/wireless/brcm80211/Kconfig b/drivers/net/wireless/brcm80211/Kconfig new file mode 100644 index 000000000000..2069fc8f7ad1 --- /dev/null +++ b/drivers/net/wireless/brcm80211/Kconfig @@ -0,0 +1,35 @@ +config BRCMUTIL + tristate + +config BRCMSMAC + tristate "Broadcom IEEE802.11n PCIe SoftMAC WLAN driver" + depends on PCI + depends on MAC80211 + depends on BCMA=n + select BRCMUTIL + select FW_LOADER + select CRC_CCITT + select CRC8 + select CORDIC + ---help--- + This module adds support for PCIe wireless adapters based on Broadcom + IEEE802.11n SoftMAC chipsets. If you choose to build a module, it'll + be called brcmsmac.ko. + +config BRCMFMAC + tristate "Broadcom IEEE802.11n embedded FullMAC WLAN driver" + depends on MMC + depends on CFG80211 + select BRCMUTIL + select FW_LOADER + ---help--- + This module adds support for embedded wireless adapters based on + Broadcom IEEE802.11n FullMAC chipsets. This driver uses the kernel's + wireless extensions subsystem. If you choose to build a module, + it'll be called brcmfmac.ko. + +config BRCMDBG + bool "Broadcom driver debug functions" + depends on BRCMSMAC || BRCMFMAC + ---help--- + Selecting this enables additional code for debug purposes. diff --git a/drivers/net/wireless/brcm80211/Makefile b/drivers/net/wireless/brcm80211/Makefile new file mode 100644 index 000000000000..f41c047eca82 --- /dev/null +++ b/drivers/net/wireless/brcm80211/Makefile @@ -0,0 +1,23 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# common flags +subdir-ccflags-$(CONFIG_BRCMDBG) += -DBCMDBG + +obj-$(CONFIG_BRCMUTIL) += brcmutil/ +obj-$(CONFIG_BRCMFMAC) += brcmfmac/ +obj-$(CONFIG_BRCMSMAC) += brcmsmac/ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/brcm80211/brcmfmac/Makefile new file mode 100644 index 000000000000..b44e3094588a --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/Makefile @@ -0,0 +1,33 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ccflags-y += \ + -Idrivers/net/wireless/brcm80211/brcmfmac \ + -Idrivers/net/wireless/brcm80211/include + +DHDOFILES = \ + wl_cfg80211.o \ + dhd_cdc.o \ + dhd_common.o \ + dhd_sdio.o \ + dhd_linux.o \ + bcmsdh.o \ + bcmsdh_sdmmc.o + +obj-$(CONFIG_BRCMFMAC) += brcmfmac.o +brcmfmac-objs += $(DHDOFILES) +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmchip.h b/drivers/net/wireless/brcm80211/brcmfmac/bcmchip.h new file mode 100644 index 000000000000..d7d3afd5a10f --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmchip.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _bcmchip_h_ +#define _bcmchip_h_ + +/* bcm4329 */ +/* SDIO device core, ID 0x829 */ +#define BCM4329_CORE_BUS_BASE 0x18011000 +/* internal memory core, ID 0x80e */ +#define BCM4329_CORE_SOCRAM_BASE 0x18003000 +/* ARM Cortex M3 core, ID 0x82a */ +#define BCM4329_CORE_ARM_BASE 0x18002000 +#define BCM4329_RAMSIZE 0x48000 +/* firmware name */ +#define BCM4329_FW_NAME "brcm/bcm4329-fullmac-4.bin" +#define BCM4329_NV_NAME "brcm/bcm4329-fullmac-4.txt" + +#endif /* _bcmchip_h_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c new file mode 100644 index 000000000000..bff9dcd6fadc --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* ****************** SDIO CARD Interface Functions **************************/ + +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/card.h> + +#include <defs.h> +#include <brcm_hw_ids.h> +#include <brcmu_utils.h> +#include <brcmu_wifi.h> +#include <soc.h> +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_dbg.h" +#include "sdio_host.h" + +#define SDIOH_API_ACCESS_RETRY_LIMIT 2 + +static void brcmf_sdioh_irqhandler(struct sdio_func *func) +{ + struct brcmf_sdio_dev *sdiodev = dev_get_drvdata(&func->card->dev); + + brcmf_dbg(TRACE, "***IRQHandler\n"); + + sdio_release_host(func); + + brcmf_sdbrcm_isr(sdiodev->bus); + + sdio_claim_host(func); +} + +int brcmf_sdcard_intr_reg(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_release_host(sdiodev->func[1]); + + return 0; +} + +int brcmf_sdcard_intr_dereg(struct brcmf_sdio_dev *sdiodev) +{ + brcmf_dbg(TRACE, "Entering\n"); + + sdio_claim_host(sdiodev->func[1]); + sdio_release_irq(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + + return 0; +} + +u8 brcmf_sdcard_cfg_read(struct brcmf_sdio_dev *sdiodev, uint fnc_num, u32 addr, + int *err) +{ + int status; + s32 retry = 0; + u8 data = 0; + + do { + if (retry) /* wait for 1 ms till bus get settled down */ + udelay(1000); + status = brcmf_sdioh_request_byte(sdiodev, SDIOH_READ, fnc_num, + addr, (u8 *) &data); + } while (status != 0 + && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT)); + if (err) + *err = status; + + brcmf_dbg(INFO, "fun = %d, addr = 0x%x, u8data = 0x%x\n", + fnc_num, addr, data); + + return data; +} + +void +brcmf_sdcard_cfg_write(struct brcmf_sdio_dev *sdiodev, uint fnc_num, u32 addr, + u8 data, int *err) +{ + int status; + s32 retry = 0; + + do { + if (retry) /* wait for 1 ms till bus get settled down */ + udelay(1000); + status = brcmf_sdioh_request_byte(sdiodev, SDIOH_WRITE, fnc_num, + addr, (u8 *) &data); + } while (status != 0 + && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT)); + if (err) + *err = status; + + brcmf_dbg(INFO, "fun = %d, addr = 0x%x, u8data = 0x%x\n", + fnc_num, addr, data); +} + +int +brcmf_sdcard_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address) +{ + int err = 0; + brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW, + (address >> 8) & SBSDIO_SBADDRLOW_MASK, &err); + if (!err) + brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_SBADDRMID, + (address >> 16) & SBSDIO_SBADDRMID_MASK, + &err); + if (!err) + brcmf_sdcard_cfg_write(sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_SBADDRHIGH, + (address >> 24) & SBSDIO_SBADDRHIGH_MASK, + &err); + + return err; +} + +u32 brcmf_sdcard_reg_read(struct brcmf_sdio_dev *sdiodev, u32 addr, uint size) +{ + int status; + u32 word = 0; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + + brcmf_dbg(INFO, "fun = 1, addr = 0x%x\n", addr); + + if (bar0 != sdiodev->sbwad) { + if (brcmf_sdcard_set_sbaddr_window(sdiodev, bar0)) + return 0xFFFFFFFF; + + sdiodev->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + if (size == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = brcmf_sdioh_request_word(sdiodev, SDIOH_READ, SDIO_FUNC_1, + addr, &word, size); + + sdiodev->regfail = (status != 0); + + brcmf_dbg(INFO, "u32data = 0x%x\n", word); + + /* if ok, return appropriately masked word */ + if (status == 0) { + switch (size) { + case sizeof(u8): + return word & 0xff; + case sizeof(u16): + return word & 0xffff; + case sizeof(u32): + return word; + default: + sdiodev->regfail = true; + + } + } + + /* otherwise, bad sdio access or invalid size */ + brcmf_dbg(ERROR, "error reading addr 0x%04x size %d\n", addr, size); + return 0xFFFFFFFF; +} + +u32 brcmf_sdcard_reg_write(struct brcmf_sdio_dev *sdiodev, u32 addr, uint size, + u32 data) +{ + int status; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + brcmf_dbg(INFO, "fun = 1, addr = 0x%x, uint%ddata = 0x%x\n", + addr, size * 8, data); + + if (bar0 != sdiodev->sbwad) { + err = brcmf_sdcard_set_sbaddr_window(sdiodev, bar0); + if (err) + return err; + + sdiodev->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + if (size == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + status = + brcmf_sdioh_request_word(sdiodev, SDIOH_WRITE, SDIO_FUNC_1, + addr, &data, size); + sdiodev->regfail = (status != 0); + + if (status == 0) + return 0; + + brcmf_dbg(ERROR, "error writing 0x%08x to addr 0x%04x size %d\n", + data, addr, size); + return 0xFFFFFFFF; +} + +bool brcmf_sdcard_regfail(struct brcmf_sdio_dev *sdiodev) +{ + return sdiodev->regfail; +} + +int +brcmf_sdcard_recv_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, + u8 *buf, uint nbytes, struct sk_buff *pkt) +{ + int status; + uint incr_fix; + uint width; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + brcmf_dbg(INFO, "fun = %d, addr = 0x%x, size = %d\n", fn, addr, nbytes); + + /* Async not implemented yet */ + if (flags & SDIO_REQ_ASYNC) + return -ENOTSUPP; + + if (bar0 != sdiodev->sbwad) { + err = brcmf_sdcard_set_sbaddr_window(sdiodev, bar0); + if (err) + return err; + + sdiodev->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + if (width == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = brcmf_sdioh_request_buffer(sdiodev, incr_fix, SDIOH_READ, + fn, addr, width, nbytes, buf, pkt); + + return status; +} + +int +brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes, struct sk_buff *pkt) +{ + uint incr_fix; + uint width; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + brcmf_dbg(INFO, "fun = %d, addr = 0x%x, size = %d\n", fn, addr, nbytes); + + /* Async not implemented yet */ + if (flags & SDIO_REQ_ASYNC) + return -ENOTSUPP; + + if (bar0 != sdiodev->sbwad) { + err = brcmf_sdcard_set_sbaddr_window(sdiodev, bar0); + if (err) + return err; + + sdiodev->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + if (width == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + return brcmf_sdioh_request_buffer(sdiodev, incr_fix, SDIOH_WRITE, fn, + addr, width, nbytes, buf, pkt); +} + +int brcmf_sdcard_rwdata(struct brcmf_sdio_dev *sdiodev, uint rw, u32 addr, + u8 *buf, uint nbytes) +{ + addr &= SBSDIO_SB_OFT_ADDR_MASK; + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + return brcmf_sdioh_request_buffer(sdiodev, SDIOH_DATA_INC, + (rw ? SDIOH_WRITE : SDIOH_READ), SDIO_FUNC_1, + addr, 4, nbytes, buf, NULL); +} + +int brcmf_sdcard_abort(struct brcmf_sdio_dev *sdiodev, uint fn) +{ + char t_func = (char)fn; + brcmf_dbg(TRACE, "Enter\n"); + + /* issue abort cmd52 command through F0 */ + brcmf_sdioh_request_byte(sdiodev, SDIOH_WRITE, SDIO_FUNC_0, + SDIO_CCCR_ABORT, &t_func); + + brcmf_dbg(TRACE, "Exit\n"); + return 0; +} + +int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) +{ + u32 regs = 0; + int ret = 0; + + ret = brcmf_sdioh_attach(sdiodev); + if (ret) + goto out; + + regs = SI_ENUM_BASE; + + /* Report the BAR, to fix if needed */ + sdiodev->sbwad = SI_ENUM_BASE; + + /* try to attach to the target device */ + sdiodev->bus = brcmf_sdbrcm_probe(0, 0, 0, 0, regs, sdiodev); + if (!sdiodev->bus) { + brcmf_dbg(ERROR, "device attach failed\n"); + ret = -ENODEV; + goto out; + } + +out: + if (ret) + brcmf_sdio_remove(sdiodev); + + return ret; +} +EXPORT_SYMBOL(brcmf_sdio_probe); + +int brcmf_sdio_remove(struct brcmf_sdio_dev *sdiodev) +{ + if (sdiodev->bus) { + brcmf_sdbrcm_disconnect(sdiodev->bus); + sdiodev->bus = NULL; + } + + brcmf_sdioh_detach(sdiodev); + + sdiodev->sbwad = 0; + + return 0; +} +EXPORT_SYMBOL(brcmf_sdio_remove); + +void brcmf_sdio_wdtmr_enable(struct brcmf_sdio_dev *sdiodev, bool enable) +{ + if (enable) + brcmf_sdbrcm_wd_timer(sdiodev->bus, BRCMF_WD_POLL_MS); + else + brcmf_sdbrcm_wd_timer(sdiodev->bus, 0); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c new file mode 100644 index 000000000000..e919de210f70 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/core.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/card.h> +#include <linux/suspend.h> +#include <linux/errno.h> +#include <linux/sched.h> /* request_irq() */ +#include <net/cfg80211.h> + +#include <defs.h> +#include <brcm_hw_ids.h> +#include <brcmu_utils.h> +#include <brcmu_wifi.h> +#include "sdio_host.h" +#include "dhd.h" +#include "dhd_dbg.h" +#include "wl_cfg80211.h" + +#define SDIO_VENDOR_ID_BROADCOM 0x02d0 + +#define DMA_ALIGN_MASK 0x03 + +#define SDIO_DEVICE_ID_BROADCOM_4329 0x4329 + +#define SDIO_FUNC1_BLOCKSIZE 64 +#define SDIO_FUNC2_BLOCKSIZE 512 + +/* devices we support, null terminated */ +static const struct sdio_device_id brcmf_sdmmc_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329)}, + { /* end: all zeroes */ }, +}; +MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); + +static bool +brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev) +{ + bool is_err = false; +#ifdef CONFIG_PM_SLEEP + is_err = atomic_read(&sdiodev->suspend); +#endif + return is_err; +} + +static void +brcmf_pm_resume_wait(struct brcmf_sdio_dev *sdiodev, wait_queue_head_t *wq) +{ +#ifdef CONFIG_PM_SLEEP + int retry = 0; + while (atomic_read(&sdiodev->suspend) && retry++ != 30) + wait_event_timeout(*wq, false, HZ/100); +#endif +} + +static inline int brcmf_sdioh_f0_write_byte(struct brcmf_sdio_dev *sdiodev, + uint regaddr, u8 *byte) +{ + struct sdio_func *sdfunc = sdiodev->func[0]; + int err_ret; + + /* + * Can only directly write to some F0 registers. + * Handle F2 enable/disable and Abort command + * as a special case. + */ + if (regaddr == SDIO_CCCR_IOEx) { + sdfunc = sdiodev->func[2]; + if (sdfunc) { + sdio_claim_host(sdfunc); + if (*byte & SDIO_FUNC_ENABLE_2) { + /* Enable Function 2 */ + err_ret = sdio_enable_func(sdfunc); + if (err_ret) + brcmf_dbg(ERROR, + "enable F2 failed:%d\n", + err_ret); + } else { + /* Disable Function 2 */ + err_ret = sdio_disable_func(sdfunc); + if (err_ret) + brcmf_dbg(ERROR, + "Disable F2 failed:%d\n", + err_ret); + } + sdio_release_host(sdfunc); + } + } else if (regaddr == SDIO_CCCR_ABORT) { + sdio_claim_host(sdfunc); + sdio_writeb(sdfunc, *byte, regaddr, &err_ret); + sdio_release_host(sdfunc); + } else if (regaddr < 0xF0) { + brcmf_dbg(ERROR, "F0 Wr:0x%02x: write disallowed\n", regaddr); + err_ret = -EPERM; + } else { + sdio_claim_host(sdfunc); + sdio_f0_writeb(sdfunc, *byte, regaddr, &err_ret); + sdio_release_host(sdfunc); + } + + return err_ret; +} + +int brcmf_sdioh_request_byte(struct brcmf_sdio_dev *sdiodev, uint rw, uint func, + uint regaddr, u8 *byte) +{ + int err_ret; + + brcmf_dbg(INFO, "rw=%d, func=%d, addr=0x%05x\n", rw, func, regaddr); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_byte_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + if (rw && func == 0) { + /* handle F0 separately */ + err_ret = brcmf_sdioh_f0_write_byte(sdiodev, regaddr, byte); + } else { + sdio_claim_host(sdiodev->func[func]); + if (rw) /* CMD52 Write */ + sdio_writeb(sdiodev->func[func], *byte, regaddr, + &err_ret); + else if (func == 0) { + *byte = sdio_f0_readb(sdiodev->func[func], regaddr, + &err_ret); + } else { + *byte = sdio_readb(sdiodev->func[func], regaddr, + &err_ret); + } + sdio_release_host(sdiodev->func[func]); + } + + if (err_ret) + brcmf_dbg(ERROR, "Failed to %s byte F%d:@0x%05x=%02x, Err: %d\n", + rw ? "write" : "read", func, regaddr, *byte, err_ret); + + return err_ret; +} + +int brcmf_sdioh_request_word(struct brcmf_sdio_dev *sdiodev, + uint rw, uint func, uint addr, u32 *word, + uint nbytes) +{ + int err_ret = -EIO; + + if (func == 0) { + brcmf_dbg(ERROR, "Only CMD52 allowed to F0\n"); + return -EINVAL; + } + + brcmf_dbg(INFO, "rw=%d, func=%d, addr=0x%05x, nbytes=%d\n", + rw, func, addr, nbytes); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_word_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + /* Claim host controller */ + sdio_claim_host(sdiodev->func[func]); + + if (rw) { /* CMD52 Write */ + if (nbytes == 4) + sdio_writel(sdiodev->func[func], *word, addr, + &err_ret); + else if (nbytes == 2) + sdio_writew(sdiodev->func[func], (*word & 0xFFFF), + addr, &err_ret); + else + brcmf_dbg(ERROR, "Invalid nbytes: %d\n", nbytes); + } else { /* CMD52 Read */ + if (nbytes == 4) + *word = sdio_readl(sdiodev->func[func], addr, &err_ret); + else if (nbytes == 2) + *word = sdio_readw(sdiodev->func[func], addr, + &err_ret) & 0xFFFF; + else + brcmf_dbg(ERROR, "Invalid nbytes: %d\n", nbytes); + } + + /* Release host controller */ + sdio_release_host(sdiodev->func[func]); + + if (err_ret) + brcmf_dbg(ERROR, "Failed to %s word, Err: 0x%08x\n", + rw ? "write" : "read", err_ret); + + return err_ret; +} + +static int +brcmf_sdioh_request_packet(struct brcmf_sdio_dev *sdiodev, uint fix_inc, + uint write, uint func, uint addr, + struct sk_buff *pkt) +{ + bool fifo = (fix_inc == SDIOH_DATA_FIX); + u32 SGCount = 0; + int err_ret = 0; + + struct sk_buff *pnext; + + brcmf_dbg(TRACE, "Enter\n"); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_packet_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + /* Claim host controller */ + sdio_claim_host(sdiodev->func[func]); + for (pnext = pkt; pnext; pnext = pnext->next) { + uint pkt_len = pnext->len; + pkt_len += 3; + pkt_len &= 0xFFFFFFFC; + + if ((write) && (!fifo)) { + err_ret = sdio_memcpy_toio(sdiodev->func[func], addr, + ((u8 *) (pnext->data)), + pkt_len); + } else if (write) { + err_ret = sdio_memcpy_toio(sdiodev->func[func], addr, + ((u8 *) (pnext->data)), + pkt_len); + } else if (fifo) { + err_ret = sdio_readsb(sdiodev->func[func], + ((u8 *) (pnext->data)), + addr, pkt_len); + } else { + err_ret = sdio_memcpy_fromio(sdiodev->func[func], + ((u8 *) (pnext->data)), + addr, pkt_len); + } + + if (err_ret) { + brcmf_dbg(ERROR, "%s FAILED %p[%d], addr=0x%05x, pkt_len=%d, ERR=0x%08x\n", + write ? "TX" : "RX", pnext, SGCount, addr, + pkt_len, err_ret); + } else { + brcmf_dbg(TRACE, "%s xfr'd %p[%d], addr=0x%05x, len=%d\n", + write ? "TX" : "RX", pnext, SGCount, addr, + pkt_len); + } + + if (!fifo) + addr += pkt_len; + SGCount++; + + } + + /* Release host controller */ + sdio_release_host(sdiodev->func[func]); + + brcmf_dbg(TRACE, "Exit\n"); + return err_ret; +} + +/* + * This function takes a buffer or packet, and fixes everything up + * so that in the end, a DMA-able packet is created. + * + * A buffer does not have an associated packet pointer, + * and may or may not be aligned. + * A packet may consist of a single packet, or a packet chain. + * If it is a packet chain, then all the packets in the chain + * must be properly aligned. + * + * If the packet data is not aligned, then there may only be + * one packet, and in this case, it is copied to a new + * aligned packet. + * + */ +int brcmf_sdioh_request_buffer(struct brcmf_sdio_dev *sdiodev, + uint fix_inc, uint write, uint func, uint addr, + uint reg_width, uint buflen_u, u8 *buffer, + struct sk_buff *pkt) +{ + int Status; + struct sk_buff *mypkt = NULL; + + brcmf_dbg(TRACE, "Enter\n"); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_buffer_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + /* Case 1: we don't have a packet. */ + if (pkt == NULL) { + brcmf_dbg(DATA, "Creating new %s Packet, len=%d\n", + write ? "TX" : "RX", buflen_u); + mypkt = brcmu_pkt_buf_get_skb(buflen_u); + if (!mypkt) { + brcmf_dbg(ERROR, "brcmu_pkt_buf_get_skb failed: len %d\n", + buflen_u); + return -EIO; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) + memcpy(mypkt->data, buffer, buflen_u); + + Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, + func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) + memcpy(buffer, mypkt->data, buflen_u); + + brcmu_pkt_buf_free_skb(mypkt); + } else if (((ulong) (pkt->data) & DMA_ALIGN_MASK) != 0) { + /* + * Case 2: We have a packet, but it is unaligned. + * In this case, we cannot have a chain (pkt->next == NULL) + */ + brcmf_dbg(DATA, "Creating aligned %s Packet, len=%d\n", + write ? "TX" : "RX", pkt->len); + mypkt = brcmu_pkt_buf_get_skb(pkt->len); + if (!mypkt) { + brcmf_dbg(ERROR, "brcmu_pkt_buf_get_skb failed: len %d\n", + pkt->len); + return -EIO; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) + memcpy(mypkt->data, pkt->data, pkt->len); + + Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, + func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) + memcpy(pkt->data, mypkt->data, mypkt->len); + + brcmu_pkt_buf_free_skb(mypkt); + } else { /* case 3: We have a packet and + it is aligned. */ + brcmf_dbg(DATA, "Aligned %s Packet, direct DMA\n", + write ? "Tx" : "Rx"); + Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, + func, addr, pkt); + } + + return Status; +} + +/* Read client card reg */ +static int +brcmf_sdioh_card_regread(struct brcmf_sdio_dev *sdiodev, int func, u32 regaddr, + int regsize, u32 *data) +{ + + if ((func == 0) || (regsize == 1)) { + u8 temp = 0; + + brcmf_sdioh_request_byte(sdiodev, SDIOH_READ, func, regaddr, + &temp); + *data = temp; + *data &= 0xff; + brcmf_dbg(DATA, "byte read data=0x%02x\n", *data); + } else { + brcmf_sdioh_request_word(sdiodev, SDIOH_READ, func, regaddr, + data, regsize); + if (regsize == 2) + *data &= 0xffff; + + brcmf_dbg(DATA, "word read data=0x%08x\n", *data); + } + + return SUCCESS; +} + +static int brcmf_sdioh_get_cisaddr(struct brcmf_sdio_dev *sdiodev, u32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i; + u32 scratch, regdata; + __le32 scratch_le; + u8 *ptr = (u8 *)&scratch_le; + + for (i = 0; i < 3; i++) { + if ((brcmf_sdioh_card_regread(sdiodev, 0, regaddr, 1, + ®data)) != SUCCESS) + brcmf_dbg(ERROR, "Can't read!\n"); + + *ptr++ = (u8) regdata; + regaddr++; + } + + /* Only the lower 17-bits are valid */ + scratch = le32_to_cpu(scratch_le); + scratch &= 0x0001FFFF; + return scratch; +} + +static int brcmf_sdioh_enablefuncs(struct brcmf_sdio_dev *sdiodev) +{ + int err_ret; + u32 fbraddr; + u8 func; + + brcmf_dbg(TRACE, "\n"); + + /* Get the Card's common CIS address */ + sdiodev->func_cis_ptr[0] = brcmf_sdioh_get_cisaddr(sdiodev, + SDIO_CCCR_CIS); + brcmf_dbg(INFO, "Card's Common CIS Ptr = 0x%x\n", + sdiodev->func_cis_ptr[0]); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIO_FBR_BASE(1), func = 1; + func <= sdiodev->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + sdiodev->func_cis_ptr[func] = + brcmf_sdioh_get_cisaddr(sdiodev, SDIO_FBR_CIS + fbraddr); + brcmf_dbg(INFO, "Function %d CIS Ptr = 0x%x\n", + func, sdiodev->func_cis_ptr[func]); + } + + /* Enable Function 1 */ + sdio_claim_host(sdiodev->func[1]); + err_ret = sdio_enable_func(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + if (err_ret) + brcmf_dbg(ERROR, "Failed to enable F1 Err: 0x%08x\n", err_ret); + + return false; +} + +/* + * Public entry points & extern's + */ +int brcmf_sdioh_attach(struct brcmf_sdio_dev *sdiodev) +{ + int err_ret = 0; + + brcmf_dbg(TRACE, "\n"); + + sdiodev->num_funcs = 2; + + sdio_claim_host(sdiodev->func[1]); + err_ret = sdio_set_block_size(sdiodev->func[1], SDIO_FUNC1_BLOCKSIZE); + sdio_release_host(sdiodev->func[1]); + if (err_ret) { + brcmf_dbg(ERROR, "Failed to set F1 blocksize\n"); + goto out; + } + + sdio_claim_host(sdiodev->func[2]); + err_ret = sdio_set_block_size(sdiodev->func[2], SDIO_FUNC2_BLOCKSIZE); + sdio_release_host(sdiodev->func[2]); + if (err_ret) { + brcmf_dbg(ERROR, "Failed to set F2 blocksize\n"); + goto out; + } + + brcmf_sdioh_enablefuncs(sdiodev); + +out: + brcmf_dbg(TRACE, "Done\n"); + return err_ret; +} + +void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev) +{ + brcmf_dbg(TRACE, "\n"); + + /* Disable Function 2 */ + sdio_claim_host(sdiodev->func[2]); + sdio_disable_func(sdiodev->func[2]); + sdio_release_host(sdiodev->func[2]); + + /* Disable Function 1 */ + sdio_claim_host(sdiodev->func[1]); + sdio_disable_func(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + +} + +static int brcmf_ops_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret = 0; + struct brcmf_sdio_dev *sdiodev; + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(TRACE, "func->class=%x\n", func->class); + brcmf_dbg(TRACE, "sdio_vendor: 0x%04x\n", func->vendor); + brcmf_dbg(TRACE, "sdio_device: 0x%04x\n", func->device); + brcmf_dbg(TRACE, "Function#: 0x%04x\n", func->num); + + if (func->num == 1) { + if (dev_get_drvdata(&func->card->dev)) { + brcmf_dbg(ERROR, "card private drvdata occupied\n"); + return -ENXIO; + } + sdiodev = kzalloc(sizeof(struct brcmf_sdio_dev), GFP_KERNEL); + if (!sdiodev) + return -ENOMEM; + sdiodev->func[0] = func->card->sdio_func[0]; + sdiodev->func[1] = func; + dev_set_drvdata(&func->card->dev, sdiodev); + + atomic_set(&sdiodev->suspend, false); + init_waitqueue_head(&sdiodev->request_byte_wait); + init_waitqueue_head(&sdiodev->request_word_wait); + init_waitqueue_head(&sdiodev->request_packet_wait); + init_waitqueue_head(&sdiodev->request_buffer_wait); + } + + if (func->num == 2) { + sdiodev = dev_get_drvdata(&func->card->dev); + if ((!sdiodev) || (sdiodev->func[1]->card != func->card)) + return -ENODEV; + sdiodev->func[2] = func; + + brcmf_dbg(TRACE, "F2 found, calling brcmf_sdio_probe...\n"); + ret = brcmf_sdio_probe(sdiodev); + } + + return ret; +} + +static void brcmf_ops_sdio_remove(struct sdio_func *func) +{ + struct brcmf_sdio_dev *sdiodev; + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(INFO, "func->class=%x\n", func->class); + brcmf_dbg(INFO, "sdio_vendor: 0x%04x\n", func->vendor); + brcmf_dbg(INFO, "sdio_device: 0x%04x\n", func->device); + brcmf_dbg(INFO, "Function#: 0x%04x\n", func->num); + + if (func->num == 2) { + sdiodev = dev_get_drvdata(&func->card->dev); + brcmf_dbg(TRACE, "F2 found, calling brcmf_sdio_remove...\n"); + brcmf_sdio_remove(sdiodev); + dev_set_drvdata(&func->card->dev, NULL); + kfree(sdiodev); + } +} + +#ifdef CONFIG_PM_SLEEP +static int brcmf_sdio_suspend(struct device *dev) +{ + mmc_pm_flag_t sdio_flags; + struct brcmf_sdio_dev *sdiodev; + struct sdio_func *func = dev_to_sdio_func(dev); + int ret = 0; + + brcmf_dbg(TRACE, "\n"); + + sdiodev = dev_get_drvdata(&func->card->dev); + + atomic_set(&sdiodev->suspend, true); + + sdio_flags = sdio_get_host_pm_caps(sdiodev->func[1]); + if (!(sdio_flags & MMC_PM_KEEP_POWER)) { + brcmf_dbg(ERROR, "Host can't keep power while suspended\n"); + return -EINVAL; + } + + ret = sdio_set_host_pm_flags(sdiodev->func[1], MMC_PM_KEEP_POWER); + if (ret) { + brcmf_dbg(ERROR, "Failed to set pm_flags\n"); + return ret; + } + + brcmf_sdio_wdtmr_enable(sdiodev, false); + + return ret; +} + +static int brcmf_sdio_resume(struct device *dev) +{ + struct brcmf_sdio_dev *sdiodev; + struct sdio_func *func = dev_to_sdio_func(dev); + + sdiodev = dev_get_drvdata(&func->card->dev); + brcmf_sdio_wdtmr_enable(sdiodev, true); + atomic_set(&sdiodev->suspend, false); + return 0; +} + +static const struct dev_pm_ops brcmf_sdio_pm_ops = { + .suspend = brcmf_sdio_suspend, + .resume = brcmf_sdio_resume, +}; +#endif /* CONFIG_PM_SLEEP */ + +static struct sdio_driver brcmf_sdmmc_driver = { + .probe = brcmf_ops_sdio_probe, + .remove = brcmf_ops_sdio_remove, + .name = "brcmfmac", + .id_table = brcmf_sdmmc_ids, +#ifdef CONFIG_PM_SLEEP + .drv = { + .pm = &brcmf_sdio_pm_ops, + }, +#endif /* CONFIG_PM_SLEEP */ +}; + +/* bus register interface */ +int brcmf_bus_register(void) +{ + brcmf_dbg(TRACE, "Enter\n"); + + return sdio_register_driver(&brcmf_sdmmc_driver); +} + +void brcmf_bus_unregister(void) +{ + brcmf_dbg(TRACE, "Enter\n"); + + sdio_unregister_driver(&brcmf_sdmmc_driver); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd.h new file mode 100644 index 000000000000..3ec74778b2e3 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd.h @@ -0,0 +1,773 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/**************** + * Common types * + */ + +#ifndef _BRCMF_H_ +#define _BRCMF_H_ + +#define BRCMF_VERSION_STR "4.218.248.5" + +/******************************************************************************* + * IO codes that are interpreted by dongle firmware + ******************************************************************************/ +#define BRCMF_C_UP 2 +#define BRCMF_C_SET_PROMISC 10 +#define BRCMF_C_GET_RATE 12 +#define BRCMF_C_GET_INFRA 19 +#define BRCMF_C_SET_INFRA 20 +#define BRCMF_C_GET_AUTH 21 +#define BRCMF_C_SET_AUTH 22 +#define BRCMF_C_GET_BSSID 23 +#define BRCMF_C_GET_SSID 25 +#define BRCMF_C_SET_SSID 26 +#define BRCMF_C_GET_CHANNEL 29 +#define BRCMF_C_GET_SRL 31 +#define BRCMF_C_GET_LRL 33 +#define BRCMF_C_GET_RADIO 37 +#define BRCMF_C_SET_RADIO 38 +#define BRCMF_C_GET_PHYTYPE 39 +#define BRCMF_C_SET_KEY 45 +#define BRCMF_C_SET_PASSIVE_SCAN 49 +#define BRCMF_C_SCAN 50 +#define BRCMF_C_SCAN_RESULTS 51 +#define BRCMF_C_DISASSOC 52 +#define BRCMF_C_REASSOC 53 +#define BRCMF_C_SET_ROAM_TRIGGER 55 +#define BRCMF_C_SET_ROAM_DELTA 57 +#define BRCMF_C_GET_DTIMPRD 77 +#define BRCMF_C_SET_COUNTRY 84 +#define BRCMF_C_GET_PM 85 +#define BRCMF_C_SET_PM 86 +#define BRCMF_C_GET_AP 117 +#define BRCMF_C_SET_AP 118 +#define BRCMF_C_GET_RSSI 127 +#define BRCMF_C_GET_WSEC 133 +#define BRCMF_C_SET_WSEC 134 +#define BRCMF_C_GET_PHY_NOISE 135 +#define BRCMF_C_GET_BSS_INFO 136 +#define BRCMF_C_SET_SCAN_CHANNEL_TIME 185 +#define BRCMF_C_SET_SCAN_UNASSOC_TIME 187 +#define BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON 201 +#define BRCMF_C_GET_VALID_CHANNELS 217 +#define BRCMF_C_GET_KEY_PRIMARY 235 +#define BRCMF_C_SET_KEY_PRIMARY 236 +#define BRCMF_C_SET_SCAN_PASSIVE_TIME 258 +#define BRCMF_C_GET_VAR 262 +#define BRCMF_C_SET_VAR 263 + +/* phy types (returned by WLC_GET_PHYTPE) */ +#define WLC_PHY_TYPE_A 0 +#define WLC_PHY_TYPE_B 1 +#define WLC_PHY_TYPE_G 2 +#define WLC_PHY_TYPE_N 4 +#define WLC_PHY_TYPE_LP 5 +#define WLC_PHY_TYPE_SSN 6 +#define WLC_PHY_TYPE_HT 7 +#define WLC_PHY_TYPE_LCN 8 +#define WLC_PHY_TYPE_NULL 0xf + +#define BRCMF_EVENTING_MASK_LEN 16 + +#define TOE_TX_CSUM_OL 0x00000001 +#define TOE_RX_CSUM_OL 0x00000002 + +#define BRCMF_BSS_INFO_VERSION 108 /* current ver of brcmf_bss_info struct */ + +/* size of brcmf_scan_params not including variable length array */ +#define BRCMF_SCAN_PARAMS_FIXED_SIZE 64 + +/* masks for channel and ssid count */ +#define BRCMF_SCAN_PARAMS_COUNT_MASK 0x0000ffff +#define BRCMF_SCAN_PARAMS_NSSID_SHIFT 16 + +#define BRCMF_SCAN_ACTION_START 1 +#define BRCMF_SCAN_ACTION_CONTINUE 2 +#define WL_SCAN_ACTION_ABORT 3 + +#define BRCMF_ISCAN_REQ_VERSION 1 + +/* brcmf_iscan_results status values */ +#define BRCMF_SCAN_RESULTS_SUCCESS 0 +#define BRCMF_SCAN_RESULTS_PARTIAL 1 +#define BRCMF_SCAN_RESULTS_PENDING 2 +#define BRCMF_SCAN_RESULTS_ABORTED 3 +#define BRCMF_SCAN_RESULTS_NO_MEM 4 + +/* Indicates this key is using soft encrypt */ +#define WL_SOFT_KEY (1 << 0) +/* primary (ie tx) key */ +#define BRCMF_PRIMARY_KEY (1 << 1) +/* Reserved for backward compat */ +#define WL_KF_RES_4 (1 << 4) +/* Reserved for backward compat */ +#define WL_KF_RES_5 (1 << 5) +/* Indicates a group key for a IBSS PEER */ +#define WL_IBSS_PEER_GROUP_KEY (1 << 6) + +/* For supporting multiple interfaces */ +#define BRCMF_MAX_IFS 16 +#define BRCMF_DEL_IF -0xe +#define BRCMF_BAD_IF -0xf + +#define DOT11_BSSTYPE_ANY 2 +#define DOT11_MAX_DEFAULT_KEYS 4 + +#define BRCMF_EVENT_MSG_LINK 0x01 +#define BRCMF_EVENT_MSG_FLUSHTXQ 0x02 +#define BRCMF_EVENT_MSG_GROUP 0x04 + +struct brcmf_event_msg { + __be16 version; + __be16 flags; + __be32 event_type; + __be32 status; + __be32 reason; + __be32 auth_type; + __be32 datalen; + u8 addr[ETH_ALEN]; + char ifname[IFNAMSIZ]; +} __packed; + +struct brcm_ethhdr { + u16 subtype; + u16 length; + u8 version; + u8 oui[3]; + u16 usr_subtype; +} __packed; + +struct brcmf_event { + struct ethhdr eth; + struct brcm_ethhdr hdr; + struct brcmf_event_msg msg; +} __packed; + +struct dngl_stats { + unsigned long rx_packets; /* total packets received */ + unsigned long tx_packets; /* total packets transmitted */ + unsigned long rx_bytes; /* total bytes received */ + unsigned long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* packets dropped by dongle */ + unsigned long tx_dropped; /* packets dropped by dongle */ + unsigned long multicast; /* multicast packets received */ +}; + +/* event codes sent by the dongle to this driver */ +#define BRCMF_E_SET_SSID 0 +#define BRCMF_E_JOIN 1 +#define BRCMF_E_START 2 +#define BRCMF_E_AUTH 3 +#define BRCMF_E_AUTH_IND 4 +#define BRCMF_E_DEAUTH 5 +#define BRCMF_E_DEAUTH_IND 6 +#define BRCMF_E_ASSOC 7 +#define BRCMF_E_ASSOC_IND 8 +#define BRCMF_E_REASSOC 9 +#define BRCMF_E_REASSOC_IND 10 +#define BRCMF_E_DISASSOC 11 +#define BRCMF_E_DISASSOC_IND 12 +#define BRCMF_E_QUIET_START 13 +#define BRCMF_E_QUIET_END 14 +#define BRCMF_E_BEACON_RX 15 +#define BRCMF_E_LINK 16 +#define BRCMF_E_MIC_ERROR 17 +#define BRCMF_E_NDIS_LINK 18 +#define BRCMF_E_ROAM 19 +#define BRCMF_E_TXFAIL 20 +#define BRCMF_E_PMKID_CACHE 21 +#define BRCMF_E_RETROGRADE_TSF 22 +#define BRCMF_E_PRUNE 23 +#define BRCMF_E_AUTOAUTH 24 +#define BRCMF_E_EAPOL_MSG 25 +#define BRCMF_E_SCAN_COMPLETE 26 +#define BRCMF_E_ADDTS_IND 27 +#define BRCMF_E_DELTS_IND 28 +#define BRCMF_E_BCNSENT_IND 29 +#define BRCMF_E_BCNRX_MSG 30 +#define BRCMF_E_BCNLOST_MSG 31 +#define BRCMF_E_ROAM_PREP 32 +#define BRCMF_E_PFN_NET_FOUND 33 +#define BRCMF_E_PFN_NET_LOST 34 +#define BRCMF_E_RESET_COMPLETE 35 +#define BRCMF_E_JOIN_START 36 +#define BRCMF_E_ROAM_START 37 +#define BRCMF_E_ASSOC_START 38 +#define BRCMF_E_IBSS_ASSOC 39 +#define BRCMF_E_RADIO 40 +#define BRCMF_E_PSM_WATCHDOG 41 +#define BRCMF_E_PROBREQ_MSG 44 +#define BRCMF_E_SCAN_CONFIRM_IND 45 +#define BRCMF_E_PSK_SUP 46 +#define BRCMF_E_COUNTRY_CODE_CHANGED 47 +#define BRCMF_E_EXCEEDED_MEDIUM_TIME 48 +#define BRCMF_E_ICV_ERROR 49 +#define BRCMF_E_UNICAST_DECODE_ERROR 50 +#define BRCMF_E_MULTICAST_DECODE_ERROR 51 +#define BRCMF_E_TRACE 52 +#define BRCMF_E_IF 54 +#define BRCMF_E_RSSI 56 +#define BRCMF_E_PFN_SCAN_COMPLETE 57 +#define BRCMF_E_EXTLOG_MSG 58 +#define BRCMF_E_ACTION_FRAME 59 +#define BRCMF_E_ACTION_FRAME_COMPLETE 60 +#define BRCMF_E_PRE_ASSOC_IND 61 +#define BRCMF_E_PRE_REASSOC_IND 62 +#define BRCMF_E_CHANNEL_ADOPTED 63 +#define BRCMF_E_AP_STARTED 64 +#define BRCMF_E_DFS_AP_STOP 65 +#define BRCMF_E_DFS_AP_RESUME 66 +#define BRCMF_E_RESERVED1 67 +#define BRCMF_E_RESERVED2 68 +#define BRCMF_E_ESCAN_RESULT 69 +#define BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE 70 +#define BRCMF_E_DCS_REQUEST 73 + +#define BRCMF_E_FIFO_CREDIT_MAP 74 + +#define BRCMF_E_LAST 75 + +#define BRCMF_E_STATUS_SUCCESS 0 +#define BRCMF_E_STATUS_FAIL 1 +#define BRCMF_E_STATUS_TIMEOUT 2 +#define BRCMF_E_STATUS_NO_NETWORKS 3 +#define BRCMF_E_STATUS_ABORT 4 +#define BRCMF_E_STATUS_NO_ACK 5 +#define BRCMF_E_STATUS_UNSOLICITED 6 +#define BRCMF_E_STATUS_ATTEMPT 7 +#define BRCMF_E_STATUS_PARTIAL 8 +#define BRCMF_E_STATUS_NEWSCAN 9 +#define BRCMF_E_STATUS_NEWASSOC 10 +#define BRCMF_E_STATUS_11HQUIET 11 +#define BRCMF_E_STATUS_SUPPRESS 12 +#define BRCMF_E_STATUS_NOCHANS 13 +#define BRCMF_E_STATUS_CS_ABORT 15 +#define BRCMF_E_STATUS_ERROR 16 + +#define BRCMF_E_REASON_INITIAL_ASSOC 0 +#define BRCMF_E_REASON_LOW_RSSI 1 +#define BRCMF_E_REASON_DEAUTH 2 +#define BRCMF_E_REASON_DISASSOC 3 +#define BRCMF_E_REASON_BCNS_LOST 4 +#define BRCMF_E_REASON_MINTXRATE 9 +#define BRCMF_E_REASON_TXFAIL 10 + +#define BRCMF_E_REASON_FAST_ROAM_FAILED 5 +#define BRCMF_E_REASON_DIRECTED_ROAM 6 +#define BRCMF_E_REASON_TSPEC_REJECTED 7 +#define BRCMF_E_REASON_BETTER_AP 8 + +#define BRCMF_E_PRUNE_ENCR_MISMATCH 1 +#define BRCMF_E_PRUNE_BCAST_BSSID 2 +#define BRCMF_E_PRUNE_MAC_DENY 3 +#define BRCMF_E_PRUNE_MAC_NA 4 +#define BRCMF_E_PRUNE_REG_PASSV 5 +#define BRCMF_E_PRUNE_SPCT_MGMT 6 +#define BRCMF_E_PRUNE_RADAR 7 +#define BRCMF_E_RSN_MISMATCH 8 +#define BRCMF_E_PRUNE_NO_COMMON_RATES 9 +#define BRCMF_E_PRUNE_BASIC_RATES 10 +#define BRCMF_E_PRUNE_CIPHER_NA 12 +#define BRCMF_E_PRUNE_KNOWN_STA 13 +#define BRCMF_E_PRUNE_WDS_PEER 15 +#define BRCMF_E_PRUNE_QBSS_LOAD 16 +#define BRCMF_E_PRUNE_HOME_AP 17 + +#define BRCMF_E_SUP_OTHER 0 +#define BRCMF_E_SUP_DECRYPT_KEY_DATA 1 +#define BRCMF_E_SUP_BAD_UCAST_WEP128 2 +#define BRCMF_E_SUP_BAD_UCAST_WEP40 3 +#define BRCMF_E_SUP_UNSUP_KEY_LEN 4 +#define BRCMF_E_SUP_PW_KEY_CIPHER 5 +#define BRCMF_E_SUP_MSG3_TOO_MANY_IE 6 +#define BRCMF_E_SUP_MSG3_IE_MISMATCH 7 +#define BRCMF_E_SUP_NO_INSTALL_FLAG 8 +#define BRCMF_E_SUP_MSG3_NO_GTK 9 +#define BRCMF_E_SUP_GRP_KEY_CIPHER 10 +#define BRCMF_E_SUP_GRP_MSG1_NO_GTK 11 +#define BRCMF_E_SUP_GTK_DECRYPT_FAIL 12 +#define BRCMF_E_SUP_SEND_FAIL 13 +#define BRCMF_E_SUP_DEAUTH 14 + +#define BRCMF_E_IF_ADD 1 +#define BRCMF_E_IF_DEL 2 +#define BRCMF_E_IF_CHANGE 3 + +#define BRCMF_E_IF_ROLE_STA 0 +#define BRCMF_E_IF_ROLE_AP 1 +#define BRCMF_E_IF_ROLE_WDS 2 + +#define BRCMF_E_LINK_BCN_LOSS 1 +#define BRCMF_E_LINK_DISASSOC 2 +#define BRCMF_E_LINK_ASSOC_REC 3 +#define BRCMF_E_LINK_BSSCFG_DIS 4 + +/* The level of bus communication with the dongle */ +enum brcmf_bus_state { + BRCMF_BUS_DOWN, /* Not ready for frame transfers */ + BRCMF_BUS_LOAD, /* Download access only (CPU reset) */ + BRCMF_BUS_DATA /* Ready for frame transfers */ +}; + +/* Pattern matching filter. Specifies an offset within received packets to + * start matching, the pattern to match, the size of the pattern, and a bitmask + * that indicates which bits within the pattern should be matched. + */ +struct brcmf_pkt_filter_pattern { + /* + * Offset within received packet to start pattern matching. + * Offset '0' is the first byte of the ethernet header. + */ + u32 offset; + /* Size of the pattern. Bitmask must be the same size.*/ + u32 size_bytes; + /* + * Variable length mask and pattern data. mask starts at offset 0. + * Pattern immediately follows mask. + */ + u8 mask_and_pattern[1]; +}; + +/* IOVAR "pkt_filter_add" parameter. Used to install packet filters. */ +struct brcmf_pkt_filter { + u32 id; /* Unique filter id, specified by app. */ + u32 type; /* Filter type (WL_PKT_FILTER_TYPE_xxx). */ + u32 negate_match; /* Negate the result of filter matches */ + union { /* Filter definitions */ + struct brcmf_pkt_filter_pattern pattern; /* Filter pattern */ + } u; +}; + +/* IOVAR "pkt_filter_enable" parameter. */ +struct brcmf_pkt_filter_enable { + u32 id; /* Unique filter id */ + u32 enable; /* Enable/disable bool */ +}; + +/* BSS info structure + * Applications MUST CHECK ie_offset field and length field to access IEs and + * next bss_info structure in a vector (in struct brcmf_scan_results) + */ +struct brcmf_bss_info { + __le32 version; /* version field */ + __le32 length; /* byte length of data in this record, + * starting at version and including IEs + */ + u8 BSSID[ETH_ALEN]; + __le16 beacon_period; /* units are Kusec */ + __le16 capability; /* Capability information */ + u8 SSID_len; + u8 SSID[32]; + struct { + __le32 count; /* # rates in this set */ + u8 rates[16]; /* rates in 500kbps units w/hi bit set if basic */ + } rateset; /* supported rates */ + __le16 chanspec; /* chanspec for bss */ + __le16 atim_window; /* units are Kusec */ + u8 dtim_period; /* DTIM period */ + __le16 RSSI; /* receive signal strength (in dBm) */ + s8 phy_noise; /* noise (in dBm) */ + + u8 n_cap; /* BSS is 802.11N Capable */ + /* 802.11N BSS Capabilities (based on HT_CAP_*): */ + __le32 nbss_cap; + u8 ctl_ch; /* 802.11N BSS control channel number */ + __le32 reserved32[1]; /* Reserved for expansion of BSS properties */ + u8 flags; /* flags */ + u8 reserved[3]; /* Reserved for expansion of BSS properties */ + u8 basic_mcs[MCSSET_LEN]; /* 802.11N BSS required MCS set */ + + __le16 ie_offset; /* offset at which IEs start, from beginning */ + __le32 ie_length; /* byte length of Information Elements */ + __le16 SNR; /* average SNR of during frame reception */ + /* Add new fields here */ + /* variable length Information Elements */ +}; + +struct brcm_rateset_le { + /* # rates in this set */ + __le32 count; + /* rates in 500kbps units w/hi bit set if basic */ + u8 rates[WL_NUMRATES]; +}; + +struct brcmf_ssid { + u32 SSID_len; + unsigned char SSID[32]; +}; + +struct brcmf_ssid_le { + __le32 SSID_len; + unsigned char SSID[32]; +}; + +struct brcmf_scan_params_le { + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[1]; /* list of chanspecs */ +}; + +/* incremental scan struct */ +struct brcmf_iscan_params_le { + __le32 version; + __le16 action; + __le16 scan_duration; + struct brcmf_scan_params_le params_le; +}; + +struct brcmf_scan_results { + u32 buflen; + u32 version; + u32 count; + struct brcmf_bss_info bss_info[1]; +}; + +struct brcmf_scan_results_le { + __le32 buflen; + __le32 version; + __le32 count; + struct brcmf_bss_info bss_info[1]; +}; + +/* used for association with a specific BSSID and chanspec list */ +struct brcmf_assoc_params_le { + /* 00:00:00:00:00:00: broadcast scan */ + u8 bssid[ETH_ALEN]; + /* 0: all available channels, otherwise count of chanspecs in + * chanspec_list */ + __le32 chanspec_num; + /* list of chanspecs */ + __le16 chanspec_list[1]; +}; + +/* used for join with or without a specific bssid and channel list */ +struct brcmf_join_params { + struct brcmf_ssid_le ssid_le; + struct brcmf_assoc_params_le params_le; +}; + +/* size of brcmf_scan_results not including variable length array */ +#define BRCMF_SCAN_RESULTS_FIXED_SIZE \ + (sizeof(struct brcmf_scan_results) - sizeof(struct brcmf_bss_info)) + +/* incremental scan results struct */ +struct brcmf_iscan_results { + union { + u32 status; + __le32 status_le; + }; + union { + struct brcmf_scan_results results; + struct brcmf_scan_results_le results_le; + }; +}; + +/* size of brcmf_iscan_results not including variable length array */ +#define BRCMF_ISCAN_RESULTS_FIXED_SIZE \ + (BRCMF_SCAN_RESULTS_FIXED_SIZE + \ + offsetof(struct brcmf_iscan_results, results)) + +struct brcmf_wsec_key { + u32 index; /* key index */ + u32 len; /* key length */ + u8 data[WLAN_MAX_KEY_LEN]; /* key data */ + u32 pad_1[18]; + u32 algo; /* CRYPTO_ALGO_AES_CCM, CRYPTO_ALGO_WEP128, etc */ + u32 flags; /* misc flags */ + u32 pad_2[3]; + u32 iv_initialized; /* has IV been initialized already? */ + u32 pad_3; + /* Rx IV */ + struct { + u32 hi; /* upper 32 bits of IV */ + u16 lo; /* lower 16 bits of IV */ + } rxiv; + u32 pad_4[2]; + u8 ea[ETH_ALEN]; /* per station */ +}; + +/* + * dongle requires same struct as above but with fields in little endian order + */ +struct brcmf_wsec_key_le { + __le32 index; /* key index */ + __le32 len; /* key length */ + u8 data[WLAN_MAX_KEY_LEN]; /* key data */ + __le32 pad_1[18]; + __le32 algo; /* CRYPTO_ALGO_AES_CCM, CRYPTO_ALGO_WEP128, etc */ + __le32 flags; /* misc flags */ + __le32 pad_2[3]; + __le32 iv_initialized; /* has IV been initialized already? */ + __le32 pad_3; + /* Rx IV */ + struct { + __le32 hi; /* upper 32 bits of IV */ + __le16 lo; /* lower 16 bits of IV */ + } rxiv; + __le32 pad_4[2]; + u8 ea[ETH_ALEN]; /* per station */ +}; + +/* Used to get specific STA parameters */ +struct brcmf_scb_val_le { + __le32 val; + u8 ea[ETH_ALEN]; +}; + +/* channel encoding */ +struct brcmf_channel_info_le { + __le32 hw_channel; + __le32 target_channel; + __le32 scan_channel; +}; + +/* Bus independent dongle command */ +struct brcmf_dcmd { + uint cmd; /* common dongle cmd definition */ + void *buf; /* pointer to user buffer */ + uint len; /* length of user buffer */ + u8 set; /* get or set request (optional) */ + uint used; /* bytes read or written (optional) */ + uint needed; /* bytes needed (optional) */ +}; + +/* Forward decls for struct brcmf_pub (see below) */ +struct brcmf_bus; /* device bus info */ +struct brcmf_proto; /* device communication protocol info */ +struct brcmf_info; /* device driver info */ +struct brcmf_cfg80211_dev; /* cfg80211 device info */ + +/* Common structure for module and instance linkage */ +struct brcmf_pub { + /* Linkage ponters */ + struct brcmf_bus *bus; + struct brcmf_proto *prot; + struct brcmf_info *info; + struct brcmf_cfg80211_dev *config; + + /* Internal brcmf items */ + bool up; /* Driver up/down (to OS) */ + bool txoff; /* Transmit flow-controlled */ + enum brcmf_bus_state busstate; + uint hdrlen; /* Total BRCMF header length (proto + bus) */ + uint maxctl; /* Max size rxctl request from proto to bus */ + uint rxsz; /* Rx buffer size bus module should use */ + u8 wme_dp; /* wme discard priority */ + + /* Dongle media info */ + bool iswl; /* Dongle-resident driver is wl */ + unsigned long drv_version; /* Version of dongle-resident driver */ + u8 mac[ETH_ALEN]; /* MAC address obtained from dongle */ + struct dngl_stats dstats; /* Stats for dongle-based data */ + + /* Additional stats for the bus level */ + + /* Data packets sent to dongle */ + unsigned long tx_packets; + /* Multicast data packets sent to dongle */ + unsigned long tx_multicast; + /* Errors in sending data to dongle */ + unsigned long tx_errors; + /* Control packets sent to dongle */ + unsigned long tx_ctlpkts; + /* Errors sending control frames to dongle */ + unsigned long tx_ctlerrs; + /* Packets sent up the network interface */ + unsigned long rx_packets; + /* Multicast packets sent up the network interface */ + unsigned long rx_multicast; + /* Errors processing rx data packets */ + unsigned long rx_errors; + /* Control frames processed from dongle */ + unsigned long rx_ctlpkts; + + /* Errors in processing rx control frames */ + unsigned long rx_ctlerrs; + /* Packets dropped locally (no memory) */ + unsigned long rx_dropped; + /* Packets flushed due to unscheduled sendup thread */ + unsigned long rx_flushed; + /* Number of times dpc scheduled by watchdog timer */ + unsigned long wd_dpc_sched; + + /* Number of packets where header read-ahead was used. */ + unsigned long rx_readahead_cnt; + /* Number of tx packets we had to realloc for headroom */ + unsigned long tx_realloc; + /* Number of flow control pkts recvd */ + unsigned long fc_packets; + + /* Last error return */ + int bcmerror; + uint tickcnt; + + /* Last error from dongle */ + int dongle_error; + + /* Suspend disable flag flag */ + int suspend_disable_flag; /* "1" to disable all extra powersaving + during suspend */ + int in_suspend; /* flag set to 1 when early suspend called */ + int dtim_skip; /* dtim skip , default 0 means wake each dtim */ + + /* Pkt filter defination */ + char *pktfilter[100]; + int pktfilter_count; + + u8 country_code[BRCM_CNTRY_BUF_SZ]; + char eventmask[BRCMF_EVENTING_MASK_LEN]; + +}; + +struct brcmf_if_event { + u8 ifidx; + u8 action; + u8 flags; + u8 bssidx; +}; + +struct bcmevent_name { + uint event; + const char *name; +}; + +extern const struct bcmevent_name bcmevent_names[]; + +/* Indication from bus module regarding presence/insertion of dongle. + * Return struct brcmf_pub pointer, used as handle to OS module in later calls. + * Returned structure should have bus and prot pointers filled in. + * bus_hdrlen specifies required headroom for bus module header. + */ +extern struct brcmf_pub *brcmf_attach(struct brcmf_bus *bus, + uint bus_hdrlen); +extern int brcmf_net_attach(struct brcmf_pub *drvr, int idx); +extern int brcmf_netdev_wait_pend8021x(struct net_device *ndev); + +extern s32 brcmf_exec_dcmd(struct net_device *dev, u32 cmd, void *arg, u32 len); + +/* Indication from bus module regarding removal/absence of dongle */ +extern void brcmf_detach(struct brcmf_pub *drvr); + +/* Indication from bus module to change flow-control state */ +extern void brcmf_txflowcontrol(struct brcmf_pub *drvr, int ifidx, bool on); + +extern bool brcmf_c_prec_enq(struct brcmf_pub *drvr, struct pktq *q, + struct sk_buff *pkt, int prec); + +/* Receive frame for delivery to OS. Callee disposes of rxp. */ +extern void brcmf_rx_frame(struct brcmf_pub *drvr, int ifidx, + struct sk_buff *rxp, int numpkt); + +/* Return pointer to interface name */ +extern char *brcmf_ifname(struct brcmf_pub *drvr, int idx); + +/* Notify tx completion */ +extern void brcmf_txcomplete(struct brcmf_pub *drvr, struct sk_buff *txp, + bool success); + +/* Query dongle */ +extern int brcmf_proto_cdc_query_dcmd(struct brcmf_pub *drvr, int ifidx, + uint cmd, void *buf, uint len); + +/* OS independent layer functions */ +extern int brcmf_os_proto_block(struct brcmf_pub *drvr); +extern int brcmf_os_proto_unblock(struct brcmf_pub *drvr); +#ifdef BCMDBG +extern int brcmf_write_to_file(struct brcmf_pub *drvr, u8 *buf, int size); +#endif /* BCMDBG */ + +extern int brcmf_ifname2idx(struct brcmf_info *drvr_priv, char *name); +extern int brcmf_c_host_event(struct brcmf_info *drvr_priv, int *idx, + void *pktdata, struct brcmf_event_msg *, + void **data_ptr); + +extern void brcmf_c_init(void); + +extern int brcmf_add_if(struct brcmf_info *drvr_priv, int ifidx, + struct net_device *ndev, char *name, u8 *mac_addr, + u32 flags, u8 bssidx); +extern void brcmf_del_if(struct brcmf_info *drvr_priv, int ifidx); + +/* Send packet to dongle via data channel */ +extern int brcmf_sendpkt(struct brcmf_pub *drvr, int ifidx,\ + struct sk_buff *pkt); + +extern int brcmf_bus_start(struct brcmf_pub *drvr); + +extern void brcmf_c_pktfilter_offload_set(struct brcmf_pub *drvr, char *arg); +extern void brcmf_c_pktfilter_offload_enable(struct brcmf_pub *drvr, char *arg, + int enable, int master_mode); + +#define BRCMF_DCMD_SMLEN 256 /* "small" cmd buffer required */ +#define BRCMF_DCMD_MEDLEN 1536 /* "med" cmd buffer required */ +#define BRCMF_DCMD_MAXLEN 8192 /* max length cmd buffer required */ + +/* message levels */ +#define BRCMF_ERROR_VAL 0x0001 +#define BRCMF_TRACE_VAL 0x0002 +#define BRCMF_INFO_VAL 0x0004 +#define BRCMF_DATA_VAL 0x0008 +#define BRCMF_CTL_VAL 0x0010 +#define BRCMF_TIMER_VAL 0x0020 +#define BRCMF_HDRS_VAL 0x0040 +#define BRCMF_BYTES_VAL 0x0080 +#define BRCMF_INTR_VAL 0x0100 +#define BRCMF_GLOM_VAL 0x0400 +#define BRCMF_EVENT_VAL 0x0800 +#define BRCMF_BTA_VAL 0x1000 +#define BRCMF_ISCAN_VAL 0x2000 + +/* Enter idle immediately (no timeout) */ +#define BRCMF_IDLE_IMMEDIATE (-1) +#define BRCMF_IDLE_ACTIVE 0 /* Do not request any SD clock change + when idle */ +#define BRCMF_IDLE_INTERVAL 1 + +#endif /* _BRCMF_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h new file mode 100644 index 000000000000..a249407c9a1b --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_BUS_H_ +#define _BRCMF_BUS_H_ + +/* Packet alignment for most efficient SDIO (can change based on platform) */ +#define BRCMF_SDALIGN (1 << 6) + +/* watchdog polling interval in ms */ +#define BRCMF_WD_POLL_MS 10 + +/* + * Exported from brcmf bus module (brcmf_usb, brcmf_sdio) + */ + +/* Indicate (dis)interest in finding dongles. */ +extern int brcmf_bus_register(void); +extern void brcmf_bus_unregister(void); + +/* obtain linux device object providing bus function */ +extern struct device *brcmf_bus_get_device(struct brcmf_bus *bus); + +/* Stop bus module: clear pending frames, disable data flow */ +extern void brcmf_sdbrcm_bus_stop(struct brcmf_bus *bus); + +/* Initialize bus module: prepare for communication w/dongle */ +extern int brcmf_sdbrcm_bus_init(struct brcmf_pub *drvr); + +/* Send a data frame to the dongle. Callee disposes of txp. */ +extern int brcmf_sdbrcm_bus_txdata(struct brcmf_bus *bus, struct sk_buff *txp); + +/* Send/receive a control message to/from the dongle. + * Expects caller to enforce a single outstanding transaction. + */ +extern int +brcmf_sdbrcm_bus_txctl(struct brcmf_bus *bus, unsigned char *msg, uint msglen); + +extern int +brcmf_sdbrcm_bus_rxctl(struct brcmf_bus *bus, unsigned char *msg, uint msglen); + +extern void brcmf_sdbrcm_wd_timer(struct brcmf_bus *bus, uint wdtick); + +#endif /* _BRCMF_BUS_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c new file mode 100644 index 000000000000..e34c5c3d1d55 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/******************************************************************************* + * Communicates with the dongle by using dcmd codes. + * For certain dcmd codes, the dongle interprets string data from the host. + ******************************************************************************/ + +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/sched.h> +#include <defs.h> + +#include <brcmu_utils.h> +#include <brcmu_wifi.h> + +#include "dhd.h" +#include "dhd_proto.h" +#include "dhd_bus.h" +#include "dhd_dbg.h" + +struct brcmf_proto_cdc_dcmd { + __le32 cmd; /* dongle command value */ + __le32 len; /* lower 16: output buflen; + * upper 16: input buflen (excludes header) */ + __le32 flags; /* flag defns given below */ + __le32 status; /* status code returned from the device */ +}; + +/* Max valid buffer size that can be sent to the dongle */ +#define CDC_MAX_MSG_SIZE (ETH_FRAME_LEN+ETH_FCS_LEN) + +/* CDC flag definitions */ +#define CDC_DCMD_ERROR 0x01 /* 1=cmd failed */ +#define CDC_DCMD_SET 0x02 /* 0=get, 1=set cmd */ +#define CDC_DCMD_IF_MASK 0xF000 /* I/F index */ +#define CDC_DCMD_IF_SHIFT 12 +#define CDC_DCMD_ID_MASK 0xFFFF0000 /* id an cmd pairing */ +#define CDC_DCMD_ID_SHIFT 16 /* ID Mask shift bits */ +#define CDC_DCMD_ID(flags) \ + (((flags) & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT) + +/* + * BDC header - Broadcom specific extension of CDC. + * Used on data packets to convey priority across USB. + */ +#define BDC_HEADER_LEN 4 +#define BDC_PROTO_VER 1 /* Protocol version */ +#define BDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */ +#define BDC_FLAG_VER_SHIFT 4 /* Protocol version shift */ +#define BDC_FLAG_SUM_GOOD 0x04 /* Good RX checksums */ +#define BDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */ +#define BDC_PRIORITY_MASK 0x7 +#define BDC_FLAG2_IF_MASK 0x0f /* packet rx interface in APSTA */ +#define BDC_FLAG2_IF_SHIFT 0 + +#define BDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags2) & BDC_FLAG2_IF_MASK) >> BDC_FLAG2_IF_SHIFT)) +#define BDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags2 = (((hdr)->flags2 & ~BDC_FLAG2_IF_MASK) | \ + ((idx) << BDC_FLAG2_IF_SHIFT))) + +struct brcmf_proto_bdc_header { + u8 flags; + u8 priority; /* 802.1d Priority, 4:7 flow control info for usb */ + u8 flags2; + u8 rssi; +}; + + +#define RETRIES 2 /* # of retries to retrieve matching dcmd response */ +#define BUS_HEADER_LEN (16+BRCMF_SDALIGN) /* Must be atleast SDPCM_RESERVE + * (amount of header tha might be added) + * plus any space that might be needed + * for alignment padding. + */ +#define ROUND_UP_MARGIN 2048 /* Biggest SDIO block size possible for + * round off at the end of buffer + */ + +struct brcmf_proto { + u16 reqid; + u8 pending; + u32 lastcmd; + u8 bus_header[BUS_HEADER_LEN]; + struct brcmf_proto_cdc_dcmd msg; + unsigned char buf[BRCMF_DCMD_MAXLEN + ROUND_UP_MARGIN]; +}; + +static int brcmf_proto_cdc_msg(struct brcmf_pub *drvr) +{ + struct brcmf_proto *prot = drvr->prot; + int len = le32_to_cpu(prot->msg.len) + + sizeof(struct brcmf_proto_cdc_dcmd); + + brcmf_dbg(TRACE, "Enter\n"); + + /* NOTE : cdc->msg.len holds the desired length of the buffer to be + * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area + * is actually sent to the dongle + */ + if (len > CDC_MAX_MSG_SIZE) + len = CDC_MAX_MSG_SIZE; + + /* Send request */ + return brcmf_sdbrcm_bus_txctl(drvr->bus, (unsigned char *)&prot->msg, + len); +} + +static int brcmf_proto_cdc_cmplt(struct brcmf_pub *drvr, u32 id, u32 len) +{ + int ret; + struct brcmf_proto *prot = drvr->prot; + + brcmf_dbg(TRACE, "Enter\n"); + + do { + ret = brcmf_sdbrcm_bus_rxctl(drvr->bus, + (unsigned char *)&prot->msg, + len + sizeof(struct brcmf_proto_cdc_dcmd)); + if (ret < 0) + break; + } while (CDC_DCMD_ID(le32_to_cpu(prot->msg.flags)) != id); + + return ret; +} + +int +brcmf_proto_cdc_query_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd, + void *buf, uint len) +{ + struct brcmf_proto *prot = drvr->prot; + struct brcmf_proto_cdc_dcmd *msg = &prot->msg; + void *info; + int ret = 0, retries = 0; + u32 id, flags; + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(CTL, "cmd %d len %d\n", cmd, len); + + /* Respond "bcmerror" and "bcmerrorstr" with local cache */ + if (cmd == BRCMF_C_GET_VAR && buf) { + if (!strcmp((char *)buf, "bcmerrorstr")) { + strncpy((char *)buf, "bcm_error", + BCME_STRLEN); + goto done; + } else if (!strcmp((char *)buf, "bcmerror")) { + *(int *)buf = drvr->dongle_error; + goto done; + } + } + + memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd)); + + msg->cmd = cpu_to_le32(cmd); + msg->len = cpu_to_le32(len); + flags = (++prot->reqid << CDC_DCMD_ID_SHIFT); + flags = (flags & ~CDC_DCMD_IF_MASK) | + (ifidx << CDC_DCMD_IF_SHIFT); + msg->flags = cpu_to_le32(flags); + + if (buf) + memcpy(prot->buf, buf, len); + + ret = brcmf_proto_cdc_msg(drvr); + if (ret < 0) { + brcmf_dbg(ERROR, "brcmf_proto_cdc_msg failed w/status %d\n", + ret); + goto done; + } + +retry: + /* wait for interrupt and get first fragment */ + ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len); + if (ret < 0) + goto done; + + flags = le32_to_cpu(msg->flags); + id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT; + + if ((id < prot->reqid) && (++retries < RETRIES)) + goto retry; + if (id != prot->reqid) { + brcmf_dbg(ERROR, "%s: unexpected request id %d (expected %d)\n", + brcmf_ifname(drvr, ifidx), id, prot->reqid); + ret = -EINVAL; + goto done; + } + + /* Check info buffer */ + info = (void *)&msg[1]; + + /* Copy info buffer */ + if (buf) { + if (ret < (int)len) + len = ret; + memcpy(buf, info, len); + } + + /* Check the ERROR flag */ + if (flags & CDC_DCMD_ERROR) { + ret = le32_to_cpu(msg->status); + /* Cache error from dongle */ + drvr->dongle_error = ret; + } + +done: + return ret; +} + +int brcmf_proto_cdc_set_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd, + void *buf, uint len) +{ + struct brcmf_proto *prot = drvr->prot; + struct brcmf_proto_cdc_dcmd *msg = &prot->msg; + int ret = 0; + u32 flags, id; + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(CTL, "cmd %d len %d\n", cmd, len); + + memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd)); + + msg->cmd = cpu_to_le32(cmd); + msg->len = cpu_to_le32(len); + flags = (++prot->reqid << CDC_DCMD_ID_SHIFT) | CDC_DCMD_SET; + flags = (flags & ~CDC_DCMD_IF_MASK) | + (ifidx << CDC_DCMD_IF_SHIFT); + msg->flags = cpu_to_le32(flags); + + if (buf) + memcpy(prot->buf, buf, len); + + ret = brcmf_proto_cdc_msg(drvr); + if (ret < 0) + goto done; + + ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len); + if (ret < 0) + goto done; + + flags = le32_to_cpu(msg->flags); + id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT; + + if (id != prot->reqid) { + brcmf_dbg(ERROR, "%s: unexpected request id %d (expected %d)\n", + brcmf_ifname(drvr, ifidx), id, prot->reqid); + ret = -EINVAL; + goto done; + } + + /* Check the ERROR flag */ + if (flags & CDC_DCMD_ERROR) { + ret = le32_to_cpu(msg->status); + /* Cache error from dongle */ + drvr->dongle_error = ret; + } + +done: + return ret; +} + +int +brcmf_proto_dcmd(struct brcmf_pub *drvr, int ifidx, struct brcmf_dcmd *dcmd, + int len) +{ + struct brcmf_proto *prot = drvr->prot; + int ret = -1; + + if (drvr->busstate == BRCMF_BUS_DOWN) { + brcmf_dbg(ERROR, "bus is down. we have nothing to do.\n"); + return ret; + } + brcmf_os_proto_block(drvr); + + brcmf_dbg(TRACE, "Enter\n"); + + if (len > BRCMF_DCMD_MAXLEN) + goto done; + + if (prot->pending == true) { + brcmf_dbg(TRACE, "CDC packet is pending!!!! cmd=0x%x (%lu) lastcmd=0x%x (%lu)\n", + dcmd->cmd, (unsigned long)dcmd->cmd, prot->lastcmd, + (unsigned long)prot->lastcmd); + if (dcmd->cmd == BRCMF_C_SET_VAR || + dcmd->cmd == BRCMF_C_GET_VAR) + brcmf_dbg(TRACE, "iovar cmd=%s\n", (char *)dcmd->buf); + + goto done; + } + + prot->pending = true; + prot->lastcmd = dcmd->cmd; + if (dcmd->set) + ret = brcmf_proto_cdc_set_dcmd(drvr, ifidx, dcmd->cmd, + dcmd->buf, len); + else { + ret = brcmf_proto_cdc_query_dcmd(drvr, ifidx, dcmd->cmd, + dcmd->buf, len); + if (ret > 0) + dcmd->used = ret - + sizeof(struct brcmf_proto_cdc_dcmd); + } + + if (ret >= 0) + ret = 0; + else { + struct brcmf_proto_cdc_dcmd *msg = &prot->msg; + /* len == needed when set/query fails from dongle */ + dcmd->needed = le32_to_cpu(msg->len); + } + + /* Intercept the wme_dp dongle cmd here */ + if (!ret && dcmd->cmd == BRCMF_C_SET_VAR && + !strcmp(dcmd->buf, "wme_dp")) { + int slen; + __le32 val = 0; + + slen = strlen("wme_dp") + 1; + if (len >= (int)(slen + sizeof(int))) + memcpy(&val, (char *)dcmd->buf + slen, sizeof(int)); + drvr->wme_dp = (u8) le32_to_cpu(val); + } + + prot->pending = false; + +done: + brcmf_os_proto_unblock(drvr); + + return ret; +} + +static bool pkt_sum_needed(struct sk_buff *skb) +{ + return skb->ip_summed == CHECKSUM_PARTIAL; +} + +static void pkt_set_sum_good(struct sk_buff *skb, bool x) +{ + skb->ip_summed = (x ? CHECKSUM_UNNECESSARY : CHECKSUM_NONE); +} + +void brcmf_proto_hdrpush(struct brcmf_pub *drvr, int ifidx, + struct sk_buff *pktbuf) +{ + struct brcmf_proto_bdc_header *h; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Push BDC header used to convey priority for buses that don't */ + + skb_push(pktbuf, BDC_HEADER_LEN); + + h = (struct brcmf_proto_bdc_header *)(pktbuf->data); + + h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT); + if (pkt_sum_needed(pktbuf)) + h->flags |= BDC_FLAG_SUM_NEEDED; + + h->priority = (pktbuf->priority & BDC_PRIORITY_MASK); + h->flags2 = 0; + h->rssi = 0; + BDC_SET_IF_IDX(h, ifidx); +} + +int brcmf_proto_hdrpull(struct brcmf_pub *drvr, int *ifidx, + struct sk_buff *pktbuf) +{ + struct brcmf_proto_bdc_header *h; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Pop BDC header used to convey priority for buses that don't */ + + if (pktbuf->len < BDC_HEADER_LEN) { + brcmf_dbg(ERROR, "rx data too short (%d < %d)\n", + pktbuf->len, BDC_HEADER_LEN); + return -EBADE; + } + + h = (struct brcmf_proto_bdc_header *)(pktbuf->data); + + *ifidx = BDC_GET_IF_IDX(h); + if (*ifidx >= BRCMF_MAX_IFS) { + brcmf_dbg(ERROR, "rx data ifnum out of range (%d)\n", *ifidx); + return -EBADE; + } + + if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) != + BDC_PROTO_VER) { + brcmf_dbg(ERROR, "%s: non-BDC packet received, flags 0x%x\n", + brcmf_ifname(drvr, *ifidx), h->flags); + return -EBADE; + } + + if (h->flags & BDC_FLAG_SUM_GOOD) { + brcmf_dbg(INFO, "%s: BDC packet received with good rx-csum, flags 0x%x\n", + brcmf_ifname(drvr, *ifidx), h->flags); + pkt_set_sum_good(pktbuf, true); + } + + pktbuf->priority = h->priority & BDC_PRIORITY_MASK; + + skb_pull(pktbuf, BDC_HEADER_LEN); + + return 0; +} + +int brcmf_proto_attach(struct brcmf_pub *drvr) +{ + struct brcmf_proto *cdc; + + cdc = kzalloc(sizeof(struct brcmf_proto), GFP_ATOMIC); + if (!cdc) + goto fail; + + /* ensure that the msg buf directly follows the cdc msg struct */ + if ((unsigned long)(&cdc->msg + 1) != (unsigned long)cdc->buf) { + brcmf_dbg(ERROR, "struct brcmf_proto is not correctly defined\n"); + goto fail; + } + + drvr->prot = cdc; + drvr->hdrlen += BDC_HEADER_LEN; + drvr->maxctl = BRCMF_DCMD_MAXLEN + + sizeof(struct brcmf_proto_cdc_dcmd) + ROUND_UP_MARGIN; + return 0; + +fail: + kfree(cdc); + return -ENOMEM; +} + +/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */ +void brcmf_proto_detach(struct brcmf_pub *drvr) +{ + kfree(drvr->prot); + drvr->prot = NULL; +} + +void brcmf_proto_dstats(struct brcmf_pub *drvr) +{ + /* No stats from dongle added yet, copy bus stats */ + drvr->dstats.tx_packets = drvr->tx_packets; + drvr->dstats.tx_errors = drvr->tx_errors; + drvr->dstats.rx_packets = drvr->rx_packets; + drvr->dstats.rx_errors = drvr->rx_errors; + drvr->dstats.rx_dropped = drvr->rx_dropped; + drvr->dstats.multicast = drvr->rx_multicast; + return; +} + +int brcmf_proto_init(struct brcmf_pub *drvr) +{ + int ret = 0; + char buf[128]; + + brcmf_dbg(TRACE, "Enter\n"); + + brcmf_os_proto_block(drvr); + + /* Get the device MAC address */ + strcpy(buf, "cur_etheraddr"); + ret = brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR, + buf, sizeof(buf)); + if (ret < 0) { + brcmf_os_proto_unblock(drvr); + return ret; + } + memcpy(drvr->mac, buf, ETH_ALEN); + + brcmf_os_proto_unblock(drvr); + + ret = brcmf_c_preinit_dcmds(drvr); + + /* Always assumes wl for now */ + drvr->iswl = true; + + return ret; +} + +void brcmf_proto_stop(struct brcmf_pub *drvr) +{ + /* Nothing to do for CDC */ +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c new file mode 100644 index 000000000000..4075fd74dd92 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c @@ -0,0 +1,872 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/netdevice.h> +#include <asm/unaligned.h> +#include <defs.h> +#include <brcmu_wifi.h> +#include <brcmu_utils.h> +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" + +#define BRCM_OUI "\x00\x10\x18" +#define DOT11_OUI_LEN 3 +#define BCMILCP_BCM_SUBTYPE_EVENT 1 +#define PKTFILTER_BUF_SIZE 2048 +#define BRCMF_ARPOL_MODE 0xb /* agent|snoop|peer_autoreply */ + +int brcmf_msg_level; + +#define MSGTRACE_VERSION 1 + +#define BRCMF_PKT_FILTER_FIXED_LEN offsetof(struct brcmf_pkt_filter, u) +#define BRCMF_PKT_FILTER_PATTERN_FIXED_LEN \ + offsetof(struct brcmf_pkt_filter_pattern, mask_and_pattern) + +#ifdef BCMDBG +static const char brcmf_version[] = + "Dongle Host Driver, version " BRCMF_VERSION_STR "\nCompiled on " + __DATE__ " at " __TIME__; +#else +static const char brcmf_version[] = + "Dongle Host Driver, version " BRCMF_VERSION_STR; +#endif + +/* Message trace header */ +struct msgtrace_hdr { + u8 version; + u8 spare; + __be16 len; /* Len of the trace */ + __be32 seqnum; /* Sequence number of message. Useful + * if the messsage has been lost + * because of DMA error or a bus reset + * (ex: SDIO Func2) + */ + __be32 discarded_bytes; /* Number of discarded bytes because of + trace overflow */ + __be32 discarded_printf; /* Number of discarded printf + because of trace overflow */ +} __packed; + +void brcmf_c_init(void) +{ + /* Init global variables at run-time, not as part of the declaration. + * This is required to support init/de-init of the driver. + * Initialization + * of globals as part of the declaration results in non-deterministic + * behaviour since the value of the globals may be different on the + * first time that the driver is initialized vs subsequent + * initializations. + */ + brcmf_msg_level = BRCMF_ERROR_VAL; +} + +bool brcmf_c_prec_enq(struct brcmf_pub *drvr, struct pktq *q, + struct sk_buff *pkt, int prec) +{ + struct sk_buff *p; + int eprec = -1; /* precedence to evict from */ + bool discard_oldest; + + /* Fast case, precedence queue is not full and we are also not + * exceeding total queue length + */ + if (!pktq_pfull(q, prec) && !pktq_full(q)) { + brcmu_pktq_penq(q, prec, pkt); + return true; + } + + /* Determine precedence from which to evict packet, if any */ + if (pktq_pfull(q, prec)) + eprec = prec; + else if (pktq_full(q)) { + p = brcmu_pktq_peek_tail(q, &eprec); + if (eprec > prec) + return false; + } + + /* Evict if needed */ + if (eprec >= 0) { + /* Detect queueing to unconfigured precedence */ + discard_oldest = ac_bitmap_tst(drvr->wme_dp, eprec); + if (eprec == prec && !discard_oldest) + return false; /* refuse newer (incoming) packet */ + /* Evict packet according to discard policy */ + p = discard_oldest ? brcmu_pktq_pdeq(q, eprec) : + brcmu_pktq_pdeq_tail(q, eprec); + if (p == NULL) + brcmf_dbg(ERROR, "brcmu_pktq_penq() failed, oldest %d\n", + discard_oldest); + + brcmu_pkt_buf_free_skb(p); + } + + /* Enqueue */ + p = brcmu_pktq_penq(q, prec, pkt); + if (p == NULL) + brcmf_dbg(ERROR, "brcmu_pktq_penq() failed\n"); + + return p != NULL; +} + +#ifdef BCMDBG +static void +brcmf_c_show_host_event(struct brcmf_event_msg *event, void *event_data) +{ + uint i, status, reason; + bool group = false, flush_txq = false, link = false; + char *auth_str, *event_name; + unsigned char *buf; + char err_msg[256], eabuf[ETHER_ADDR_STR_LEN]; + static struct { + uint event; + char *event_name; + } event_names[] = { + { + BRCMF_E_SET_SSID, "SET_SSID"}, { + BRCMF_E_JOIN, "JOIN"}, { + BRCMF_E_START, "START"}, { + BRCMF_E_AUTH, "AUTH"}, { + BRCMF_E_AUTH_IND, "AUTH_IND"}, { + BRCMF_E_DEAUTH, "DEAUTH"}, { + BRCMF_E_DEAUTH_IND, "DEAUTH_IND"}, { + BRCMF_E_ASSOC, "ASSOC"}, { + BRCMF_E_ASSOC_IND, "ASSOC_IND"}, { + BRCMF_E_REASSOC, "REASSOC"}, { + BRCMF_E_REASSOC_IND, "REASSOC_IND"}, { + BRCMF_E_DISASSOC, "DISASSOC"}, { + BRCMF_E_DISASSOC_IND, "DISASSOC_IND"}, { + BRCMF_E_QUIET_START, "START_QUIET"}, { + BRCMF_E_QUIET_END, "END_QUIET"}, { + BRCMF_E_BEACON_RX, "BEACON_RX"}, { + BRCMF_E_LINK, "LINK"}, { + BRCMF_E_MIC_ERROR, "MIC_ERROR"}, { + BRCMF_E_NDIS_LINK, "NDIS_LINK"}, { + BRCMF_E_ROAM, "ROAM"}, { + BRCMF_E_TXFAIL, "TXFAIL"}, { + BRCMF_E_PMKID_CACHE, "PMKID_CACHE"}, { + BRCMF_E_RETROGRADE_TSF, "RETROGRADE_TSF"}, { + BRCMF_E_PRUNE, "PRUNE"}, { + BRCMF_E_AUTOAUTH, "AUTOAUTH"}, { + BRCMF_E_EAPOL_MSG, "EAPOL_MSG"}, { + BRCMF_E_SCAN_COMPLETE, "SCAN_COMPLETE"}, { + BRCMF_E_ADDTS_IND, "ADDTS_IND"}, { + BRCMF_E_DELTS_IND, "DELTS_IND"}, { + BRCMF_E_BCNSENT_IND, "BCNSENT_IND"}, { + BRCMF_E_BCNRX_MSG, "BCNRX_MSG"}, { + BRCMF_E_BCNLOST_MSG, "BCNLOST_MSG"}, { + BRCMF_E_ROAM_PREP, "ROAM_PREP"}, { + BRCMF_E_PFN_NET_FOUND, "PNO_NET_FOUND"}, { + BRCMF_E_PFN_NET_LOST, "PNO_NET_LOST"}, { + BRCMF_E_RESET_COMPLETE, "RESET_COMPLETE"}, { + BRCMF_E_JOIN_START, "JOIN_START"}, { + BRCMF_E_ROAM_START, "ROAM_START"}, { + BRCMF_E_ASSOC_START, "ASSOC_START"}, { + BRCMF_E_IBSS_ASSOC, "IBSS_ASSOC"}, { + BRCMF_E_RADIO, "RADIO"}, { + BRCMF_E_PSM_WATCHDOG, "PSM_WATCHDOG"}, { + BRCMF_E_PROBREQ_MSG, "PROBREQ_MSG"}, { + BRCMF_E_SCAN_CONFIRM_IND, "SCAN_CONFIRM_IND"}, { + BRCMF_E_PSK_SUP, "PSK_SUP"}, { + BRCMF_E_COUNTRY_CODE_CHANGED, "COUNTRY_CODE_CHANGED"}, { + BRCMF_E_EXCEEDED_MEDIUM_TIME, "EXCEEDED_MEDIUM_TIME"}, { + BRCMF_E_ICV_ERROR, "ICV_ERROR"}, { + BRCMF_E_UNICAST_DECODE_ERROR, "UNICAST_DECODE_ERROR"}, { + BRCMF_E_MULTICAST_DECODE_ERROR, "MULTICAST_DECODE_ERROR"}, { + BRCMF_E_TRACE, "TRACE"}, { + BRCMF_E_ACTION_FRAME, "ACTION FRAME"}, { + BRCMF_E_ACTION_FRAME_COMPLETE, "ACTION FRAME TX COMPLETE"}, { + BRCMF_E_IF, "IF"}, { + BRCMF_E_RSSI, "RSSI"}, { + BRCMF_E_PFN_SCAN_COMPLETE, "SCAN_COMPLETE"} + }; + uint event_type, flags, auth_type, datalen; + static u32 seqnum_prev; + struct msgtrace_hdr hdr; + u32 nblost; + char *s, *p; + + event_type = be32_to_cpu(event->event_type); + flags = be16_to_cpu(event->flags); + status = be32_to_cpu(event->status); + reason = be32_to_cpu(event->reason); + auth_type = be32_to_cpu(event->auth_type); + datalen = be32_to_cpu(event->datalen); + /* debug dump of event messages */ + sprintf(eabuf, "%pM", event->addr); + + event_name = "UNKNOWN"; + for (i = 0; i < ARRAY_SIZE(event_names); i++) { + if (event_names[i].event == event_type) + event_name = event_names[i].event_name; + } + + brcmf_dbg(EVENT, "EVENT: %s, event ID = %d\n", event_name, event_type); + brcmf_dbg(EVENT, "flags 0x%04x, status %d, reason %d, auth_type %d MAC %s\n", + flags, status, reason, auth_type, eabuf); + + if (flags & BRCMF_EVENT_MSG_LINK) + link = true; + if (flags & BRCMF_EVENT_MSG_GROUP) + group = true; + if (flags & BRCMF_EVENT_MSG_FLUSHTXQ) + flush_txq = true; + + switch (event_type) { + case BRCMF_E_START: + case BRCMF_E_DEAUTH: + case BRCMF_E_DISASSOC: + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s\n", event_name, eabuf); + break; + + case BRCMF_E_ASSOC_IND: + case BRCMF_E_REASSOC_IND: + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s\n", event_name, eabuf); + break; + + case BRCMF_E_ASSOC: + case BRCMF_E_REASSOC: + if (status == BRCMF_E_STATUS_SUCCESS) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, SUCCESS\n", + event_name, eabuf); + else if (status == BRCMF_E_STATUS_TIMEOUT) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, TIMEOUT\n", + event_name, eabuf); + else if (status == BRCMF_E_STATUS_FAIL) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, FAILURE, reason %d\n", + event_name, eabuf, (int)reason); + else + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, unexpected status %d\n", + event_name, eabuf, (int)status); + break; + + case BRCMF_E_DEAUTH_IND: + case BRCMF_E_DISASSOC_IND: + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, reason %d\n", + event_name, eabuf, (int)reason); + break; + + case BRCMF_E_AUTH: + case BRCMF_E_AUTH_IND: + if (auth_type == WLAN_AUTH_OPEN) + auth_str = "Open System"; + else if (auth_type == WLAN_AUTH_SHARED_KEY) + auth_str = "Shared Key"; + else { + sprintf(err_msg, "AUTH unknown: %d", (int)auth_type); + auth_str = err_msg; + } + if (event_type == BRCMF_E_AUTH_IND) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, %s\n", + event_name, eabuf, auth_str); + else if (status == BRCMF_E_STATUS_SUCCESS) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, %s, SUCCESS\n", + event_name, eabuf, auth_str); + else if (status == BRCMF_E_STATUS_TIMEOUT) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, %s, TIMEOUT\n", + event_name, eabuf, auth_str); + else if (status == BRCMF_E_STATUS_FAIL) { + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, %s, FAILURE, reason %d\n", + event_name, eabuf, auth_str, (int)reason); + } + + break; + + case BRCMF_E_JOIN: + case BRCMF_E_ROAM: + case BRCMF_E_SET_SSID: + if (status == BRCMF_E_STATUS_SUCCESS) + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s\n", + event_name, eabuf); + else if (status == BRCMF_E_STATUS_FAIL) + brcmf_dbg(EVENT, "MACEVENT: %s, failed\n", event_name); + else if (status == BRCMF_E_STATUS_NO_NETWORKS) + brcmf_dbg(EVENT, "MACEVENT: %s, no networks found\n", + event_name); + else + brcmf_dbg(EVENT, "MACEVENT: %s, unexpected status %d\n", + event_name, (int)status); + break; + + case BRCMF_E_BEACON_RX: + if (status == BRCMF_E_STATUS_SUCCESS) + brcmf_dbg(EVENT, "MACEVENT: %s, SUCCESS\n", event_name); + else if (status == BRCMF_E_STATUS_FAIL) + brcmf_dbg(EVENT, "MACEVENT: %s, FAIL\n", event_name); + else + brcmf_dbg(EVENT, "MACEVENT: %s, status %d\n", + event_name, status); + break; + + case BRCMF_E_LINK: + brcmf_dbg(EVENT, "MACEVENT: %s %s\n", + event_name, link ? "UP" : "DOWN"); + break; + + case BRCMF_E_MIC_ERROR: + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s, Group %d, Flush %d\n", + event_name, eabuf, group, flush_txq); + break; + + case BRCMF_E_ICV_ERROR: + case BRCMF_E_UNICAST_DECODE_ERROR: + case BRCMF_E_MULTICAST_DECODE_ERROR: + brcmf_dbg(EVENT, "MACEVENT: %s, MAC %s\n", event_name, eabuf); + break; + + case BRCMF_E_TXFAIL: + brcmf_dbg(EVENT, "MACEVENT: %s, RA %s\n", event_name, eabuf); + break; + + case BRCMF_E_SCAN_COMPLETE: + case BRCMF_E_PMKID_CACHE: + brcmf_dbg(EVENT, "MACEVENT: %s\n", event_name); + break; + + case BRCMF_E_PFN_NET_FOUND: + case BRCMF_E_PFN_NET_LOST: + case BRCMF_E_PFN_SCAN_COMPLETE: + brcmf_dbg(EVENT, "PNOEVENT: %s\n", event_name); + break; + + case BRCMF_E_PSK_SUP: + case BRCMF_E_PRUNE: + brcmf_dbg(EVENT, "MACEVENT: %s, status %d, reason %d\n", + event_name, (int)status, (int)reason); + break; + + case BRCMF_E_TRACE: + buf = (unsigned char *) event_data; + memcpy(&hdr, buf, sizeof(struct msgtrace_hdr)); + + if (hdr.version != MSGTRACE_VERSION) { + brcmf_dbg(ERROR, + "MACEVENT: %s [unsupported version --> brcmf" + " version:%d dongle version:%d]\n", + event_name, MSGTRACE_VERSION, hdr.version); + /* Reset datalen to avoid display below */ + datalen = 0; + break; + } + + /* There are 2 bytes available at the end of data */ + *(buf + sizeof(struct msgtrace_hdr) + + be16_to_cpu(hdr.len)) = '\0'; + + if (be32_to_cpu(hdr.discarded_bytes) + || be32_to_cpu(hdr.discarded_printf)) + brcmf_dbg(ERROR, + "WLC_E_TRACE: [Discarded traces in dongle -->" + " discarded_bytes %d discarded_printf %d]\n", + be32_to_cpu(hdr.discarded_bytes), + be32_to_cpu(hdr.discarded_printf)); + + nblost = be32_to_cpu(hdr.seqnum) - seqnum_prev - 1; + if (nblost > 0) + brcmf_dbg(ERROR, "WLC_E_TRACE: [Event lost --> seqnum " + " %d nblost %d\n", be32_to_cpu(hdr.seqnum), + nblost); + seqnum_prev = be32_to_cpu(hdr.seqnum); + + /* Display the trace buffer. Advance from \n to \n to + * avoid display big + * printf (issue with Linux printk ) + */ + p = (char *)&buf[sizeof(struct msgtrace_hdr)]; + while ((s = strstr(p, "\n")) != NULL) { + *s = '\0'; + printk(KERN_DEBUG"%s\n", p); + p = s + 1; + } + printk(KERN_DEBUG "%s\n", p); + + /* Reset datalen to avoid display below */ + datalen = 0; + break; + + case BRCMF_E_RSSI: + brcmf_dbg(EVENT, "MACEVENT: %s %d\n", + event_name, be32_to_cpu(*((__be32 *)event_data))); + break; + + default: + brcmf_dbg(EVENT, + "MACEVENT: %s %d, MAC %s, status %d, reason %d, " + "auth %d\n", event_name, event_type, eabuf, + (int)status, (int)reason, (int)auth_type); + break; + } + + /* show any appended data */ + if (datalen) { + buf = (unsigned char *) event_data; + brcmf_dbg(EVENT, " data (%d) : ", datalen); + for (i = 0; i < datalen; i++) + brcmf_dbg(EVENT, " 0x%02x ", *buf++); + brcmf_dbg(EVENT, "\n"); + } +} +#endif /* BCMDBG */ + +int +brcmf_c_host_event(struct brcmf_info *drvr_priv, int *ifidx, void *pktdata, + struct brcmf_event_msg *event, void **data_ptr) +{ + /* check whether packet is a BRCM event pkt */ + struct brcmf_event *pvt_data = (struct brcmf_event *) pktdata; + struct brcmf_if_event *ifevent; + char *event_data; + u32 type, status; + u16 flags; + int evlen; + + if (memcmp(BRCM_OUI, &pvt_data->hdr.oui[0], DOT11_OUI_LEN)) { + brcmf_dbg(ERROR, "mismatched OUI, bailing\n"); + return -EBADE; + } + + /* BRCM event pkt may be unaligned - use xxx_ua to load user_subtype. */ + if (get_unaligned_be16(&pvt_data->hdr.usr_subtype) != + BCMILCP_BCM_SUBTYPE_EVENT) { + brcmf_dbg(ERROR, "mismatched subtype, bailing\n"); + return -EBADE; + } + + *data_ptr = &pvt_data[1]; + event_data = *data_ptr; + + /* memcpy since BRCM event pkt may be unaligned. */ + memcpy(event, &pvt_data->msg, sizeof(struct brcmf_event_msg)); + + type = get_unaligned_be32(&event->event_type); + flags = get_unaligned_be16(&event->flags); + status = get_unaligned_be32(&event->status); + evlen = get_unaligned_be32(&event->datalen) + + sizeof(struct brcmf_event); + + switch (type) { + case BRCMF_E_IF: + ifevent = (struct brcmf_if_event *) event_data; + brcmf_dbg(TRACE, "if event\n"); + + if (ifevent->ifidx > 0 && ifevent->ifidx < BRCMF_MAX_IFS) { + if (ifevent->action == BRCMF_E_IF_ADD) + brcmf_add_if(drvr_priv, ifevent->ifidx, NULL, + event->ifname, + pvt_data->eth.h_dest, + ifevent->flags, ifevent->bssidx); + else + brcmf_del_if(drvr_priv, ifevent->ifidx); + } else { + brcmf_dbg(ERROR, "Invalid ifidx %d for %s\n", + ifevent->ifidx, event->ifname); + } + + /* send up the if event: btamp user needs it */ + *ifidx = brcmf_ifname2idx(drvr_priv, event->ifname); + break; + + /* These are what external supplicant/authenticator wants */ + case BRCMF_E_LINK: + case BRCMF_E_ASSOC_IND: + case BRCMF_E_REASSOC_IND: + case BRCMF_E_DISASSOC_IND: + case BRCMF_E_MIC_ERROR: + default: + /* Fall through: this should get _everything_ */ + + *ifidx = brcmf_ifname2idx(drvr_priv, event->ifname); + brcmf_dbg(TRACE, "MAC event %d, flags %x, status %x\n", + type, flags, status); + + /* put it back to BRCMF_E_NDIS_LINK */ + if (type == BRCMF_E_NDIS_LINK) { + u32 temp1; + __be32 temp2; + + temp1 = get_unaligned_be32(&event->event_type); + brcmf_dbg(TRACE, "Converted to WLC_E_LINK type %d\n", + temp1); + + temp2 = cpu_to_be32(BRCMF_E_NDIS_LINK); + memcpy((void *)(&pvt_data->msg.event_type), &temp2, + sizeof(pvt_data->msg.event_type)); + } + break; + } + +#ifdef BCMDBG + brcmf_c_show_host_event(event, event_data); +#endif /* BCMDBG */ + + return 0; +} + +/* Convert user's input in hex pattern to byte-size mask */ +static int brcmf_c_pattern_atoh(char *src, char *dst) +{ + int i; + if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) { + brcmf_dbg(ERROR, "Mask invalid format. Needs to start with 0x\n"); + return -EINVAL; + } + src = src + 2; /* Skip past 0x */ + if (strlen(src) % 2 != 0) { + brcmf_dbg(ERROR, "Mask invalid format. Length must be even.\n"); + return -EINVAL; + } + for (i = 0; *src != '\0'; i++) { + unsigned long res; + char num[3]; + strncpy(num, src, 2); + num[2] = '\0'; + if (kstrtoul(num, 16, &res)) + return -EINVAL; + dst[i] = (u8)res; + src += 2; + } + return i; +} + +void +brcmf_c_pktfilter_offload_enable(struct brcmf_pub *drvr, char *arg, int enable, + int master_mode) +{ + unsigned long res; + char *argv[8]; + int i = 0; + const char *str; + int buf_len; + int str_len; + char *arg_save = NULL, *arg_org = NULL; + int rc; + char buf[128]; + struct brcmf_pkt_filter_enable enable_parm; + struct brcmf_pkt_filter_enable *pkt_filterp; + + arg_save = kmalloc(strlen(arg) + 1, GFP_ATOMIC); + if (!arg_save) + goto fail; + + arg_org = arg_save; + memcpy(arg_save, arg, strlen(arg) + 1); + + argv[i] = strsep(&arg_save, " "); + + i = 0; + if (NULL == argv[i]) { + brcmf_dbg(ERROR, "No args provided\n"); + goto fail; + } + + str = "pkt_filter_enable"; + str_len = strlen(str); + strncpy(buf, str, str_len); + buf[str_len] = '\0'; + buf_len = str_len + 1; + + pkt_filterp = (struct brcmf_pkt_filter_enable *) (buf + str_len + 1); + + /* Parse packet filter id. */ + enable_parm.id = 0; + if (!kstrtoul(argv[i], 0, &res)) + enable_parm.id = (u32)res; + + /* Parse enable/disable value. */ + enable_parm.enable = enable; + + buf_len += sizeof(enable_parm); + memcpy((char *)pkt_filterp, &enable_parm, sizeof(enable_parm)); + + /* Enable/disable the specified filter. */ + rc = brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, buf, buf_len); + rc = rc >= 0 ? 0 : rc; + if (rc) + brcmf_dbg(TRACE, "failed to add pktfilter %s, retcode = %d\n", + arg, rc); + else + brcmf_dbg(TRACE, "successfully added pktfilter %s\n", arg); + + /* Contorl the master mode */ + brcmu_mkiovar("pkt_filter_mode", (char *)&master_mode, 4, buf, + sizeof(buf)); + rc = brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, buf, + sizeof(buf)); + rc = rc >= 0 ? 0 : rc; + if (rc) + brcmf_dbg(TRACE, "failed to add pktfilter %s, retcode = %d\n", + arg, rc); + +fail: + kfree(arg_org); +} + +void brcmf_c_pktfilter_offload_set(struct brcmf_pub *drvr, char *arg) +{ + const char *str; + struct brcmf_pkt_filter pkt_filter; + struct brcmf_pkt_filter *pkt_filterp; + unsigned long res; + int buf_len; + int str_len; + int rc; + u32 mask_size; + u32 pattern_size; + char *argv[8], *buf = NULL; + int i = 0; + char *arg_save = NULL, *arg_org = NULL; + + arg_save = kstrdup(arg, GFP_ATOMIC); + if (!arg_save) + goto fail; + + arg_org = arg_save; + + buf = kmalloc(PKTFILTER_BUF_SIZE, GFP_ATOMIC); + if (!buf) + goto fail; + + argv[i] = strsep(&arg_save, " "); + while (argv[i++]) + argv[i] = strsep(&arg_save, " "); + + i = 0; + if (NULL == argv[i]) { + brcmf_dbg(ERROR, "No args provided\n"); + goto fail; + } + + str = "pkt_filter_add"; + strcpy(buf, str); + str_len = strlen(str); + buf_len = str_len + 1; + + pkt_filterp = (struct brcmf_pkt_filter *) (buf + str_len + 1); + + /* Parse packet filter id. */ + pkt_filter.id = 0; + if (!kstrtoul(argv[i], 0, &res)) + pkt_filter.id = (u32)res; + + if (NULL == argv[++i]) { + brcmf_dbg(ERROR, "Polarity not provided\n"); + goto fail; + } + + /* Parse filter polarity. */ + pkt_filter.negate_match = 0; + if (!kstrtoul(argv[i], 0, &res)) + pkt_filter.negate_match = (u32)res; + + if (NULL == argv[++i]) { + brcmf_dbg(ERROR, "Filter type not provided\n"); + goto fail; + } + + /* Parse filter type. */ + pkt_filter.type = 0; + if (!kstrtoul(argv[i], 0, &res)) + pkt_filter.type = (u32)res; + + if (NULL == argv[++i]) { + brcmf_dbg(ERROR, "Offset not provided\n"); + goto fail; + } + + /* Parse pattern filter offset. */ + pkt_filter.u.pattern.offset = 0; + if (!kstrtoul(argv[i], 0, &res)) + pkt_filter.u.pattern.offset = (u32)res; + + if (NULL == argv[++i]) { + brcmf_dbg(ERROR, "Bitmask not provided\n"); + goto fail; + } + + /* Parse pattern filter mask. */ + mask_size = + brcmf_c_pattern_atoh + (argv[i], (char *)pkt_filterp->u.pattern.mask_and_pattern); + + if (NULL == argv[++i]) { + brcmf_dbg(ERROR, "Pattern not provided\n"); + goto fail; + } + + /* Parse pattern filter pattern. */ + pattern_size = + brcmf_c_pattern_atoh(argv[i], + (char *)&pkt_filterp->u.pattern. + mask_and_pattern[mask_size]); + + if (mask_size != pattern_size) { + brcmf_dbg(ERROR, "Mask and pattern not the same size\n"); + goto fail; + } + + pkt_filter.u.pattern.size_bytes = mask_size; + buf_len += BRCMF_PKT_FILTER_FIXED_LEN; + buf_len += (BRCMF_PKT_FILTER_PATTERN_FIXED_LEN + 2 * mask_size); + + /* Keep-alive attributes are set in local + * variable (keep_alive_pkt), and + ** then memcpy'ed into buffer (keep_alive_pktp) since there is no + ** guarantee that the buffer is properly aligned. + */ + memcpy((char *)pkt_filterp, + &pkt_filter, + BRCMF_PKT_FILTER_FIXED_LEN + BRCMF_PKT_FILTER_PATTERN_FIXED_LEN); + + rc = brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, buf, buf_len); + rc = rc >= 0 ? 0 : rc; + + if (rc) + brcmf_dbg(TRACE, "failed to add pktfilter %s, retcode = %d\n", + arg, rc); + else + brcmf_dbg(TRACE, "successfully added pktfilter %s\n", arg); + +fail: + kfree(arg_org); + + kfree(buf); +} + +static void brcmf_c_arp_offload_set(struct brcmf_pub *drvr, int arp_mode) +{ + char iovbuf[32]; + int retcode; + + brcmu_mkiovar("arp_ol", (char *)&arp_mode, 4, iovbuf, sizeof(iovbuf)); + retcode = brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, + iovbuf, sizeof(iovbuf)); + retcode = retcode >= 0 ? 0 : retcode; + if (retcode) + brcmf_dbg(TRACE, "failed to set ARP offload mode to 0x%x, retcode = %d\n", + arp_mode, retcode); + else + brcmf_dbg(TRACE, "successfully set ARP offload mode to 0x%x\n", + arp_mode); +} + +static void brcmf_c_arp_offload_enable(struct brcmf_pub *drvr, int arp_enable) +{ + char iovbuf[32]; + int retcode; + + brcmu_mkiovar("arpoe", (char *)&arp_enable, 4, iovbuf, sizeof(iovbuf)); + retcode = brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, + iovbuf, sizeof(iovbuf)); + retcode = retcode >= 0 ? 0 : retcode; + if (retcode) + brcmf_dbg(TRACE, "failed to enable ARP offload to %d, retcode = %d\n", + arp_enable, retcode); + else + brcmf_dbg(TRACE, "successfully enabled ARP offload to %d\n", + arp_enable); +} + +int brcmf_c_preinit_dcmds(struct brcmf_pub *drvr) +{ + char iovbuf[BRCMF_EVENTING_MASK_LEN + 12]; /* Room for + "event_msgs" + '\0' + bitvec */ + uint up = 0; + char buf[128], *ptr; + u32 dongle_align = BRCMF_SDALIGN; + u32 glom = 0; + u32 roaming = 1; + uint bcn_timeout = 3; + int scan_assoc_time = 40; + int scan_unassoc_time = 40; + int i; + + brcmf_os_proto_block(drvr); + + /* Set Country code */ + if (drvr->country_code[0] != 0) { + if (brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_COUNTRY, + drvr->country_code, + sizeof(drvr->country_code)) < 0) + brcmf_dbg(ERROR, "country code setting failed\n"); + } + + /* query for 'ver' to get version info from firmware */ + memset(buf, 0, sizeof(buf)); + ptr = buf; + brcmu_mkiovar("ver", NULL, 0, buf, sizeof(buf)); + brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR, buf, sizeof(buf)); + strsep(&ptr, "\n"); + /* Print fw version info */ + brcmf_dbg(ERROR, "Firmware version = %s\n", buf); + + /* Match Host and Dongle rx alignment */ + brcmu_mkiovar("bus:txglomalign", (char *)&dongle_align, 4, iovbuf, + sizeof(iovbuf)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, iovbuf, + sizeof(iovbuf)); + + /* disable glom option per default */ + brcmu_mkiovar("bus:txglom", (char *)&glom, 4, iovbuf, sizeof(iovbuf)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, iovbuf, + sizeof(iovbuf)); + + /* Setup timeout if Beacons are lost and roam is off to report + link down */ + brcmu_mkiovar("bcn_timeout", (char *)&bcn_timeout, 4, iovbuf, + sizeof(iovbuf)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, iovbuf, + sizeof(iovbuf)); + + /* Enable/Disable build-in roaming to allowed ext supplicant to take + of romaing */ + brcmu_mkiovar("roam_off", (char *)&roaming, 4, + iovbuf, sizeof(iovbuf)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, iovbuf, + sizeof(iovbuf)); + + /* Force STA UP */ + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_UP, (char *)&up, sizeof(up)); + + /* Setup event_msgs */ + brcmu_mkiovar("event_msgs", drvr->eventmask, BRCMF_EVENTING_MASK_LEN, + iovbuf, sizeof(iovbuf)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_VAR, iovbuf, + sizeof(iovbuf)); + + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_SCAN_CHANNEL_TIME, + (char *)&scan_assoc_time, sizeof(scan_assoc_time)); + brcmf_proto_cdc_set_dcmd(drvr, 0, BRCMF_C_SET_SCAN_UNASSOC_TIME, + (char *)&scan_unassoc_time, sizeof(scan_unassoc_time)); + + /* Set and enable ARP offload feature */ + brcmf_c_arp_offload_set(drvr, BRCMF_ARPOL_MODE); + brcmf_c_arp_offload_enable(drvr, true); + + /* Set up pkt filter */ + for (i = 0; i < drvr->pktfilter_count; i++) { + brcmf_c_pktfilter_offload_set(drvr, drvr->pktfilter[i]); + brcmf_c_pktfilter_offload_enable(drvr, drvr->pktfilter[i], + 0, true); + } + + brcmf_os_proto_unblock(drvr); + + return 0; +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h new file mode 100644 index 000000000000..7467922f0536 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_DBG_H_ +#define _BRCMF_DBG_H_ + +#if defined(BCMDBG) + +#define brcmf_dbg(level, fmt, ...) \ +do { \ + if (BRCMF_ERROR_VAL == BRCMF_##level##_VAL) { \ + if (brcmf_msg_level & BRCMF_##level##_VAL) { \ + if (net_ratelimit()) \ + printk(KERN_DEBUG "%s: " fmt, \ + __func__, ##__VA_ARGS__); \ + } \ + } else { \ + if (brcmf_msg_level & BRCMF_##level##_VAL) { \ + printk(KERN_DEBUG "%s: " fmt, \ + __func__, ##__VA_ARGS__); \ + } \ + } \ +} while (0) + +#define BRCMF_DATA_ON() (brcmf_msg_level & BRCMF_DATA_VAL) +#define BRCMF_CTL_ON() (brcmf_msg_level & BRCMF_CTL_VAL) +#define BRCMF_HDRS_ON() (brcmf_msg_level & BRCMF_HDRS_VAL) +#define BRCMF_BYTES_ON() (brcmf_msg_level & BRCMF_BYTES_VAL) +#define BRCMF_GLOM_ON() (brcmf_msg_level & BRCMF_GLOM_VAL) + +#else /* (defined BCMDBG) || (defined BCMDBG) */ + +#define brcmf_dbg(level, fmt, ...) no_printk(fmt, ##__VA_ARGS__) + +#define BRCMF_DATA_ON() 0 +#define BRCMF_CTL_ON() 0 +#define BRCMF_HDRS_ON() 0 +#define BRCMF_BYTES_ON() 0 +#define BRCMF_GLOM_ON() 0 + +#endif /* defined(BCMDBG) */ + +extern int brcmf_msg_level; + +#endif /* _BRCMF_DBG_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c new file mode 100644 index 000000000000..99ba5e3e45fa --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c @@ -0,0 +1,1354 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/mmc/sdio_func.h> +#include <linux/random.h> +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/fcntl.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/hardirq.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <net/cfg80211.h> +#include <net/rtnetlink.h> +#include <defs.h> +#include <brcmu_utils.h> +#include <brcmu_wifi.h> + +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" +#include "wl_cfg80211.h" +#include "bcmchip.h" + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN fullmac driver."); +MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN fullmac cards"); +MODULE_LICENSE("Dual BSD/GPL"); + + +/* Interface control information */ +struct brcmf_if { + struct brcmf_info *info; /* back pointer to brcmf_info */ + /* OS/stack specifics */ + struct net_device *ndev; + struct net_device_stats stats; + int idx; /* iface idx in dongle */ + int state; /* interface state */ + u8 mac_addr[ETH_ALEN]; /* assigned MAC address */ +}; + +/* Local private structure (extension of pub) */ +struct brcmf_info { + struct brcmf_pub pub; + + /* OS/stack specifics */ + struct brcmf_if *iflist[BRCMF_MAX_IFS]; + + struct mutex proto_block; + + struct work_struct setmacaddr_work; + struct work_struct multicast_work; + u8 macvalue[ETH_ALEN]; + atomic_t pend_8021x_cnt; +}; + +/* Error bits */ +module_param(brcmf_msg_level, int, 0); + + +static int brcmf_net2idx(struct brcmf_info *drvr_priv, struct net_device *ndev) +{ + int i = 0; + + while (i < BRCMF_MAX_IFS) { + if (drvr_priv->iflist[i] && drvr_priv->iflist[i]->ndev == ndev) + return i; + i++; + } + + return BRCMF_BAD_IF; +} + +int brcmf_ifname2idx(struct brcmf_info *drvr_priv, char *name) +{ + int i = BRCMF_MAX_IFS; + struct brcmf_if *ifp; + + if (name == NULL || *name == '\0') + return 0; + + while (--i > 0) { + ifp = drvr_priv->iflist[i]; + if (ifp && !strncmp(ifp->ndev->name, name, IFNAMSIZ)) + break; + } + + brcmf_dbg(TRACE, "return idx %d for \"%s\"\n", i, name); + + return i; /* default - the primary interface */ +} + +char *brcmf_ifname(struct brcmf_pub *drvr, int ifidx) +{ + struct brcmf_info *drvr_priv = drvr->info; + + if (ifidx < 0 || ifidx >= BRCMF_MAX_IFS) { + brcmf_dbg(ERROR, "ifidx %d out of range\n", ifidx); + return "<if_bad>"; + } + + if (drvr_priv->iflist[ifidx] == NULL) { + brcmf_dbg(ERROR, "null i/f %d\n", ifidx); + return "<if_null>"; + } + + if (drvr_priv->iflist[ifidx]->ndev) + return drvr_priv->iflist[ifidx]->ndev->name; + + return "<if_none>"; +} + +static void _brcmf_set_multicast_list(struct work_struct *work) +{ + struct net_device *ndev; + struct netdev_hw_addr *ha; + u32 allmulti, cnt; + __le32 cnt_le; + __le32 allmulti_le; + + struct brcmf_dcmd dcmd; + char *buf, *bufp; + uint buflen; + int ret; + + struct brcmf_info *drvr_priv = container_of(work, struct brcmf_info, + multicast_work); + + ndev = drvr_priv->iflist[0]->ndev; + cnt = netdev_mc_count(ndev); + + /* Determine initial value of allmulti flag */ + allmulti = (ndev->flags & IFF_ALLMULTI) ? true : false; + + /* Send down the multicast list first. */ + + buflen = sizeof("mcast_list") + sizeof(cnt) + (cnt * ETH_ALEN); + bufp = buf = kmalloc(buflen, GFP_ATOMIC); + if (!bufp) + return; + + strcpy(bufp, "mcast_list"); + bufp += strlen("mcast_list") + 1; + + cnt_le = cpu_to_le32(cnt); + memcpy(bufp, &cnt_le, sizeof(cnt)); + bufp += sizeof(cnt_le); + + netdev_for_each_mc_addr(ha, ndev) { + if (!cnt) + break; + memcpy(bufp, ha->addr, ETH_ALEN); + bufp += ETH_ALEN; + cnt--; + } + + memset(&dcmd, 0, sizeof(dcmd)); + dcmd.cmd = BRCMF_C_SET_VAR; + dcmd.buf = buf; + dcmd.len = buflen; + dcmd.set = true; + + ret = brcmf_proto_dcmd(&drvr_priv->pub, 0, &dcmd, dcmd.len); + if (ret < 0) { + brcmf_dbg(ERROR, "%s: set mcast_list failed, cnt %d\n", + brcmf_ifname(&drvr_priv->pub, 0), cnt); + allmulti = cnt ? true : allmulti; + } + + kfree(buf); + + /* Now send the allmulti setting. This is based on the setting in the + * net_device flags, but might be modified above to be turned on if we + * were trying to set some addresses and dongle rejected it... + */ + + buflen = sizeof("allmulti") + sizeof(allmulti); + buf = kmalloc(buflen, GFP_ATOMIC); + if (!buf) + return; + + allmulti_le = cpu_to_le32(allmulti); + + if (!brcmu_mkiovar + ("allmulti", (void *)&allmulti_le, + sizeof(allmulti_le), buf, buflen)) { + brcmf_dbg(ERROR, "%s: mkiovar failed for allmulti, datalen %d buflen %u\n", + brcmf_ifname(&drvr_priv->pub, 0), + (int)sizeof(allmulti), buflen); + kfree(buf); + return; + } + + memset(&dcmd, 0, sizeof(dcmd)); + dcmd.cmd = BRCMF_C_SET_VAR; + dcmd.buf = buf; + dcmd.len = buflen; + dcmd.set = true; + + ret = brcmf_proto_dcmd(&drvr_priv->pub, 0, &dcmd, dcmd.len); + if (ret < 0) { + brcmf_dbg(ERROR, "%s: set allmulti %d failed\n", + brcmf_ifname(&drvr_priv->pub, 0), + le32_to_cpu(allmulti_le)); + } + + kfree(buf); + + /* Finally, pick up the PROMISC flag as well, like the NIC + driver does */ + + allmulti = (ndev->flags & IFF_PROMISC) ? true : false; + allmulti_le = cpu_to_le32(allmulti); + + memset(&dcmd, 0, sizeof(dcmd)); + dcmd.cmd = BRCMF_C_SET_PROMISC; + dcmd.buf = &allmulti_le; + dcmd.len = sizeof(allmulti_le); + dcmd.set = true; + + ret = brcmf_proto_dcmd(&drvr_priv->pub, 0, &dcmd, dcmd.len); + if (ret < 0) { + brcmf_dbg(ERROR, "%s: set promisc %d failed\n", + brcmf_ifname(&drvr_priv->pub, 0), + le32_to_cpu(allmulti_le)); + } +} + +static void +_brcmf_set_mac_address(struct work_struct *work) +{ + char buf[32]; + struct brcmf_dcmd dcmd; + int ret; + + struct brcmf_info *drvr_priv = container_of(work, struct brcmf_info, + setmacaddr_work); + + brcmf_dbg(TRACE, "enter\n"); + if (!brcmu_mkiovar("cur_etheraddr", (char *)drvr_priv->macvalue, + ETH_ALEN, buf, 32)) { + brcmf_dbg(ERROR, "%s: mkiovar failed for cur_etheraddr\n", + brcmf_ifname(&drvr_priv->pub, 0)); + return; + } + memset(&dcmd, 0, sizeof(dcmd)); + dcmd.cmd = BRCMF_C_SET_VAR; + dcmd.buf = buf; + dcmd.len = 32; + dcmd.set = true; + + ret = brcmf_proto_dcmd(&drvr_priv->pub, 0, &dcmd, dcmd.len); + if (ret < 0) + brcmf_dbg(ERROR, "%s: set cur_etheraddr failed\n", + brcmf_ifname(&drvr_priv->pub, 0)); + else + memcpy(drvr_priv->iflist[0]->ndev->dev_addr, + drvr_priv->macvalue, ETH_ALEN); + + return; +} + +static int brcmf_netdev_set_mac_address(struct net_device *ndev, void *addr) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + struct sockaddr *sa = (struct sockaddr *)addr; + int ifidx; + + ifidx = brcmf_net2idx(drvr_priv, ndev); + if (ifidx == BRCMF_BAD_IF) + return -1; + + memcpy(&drvr_priv->macvalue, sa->sa_data, ETH_ALEN); + schedule_work(&drvr_priv->setmacaddr_work); + return 0; +} + +static void brcmf_netdev_set_multicast_list(struct net_device *ndev) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + int ifidx; + + ifidx = brcmf_net2idx(drvr_priv, ndev); + if (ifidx == BRCMF_BAD_IF) + return; + + schedule_work(&drvr_priv->multicast_work); +} + +int brcmf_sendpkt(struct brcmf_pub *drvr, int ifidx, struct sk_buff *pktbuf) +{ + struct brcmf_info *drvr_priv = drvr->info; + + /* Reject if down */ + if (!drvr->up || (drvr->busstate == BRCMF_BUS_DOWN)) + return -ENODEV; + + /* Update multicast statistic */ + if (pktbuf->len >= ETH_ALEN) { + u8 *pktdata = (u8 *) (pktbuf->data); + struct ethhdr *eh = (struct ethhdr *)pktdata; + + if (is_multicast_ether_addr(eh->h_dest)) + drvr->tx_multicast++; + if (ntohs(eh->h_proto) == ETH_P_PAE) + atomic_inc(&drvr_priv->pend_8021x_cnt); + } + + /* If the protocol uses a data header, apply it */ + brcmf_proto_hdrpush(drvr, ifidx, pktbuf); + + /* Use bus module to send data frame */ + return brcmf_sdbrcm_bus_txdata(drvr->bus, pktbuf); +} + +static int brcmf_netdev_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + int ret; + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + int ifidx; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Reject if down */ + if (!drvr_priv->pub.up || (drvr_priv->pub.busstate == BRCMF_BUS_DOWN)) { + brcmf_dbg(ERROR, "xmit rejected pub.up=%d busstate=%d\n", + drvr_priv->pub.up, drvr_priv->pub.busstate); + netif_stop_queue(ndev); + return -ENODEV; + } + + ifidx = brcmf_net2idx(drvr_priv, ndev); + if (ifidx == BRCMF_BAD_IF) { + brcmf_dbg(ERROR, "bad ifidx %d\n", ifidx); + netif_stop_queue(ndev); + return -ENODEV; + } + + /* Make sure there's enough room for any header */ + if (skb_headroom(skb) < drvr_priv->pub.hdrlen) { + struct sk_buff *skb2; + + brcmf_dbg(INFO, "%s: insufficient headroom\n", + brcmf_ifname(&drvr_priv->pub, ifidx)); + drvr_priv->pub.tx_realloc++; + skb2 = skb_realloc_headroom(skb, drvr_priv->pub.hdrlen); + dev_kfree_skb(skb); + skb = skb2; + if (skb == NULL) { + brcmf_dbg(ERROR, "%s: skb_realloc_headroom failed\n", + brcmf_ifname(&drvr_priv->pub, ifidx)); + ret = -ENOMEM; + goto done; + } + } + + ret = brcmf_sendpkt(&drvr_priv->pub, ifidx, skb); + +done: + if (ret) + drvr_priv->pub.dstats.tx_dropped++; + else + drvr_priv->pub.tx_packets++; + + /* Return ok: we always eat the packet */ + return 0; +} + +void brcmf_txflowcontrol(struct brcmf_pub *drvr, int ifidx, bool state) +{ + struct net_device *ndev; + struct brcmf_info *drvr_priv = drvr->info; + + brcmf_dbg(TRACE, "Enter\n"); + + drvr->txoff = state; + ndev = drvr_priv->iflist[ifidx]->ndev; + if (state == ON) + netif_stop_queue(ndev); + else + netif_wake_queue(ndev); +} + +static int brcmf_host_event(struct brcmf_info *drvr_priv, int *ifidx, + void *pktdata, struct brcmf_event_msg *event, + void **data) +{ + int bcmerror = 0; + + bcmerror = brcmf_c_host_event(drvr_priv, ifidx, pktdata, event, data); + if (bcmerror != 0) + return bcmerror; + + if (drvr_priv->iflist[*ifidx]->ndev) + brcmf_cfg80211_event(drvr_priv->iflist[*ifidx]->ndev, + event, *data); + + return bcmerror; +} + +void brcmf_rx_frame(struct brcmf_pub *drvr, int ifidx, struct sk_buff *skb, + int numpkt) +{ + struct brcmf_info *drvr_priv = drvr->info; + unsigned char *eth; + uint len; + void *data; + struct sk_buff *pnext, *save_pktbuf; + int i; + struct brcmf_if *ifp; + struct brcmf_event_msg event; + + brcmf_dbg(TRACE, "Enter\n"); + + save_pktbuf = skb; + + for (i = 0; skb && i < numpkt; i++, skb = pnext) { + + pnext = skb->next; + skb->next = NULL; + + /* Get the protocol, maintain skb around eth_type_trans() + * The main reason for this hack is for the limitation of + * Linux 2.4 where 'eth_type_trans' uses the + * 'net->hard_header_len' + * to perform skb_pull inside vs ETH_HLEN. Since to avoid + * coping of the packet coming from the network stack to add + * BDC, Hardware header etc, during network interface + * registration + * we set the 'net->hard_header_len' to ETH_HLEN + extra space + * required + * for BDC, Hardware header etc. and not just the ETH_HLEN + */ + eth = skb->data; + len = skb->len; + + ifp = drvr_priv->iflist[ifidx]; + if (ifp == NULL) + ifp = drvr_priv->iflist[0]; + + skb->dev = ifp->ndev; + skb->protocol = eth_type_trans(skb, skb->dev); + + if (skb->pkt_type == PACKET_MULTICAST) + drvr_priv->pub.rx_multicast++; + + skb->data = eth; + skb->len = len; + + /* Strip header, count, deliver upward */ + skb_pull(skb, ETH_HLEN); + + /* Process special event packets and then discard them */ + if (ntohs(skb->protocol) == ETH_P_LINK_CTL) + brcmf_host_event(drvr_priv, &ifidx, + skb_mac_header(skb), + &event, &data); + + if (drvr_priv->iflist[ifidx] && + !drvr_priv->iflist[ifidx]->state) + ifp = drvr_priv->iflist[ifidx]; + + if (ifp->ndev) + ifp->ndev->last_rx = jiffies; + + drvr->dstats.rx_bytes += skb->len; + drvr->rx_packets++; /* Local count */ + + if (in_interrupt()) + netif_rx(skb); + else + /* If the receive is not processed inside an ISR, + * the softirqd must be woken explicitly to service + * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled + * by netif_rx_ni(), but in earlier kernels, we need + * to do it manually. + */ + netif_rx_ni(skb); + } +} + +void brcmf_txcomplete(struct brcmf_pub *drvr, struct sk_buff *txp, bool success) +{ + uint ifidx; + struct brcmf_info *drvr_priv = drvr->info; + struct ethhdr *eh; + u16 type; + + brcmf_proto_hdrpull(drvr, &ifidx, txp); + + eh = (struct ethhdr *)(txp->data); + type = ntohs(eh->h_proto); + + if (type == ETH_P_PAE) + atomic_dec(&drvr_priv->pend_8021x_cnt); + +} + +static struct net_device_stats *brcmf_netdev_get_stats(struct net_device *ndev) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + struct brcmf_if *ifp; + int ifidx; + + brcmf_dbg(TRACE, "Enter\n"); + + ifidx = brcmf_net2idx(drvr_priv, ndev); + if (ifidx == BRCMF_BAD_IF) + return NULL; + + ifp = drvr_priv->iflist[ifidx]; + + if (drvr_priv->pub.up) + /* Use the protocol to get dongle stats */ + brcmf_proto_dstats(&drvr_priv->pub); + + /* Copy dongle stats to net device stats */ + ifp->stats.rx_packets = drvr_priv->pub.dstats.rx_packets; + ifp->stats.tx_packets = drvr_priv->pub.dstats.tx_packets; + ifp->stats.rx_bytes = drvr_priv->pub.dstats.rx_bytes; + ifp->stats.tx_bytes = drvr_priv->pub.dstats.tx_bytes; + ifp->stats.rx_errors = drvr_priv->pub.dstats.rx_errors; + ifp->stats.tx_errors = drvr_priv->pub.dstats.tx_errors; + ifp->stats.rx_dropped = drvr_priv->pub.dstats.rx_dropped; + ifp->stats.tx_dropped = drvr_priv->pub.dstats.tx_dropped; + ifp->stats.multicast = drvr_priv->pub.dstats.multicast; + + return &ifp->stats; +} + +/* Retrieve current toe component enables, which are kept + as a bitmap in toe_ol iovar */ +static int brcmf_toe_get(struct brcmf_info *drvr_priv, int ifidx, u32 *toe_ol) +{ + struct brcmf_dcmd dcmd; + char buf[32]; + int ret; + + memset(&dcmd, 0, sizeof(dcmd)); + + dcmd.cmd = BRCMF_C_GET_VAR; + dcmd.buf = buf; + dcmd.len = (uint) sizeof(buf); + dcmd.set = false; + + strcpy(buf, "toe_ol"); + ret = brcmf_proto_dcmd(&drvr_priv->pub, ifidx, &dcmd, dcmd.len); + if (ret < 0) { + /* Check for older dongle image that doesn't support toe_ol */ + if (ret == -EIO) { + brcmf_dbg(ERROR, "%s: toe not supported by device\n", + brcmf_ifname(&drvr_priv->pub, ifidx)); + return -EOPNOTSUPP; + } + + brcmf_dbg(INFO, "%s: could not get toe_ol: ret=%d\n", + brcmf_ifname(&drvr_priv->pub, ifidx), ret); + return ret; + } + + memcpy(toe_ol, buf, sizeof(u32)); + return 0; +} + +/* Set current toe component enables in toe_ol iovar, + and set toe global enable iovar */ +static int brcmf_toe_set(struct brcmf_info *drvr_priv, int ifidx, u32 toe_ol) +{ + struct brcmf_dcmd dcmd; + char buf[32]; + int toe, ret; + + memset(&dcmd, 0, sizeof(dcmd)); + + dcmd.cmd = BRCMF_C_SET_VAR; + dcmd.buf = buf; + dcmd.len = (uint) sizeof(buf); + dcmd.set = true; + + /* Set toe_ol as requested */ + + strcpy(buf, "toe_ol"); + memcpy(&buf[sizeof("toe_ol")], &toe_ol, sizeof(u32)); + + ret = brcmf_proto_dcmd(&drvr_priv->pub, ifidx, &dcmd, dcmd.len); + if (ret < 0) { + brcmf_dbg(ERROR, "%s: could not set toe_ol: ret=%d\n", + brcmf_ifname(&drvr_priv->pub, ifidx), ret); + return ret; + } + + /* Enable toe globally only if any components are enabled. */ + + toe = (toe_ol != 0); + + strcpy(buf, "toe"); + memcpy(&buf[sizeof("toe")], &toe, sizeof(u32)); + + ret = brcmf_proto_dcmd(&drvr_priv->pub, ifidx, &dcmd, dcmd.len); + if (ret < 0) { + brcmf_dbg(ERROR, "%s: could not set toe: ret=%d\n", + brcmf_ifname(&drvr_priv->pub, ifidx), ret); + return ret; + } + + return 0; +} + +static void brcmf_ethtool_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + + sprintf(info->driver, KBUILD_MODNAME); + sprintf(info->version, "%lu", drvr_priv->pub.drv_version); + sprintf(info->fw_version, "%s", BCM4329_FW_NAME); + sprintf(info->bus_info, "%s", + dev_name(brcmf_bus_get_device(drvr_priv->pub.bus))); +} + +static struct ethtool_ops brcmf_ethtool_ops = { + .get_drvinfo = brcmf_ethtool_get_drvinfo +}; + +static int brcmf_ethtool(struct brcmf_info *drvr_priv, void __user *uaddr) +{ + struct ethtool_drvinfo info; + char drvname[sizeof(info.driver)]; + u32 cmd; + struct ethtool_value edata; + u32 toe_cmpnt, csum_dir; + int ret; + + brcmf_dbg(TRACE, "Enter\n"); + + /* all ethtool calls start with a cmd word */ + if (copy_from_user(&cmd, uaddr, sizeof(u32))) + return -EFAULT; + + switch (cmd) { + case ETHTOOL_GDRVINFO: + /* Copy out any request driver name */ + if (copy_from_user(&info, uaddr, sizeof(info))) + return -EFAULT; + strncpy(drvname, info.driver, sizeof(info.driver)); + drvname[sizeof(info.driver) - 1] = '\0'; + + /* clear struct for return */ + memset(&info, 0, sizeof(info)); + info.cmd = cmd; + + /* if requested, identify ourselves */ + if (strcmp(drvname, "?dhd") == 0) { + sprintf(info.driver, "dhd"); + strcpy(info.version, BRCMF_VERSION_STR); + } + + /* otherwise, require dongle to be up */ + else if (!drvr_priv->pub.up) { + brcmf_dbg(ERROR, "dongle is not up\n"); + return -ENODEV; + } + + /* finally, report dongle driver type */ + else if (drvr_priv->pub.iswl) + sprintf(info.driver, "wl"); + else + sprintf(info.driver, "xx"); + + sprintf(info.version, "%lu", drvr_priv->pub.drv_version); + if (copy_to_user(uaddr, &info, sizeof(info))) + return -EFAULT; + brcmf_dbg(CTL, "given %*s, returning %s\n", + (int)sizeof(drvname), drvname, info.driver); + break; + + /* Get toe offload components from dongle */ + case ETHTOOL_GRXCSUM: + case ETHTOOL_GTXCSUM: + ret = brcmf_toe_get(drvr_priv, 0, &toe_cmpnt); + if (ret < 0) + return ret; + + csum_dir = + (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + edata.cmd = cmd; + edata.data = (toe_cmpnt & csum_dir) ? 1 : 0; + + if (copy_to_user(uaddr, &edata, sizeof(edata))) + return -EFAULT; + break; + + /* Set toe offload components in dongle */ + case ETHTOOL_SRXCSUM: + case ETHTOOL_STXCSUM: + if (copy_from_user(&edata, uaddr, sizeof(edata))) + return -EFAULT; + + /* Read the current settings, update and write back */ + ret = brcmf_toe_get(drvr_priv, 0, &toe_cmpnt); + if (ret < 0) + return ret; + + csum_dir = + (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + if (edata.data != 0) + toe_cmpnt |= csum_dir; + else + toe_cmpnt &= ~csum_dir; + + ret = brcmf_toe_set(drvr_priv, 0, toe_cmpnt); + if (ret < 0) + return ret; + + /* If setting TX checksum mode, tell Linux the new mode */ + if (cmd == ETHTOOL_STXCSUM) { + if (edata.data) + drvr_priv->iflist[0]->ndev->features |= + NETIF_F_IP_CSUM; + else + drvr_priv->iflist[0]->ndev->features &= + ~NETIF_F_IP_CSUM; + } + + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int brcmf_netdev_ioctl_entry(struct net_device *ndev, struct ifreq *ifr, + int cmd) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + int ifidx; + + ifidx = brcmf_net2idx(drvr_priv, ndev); + brcmf_dbg(TRACE, "ifidx %d, cmd 0x%04x\n", ifidx, cmd); + + if (ifidx == BRCMF_BAD_IF) + return -1; + + if (cmd == SIOCETHTOOL) + return brcmf_ethtool(drvr_priv, ifr->ifr_data); + + return -EOPNOTSUPP; +} + +/* called only from within this driver. Sends a command to the dongle. */ +s32 brcmf_exec_dcmd(struct net_device *ndev, u32 cmd, void *arg, u32 len) +{ + struct brcmf_dcmd dcmd; + s32 err = 0; + int buflen = 0; + bool is_set_key_cmd; + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + int ifidx; + + memset(&dcmd, 0, sizeof(dcmd)); + dcmd.cmd = cmd; + dcmd.buf = arg; + dcmd.len = len; + + ifidx = brcmf_net2idx(drvr_priv, ndev); + + if (dcmd.buf != NULL) + buflen = min_t(uint, dcmd.len, BRCMF_DCMD_MAXLEN); + + /* send to dongle (must be up, and wl) */ + if ((drvr_priv->pub.busstate != BRCMF_BUS_DATA)) { + brcmf_dbg(ERROR, "DONGLE_DOWN\n"); + err = -EIO; + goto done; + } + + if (!drvr_priv->pub.iswl) { + err = -EIO; + goto done; + } + + /* + * Intercept BRCMF_C_SET_KEY CMD - serialize M4 send and + * set key CMD to prevent M4 encryption. + */ + is_set_key_cmd = ((dcmd.cmd == BRCMF_C_SET_KEY) || + ((dcmd.cmd == BRCMF_C_SET_VAR) && + !(strncmp("wsec_key", dcmd.buf, 9))) || + ((dcmd.cmd == BRCMF_C_SET_VAR) && + !(strncmp("bsscfg:wsec_key", dcmd.buf, 15)))); + if (is_set_key_cmd) + brcmf_netdev_wait_pend8021x(ndev); + + err = brcmf_proto_dcmd(&drvr_priv->pub, ifidx, &dcmd, buflen); + +done: + if (err > 0) + err = 0; + + return err; +} + +static int brcmf_netdev_stop(struct net_device *ndev) +{ + struct brcmf_pub *drvr = *(struct brcmf_pub **) netdev_priv(ndev); + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_cfg80211_down(drvr->config); + if (drvr->up == 0) + return 0; + + /* Set state and stop OS transmissions */ + drvr->up = 0; + netif_stop_queue(ndev); + + return 0; +} + +static int brcmf_netdev_open(struct net_device *ndev) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **) + netdev_priv(ndev); + u32 toe_ol; + int ifidx = brcmf_net2idx(drvr_priv, ndev); + s32 ret = 0; + + brcmf_dbg(TRACE, "ifidx %d\n", ifidx); + + if (ifidx == 0) { /* do it only for primary eth0 */ + + /* try to bring up bus */ + ret = brcmf_bus_start(&drvr_priv->pub); + if (ret != 0) { + brcmf_dbg(ERROR, "failed with code %d\n", ret); + return -1; + } + atomic_set(&drvr_priv->pend_8021x_cnt, 0); + + memcpy(ndev->dev_addr, drvr_priv->pub.mac, ETH_ALEN); + + /* Get current TOE mode from dongle */ + if (brcmf_toe_get(drvr_priv, ifidx, &toe_ol) >= 0 + && (toe_ol & TOE_TX_CSUM_OL) != 0) + drvr_priv->iflist[ifidx]->ndev->features |= + NETIF_F_IP_CSUM; + else + drvr_priv->iflist[ifidx]->ndev->features &= + ~NETIF_F_IP_CSUM; + } + /* Allow transmit calls */ + netif_start_queue(ndev); + drvr_priv->pub.up = 1; + if (brcmf_cfg80211_up(drvr_priv->pub.config)) { + brcmf_dbg(ERROR, "failed to bring up cfg80211\n"); + return -1; + } + + return ret; +} + +int +brcmf_add_if(struct brcmf_info *drvr_priv, int ifidx, struct net_device *ndev, + char *name, u8 *mac_addr, u32 flags, u8 bssidx) +{ + struct brcmf_if *ifp; + int ret = 0, err = 0; + + brcmf_dbg(TRACE, "idx %d, handle->%p\n", ifidx, ndev); + + ifp = drvr_priv->iflist[ifidx]; + if (!ifp) { + ifp = kmalloc(sizeof(struct brcmf_if), GFP_ATOMIC); + if (!ifp) + return -ENOMEM; + } + + memset(ifp, 0, sizeof(struct brcmf_if)); + ifp->info = drvr_priv; + drvr_priv->iflist[ifidx] = ifp; + if (mac_addr != NULL) + memcpy(&ifp->mac_addr, mac_addr, ETH_ALEN); + + if (ndev == NULL) { + ifp->state = BRCMF_E_IF_ADD; + ifp->idx = ifidx; + /* + * Delete the existing interface before overwriting it + * in case we missed the BRCMF_E_IF_DEL event. + */ + if (ifp->ndev != NULL) { + brcmf_dbg(ERROR, "ERROR: netdev:%s already exists, try free & unregister\n", + ifp->ndev->name); + netif_stop_queue(ifp->ndev); + unregister_netdev(ifp->ndev); + free_netdev(ifp->ndev); + } + + /* Allocate netdev, including space for private structure */ + ifp->ndev = alloc_netdev(sizeof(drvr_priv), "wlan%d", + ether_setup); + if (!ifp->ndev) { + brcmf_dbg(ERROR, "OOM - alloc_netdev\n"); + ret = -ENOMEM; + } + + if (ret == 0) { + memcpy(netdev_priv(ifp->ndev), &drvr_priv, + sizeof(drvr_priv)); + err = brcmf_net_attach(&drvr_priv->pub, ifp->idx); + if (err != 0) { + brcmf_dbg(ERROR, "brcmf_net_attach failed, err %d\n", + err); + ret = -EOPNOTSUPP; + } else { + brcmf_dbg(TRACE, " ==== pid:%x, net_device for if:%s created ===\n", + current->pid, ifp->ndev->name); + ifp->state = 0; + } + } + + if (ret < 0) { + if (ifp->ndev) + free_netdev(ifp->ndev); + + drvr_priv->iflist[ifp->idx] = NULL; + kfree(ifp); + } + } else + ifp->ndev = ndev; + + return 0; +} + +void brcmf_del_if(struct brcmf_info *drvr_priv, int ifidx) +{ + struct brcmf_if *ifp; + + brcmf_dbg(TRACE, "idx %d\n", ifidx); + + ifp = drvr_priv->iflist[ifidx]; + if (!ifp) { + brcmf_dbg(ERROR, "Null interface\n"); + return; + } + + ifp->state = BRCMF_E_IF_DEL; + ifp->idx = ifidx; + if (ifp->ndev != NULL) { + netif_stop_queue(ifp->ndev); + unregister_netdev(ifp->ndev); + free_netdev(ifp->ndev); + drvr_priv->iflist[ifidx] = NULL; + kfree(ifp); + } +} + +struct brcmf_pub *brcmf_attach(struct brcmf_bus *bus, uint bus_hdrlen) +{ + struct brcmf_info *drvr_priv = NULL; + struct net_device *ndev; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Allocate netdev, including space for private structure */ + ndev = alloc_netdev(sizeof(drvr_priv), "wlan%d", ether_setup); + if (!ndev) { + brcmf_dbg(ERROR, "OOM - alloc_netdev\n"); + goto fail; + } + + /* Allocate primary brcmf_info */ + drvr_priv = kzalloc(sizeof(struct brcmf_info), GFP_ATOMIC); + if (!drvr_priv) + goto fail; + + /* + * Save the brcmf_info into the priv + */ + memcpy(netdev_priv(ndev), &drvr_priv, sizeof(drvr_priv)); + + if (brcmf_add_if(drvr_priv, 0, ndev, ndev->name, NULL, 0, 0) == + BRCMF_BAD_IF) + goto fail; + + ndev->netdev_ops = NULL; + mutex_init(&drvr_priv->proto_block); + + /* Link to info module */ + drvr_priv->pub.info = drvr_priv; + + /* Link to bus module */ + drvr_priv->pub.bus = bus; + drvr_priv->pub.hdrlen = bus_hdrlen; + + /* Attach and link in the protocol */ + if (brcmf_proto_attach(&drvr_priv->pub) != 0) { + brcmf_dbg(ERROR, "brcmf_prot_attach failed\n"); + goto fail; + } + + /* Attach and link in the cfg80211 */ + drvr_priv->pub.config = + brcmf_cfg80211_attach(ndev, + brcmf_bus_get_device(bus), + &drvr_priv->pub); + if (drvr_priv->pub.config == NULL) { + brcmf_dbg(ERROR, "wl_cfg80211_attach failed\n"); + goto fail; + } + + INIT_WORK(&drvr_priv->setmacaddr_work, _brcmf_set_mac_address); + INIT_WORK(&drvr_priv->multicast_work, _brcmf_set_multicast_list); + + /* + * Save the brcmf_info into the priv + */ + memcpy(netdev_priv(ndev), &drvr_priv, sizeof(drvr_priv)); + + return &drvr_priv->pub; + +fail: + if (ndev) + free_netdev(ndev); + if (drvr_priv) + brcmf_detach(&drvr_priv->pub); + + return NULL; +} + +int brcmf_bus_start(struct brcmf_pub *drvr) +{ + int ret = -1; + struct brcmf_info *drvr_priv = drvr->info; + /* Room for "event_msgs" + '\0' + bitvec */ + char iovbuf[BRCMF_EVENTING_MASK_LEN + 12]; + + brcmf_dbg(TRACE, "\n"); + + /* Bring up the bus */ + ret = brcmf_sdbrcm_bus_init(&drvr_priv->pub); + if (ret != 0) { + brcmf_dbg(ERROR, "brcmf_sdbrcm_bus_init failed %d\n", ret); + return ret; + } + + /* If bus is not ready, can't come up */ + if (drvr_priv->pub.busstate != BRCMF_BUS_DATA) { + brcmf_dbg(ERROR, "failed bus is not ready\n"); + return -ENODEV; + } + + brcmu_mkiovar("event_msgs", drvr->eventmask, BRCMF_EVENTING_MASK_LEN, + iovbuf, sizeof(iovbuf)); + brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR, iovbuf, + sizeof(iovbuf)); + memcpy(drvr->eventmask, iovbuf, BRCMF_EVENTING_MASK_LEN); + + setbit(drvr->eventmask, BRCMF_E_SET_SSID); + setbit(drvr->eventmask, BRCMF_E_PRUNE); + setbit(drvr->eventmask, BRCMF_E_AUTH); + setbit(drvr->eventmask, BRCMF_E_REASSOC); + setbit(drvr->eventmask, BRCMF_E_REASSOC_IND); + setbit(drvr->eventmask, BRCMF_E_DEAUTH_IND); + setbit(drvr->eventmask, BRCMF_E_DISASSOC_IND); + setbit(drvr->eventmask, BRCMF_E_DISASSOC); + setbit(drvr->eventmask, BRCMF_E_JOIN); + setbit(drvr->eventmask, BRCMF_E_ASSOC_IND); + setbit(drvr->eventmask, BRCMF_E_PSK_SUP); + setbit(drvr->eventmask, BRCMF_E_LINK); + setbit(drvr->eventmask, BRCMF_E_NDIS_LINK); + setbit(drvr->eventmask, BRCMF_E_MIC_ERROR); + setbit(drvr->eventmask, BRCMF_E_PMKID_CACHE); + setbit(drvr->eventmask, BRCMF_E_TXFAIL); + setbit(drvr->eventmask, BRCMF_E_JOIN_START); + setbit(drvr->eventmask, BRCMF_E_SCAN_COMPLETE); + +/* enable dongle roaming event */ + + drvr->pktfilter_count = 1; + /* Setup filter to allow only unicast */ + drvr->pktfilter[0] = "100 0 0 0 0x01 0x00"; + + /* Bus is ready, do any protocol initialization */ + ret = brcmf_proto_init(&drvr_priv->pub); + if (ret < 0) + return ret; + + return 0; +} + +static struct net_device_ops brcmf_netdev_ops_pri = { + .ndo_open = brcmf_netdev_open, + .ndo_stop = brcmf_netdev_stop, + .ndo_get_stats = brcmf_netdev_get_stats, + .ndo_do_ioctl = brcmf_netdev_ioctl_entry, + .ndo_start_xmit = brcmf_netdev_start_xmit, + .ndo_set_mac_address = brcmf_netdev_set_mac_address, + .ndo_set_multicast_list = brcmf_netdev_set_multicast_list +}; + +int brcmf_net_attach(struct brcmf_pub *drvr, int ifidx) +{ + struct brcmf_info *drvr_priv = drvr->info; + struct net_device *ndev; + u8 temp_addr[ETH_ALEN] = { + 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33}; + + brcmf_dbg(TRACE, "ifidx %d\n", ifidx); + + ndev = drvr_priv->iflist[ifidx]->ndev; + ndev->netdev_ops = &brcmf_netdev_ops_pri; + + /* + * We have to use the primary MAC for virtual interfaces + */ + if (ifidx != 0) { + /* for virtual interfaces use the primary MAC */ + memcpy(temp_addr, drvr_priv->pub.mac, ETH_ALEN); + + } + + if (ifidx == 1) { + brcmf_dbg(TRACE, "ACCESS POINT MAC:\n"); + /* ACCESSPOINT INTERFACE CASE */ + temp_addr[0] |= 0X02; /* set bit 2 , + - Locally Administered address */ + + } + ndev->hard_header_len = ETH_HLEN + drvr_priv->pub.hdrlen; + ndev->ethtool_ops = &brcmf_ethtool_ops; + + drvr_priv->pub.rxsz = ndev->mtu + ndev->hard_header_len + + drvr_priv->pub.hdrlen; + + memcpy(ndev->dev_addr, temp_addr, ETH_ALEN); + + if (register_netdev(ndev) != 0) { + brcmf_dbg(ERROR, "couldn't register the net device\n"); + goto fail; + } + + brcmf_dbg(INFO, "%s: Broadcom Dongle Host Driver\n", ndev->name); + + return 0; + +fail: + ndev->netdev_ops = NULL; + return -EBADE; +} + +static void brcmf_bus_detach(struct brcmf_pub *drvr) +{ + struct brcmf_info *drvr_priv; + + brcmf_dbg(TRACE, "Enter\n"); + + if (drvr) { + drvr_priv = drvr->info; + if (drvr_priv) { + /* Stop the protocol module */ + brcmf_proto_stop(&drvr_priv->pub); + + /* Stop the bus module */ + brcmf_sdbrcm_bus_stop(drvr_priv->pub.bus); + } + } +} + +void brcmf_detach(struct brcmf_pub *drvr) +{ + struct brcmf_info *drvr_priv; + + brcmf_dbg(TRACE, "Enter\n"); + + if (drvr) { + drvr_priv = drvr->info; + if (drvr_priv) { + struct brcmf_if *ifp; + int i; + + for (i = 1; i < BRCMF_MAX_IFS; i++) + if (drvr_priv->iflist[i]) + brcmf_del_if(drvr_priv, i); + + ifp = drvr_priv->iflist[0]; + if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) { + rtnl_lock(); + brcmf_netdev_stop(ifp->ndev); + rtnl_unlock(); + unregister_netdev(ifp->ndev); + } + + cancel_work_sync(&drvr_priv->setmacaddr_work); + cancel_work_sync(&drvr_priv->multicast_work); + + brcmf_bus_detach(drvr); + + if (drvr->prot) + brcmf_proto_detach(drvr); + + brcmf_cfg80211_detach(drvr->config); + + free_netdev(ifp->ndev); + kfree(ifp); + kfree(drvr_priv); + } + } +} + +static void __exit brcmf_module_cleanup(void) +{ + brcmf_dbg(TRACE, "Enter\n"); + + brcmf_bus_unregister(); +} + +static int __init brcmf_module_init(void) +{ + int error; + + brcmf_dbg(TRACE, "Enter\n"); + + error = brcmf_bus_register(); + + if (error) { + brcmf_dbg(ERROR, "brcmf_bus_register failed\n"); + goto failed; + } + return 0; + +failed: + return -EINVAL; +} + +module_init(brcmf_module_init); +module_exit(brcmf_module_cleanup); + +int brcmf_os_proto_block(struct brcmf_pub *drvr) +{ + struct brcmf_info *drvr_priv = drvr->info; + + if (drvr_priv) { + mutex_lock(&drvr_priv->proto_block); + return 1; + } + return 0; +} + +int brcmf_os_proto_unblock(struct brcmf_pub *drvr) +{ + struct brcmf_info *drvr_priv = drvr->info; + + if (drvr_priv) { + mutex_unlock(&drvr_priv->proto_block); + return 1; + } + + return 0; +} + +static int brcmf_get_pend_8021x_cnt(struct brcmf_info *drvr_priv) +{ + return atomic_read(&drvr_priv->pend_8021x_cnt); +} + +#define MAX_WAIT_FOR_8021X_TX 10 + +int brcmf_netdev_wait_pend8021x(struct net_device *ndev) +{ + struct brcmf_info *drvr_priv = *(struct brcmf_info **)netdev_priv(ndev); + int timeout = 10 * HZ / 1000; + int ntimes = MAX_WAIT_FOR_8021X_TX; + int pend = brcmf_get_pend_8021x_cnt(drvr_priv); + + while (ntimes && pend) { + if (pend) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + set_current_state(TASK_RUNNING); + ntimes--; + } + pend = brcmf_get_pend_8021x_cnt(drvr_priv); + } + return pend; +} + +#ifdef BCMDBG +int brcmf_write_to_file(struct brcmf_pub *drvr, u8 *buf, int size) +{ + int ret = 0; + struct file *fp; + mm_segment_t old_fs; + loff_t pos = 0; + + /* change to KERNEL_DS address limit */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + /* open file to write */ + fp = filp_open("/tmp/mem_dump", O_WRONLY | O_CREAT, 0640); + if (!fp) { + brcmf_dbg(ERROR, "open file error\n"); + ret = -1; + goto exit; + } + + /* Write buf to file */ + fp->f_op->write(fp, buf, size, &pos); + +exit: + /* free buf before return */ + kfree(buf); + /* close file before return */ + if (fp) + filp_close(fp, current->files); + /* restore previous address limit */ + set_fs(old_fs); + + return ret; +} +#endif /* BCMDBG */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h new file mode 100644 index 000000000000..4ee1ea846f6d --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_PROTO_H_ +#define _BRCMF_PROTO_H_ + +/* + * Exported from the brcmf protocol module (brcmf_cdc) + */ + +/* Linkage, sets prot link and updates hdrlen in pub */ +extern int brcmf_proto_attach(struct brcmf_pub *drvr); + +/* Unlink, frees allocated protocol memory (including brcmf_proto) */ +extern void brcmf_proto_detach(struct brcmf_pub *drvr); + +/* Initialize protocol: sync w/dongle state. + * Sets dongle media info (iswl, drv_version, mac address). + */ +extern int brcmf_proto_init(struct brcmf_pub *drvr); + +/* Stop protocol: sync w/dongle state. */ +extern void brcmf_proto_stop(struct brcmf_pub *drvr); + +/* Add any protocol-specific data header. + * Caller must reserve prot_hdrlen prepend space. + */ +extern void brcmf_proto_hdrpush(struct brcmf_pub *, int ifidx, + struct sk_buff *txp); + +/* Remove any protocol-specific data header. */ +extern int brcmf_proto_hdrpull(struct brcmf_pub *, int *ifidx, + struct sk_buff *rxp); + +/* Use protocol to issue command to dongle */ +extern int brcmf_proto_dcmd(struct brcmf_pub *drvr, int ifidx, + struct brcmf_dcmd *dcmd, int len); + +/* Update local copy of dongle statistics */ +extern void brcmf_proto_dstats(struct brcmf_pub *drvr); + +extern int brcmf_c_preinit_dcmds(struct brcmf_pub *drvr); + +extern int brcmf_proto_cdc_set_dcmd(struct brcmf_pub *drvr, int ifidx, + uint cmd, void *buf, uint len); + +#endif /* _BRCMF_PROTO_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c new file mode 100644 index 000000000000..6885755f4ec6 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -0,0 +1,4581 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/printk.h> +#include <linux/pci_ids.h> +#include <linux/netdevice.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/card.h> +#include <linux/semaphore.h> +#include <linux/firmware.h> +#include <asm/unaligned.h> +#include <defs.h> +#include <brcmu_wifi.h> +#include <brcmu_utils.h> +#include <brcm_hw_ids.h> +#include <soc.h> +#include "sdio_host.h" + +#define DCMD_RESP_TIMEOUT 2000 /* In milli second */ + +#ifdef BCMDBG + +#define BRCMF_TRAP_INFO_SIZE 80 + +#define CBUF_LEN (128) + +struct rte_log_le { + __le32 buf; /* Can't be pointer on (64-bit) hosts */ + __le32 buf_size; + __le32 idx; + char *_buf_compat; /* Redundant pointer for backward compat. */ +}; + +struct rte_console { + /* Virtual UART + * When there is no UART (e.g. Quickturn), + * the host should write a complete + * input line directly into cbuf and then write + * the length into vcons_in. + * This may also be used when there is a real UART + * (at risk of conflicting with + * the real UART). vcons_out is currently unused. + */ + uint vcons_in; + uint vcons_out; + + /* Output (logging) buffer + * Console output is written to a ring buffer log_buf at index log_idx. + * The host may read the output when it sees log_idx advance. + * Output will be lost if the output wraps around faster than the host + * polls. + */ + struct rte_log_le log_le; + + /* Console input line buffer + * Characters are read one at a time into cbuf + * until <CR> is received, then + * the buffer is processed as a command line. + * Also used for virtual UART. + */ + uint cbuf_idx; + char cbuf[CBUF_LEN]; +}; + +#endif /* BCMDBG */ +#include <chipcommon.h> + +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" +#include <bcmchip.h> + +#define TXQLEN 2048 /* bulk tx queue length */ +#define TXHI (TXQLEN - 256) /* turn on flow control above TXHI */ +#define TXLOW (TXHI - 256) /* turn off flow control below TXLOW */ +#define PRIOMASK 7 + +#define TXRETRIES 2 /* # of retries for tx frames */ + +#define BRCMF_RXBOUND 50 /* Default for max rx frames in + one scheduling */ + +#define BRCMF_TXBOUND 20 /* Default for max tx frames in + one scheduling */ + +#define BRCMF_TXMINMAX 1 /* Max tx frames if rx still pending */ + +#define MEMBLOCK 2048 /* Block size used for downloading + of dongle image */ +#define MAX_DATA_BUF (32 * 1024) /* Must be large enough to hold + biggest possible glom */ + +#define BRCMF_FIRSTREAD (1 << 6) + + +/* SBSDIO_DEVICE_CTL */ + +/* 1: device will assert busy signal when receiving CMD53 */ +#define SBSDIO_DEVCTL_SETBUSY 0x01 +/* 1: assertion of sdio interrupt is synchronous to the sdio clock */ +#define SBSDIO_DEVCTL_SPI_INTR_SYNC 0x02 +/* 1: mask all interrupts to host except the chipActive (rev 8) */ +#define SBSDIO_DEVCTL_CA_INT_ONLY 0x04 +/* 1: isolate internal sdio signals, put external pads in tri-state; requires + * sdio bus power cycle to clear (rev 9) */ +#define SBSDIO_DEVCTL_PADS_ISO 0x08 +/* Force SD->SB reset mapping (rev 11) */ +#define SBSDIO_DEVCTL_SB_RST_CTL 0x30 +/* Determined by CoreControl bit */ +#define SBSDIO_DEVCTL_RST_CORECTL 0x00 +/* Force backplane reset */ +#define SBSDIO_DEVCTL_RST_BPRESET 0x10 +/* Force no backplane reset */ +#define SBSDIO_DEVCTL_RST_NOBPRESET 0x20 + +/* SBSDIO_FUNC1_CHIPCLKCSR */ + +/* Force ALP request to backplane */ +#define SBSDIO_FORCE_ALP 0x01 +/* Force HT request to backplane */ +#define SBSDIO_FORCE_HT 0x02 +/* Force ILP request to backplane */ +#define SBSDIO_FORCE_ILP 0x04 +/* Make ALP ready (power up xtal) */ +#define SBSDIO_ALP_AVAIL_REQ 0x08 +/* Make HT ready (power up PLL) */ +#define SBSDIO_HT_AVAIL_REQ 0x10 +/* Squelch clock requests from HW */ +#define SBSDIO_FORCE_HW_CLKREQ_OFF 0x20 +/* Status: ALP is ready */ +#define SBSDIO_ALP_AVAIL 0x40 +/* Status: HT is ready */ +#define SBSDIO_HT_AVAIL 0x80 + +#define SBSDIO_AVBITS (SBSDIO_HT_AVAIL | SBSDIO_ALP_AVAIL) +#define SBSDIO_ALPAV(regval) ((regval) & SBSDIO_AVBITS) +#define SBSDIO_HTAV(regval) (((regval) & SBSDIO_AVBITS) == SBSDIO_AVBITS) +#define SBSDIO_ALPONLY(regval) (SBSDIO_ALPAV(regval) && !SBSDIO_HTAV(regval)) + +#define SBSDIO_CLKAV(regval, alponly) \ + (SBSDIO_ALPAV(regval) && (alponly ? 1 : SBSDIO_HTAV(regval))) + +/* direct(mapped) cis space */ + +/* MAPPED common CIS address */ +#define SBSDIO_CIS_BASE_COMMON 0x1000 +/* maximum bytes in one CIS */ +#define SBSDIO_CIS_SIZE_LIMIT 0x200 +/* cis offset addr is < 17 bits */ +#define SBSDIO_CIS_OFT_ADDR_MASK 0x1FFFF + +/* manfid tuple length, include tuple, link bytes */ +#define SBSDIO_CIS_MANFID_TUPLE_LEN 6 + +/* intstatus */ +#define I_SMB_SW0 (1 << 0) /* To SB Mail S/W interrupt 0 */ +#define I_SMB_SW1 (1 << 1) /* To SB Mail S/W interrupt 1 */ +#define I_SMB_SW2 (1 << 2) /* To SB Mail S/W interrupt 2 */ +#define I_SMB_SW3 (1 << 3) /* To SB Mail S/W interrupt 3 */ +#define I_SMB_SW_MASK 0x0000000f /* To SB Mail S/W interrupts mask */ +#define I_SMB_SW_SHIFT 0 /* To SB Mail S/W interrupts shift */ +#define I_HMB_SW0 (1 << 4) /* To Host Mail S/W interrupt 0 */ +#define I_HMB_SW1 (1 << 5) /* To Host Mail S/W interrupt 1 */ +#define I_HMB_SW2 (1 << 6) /* To Host Mail S/W interrupt 2 */ +#define I_HMB_SW3 (1 << 7) /* To Host Mail S/W interrupt 3 */ +#define I_HMB_SW_MASK 0x000000f0 /* To Host Mail S/W interrupts mask */ +#define I_HMB_SW_SHIFT 4 /* To Host Mail S/W interrupts shift */ +#define I_WR_OOSYNC (1 << 8) /* Write Frame Out Of Sync */ +#define I_RD_OOSYNC (1 << 9) /* Read Frame Out Of Sync */ +#define I_PC (1 << 10) /* descriptor error */ +#define I_PD (1 << 11) /* data error */ +#define I_DE (1 << 12) /* Descriptor protocol Error */ +#define I_RU (1 << 13) /* Receive descriptor Underflow */ +#define I_RO (1 << 14) /* Receive fifo Overflow */ +#define I_XU (1 << 15) /* Transmit fifo Underflow */ +#define I_RI (1 << 16) /* Receive Interrupt */ +#define I_BUSPWR (1 << 17) /* SDIO Bus Power Change (rev 9) */ +#define I_XMTDATA_AVAIL (1 << 23) /* bits in fifo */ +#define I_XI (1 << 24) /* Transmit Interrupt */ +#define I_RF_TERM (1 << 25) /* Read Frame Terminate */ +#define I_WF_TERM (1 << 26) /* Write Frame Terminate */ +#define I_PCMCIA_XU (1 << 27) /* PCMCIA Transmit FIFO Underflow */ +#define I_SBINT (1 << 28) /* sbintstatus Interrupt */ +#define I_CHIPACTIVE (1 << 29) /* chip from doze to active state */ +#define I_SRESET (1 << 30) /* CCCR RES interrupt */ +#define I_IOE2 (1U << 31) /* CCCR IOE2 Bit Changed */ +#define I_ERRORS (I_PC | I_PD | I_DE | I_RU | I_RO | I_XU) +#define I_DMA (I_RI | I_XI | I_ERRORS) + +/* corecontrol */ +#define CC_CISRDY (1 << 0) /* CIS Ready */ +#define CC_BPRESEN (1 << 1) /* CCCR RES signal */ +#define CC_F2RDY (1 << 2) /* set CCCR IOR2 bit */ +#define CC_CLRPADSISO (1 << 3) /* clear SDIO pads isolation */ +#define CC_XMTDATAAVAIL_MODE (1 << 4) +#define CC_XMTDATAAVAIL_CTRL (1 << 5) + +/* SDA_FRAMECTRL */ +#define SFC_RF_TERM (1 << 0) /* Read Frame Terminate */ +#define SFC_WF_TERM (1 << 1) /* Write Frame Terminate */ +#define SFC_CRC4WOOS (1 << 2) /* CRC error for write out of sync */ +#define SFC_ABORTALL (1 << 3) /* Abort all in-progress frames */ + +/* HW frame tag */ +#define SDPCM_FRAMETAG_LEN 4 /* 2 bytes len, 2 bytes check val */ + +/* Total length of frame header for dongle protocol */ +#define SDPCM_HDRLEN (SDPCM_FRAMETAG_LEN + SDPCM_SWHEADER_LEN) +#define SDPCM_RESERVE (SDPCM_HDRLEN + BRCMF_SDALIGN) + +/* + * Software allocation of To SB Mailbox resources + */ + +/* tosbmailbox bits corresponding to intstatus bits */ +#define SMB_NAK (1 << 0) /* Frame NAK */ +#define SMB_INT_ACK (1 << 1) /* Host Interrupt ACK */ +#define SMB_USE_OOB (1 << 2) /* Use OOB Wakeup */ +#define SMB_DEV_INT (1 << 3) /* Miscellaneous Interrupt */ + +/* tosbmailboxdata */ +#define SMB_DATA_VERSION_SHIFT 16 /* host protocol version */ + +/* + * Software allocation of To Host Mailbox resources + */ + +/* intstatus bits */ +#define I_HMB_FC_STATE I_HMB_SW0 /* Flow Control State */ +#define I_HMB_FC_CHANGE I_HMB_SW1 /* Flow Control State Changed */ +#define I_HMB_FRAME_IND I_HMB_SW2 /* Frame Indication */ +#define I_HMB_HOST_INT I_HMB_SW3 /* Miscellaneous Interrupt */ + +/* tohostmailboxdata */ +#define HMB_DATA_NAKHANDLED 1 /* retransmit NAK'd frame */ +#define HMB_DATA_DEVREADY 2 /* talk to host after enable */ +#define HMB_DATA_FC 4 /* per prio flowcontrol update flag */ +#define HMB_DATA_FWREADY 8 /* fw ready for protocol activity */ + +#define HMB_DATA_FCDATA_MASK 0xff000000 +#define HMB_DATA_FCDATA_SHIFT 24 + +#define HMB_DATA_VERSION_MASK 0x00ff0000 +#define HMB_DATA_VERSION_SHIFT 16 + +/* + * Software-defined protocol header + */ + +/* Current protocol version */ +#define SDPCM_PROT_VERSION 4 + +/* SW frame header */ +#define SDPCM_PACKET_SEQUENCE(p) (((u8 *)p)[0] & 0xff) + +#define SDPCM_CHANNEL_MASK 0x00000f00 +#define SDPCM_CHANNEL_SHIFT 8 +#define SDPCM_PACKET_CHANNEL(p) (((u8 *)p)[1] & 0x0f) + +#define SDPCM_NEXTLEN_OFFSET 2 + +/* Data Offset from SOF (HW Tag, SW Tag, Pad) */ +#define SDPCM_DOFFSET_OFFSET 3 /* Data Offset */ +#define SDPCM_DOFFSET_VALUE(p) (((u8 *)p)[SDPCM_DOFFSET_OFFSET] & 0xff) +#define SDPCM_DOFFSET_MASK 0xff000000 +#define SDPCM_DOFFSET_SHIFT 24 +#define SDPCM_FCMASK_OFFSET 4 /* Flow control */ +#define SDPCM_FCMASK_VALUE(p) (((u8 *)p)[SDPCM_FCMASK_OFFSET] & 0xff) +#define SDPCM_WINDOW_OFFSET 5 /* Credit based fc */ +#define SDPCM_WINDOW_VALUE(p) (((u8 *)p)[SDPCM_WINDOW_OFFSET] & 0xff) + +#define SDPCM_SWHEADER_LEN 8 /* SW header is 64 bits */ + +/* logical channel numbers */ +#define SDPCM_CONTROL_CHANNEL 0 /* Control channel Id */ +#define SDPCM_EVENT_CHANNEL 1 /* Asyc Event Indication Channel Id */ +#define SDPCM_DATA_CHANNEL 2 /* Data Xmit/Recv Channel Id */ +#define SDPCM_GLOM_CHANNEL 3 /* For coalesced packets */ +#define SDPCM_TEST_CHANNEL 15 /* Reserved for test/debug packets */ + +#define SDPCM_SEQUENCE_WRAP 256 /* wrap-around val for 8bit frame seq */ + +#define SDPCM_GLOMDESC(p) (((u8 *)p)[1] & 0x80) + +/* + * Shared structure between dongle and the host. + * The structure contains pointers to trap or assert information. + */ +#define SDPCM_SHARED_VERSION 0x0002 +#define SDPCM_SHARED_VERSION_MASK 0x00FF +#define SDPCM_SHARED_ASSERT_BUILT 0x0100 +#define SDPCM_SHARED_ASSERT 0x0200 +#define SDPCM_SHARED_TRAP 0x0400 + +/* Space for header read, limit for data packets */ +#define MAX_HDR_READ (1 << 6) +#define MAX_RX_DATASZ 2048 + +/* Maximum milliseconds to wait for F2 to come up */ +#define BRCMF_WAIT_F2RDY 3000 + +/* Bump up limit on waiting for HT to account for first startup; + * if the image is doing a CRC calculation before programming the PMU + * for HT availability, it could take a couple hundred ms more, so + * max out at a 1 second (1000000us). + */ +#undef PMU_MAX_TRANSITION_DLY +#define PMU_MAX_TRANSITION_DLY 1000000 + +/* Value for ChipClockCSR during initial setup */ +#define BRCMF_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | \ + SBSDIO_ALP_AVAIL_REQ) + +/* Flags for SDH calls */ +#define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED) + +/* sbimstate */ +#define SBIM_IBE 0x20000 /* inbanderror */ +#define SBIM_TO 0x40000 /* timeout */ +#define SBIM_BY 0x01800000 /* busy (sonics >= 2.3) */ +#define SBIM_RJ 0x02000000 /* reject (sonics >= 2.3) */ + +/* sbtmstatelow */ + +/* reset */ +#define SBTML_RESET 0x0001 +/* reject field */ +#define SBTML_REJ_MASK 0x0006 +/* reject */ +#define SBTML_REJ 0x0002 +/* temporary reject, for error recovery */ +#define SBTML_TMPREJ 0x0004 + +/* Shift to locate the SI control flags in sbtml */ +#define SBTML_SICF_SHIFT 16 + +/* sbtmstatehigh */ +#define SBTMH_SERR 0x0001 /* serror */ +#define SBTMH_INT 0x0002 /* interrupt */ +#define SBTMH_BUSY 0x0004 /* busy */ +#define SBTMH_TO 0x0020 /* timeout (sonics >= 2.3) */ + +/* Shift to locate the SI status flags in sbtmh */ +#define SBTMH_SISF_SHIFT 16 + +/* sbidlow */ +#define SBIDL_INIT 0x80 /* initiator */ + +/* sbidhigh */ +#define SBIDH_RC_MASK 0x000f /* revision code */ +#define SBIDH_RCE_MASK 0x7000 /* revision code extension field */ +#define SBIDH_RCE_SHIFT 8 +#define SBCOREREV(sbidh) \ + ((((sbidh) & SBIDH_RCE_MASK) >> SBIDH_RCE_SHIFT) | \ + ((sbidh) & SBIDH_RC_MASK)) +#define SBIDH_CC_MASK 0x8ff0 /* core code */ +#define SBIDH_CC_SHIFT 4 +#define SBIDH_VC_MASK 0xffff0000 /* vendor code */ +#define SBIDH_VC_SHIFT 16 + +/* + * Conversion of 802.1D priority to precedence level + */ +static uint prio2prec(u32 prio) +{ + return (prio == PRIO_8021D_NONE || prio == PRIO_8021D_BE) ? + (prio^2) : prio; +} + +/* + * Core reg address translation. + * Both macro's returns a 32 bits byte address on the backplane bus. + */ +#define CORE_CC_REG(base, field) \ + (base + offsetof(struct chipcregs, field)) +#define CORE_BUS_REG(base, field) \ + (base + offsetof(struct sdpcmd_regs, field)) +#define CORE_SB(base, field) \ + (base + SBCONFIGOFF + offsetof(struct sbconfig, field)) + +/* core registers */ +struct sdpcmd_regs { + u32 corecontrol; /* 0x00, rev8 */ + u32 corestatus; /* rev8 */ + u32 PAD[1]; + u32 biststatus; /* rev8 */ + + /* PCMCIA access */ + u16 pcmciamesportaladdr; /* 0x010, rev8 */ + u16 PAD[1]; + u16 pcmciamesportalmask; /* rev8 */ + u16 PAD[1]; + u16 pcmciawrframebc; /* rev8 */ + u16 PAD[1]; + u16 pcmciaunderflowtimer; /* rev8 */ + u16 PAD[1]; + + /* interrupt */ + u32 intstatus; /* 0x020, rev8 */ + u32 hostintmask; /* rev8 */ + u32 intmask; /* rev8 */ + u32 sbintstatus; /* rev8 */ + u32 sbintmask; /* rev8 */ + u32 funcintmask; /* rev4 */ + u32 PAD[2]; + u32 tosbmailbox; /* 0x040, rev8 */ + u32 tohostmailbox; /* rev8 */ + u32 tosbmailboxdata; /* rev8 */ + u32 tohostmailboxdata; /* rev8 */ + + /* synchronized access to registers in SDIO clock domain */ + u32 sdioaccess; /* 0x050, rev8 */ + u32 PAD[3]; + + /* PCMCIA frame control */ + u8 pcmciaframectrl; /* 0x060, rev8 */ + u8 PAD[3]; + u8 pcmciawatermark; /* rev8 */ + u8 PAD[155]; + + /* interrupt batching control */ + u32 intrcvlazy; /* 0x100, rev8 */ + u32 PAD[3]; + + /* counters */ + u32 cmd52rd; /* 0x110, rev8 */ + u32 cmd52wr; /* rev8 */ + u32 cmd53rd; /* rev8 */ + u32 cmd53wr; /* rev8 */ + u32 abort; /* rev8 */ + u32 datacrcerror; /* rev8 */ + u32 rdoutofsync; /* rev8 */ + u32 wroutofsync; /* rev8 */ + u32 writebusy; /* rev8 */ + u32 readwait; /* rev8 */ + u32 readterm; /* rev8 */ + u32 writeterm; /* rev8 */ + u32 PAD[40]; + u32 clockctlstatus; /* rev8 */ + u32 PAD[7]; + + u32 PAD[128]; /* DMA engines */ + + /* SDIO/PCMCIA CIS region */ + char cis[512]; /* 0x400-0x5ff, rev6 */ + + /* PCMCIA function control registers */ + char pcmciafcr[256]; /* 0x600-6ff, rev6 */ + u16 PAD[55]; + + /* PCMCIA backplane access */ + u16 backplanecsr; /* 0x76E, rev6 */ + u16 backplaneaddr0; /* rev6 */ + u16 backplaneaddr1; /* rev6 */ + u16 backplaneaddr2; /* rev6 */ + u16 backplaneaddr3; /* rev6 */ + u16 backplanedata0; /* rev6 */ + u16 backplanedata1; /* rev6 */ + u16 backplanedata2; /* rev6 */ + u16 backplanedata3; /* rev6 */ + u16 PAD[31]; + + /* sprom "size" & "blank" info */ + u16 spromstatus; /* 0x7BE, rev2 */ + u32 PAD[464]; + + u16 PAD[0x80]; +}; + +#ifdef BCMDBG +/* Device console log buffer state */ +struct brcmf_console { + uint count; /* Poll interval msec counter */ + uint log_addr; /* Log struct address (fixed) */ + struct rte_log_le log_le; /* Log struct (host copy) */ + uint bufsize; /* Size of log buffer */ + u8 *buf; /* Log buffer (host copy) */ + uint last; /* Last buffer read index */ +}; +#endif /* BCMDBG */ + +struct sdpcm_shared { + u32 flags; + u32 trap_addr; + u32 assert_exp_addr; + u32 assert_file_addr; + u32 assert_line; + u32 console_addr; /* Address of struct rte_console */ + u32 msgtrace_addr; + u8 tag[32]; +}; + +struct sdpcm_shared_le { + __le32 flags; + __le32 trap_addr; + __le32 assert_exp_addr; + __le32 assert_file_addr; + __le32 assert_line; + __le32 console_addr; /* Address of struct rte_console */ + __le32 msgtrace_addr; + u8 tag[32]; +}; + + +/* misc chip info needed by some of the routines */ +struct chip_info { + u32 chip; + u32 chiprev; + u32 cccorebase; + u32 ccrev; + u32 cccaps; + u32 buscorebase; /* 32 bits backplane bus address */ + u32 buscorerev; + u32 buscoretype; + u32 ramcorebase; + u32 armcorebase; + u32 pmurev; + u32 ramsize; +}; + +/* Private data for SDIO bus interaction */ +struct brcmf_bus { + struct brcmf_pub *drvr; + + struct brcmf_sdio_dev *sdiodev; /* sdio device handler */ + struct chip_info *ci; /* Chip info struct */ + char *vars; /* Variables (from CIS and/or other) */ + uint varsz; /* Size of variables buffer */ + + u32 ramsize; /* Size of RAM in SOCRAM (bytes) */ + + u32 hostintmask; /* Copy of Host Interrupt Mask */ + u32 intstatus; /* Intstatus bits (events) pending */ + bool dpc_sched; /* Indicates DPC schedule (intrpt rcvd) */ + bool fcstate; /* State of dongle flow-control */ + + uint blocksize; /* Block size of SDIO transfers */ + uint roundup; /* Max roundup limit */ + + struct pktq txq; /* Queue length used for flow-control */ + u8 flowcontrol; /* per prio flow control bitmask */ + u8 tx_seq; /* Transmit sequence number (next) */ + u8 tx_max; /* Maximum transmit sequence allowed */ + + u8 hdrbuf[MAX_HDR_READ + BRCMF_SDALIGN]; + u8 *rxhdr; /* Header of current rx frame (in hdrbuf) */ + u16 nextlen; /* Next Read Len from last header */ + u8 rx_seq; /* Receive sequence number (expected) */ + bool rxskip; /* Skip receive (awaiting NAK ACK) */ + + uint rxbound; /* Rx frames to read before resched */ + uint txbound; /* Tx frames to send before resched */ + uint txminmax; + + struct sk_buff *glomd; /* Packet containing glomming descriptor */ + struct sk_buff *glom; /* Packet chain for glommed superframe */ + uint glomerr; /* Glom packet read errors */ + + u8 *rxbuf; /* Buffer for receiving control packets */ + uint rxblen; /* Allocated length of rxbuf */ + u8 *rxctl; /* Aligned pointer into rxbuf */ + u8 *databuf; /* Buffer for receiving big glom packet */ + u8 *dataptr; /* Aligned pointer into databuf */ + uint rxlen; /* Length of valid data in buffer */ + + u8 sdpcm_ver; /* Bus protocol reported by dongle */ + + bool intr; /* Use interrupts */ + bool poll; /* Use polling */ + bool ipend; /* Device interrupt is pending */ + uint intrcount; /* Count of device interrupt callbacks */ + uint lastintrs; /* Count as of last watchdog timer */ + uint spurious; /* Count of spurious interrupts */ + uint pollrate; /* Ticks between device polls */ + uint polltick; /* Tick counter */ + uint pollcnt; /* Count of active polls */ + +#ifdef BCMDBG + uint console_interval; + struct brcmf_console console; /* Console output polling support */ + uint console_addr; /* Console address from shared struct */ +#endif /* BCMDBG */ + + uint regfails; /* Count of R_REG failures */ + + uint clkstate; /* State of sd and backplane clock(s) */ + bool activity; /* Activity flag for clock down */ + s32 idletime; /* Control for activity timeout */ + s32 idlecount; /* Activity timeout counter */ + s32 idleclock; /* How to set bus driver when idle */ + s32 sd_rxchain; + bool use_rxchain; /* If brcmf should use PKT chains */ + bool sleeping; /* Is SDIO bus sleeping? */ + bool rxflow_mode; /* Rx flow control mode */ + bool rxflow; /* Is rx flow control on */ + bool alp_only; /* Don't use HT clock (ALP only) */ +/* Field to decide if rx of control frames happen in rxbuf or lb-pool */ + bool usebufpool; + + /* Some additional counters */ + uint tx_sderrs; /* Count of tx attempts with sd errors */ + uint fcqueued; /* Tx packets that got queued */ + uint rxrtx; /* Count of rtx requests (NAK to dongle) */ + uint rx_toolong; /* Receive frames too long to receive */ + uint rxc_errors; /* SDIO errors when reading control frames */ + uint rx_hdrfail; /* SDIO errors on header reads */ + uint rx_badhdr; /* Bad received headers (roosync?) */ + uint rx_badseq; /* Mismatched rx sequence number */ + uint fc_rcvd; /* Number of flow-control events received */ + uint fc_xoff; /* Number which turned on flow-control */ + uint fc_xon; /* Number which turned off flow-control */ + uint rxglomfail; /* Failed deglom attempts */ + uint rxglomframes; /* Number of glom frames (superframes) */ + uint rxglompkts; /* Number of packets from glom frames */ + uint f2rxhdrs; /* Number of header reads */ + uint f2rxdata; /* Number of frame data reads */ + uint f2txdata; /* Number of f2 frame writes */ + uint f1regdata; /* Number of f1 register accesses */ + + u8 *ctrl_frame_buf; + u32 ctrl_frame_len; + bool ctrl_frame_stat; + + spinlock_t txqlock; + wait_queue_head_t ctrl_wait; + wait_queue_head_t dcmd_resp_wait; + + struct timer_list timer; + struct completion watchdog_wait; + struct task_struct *watchdog_tsk; + bool wd_timer_valid; + uint save_ms; + + struct task_struct *dpc_tsk; + struct completion dpc_wait; + + struct semaphore sdsem; + + const char *fw_name; + const struct firmware *firmware; + const char *nv_name; + u32 fw_ptr; +}; + +struct sbconfig { + u32 PAD[2]; + u32 sbipsflag; /* initiator port ocp slave flag */ + u32 PAD[3]; + u32 sbtpsflag; /* target port ocp slave flag */ + u32 PAD[11]; + u32 sbtmerrloga; /* (sonics >= 2.3) */ + u32 PAD; + u32 sbtmerrlog; /* (sonics >= 2.3) */ + u32 PAD[3]; + u32 sbadmatch3; /* address match3 */ + u32 PAD; + u32 sbadmatch2; /* address match2 */ + u32 PAD; + u32 sbadmatch1; /* address match1 */ + u32 PAD[7]; + u32 sbimstate; /* initiator agent state */ + u32 sbintvec; /* interrupt mask */ + u32 sbtmstatelow; /* target state */ + u32 sbtmstatehigh; /* target state */ + u32 sbbwa0; /* bandwidth allocation table0 */ + u32 PAD; + u32 sbimconfiglow; /* initiator configuration */ + u32 sbimconfighigh; /* initiator configuration */ + u32 sbadmatch0; /* address match0 */ + u32 PAD; + u32 sbtmconfiglow; /* target configuration */ + u32 sbtmconfighigh; /* target configuration */ + u32 sbbconfig; /* broadcast configuration */ + u32 PAD; + u32 sbbstate; /* broadcast state */ + u32 PAD[3]; + u32 sbactcnfg; /* activate configuration */ + u32 PAD[3]; + u32 sbflagst; /* current sbflags */ + u32 PAD[3]; + u32 sbidlow; /* identification */ + u32 sbidhigh; /* identification */ +}; + +/* clkstate */ +#define CLK_NONE 0 +#define CLK_SDONLY 1 +#define CLK_PENDING 2 /* Not used yet */ +#define CLK_AVAIL 3 + +#ifdef BCMDBG +static int qcount[NUMPRIO]; +static int tx_packets[NUMPRIO]; +#endif /* BCMDBG */ + +#define SDIO_DRIVE_STRENGTH 6 /* in milliamps */ + +#define RETRYCHAN(chan) ((chan) == SDPCM_EVENT_CHANNEL) + +/* Retry count for register access failures */ +static const uint retry_limit = 2; + +/* Limit on rounding up frames */ +static const uint max_roundup = 512; + +#define ALIGNMENT 4 + +static void pkt_align(struct sk_buff *p, int len, int align) +{ + uint datalign; + datalign = (unsigned long)(p->data); + datalign = roundup(datalign, (align)) - datalign; + if (datalign) + skb_pull(p, datalign); + __skb_trim(p, len); +} + +/* To check if there's window offered */ +static bool data_ok(struct brcmf_bus *bus) +{ + return (u8)(bus->tx_max - bus->tx_seq) != 0 && + ((u8)(bus->tx_max - bus->tx_seq) & 0x80) == 0; +} + +/* + * Reads a register in the SDIO hardware block. This block occupies a series of + * adresses on the 32 bit backplane bus. + */ +static void +r_sdreg32(struct brcmf_bus *bus, u32 *regvar, u32 reg_offset, u32 *retryvar) +{ + *retryvar = 0; + do { + *regvar = brcmf_sdcard_reg_read(bus->sdiodev, + bus->ci->buscorebase + reg_offset, sizeof(u32)); + } while (brcmf_sdcard_regfail(bus->sdiodev) && + (++(*retryvar) <= retry_limit)); + if (*retryvar) { + bus->regfails += (*retryvar-1); + if (*retryvar > retry_limit) { + brcmf_dbg(ERROR, "FAILED READ %Xh\n", reg_offset); + *regvar = 0; + } + } +} + +static void +w_sdreg32(struct brcmf_bus *bus, u32 regval, u32 reg_offset, u32 *retryvar) +{ + *retryvar = 0; + do { + brcmf_sdcard_reg_write(bus->sdiodev, + bus->ci->buscorebase + reg_offset, + sizeof(u32), regval); + } while (brcmf_sdcard_regfail(bus->sdiodev) && + (++(*retryvar) <= retry_limit)); + if (*retryvar) { + bus->regfails += (*retryvar-1); + if (*retryvar > retry_limit) + brcmf_dbg(ERROR, "FAILED REGISTER WRITE %Xh\n", + reg_offset); + } +} + +#define PKT_AVAILABLE() (intstatus & I_HMB_FRAME_IND) + +#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE) + +/* Packet free applicable unconditionally for sdio and sdspi. + * Conditional if bufpool was present for gspi bus. + */ +static void brcmf_sdbrcm_pktfree2(struct brcmf_bus *bus, struct sk_buff *pkt) +{ + if (bus->usebufpool) + brcmu_pkt_buf_free_skb(pkt); +} + +/* Turn backplane clock on or off */ +static int brcmf_sdbrcm_htclk(struct brcmf_bus *bus, bool on, bool pendok) +{ + int err; + u8 clkctl, clkreq, devctl; + unsigned long timeout; + + brcmf_dbg(TRACE, "Enter\n"); + + clkctl = 0; + + if (on) { + /* Request HT Avail */ + clkreq = + bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ; + + if ((bus->ci->chip == BCM4329_CHIP_ID) + && (bus->ci->chiprev == 0)) + clkreq |= SBSDIO_FORCE_ALP; + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); + if (err) { + brcmf_dbg(ERROR, "HT Avail request error: %d\n", err); + return -EBADE; + } + + if (pendok && ((bus->ci->buscoretype == PCMCIA_CORE_ID) + && (bus->ci->buscorerev == 9))) { + u32 dummy, retries; + r_sdreg32(bus, &dummy, + offsetof(struct sdpcmd_regs, clockctlstatus), + &retries); + } + + /* Check current status */ + clkctl = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + brcmf_dbg(ERROR, "HT Avail read error: %d\n", err); + return -EBADE; + } + + /* Go to pending and await interrupt if appropriate */ + if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) { + /* Allow only clock-available interrupt */ + devctl = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_dbg(ERROR, "Devctl error setting CA: %d\n", + err); + return -EBADE; + } + + devctl |= SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, devctl, &err); + brcmf_dbg(INFO, "CLKCTL: set PENDING\n"); + bus->clkstate = CLK_PENDING; + + return 0; + } else if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = + brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, devctl, &err); + } + + /* Otherwise, wait here (polling) for HT Avail */ + timeout = jiffies + + msecs_to_jiffies(PMU_MAX_TRANSITION_DLY/1000); + while (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + clkctl = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + &err); + if (time_after(jiffies, timeout)) + break; + else + usleep_range(5000, 10000); + } + if (err) { + brcmf_dbg(ERROR, "HT Avail request error: %d\n", err); + return -EBADE; + } + if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + brcmf_dbg(ERROR, "HT Avail timeout (%d): clkctl 0x%02x\n", + PMU_MAX_TRANSITION_DLY, clkctl); + return -EBADE; + } + + /* Mark clock available */ + bus->clkstate = CLK_AVAIL; + brcmf_dbg(INFO, "CLKCTL: turned ON\n"); + +#if defined(BCMDBG) + if (bus->alp_only != true) { + if (SBSDIO_ALPONLY(clkctl)) + brcmf_dbg(ERROR, "HT Clock should be on\n"); + } +#endif /* defined (BCMDBG) */ + + bus->activity = true; + } else { + clkreq = 0; + + if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, devctl, &err); + } + + bus->clkstate = CLK_SDONLY; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); + brcmf_dbg(INFO, "CLKCTL: turned OFF\n"); + if (err) { + brcmf_dbg(ERROR, "Failed access turning clock off: %d\n", + err); + return -EBADE; + } + } + return 0; +} + +/* Change idle/active SD state */ +static int brcmf_sdbrcm_sdclk(struct brcmf_bus *bus, bool on) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (on) + bus->clkstate = CLK_SDONLY; + else + bus->clkstate = CLK_NONE; + + return 0; +} + +/* Transition SD and backplane clock readiness */ +static int brcmf_sdbrcm_clkctl(struct brcmf_bus *bus, uint target, bool pendok) +{ +#ifdef BCMDBG + uint oldstate = bus->clkstate; +#endif /* BCMDBG */ + + brcmf_dbg(TRACE, "Enter\n"); + + /* Early exit if we're already there */ + if (bus->clkstate == target) { + if (target == CLK_AVAIL) { + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + bus->activity = true; + } + return 0; + } + + switch (target) { + case CLK_AVAIL: + /* Make sure SD clock is available */ + if (bus->clkstate == CLK_NONE) + brcmf_sdbrcm_sdclk(bus, true); + /* Now request HT Avail on the backplane */ + brcmf_sdbrcm_htclk(bus, true, pendok); + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + bus->activity = true; + break; + + case CLK_SDONLY: + /* Remove HT request, or bring up SD clock */ + if (bus->clkstate == CLK_NONE) + brcmf_sdbrcm_sdclk(bus, true); + else if (bus->clkstate == CLK_AVAIL) + brcmf_sdbrcm_htclk(bus, false, false); + else + brcmf_dbg(ERROR, "request for %d -> %d\n", + bus->clkstate, target); + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + break; + + case CLK_NONE: + /* Make sure to remove HT request */ + if (bus->clkstate == CLK_AVAIL) + brcmf_sdbrcm_htclk(bus, false, false); + /* Now remove the SD clock */ + brcmf_sdbrcm_sdclk(bus, false); + brcmf_sdbrcm_wd_timer(bus, 0); + break; + } +#ifdef BCMDBG + brcmf_dbg(INFO, "%d -> %d\n", oldstate, bus->clkstate); +#endif /* BCMDBG */ + + return 0; +} + +static int brcmf_sdbrcm_bussleep(struct brcmf_bus *bus, bool sleep) +{ + uint retries = 0; + + brcmf_dbg(INFO, "request %s (currently %s)\n", + sleep ? "SLEEP" : "WAKE", + bus->sleeping ? "SLEEP" : "WAKE"); + + /* Done if we're already in the requested state */ + if (sleep == bus->sleeping) + return 0; + + /* Going to sleep: set the alarm and turn off the lights... */ + if (sleep) { + /* Don't sleep if something is pending */ + if (bus->dpc_sched || bus->rxskip || pktq_len(&bus->txq)) + return -EBUSY; + + /* Make sure the controller has the bus up */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + /* Tell device to start using OOB wakeup */ + w_sdreg32(bus, SMB_USE_OOB, + offsetof(struct sdpcmd_regs, tosbmailbox), &retries); + if (retries > retry_limit) + brcmf_dbg(ERROR, "CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"); + + /* Turn off our contribution to the HT clock request */ + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + SBSDIO_FORCE_HW_CLKREQ_OFF, NULL); + + /* Isolate the bus */ + if (bus->ci->chip != BCM4329_CHIP_ID) { + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, + SBSDIO_DEVCTL_PADS_ISO, NULL); + } + + /* Change state */ + bus->sleeping = true; + + } else { + /* Waking up: bus power up is ok, set local state */ + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); + + /* Force pad isolation off if possible + (in case power never toggled) */ + if ((bus->ci->buscoretype == PCMCIA_CORE_ID) + && (bus->ci->buscorerev >= 10)) + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, 0, NULL); + + /* Make sure the controller has the bus up */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + /* Send misc interrupt to indicate OOB not needed */ + w_sdreg32(bus, 0, offsetof(struct sdpcmd_regs, tosbmailboxdata), + &retries); + if (retries <= retry_limit) + w_sdreg32(bus, SMB_DEV_INT, + offsetof(struct sdpcmd_regs, tosbmailbox), + &retries); + + if (retries > retry_limit) + brcmf_dbg(ERROR, "CANNOT SIGNAL CHIP TO CLEAR OOB!!\n"); + + /* Make sure we have SD bus access */ + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + + /* Change state */ + bus->sleeping = false; + } + + return 0; +} + +static void bus_wake(struct brcmf_bus *bus) +{ + if (bus->sleeping) + brcmf_sdbrcm_bussleep(bus, false); +} + +static u32 brcmf_sdbrcm_hostmail(struct brcmf_bus *bus) +{ + u32 intstatus = 0; + u32 hmb_data; + u8 fcbits; + uint retries = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Read mailbox data and ack that we did so */ + r_sdreg32(bus, &hmb_data, + offsetof(struct sdpcmd_regs, tohostmailboxdata), &retries); + + if (retries <= retry_limit) + w_sdreg32(bus, SMB_INT_ACK, + offsetof(struct sdpcmd_regs, tosbmailbox), &retries); + bus->f1regdata += 2; + + /* Dongle recomposed rx frames, accept them again */ + if (hmb_data & HMB_DATA_NAKHANDLED) { + brcmf_dbg(INFO, "Dongle reports NAK handled, expect rtx of %d\n", + bus->rx_seq); + if (!bus->rxskip) + brcmf_dbg(ERROR, "unexpected NAKHANDLED!\n"); + + bus->rxskip = false; + intstatus |= I_HMB_FRAME_IND; + } + + /* + * DEVREADY does not occur with gSPI. + */ + if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) { + bus->sdpcm_ver = + (hmb_data & HMB_DATA_VERSION_MASK) >> + HMB_DATA_VERSION_SHIFT; + if (bus->sdpcm_ver != SDPCM_PROT_VERSION) + brcmf_dbg(ERROR, "Version mismatch, dongle reports %d, " + "expecting %d\n", + bus->sdpcm_ver, SDPCM_PROT_VERSION); + else + brcmf_dbg(INFO, "Dongle ready, protocol version %d\n", + bus->sdpcm_ver); + } + + /* + * Flow Control has been moved into the RX headers and this out of band + * method isn't used any more. + * remaining backward compatible with older dongles. + */ + if (hmb_data & HMB_DATA_FC) { + fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >> + HMB_DATA_FCDATA_SHIFT; + + if (fcbits & ~bus->flowcontrol) + bus->fc_xoff++; + + if (bus->flowcontrol & ~fcbits) + bus->fc_xon++; + + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Shouldn't be any others */ + if (hmb_data & ~(HMB_DATA_DEVREADY | + HMB_DATA_NAKHANDLED | + HMB_DATA_FC | + HMB_DATA_FWREADY | + HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) + brcmf_dbg(ERROR, "Unknown mailbox data content: 0x%02x\n", + hmb_data); + + return intstatus; +} + +static void brcmf_sdbrcm_rxfail(struct brcmf_bus *bus, bool abort, bool rtx) +{ + uint retries = 0; + u16 lastrbc; + u8 hi, lo; + int err; + + brcmf_dbg(ERROR, "%sterminate frame%s\n", + abort ? "abort command, " : "", + rtx ? ", send NAK" : ""); + + if (abort) + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_FRAMECTRL, + SFC_RF_TERM, &err); + bus->f1regdata++; + + /* Wait until the packet has been flushed (device/FIFO stable) */ + for (lastrbc = retries = 0xffff; retries > 0; retries--) { + hi = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_RFRAMEBCHI, NULL); + lo = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_RFRAMEBCLO, NULL); + bus->f1regdata += 2; + + if ((hi == 0) && (lo == 0)) + break; + + if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) { + brcmf_dbg(ERROR, "count growing: last 0x%04x now 0x%04x\n", + lastrbc, (hi << 8) + lo); + } + lastrbc = (hi << 8) + lo; + } + + if (!retries) + brcmf_dbg(ERROR, "count never zeroed: last 0x%04x\n", lastrbc); + else + brcmf_dbg(INFO, "flush took %d iterations\n", 0xffff - retries); + + if (rtx) { + bus->rxrtx++; + w_sdreg32(bus, SMB_NAK, + offsetof(struct sdpcmd_regs, tosbmailbox), &retries); + + bus->f1regdata++; + if (retries <= retry_limit) + bus->rxskip = true; + } + + /* Clear partial in any case */ + bus->nextlen = 0; + + /* If we can't reach the device, signal failure */ + if (err || brcmf_sdcard_regfail(bus->sdiodev)) + bus->drvr->busstate = BRCMF_BUS_DOWN; +} + +static u8 brcmf_sdbrcm_rxglom(struct brcmf_bus *bus, u8 rxseq) +{ + u16 dlen, totlen; + u8 *dptr, num = 0; + + u16 sublen, check; + struct sk_buff *pfirst, *plast, *pnext, *save_pfirst; + + int errcode; + u8 chan, seq, doff, sfdoff; + u8 txmax; + + int ifidx = 0; + bool usechain = bus->use_rxchain; + + /* If packets, issue read(s) and send up packet chain */ + /* Return sequence numbers consumed? */ + + brcmf_dbg(TRACE, "start: glomd %p glom %p\n", bus->glomd, bus->glom); + + /* If there's a descriptor, generate the packet chain */ + if (bus->glomd) { + pfirst = plast = pnext = NULL; + dlen = (u16) (bus->glomd->len); + dptr = bus->glomd->data; + if (!dlen || (dlen & 1)) { + brcmf_dbg(ERROR, "bad glomd len(%d), ignore descriptor\n", + dlen); + dlen = 0; + } + + for (totlen = num = 0; dlen; num++) { + /* Get (and move past) next length */ + sublen = get_unaligned_le16(dptr); + dlen -= sizeof(u16); + dptr += sizeof(u16); + if ((sublen < SDPCM_HDRLEN) || + ((num == 0) && (sublen < (2 * SDPCM_HDRLEN)))) { + brcmf_dbg(ERROR, "descriptor len %d bad: %d\n", + num, sublen); + pnext = NULL; + break; + } + if (sublen % BRCMF_SDALIGN) { + brcmf_dbg(ERROR, "sublen %d not multiple of %d\n", + sublen, BRCMF_SDALIGN); + usechain = false; + } + totlen += sublen; + + /* For last frame, adjust read len so total + is a block multiple */ + if (!dlen) { + sublen += + (roundup(totlen, bus->blocksize) - totlen); + totlen = roundup(totlen, bus->blocksize); + } + + /* Allocate/chain packet for next subframe */ + pnext = brcmu_pkt_buf_get_skb(sublen + BRCMF_SDALIGN); + if (pnext == NULL) { + brcmf_dbg(ERROR, "bcm_pkt_buf_get_skb failed, num %d len %d\n", + num, sublen); + break; + } + if (!pfirst) { + pfirst = plast = pnext; + } else { + plast->next = pnext; + plast = pnext; + } + + /* Adhere to start alignment requirements */ + pkt_align(pnext, sublen, BRCMF_SDALIGN); + } + + /* If all allocations succeeded, save packet chain + in bus structure */ + if (pnext) { + brcmf_dbg(GLOM, "allocated %d-byte packet chain for %d subframes\n", + totlen, num); + if (BRCMF_GLOM_ON() && bus->nextlen && + totlen != bus->nextlen) { + brcmf_dbg(GLOM, "glomdesc mismatch: nextlen %d glomdesc %d rxseq %d\n", + bus->nextlen, totlen, rxseq); + } + bus->glom = pfirst; + pfirst = pnext = NULL; + } else { + if (pfirst) + brcmu_pkt_buf_free_skb(pfirst); + bus->glom = NULL; + num = 0; + } + + /* Done with descriptor packet */ + brcmu_pkt_buf_free_skb(bus->glomd); + bus->glomd = NULL; + bus->nextlen = 0; + } + + /* Ok -- either we just generated a packet chain, + or had one from before */ + if (bus->glom) { + if (BRCMF_GLOM_ON()) { + brcmf_dbg(GLOM, "try superframe read, packet chain:\n"); + for (pnext = bus->glom; pnext; pnext = pnext->next) { + brcmf_dbg(GLOM, " %p: %p len 0x%04x (%d)\n", + pnext, (u8 *) (pnext->data), + pnext->len, pnext->len); + } + } + + pfirst = bus->glom; + dlen = (u16) brcmu_pkttotlen(pfirst); + + /* Do an SDIO read for the superframe. Configurable iovar to + * read directly into the chained packet, or allocate a large + * packet and and copy into the chain. + */ + if (usechain) { + errcode = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, + F2SYNC, (u8 *) pfirst->data, dlen, + pfirst); + } else if (bus->dataptr) { + errcode = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, + F2SYNC, bus->dataptr, dlen, + NULL); + sublen = (u16) brcmu_pktfrombuf(pfirst, 0, dlen, + bus->dataptr); + if (sublen != dlen) { + brcmf_dbg(ERROR, "FAILED TO COPY, dlen %d sublen %d\n", + dlen, sublen); + errcode = -1; + } + pnext = NULL; + } else { + brcmf_dbg(ERROR, "COULDN'T ALLOC %d-BYTE GLOM, FORCE FAILURE\n", + dlen); + errcode = -1; + } + bus->f2rxdata++; + + /* On failure, kill the superframe, allow a couple retries */ + if (errcode < 0) { + brcmf_dbg(ERROR, "glom read of %d bytes failed: %d\n", + dlen, errcode); + bus->drvr->rx_errors++; + + if (bus->glomerr++ < 3) { + brcmf_sdbrcm_rxfail(bus, true, true); + } else { + bus->glomerr = 0; + brcmf_sdbrcm_rxfail(bus, true, false); + brcmu_pkt_buf_free_skb(bus->glom); + bus->rxglomfail++; + bus->glom = NULL; + } + return 0; + } +#ifdef BCMDBG + if (BRCMF_GLOM_ON()) { + printk(KERN_DEBUG "SUPERFRAME:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + pfirst->data, min_t(int, pfirst->len, 48)); + } +#endif + + /* Validate the superframe header */ + dptr = (u8 *) (pfirst->data); + sublen = get_unaligned_le16(dptr); + check = get_unaligned_le16(dptr + sizeof(u16)); + + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]); + bus->nextlen = dptr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + brcmf_dbg(INFO, "nextlen too large (%d) seq %d\n", + bus->nextlen, seq); + bus->nextlen = 0; + } + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + + errcode = 0; + if ((u16)~(sublen ^ check)) { + brcmf_dbg(ERROR, "(superframe): HW hdr error: len/check 0x%04x/0x%04x\n", + sublen, check); + errcode = -1; + } else if (roundup(sublen, bus->blocksize) != dlen) { + brcmf_dbg(ERROR, "(superframe): len 0x%04x, rounded 0x%04x, expect 0x%04x\n", + sublen, roundup(sublen, bus->blocksize), + dlen); + errcode = -1; + } else if (SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]) != + SDPCM_GLOM_CHANNEL) { + brcmf_dbg(ERROR, "(superframe): bad channel %d\n", + SDPCM_PACKET_CHANNEL( + &dptr[SDPCM_FRAMETAG_LEN])); + errcode = -1; + } else if (SDPCM_GLOMDESC(&dptr[SDPCM_FRAMETAG_LEN])) { + brcmf_dbg(ERROR, "(superframe): got 2nd descriptor?\n"); + errcode = -1; + } else if ((doff < SDPCM_HDRLEN) || + (doff > (pfirst->len - SDPCM_HDRLEN))) { + brcmf_dbg(ERROR, "(superframe): Bad data offset %d: HW %d pkt %d min %d\n", + doff, sublen, pfirst->len, SDPCM_HDRLEN); + errcode = -1; + } + + /* Check sequence number of superframe SW header */ + if (rxseq != seq) { + brcmf_dbg(INFO, "(superframe) rx_seq %d, expected %d\n", + seq, rxseq); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((u8) (txmax - bus->tx_seq) > 0x40) { + brcmf_dbg(ERROR, "unlikely tx max %d with tx_seq %d\n", + txmax, bus->tx_seq); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + + /* Remove superframe header, remember offset */ + skb_pull(pfirst, doff); + sfdoff = doff; + + /* Validate all the subframe headers */ + for (num = 0, pnext = pfirst; pnext && !errcode; + num++, pnext = pnext->next) { + dptr = (u8 *) (pnext->data); + dlen = (u16) (pnext->len); + sublen = get_unaligned_le16(dptr); + check = get_unaligned_le16(dptr + sizeof(u16)); + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); +#ifdef BCMDBG + if (BRCMF_GLOM_ON()) { + printk(KERN_DEBUG "subframe:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + dptr, 32); + } +#endif + + if ((u16)~(sublen ^ check)) { + brcmf_dbg(ERROR, "(subframe %d): HW hdr error: len/check 0x%04x/0x%04x\n", + num, sublen, check); + errcode = -1; + } else if ((sublen > dlen) || (sublen < SDPCM_HDRLEN)) { + brcmf_dbg(ERROR, "(subframe %d): length mismatch: len 0x%04x, expect 0x%04x\n", + num, sublen, dlen); + errcode = -1; + } else if ((chan != SDPCM_DATA_CHANNEL) && + (chan != SDPCM_EVENT_CHANNEL)) { + brcmf_dbg(ERROR, "(subframe %d): bad channel %d\n", + num, chan); + errcode = -1; + } else if ((doff < SDPCM_HDRLEN) || (doff > sublen)) { + brcmf_dbg(ERROR, "(subframe %d): Bad data offset %d: HW %d min %d\n", + num, doff, sublen, SDPCM_HDRLEN); + errcode = -1; + } + } + + if (errcode) { + /* Terminate frame on error, request + a couple retries */ + if (bus->glomerr++ < 3) { + /* Restore superframe header space */ + skb_push(pfirst, sfdoff); + brcmf_sdbrcm_rxfail(bus, true, true); + } else { + bus->glomerr = 0; + brcmf_sdbrcm_rxfail(bus, true, false); + brcmu_pkt_buf_free_skb(bus->glom); + bus->rxglomfail++; + bus->glom = NULL; + } + bus->nextlen = 0; + return 0; + } + + /* Basic SD framing looks ok - process each packet (header) */ + save_pfirst = pfirst; + bus->glom = NULL; + plast = NULL; + + for (num = 0; pfirst; rxseq++, pfirst = pnext) { + pnext = pfirst->next; + pfirst->next = NULL; + + dptr = (u8 *) (pfirst->data); + sublen = get_unaligned_le16(dptr); + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + + brcmf_dbg(GLOM, "Get subframe %d, %p(%p/%d), sublen %d chan %d seq %d\n", + num, pfirst, pfirst->data, + pfirst->len, sublen, chan, seq); + + /* precondition: chan == SDPCM_DATA_CHANNEL || + chan == SDPCM_EVENT_CHANNEL */ + + if (rxseq != seq) { + brcmf_dbg(GLOM, "rx_seq %d, expected %d\n", + seq, rxseq); + bus->rx_badseq++; + rxseq = seq; + } +#ifdef BCMDBG + if (BRCMF_BYTES_ON() && BRCMF_DATA_ON()) { + printk(KERN_DEBUG "Rx Subframe Data:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + dptr, dlen); + } +#endif + + __skb_trim(pfirst, sublen); + skb_pull(pfirst, doff); + + if (pfirst->len == 0) { + brcmu_pkt_buf_free_skb(pfirst); + if (plast) + plast->next = pnext; + else + save_pfirst = pnext; + + continue; + } else if (brcmf_proto_hdrpull(bus->drvr, &ifidx, + pfirst) != 0) { + brcmf_dbg(ERROR, "rx protocol error\n"); + bus->drvr->rx_errors++; + brcmu_pkt_buf_free_skb(pfirst); + if (plast) + plast->next = pnext; + else + save_pfirst = pnext; + + continue; + } + + /* this packet will go up, link back into + chain and count it */ + pfirst->next = pnext; + plast = pfirst; + num++; + +#ifdef BCMDBG + if (BRCMF_GLOM_ON()) { + brcmf_dbg(GLOM, "subframe %d to stack, %p (%p/%d) nxt/lnk %p/%p\n", + num, pfirst, pfirst->data, + pfirst->len, pfirst->next, + pfirst->prev); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + pfirst->data, + min_t(int, pfirst->len, 32)); + } +#endif /* BCMDBG */ + } + if (num) { + up(&bus->sdsem); + brcmf_rx_frame(bus->drvr, ifidx, save_pfirst, num); + down(&bus->sdsem); + } + + bus->rxglomframes++; + bus->rxglompkts += num; + } + return num; +} + +static int brcmf_sdbrcm_dcmd_resp_wait(struct brcmf_bus *bus, uint *condition, + bool *pending) +{ + DECLARE_WAITQUEUE(wait, current); + int timeout = msecs_to_jiffies(DCMD_RESP_TIMEOUT); + + /* Wait until control frame is available */ + add_wait_queue(&bus->dcmd_resp_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (!(*condition) && (!signal_pending(current) && timeout)) + timeout = schedule_timeout(timeout); + + if (signal_pending(current)) + *pending = true; + + set_current_state(TASK_RUNNING); + remove_wait_queue(&bus->dcmd_resp_wait, &wait); + + return timeout; +} + +static int brcmf_sdbrcm_dcmd_resp_wake(struct brcmf_bus *bus) +{ + if (waitqueue_active(&bus->dcmd_resp_wait)) + wake_up_interruptible(&bus->dcmd_resp_wait); + + return 0; +} +static void +brcmf_sdbrcm_read_control(struct brcmf_bus *bus, u8 *hdr, uint len, uint doff) +{ + uint rdlen, pad; + + int sdret; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Set rxctl for frame (w/optional alignment) */ + bus->rxctl = bus->rxbuf; + bus->rxctl += BRCMF_FIRSTREAD; + pad = ((unsigned long)bus->rxctl % BRCMF_SDALIGN); + if (pad) + bus->rxctl += (BRCMF_SDALIGN - pad); + bus->rxctl -= BRCMF_FIRSTREAD; + + /* Copy the already-read portion over */ + memcpy(bus->rxctl, hdr, BRCMF_FIRSTREAD); + if (len <= BRCMF_FIRSTREAD) + goto gotpkt; + + /* Raise rdlen to next SDIO block to avoid tail command */ + rdlen = len - BRCMF_FIRSTREAD; + if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((len + pad) < bus->drvr->maxctl)) + rdlen += pad; + } else if (rdlen % BRCMF_SDALIGN) { + rdlen += BRCMF_SDALIGN - (rdlen % BRCMF_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (rdlen & (ALIGNMENT - 1)) + rdlen = roundup(rdlen, ALIGNMENT); + + /* Drop if the read is too big or it exceeds our maximum */ + if ((rdlen + BRCMF_FIRSTREAD) > bus->drvr->maxctl) { + brcmf_dbg(ERROR, "%d-byte control read exceeds %d-byte buffer\n", + rdlen, bus->drvr->maxctl); + bus->drvr->rx_errors++; + brcmf_sdbrcm_rxfail(bus, false, false); + goto done; + } + + if ((len - doff) > bus->drvr->maxctl) { + brcmf_dbg(ERROR, "%d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n", + len, len - doff, bus->drvr->maxctl); + bus->drvr->rx_errors++; + bus->rx_toolong++; + brcmf_sdbrcm_rxfail(bus, false, false); + goto done; + } + + /* Read remainder of frame body into the rxctl buffer */ + sdret = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, + F2SYNC, (bus->rxctl + BRCMF_FIRSTREAD), rdlen, + NULL); + bus->f2rxdata++; + + /* Control frame failures need retransmission */ + if (sdret < 0) { + brcmf_dbg(ERROR, "read %d control bytes failed: %d\n", + rdlen, sdret); + bus->rxc_errors++; + brcmf_sdbrcm_rxfail(bus, true, true); + goto done; + } + +gotpkt: + +#ifdef BCMDBG + if (BRCMF_BYTES_ON() && BRCMF_CTL_ON()) { + printk(KERN_DEBUG "RxCtrl:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, bus->rxctl, len); + } +#endif + + /* Point to valid data and indicate its length */ + bus->rxctl += doff; + bus->rxlen = len - doff; + +done: + /* Awake any waiters */ + brcmf_sdbrcm_dcmd_resp_wake(bus); +} + +/* Pad read to blocksize for efficiency */ +static void brcmf_pad(struct brcmf_bus *bus, u16 *pad, u16 *rdlen) +{ + if (bus->roundup && bus->blocksize && *rdlen > bus->blocksize) { + *pad = bus->blocksize - (*rdlen % bus->blocksize); + if (*pad <= bus->roundup && *pad < bus->blocksize && + *rdlen + *pad + BRCMF_FIRSTREAD < MAX_RX_DATASZ) + *rdlen += *pad; + } else if (*rdlen % BRCMF_SDALIGN) { + *rdlen += BRCMF_SDALIGN - (*rdlen % BRCMF_SDALIGN); + } +} + +static void +brcmf_alloc_pkt_and_read(struct brcmf_bus *bus, u16 rdlen, + struct sk_buff **pkt, u8 **rxbuf) +{ + int sdret; /* Return code from calls */ + + *pkt = brcmu_pkt_buf_get_skb(rdlen + BRCMF_SDALIGN); + if (*pkt == NULL) + return; + + pkt_align(*pkt, rdlen, BRCMF_SDALIGN); + *rxbuf = (u8 *) ((*pkt)->data); + /* Read the entire frame */ + sdret = brcmf_sdcard_recv_buf(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, + *rxbuf, rdlen, *pkt); + bus->f2rxdata++; + + if (sdret < 0) { + brcmf_dbg(ERROR, "(nextlen): read %d bytes failed: %d\n", + rdlen, sdret); + brcmu_pkt_buf_free_skb(*pkt); + bus->drvr->rx_errors++; + /* Force retry w/normal header read. + * Don't attempt NAK for + * gSPI + */ + brcmf_sdbrcm_rxfail(bus, true, true); + *pkt = NULL; + } +} + +/* Checks the header */ +static int +brcmf_check_rxbuf(struct brcmf_bus *bus, struct sk_buff *pkt, u8 *rxbuf, + u8 rxseq, u16 nextlen, u16 *len) +{ + u16 check; + bool len_consistent; /* Result of comparing readahead len and + len from hw-hdr */ + + memcpy(bus->rxhdr, rxbuf, SDPCM_HDRLEN); + + /* Extract hardware header fields */ + *len = get_unaligned_le16(bus->rxhdr); + check = get_unaligned_le16(bus->rxhdr + sizeof(u16)); + + /* All zeros means readahead info was bad */ + if (!(*len | check)) { + brcmf_dbg(INFO, "(nextlen): read zeros in HW header???\n"); + goto fail; + } + + /* Validate check bytes */ + if ((u16)~(*len ^ check)) { + brcmf_dbg(ERROR, "(nextlen): HW hdr error: nextlen/len/check 0x%04x/0x%04x/0x%04x\n", + nextlen, *len, check); + bus->rx_badhdr++; + brcmf_sdbrcm_rxfail(bus, false, false); + goto fail; + } + + /* Validate frame length */ + if (*len < SDPCM_HDRLEN) { + brcmf_dbg(ERROR, "(nextlen): HW hdr length invalid: %d\n", + *len); + goto fail; + } + + /* Check for consistency with readahead info */ + len_consistent = (nextlen != (roundup(*len, 16) >> 4)); + if (len_consistent) { + /* Mismatch, force retry w/normal + header (may be >4K) */ + brcmf_dbg(ERROR, "(nextlen): mismatch, nextlen %d len %d rnd %d; expected rxseq %d\n", + nextlen, *len, roundup(*len, 16), + rxseq); + brcmf_sdbrcm_rxfail(bus, true, true); + goto fail; + } + + return 0; + +fail: + brcmf_sdbrcm_pktfree2(bus, pkt); + return -EINVAL; +} + +/* Return true if there may be more frames to read */ +static uint +brcmf_sdbrcm_readframes(struct brcmf_bus *bus, uint maxframes, bool *finished) +{ + u16 len, check; /* Extracted hardware header fields */ + u8 chan, seq, doff; /* Extracted software header fields */ + u8 fcbits; /* Extracted fcbits from software header */ + + struct sk_buff *pkt; /* Packet for event or data frames */ + u16 pad; /* Number of pad bytes to read */ + u16 rdlen; /* Total number of bytes to read */ + u8 rxseq; /* Next sequence number to expect */ + uint rxleft = 0; /* Remaining number of frames allowed */ + int sdret; /* Return code from calls */ + u8 txmax; /* Maximum tx sequence offered */ + u8 *rxbuf; + int ifidx = 0; + uint rxcount = 0; /* Total frames read */ + + brcmf_dbg(TRACE, "Enter\n"); + + /* Not finished unless we encounter no more frames indication */ + *finished = false; + + for (rxseq = bus->rx_seq, rxleft = maxframes; + !bus->rxskip && rxleft && bus->drvr->busstate != BRCMF_BUS_DOWN; + rxseq++, rxleft--) { + + /* Handle glomming separately */ + if (bus->glom || bus->glomd) { + u8 cnt; + brcmf_dbg(GLOM, "calling rxglom: glomd %p, glom %p\n", + bus->glomd, bus->glom); + cnt = brcmf_sdbrcm_rxglom(bus, rxseq); + brcmf_dbg(GLOM, "rxglom returned %d\n", cnt); + rxseq += cnt - 1; + rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1; + continue; + } + + /* Try doing single read if we can */ + if (bus->nextlen) { + u16 nextlen = bus->nextlen; + bus->nextlen = 0; + + rdlen = len = nextlen << 4; + brcmf_pad(bus, &pad, &rdlen); + + /* + * After the frame is received we have to + * distinguish whether it is data + * or non-data frame. + */ + brcmf_alloc_pkt_and_read(bus, rdlen, &pkt, &rxbuf); + if (pkt == NULL) { + /* Give up on data, request rtx of events */ + brcmf_dbg(ERROR, "(nextlen): brcmf_alloc_pkt_and_read failed: len %d rdlen %d expected rxseq %d\n", + len, rdlen, rxseq); + continue; + } + + if (brcmf_check_rxbuf(bus, pkt, rxbuf, rxseq, nextlen, + &len) < 0) + continue; + + /* Extract software header fields */ + chan = SDPCM_PACKET_CHANNEL( + &bus->rxhdr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE( + &bus->rxhdr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE( + &bus->rxhdr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE( + &bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + bus->nextlen = + bus->rxhdr[SDPCM_FRAMETAG_LEN + + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + brcmf_dbg(INFO, "(nextlen): got frame w/nextlen too large (%d), seq %d\n", + bus->nextlen, seq); + bus->nextlen = 0; + } + + bus->drvr->rx_readahead_cnt++; + + /* Handle Flow Control */ + fcbits = SDPCM_FCMASK_VALUE( + &bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + if (bus->flowcontrol != fcbits) { + if (~bus->flowcontrol & fcbits) + bus->fc_xoff++; + + if (bus->flowcontrol & ~fcbits) + bus->fc_xon++; + + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Check and update sequence number */ + if (rxseq != seq) { + brcmf_dbg(INFO, "(nextlen): rx_seq %d, expected %d\n", + seq, rxseq); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((u8) (txmax - bus->tx_seq) > 0x40) { + brcmf_dbg(ERROR, "got unlikely tx max %d with tx_seq %d\n", + txmax, bus->tx_seq); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + +#ifdef BCMDBG + if (BRCMF_BYTES_ON() && BRCMF_DATA_ON()) { + printk(KERN_DEBUG "Rx Data:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + rxbuf, len); + } else if (BRCMF_HDRS_ON()) { + printk(KERN_DEBUG "RxHdr:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + bus->rxhdr, SDPCM_HDRLEN); + } +#endif + + if (chan == SDPCM_CONTROL_CHANNEL) { + brcmf_dbg(ERROR, "(nextlen): readahead on control packet %d?\n", + seq); + /* Force retry w/normal header read */ + bus->nextlen = 0; + brcmf_sdbrcm_rxfail(bus, false, true); + brcmf_sdbrcm_pktfree2(bus, pkt); + continue; + } + + /* Validate data offset */ + if ((doff < SDPCM_HDRLEN) || (doff > len)) { + brcmf_dbg(ERROR, "(nextlen): bad data offset %d: HW len %d min %d\n", + doff, len, SDPCM_HDRLEN); + brcmf_sdbrcm_rxfail(bus, false, false); + brcmf_sdbrcm_pktfree2(bus, pkt); + continue; + } + + /* All done with this one -- now deliver the packet */ + goto deliver; + } + + /* Read frame header (hardware and software) */ + sdret = brcmf_sdcard_recv_buf(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, bus->rxhdr, + BRCMF_FIRSTREAD, NULL); + bus->f2rxhdrs++; + + if (sdret < 0) { + brcmf_dbg(ERROR, "RXHEADER FAILED: %d\n", sdret); + bus->rx_hdrfail++; + brcmf_sdbrcm_rxfail(bus, true, true); + continue; + } +#ifdef BCMDBG + if (BRCMF_BYTES_ON() || BRCMF_HDRS_ON()) { + printk(KERN_DEBUG "RxHdr:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + bus->rxhdr, SDPCM_HDRLEN); + } +#endif + + /* Extract hardware header fields */ + len = get_unaligned_le16(bus->rxhdr); + check = get_unaligned_le16(bus->rxhdr + sizeof(u16)); + + /* All zeros means no more frames */ + if (!(len | check)) { + *finished = true; + break; + } + + /* Validate check bytes */ + if ((u16) ~(len ^ check)) { + brcmf_dbg(ERROR, "HW hdr err: len/check 0x%04x/0x%04x\n", + len, check); + bus->rx_badhdr++; + brcmf_sdbrcm_rxfail(bus, false, false); + continue; + } + + /* Validate frame length */ + if (len < SDPCM_HDRLEN) { + brcmf_dbg(ERROR, "HW hdr length invalid: %d\n", len); + continue; + } + + /* Extract software header fields */ + chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + /* Validate data offset */ + if ((doff < SDPCM_HDRLEN) || (doff > len)) { + brcmf_dbg(ERROR, "Bad data offset %d: HW len %d, min %d seq %d\n", + doff, len, SDPCM_HDRLEN, seq); + bus->rx_badhdr++; + brcmf_sdbrcm_rxfail(bus, false, false); + continue; + } + + /* Save the readahead length if there is one */ + bus->nextlen = + bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + brcmf_dbg(INFO, "(nextlen): got frame w/nextlen too large (%d), seq %d\n", + bus->nextlen, seq); + bus->nextlen = 0; + } + + /* Handle Flow Control */ + fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + if (bus->flowcontrol != fcbits) { + if (~bus->flowcontrol & fcbits) + bus->fc_xoff++; + + if (bus->flowcontrol & ~fcbits) + bus->fc_xon++; + + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Check and update sequence number */ + if (rxseq != seq) { + brcmf_dbg(INFO, "rx_seq %d, expected %d\n", seq, rxseq); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((u8) (txmax - bus->tx_seq) > 0x40) { + brcmf_dbg(ERROR, "unlikely tx max %d with tx_seq %d\n", + txmax, bus->tx_seq); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + + /* Call a separate function for control frames */ + if (chan == SDPCM_CONTROL_CHANNEL) { + brcmf_sdbrcm_read_control(bus, bus->rxhdr, len, doff); + continue; + } + + /* precondition: chan is either SDPCM_DATA_CHANNEL, + SDPCM_EVENT_CHANNEL, SDPCM_TEST_CHANNEL or + SDPCM_GLOM_CHANNEL */ + + /* Length to read */ + rdlen = (len > BRCMF_FIRSTREAD) ? (len - BRCMF_FIRSTREAD) : 0; + + /* May pad read to blocksize for efficiency */ + if (bus->roundup && bus->blocksize && + (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((rdlen + pad + BRCMF_FIRSTREAD) < MAX_RX_DATASZ)) + rdlen += pad; + } else if (rdlen % BRCMF_SDALIGN) { + rdlen += BRCMF_SDALIGN - (rdlen % BRCMF_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (rdlen & (ALIGNMENT - 1)) + rdlen = roundup(rdlen, ALIGNMENT); + + if ((rdlen + BRCMF_FIRSTREAD) > MAX_RX_DATASZ) { + /* Too long -- skip this frame */ + brcmf_dbg(ERROR, "too long: len %d rdlen %d\n", + len, rdlen); + bus->drvr->rx_errors++; + bus->rx_toolong++; + brcmf_sdbrcm_rxfail(bus, false, false); + continue; + } + + pkt = brcmu_pkt_buf_get_skb(rdlen + + BRCMF_FIRSTREAD + BRCMF_SDALIGN); + if (!pkt) { + /* Give up on data, request rtx of events */ + brcmf_dbg(ERROR, "brcmu_pkt_buf_get_skb failed: rdlen %d chan %d\n", + rdlen, chan); + bus->drvr->rx_dropped++; + brcmf_sdbrcm_rxfail(bus, false, RETRYCHAN(chan)); + continue; + } + + /* Leave room for what we already read, and align remainder */ + skb_pull(pkt, BRCMF_FIRSTREAD); + pkt_align(pkt, rdlen, BRCMF_SDALIGN); + + /* Read the remaining frame data */ + sdret = brcmf_sdcard_recv_buf(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, ((u8 *) (pkt->data)), + rdlen, pkt); + bus->f2rxdata++; + + if (sdret < 0) { + brcmf_dbg(ERROR, "read %d %s bytes failed: %d\n", rdlen, + ((chan == SDPCM_EVENT_CHANNEL) ? "event" + : ((chan == SDPCM_DATA_CHANNEL) ? "data" + : "test")), sdret); + brcmu_pkt_buf_free_skb(pkt); + bus->drvr->rx_errors++; + brcmf_sdbrcm_rxfail(bus, true, RETRYCHAN(chan)); + continue; + } + + /* Copy the already-read portion */ + skb_push(pkt, BRCMF_FIRSTREAD); + memcpy(pkt->data, bus->rxhdr, BRCMF_FIRSTREAD); + +#ifdef BCMDBG + if (BRCMF_BYTES_ON() && BRCMF_DATA_ON()) { + printk(KERN_DEBUG "Rx Data:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + pkt->data, len); + } +#endif + +deliver: + /* Save superframe descriptor and allocate packet frame */ + if (chan == SDPCM_GLOM_CHANNEL) { + if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_FRAMETAG_LEN])) { + brcmf_dbg(GLOM, "glom descriptor, %d bytes:\n", + len); +#ifdef BCMDBG + if (BRCMF_GLOM_ON()) { + printk(KERN_DEBUG "Glom Data:\n"); + print_hex_dump_bytes("", + DUMP_PREFIX_OFFSET, + pkt->data, len); + } +#endif + __skb_trim(pkt, len); + skb_pull(pkt, SDPCM_HDRLEN); + bus->glomd = pkt; + } else { + brcmf_dbg(ERROR, "%s: glom superframe w/o " + "descriptor!\n", __func__); + brcmf_sdbrcm_rxfail(bus, false, false); + } + continue; + } + + /* Fill in packet len and prio, deliver upward */ + __skb_trim(pkt, len); + skb_pull(pkt, doff); + + if (pkt->len == 0) { + brcmu_pkt_buf_free_skb(pkt); + continue; + } else if (brcmf_proto_hdrpull(bus->drvr, &ifidx, pkt) != 0) { + brcmf_dbg(ERROR, "rx protocol error\n"); + brcmu_pkt_buf_free_skb(pkt); + bus->drvr->rx_errors++; + continue; + } + + /* Unlock during rx call */ + up(&bus->sdsem); + brcmf_rx_frame(bus->drvr, ifidx, pkt, 1); + down(&bus->sdsem); + } + rxcount = maxframes - rxleft; +#ifdef BCMDBG + /* Message if we hit the limit */ + if (!rxleft) + brcmf_dbg(DATA, "hit rx limit of %d frames\n", + maxframes); + else +#endif /* BCMDBG */ + brcmf_dbg(DATA, "processed %d frames\n", rxcount); + /* Back off rxseq if awaiting rtx, update rx_seq */ + if (bus->rxskip) + rxseq--; + bus->rx_seq = rxseq; + + return rxcount; +} + +static int +brcmf_sdbrcm_send_buf(struct brcmf_bus *bus, u32 addr, uint fn, uint flags, + u8 *buf, uint nbytes, struct sk_buff *pkt) +{ + return brcmf_sdcard_send_buf + (bus->sdiodev, addr, fn, flags, buf, nbytes, pkt); +} + +static void +brcmf_sdbrcm_wait_for_event(struct brcmf_bus *bus, bool *lockvar) +{ + up(&bus->sdsem); + wait_event_interruptible_timeout(bus->ctrl_wait, + (*lockvar == false), HZ * 2); + down(&bus->sdsem); + return; +} + +static void +brcmf_sdbrcm_wait_event_wakeup(struct brcmf_bus *bus) +{ + if (waitqueue_active(&bus->ctrl_wait)) + wake_up_interruptible(&bus->ctrl_wait); + return; +} + +/* Writes a HW/SW header into the packet and sends it. */ +/* Assumes: (a) header space already there, (b) caller holds lock */ +static int brcmf_sdbrcm_txpkt(struct brcmf_bus *bus, struct sk_buff *pkt, + uint chan, bool free_pkt) +{ + int ret; + u8 *frame; + u16 len, pad = 0; + u32 swheader; + struct sk_buff *new; + int i; + + brcmf_dbg(TRACE, "Enter\n"); + + frame = (u8 *) (pkt->data); + + /* Add alignment padding, allocate new packet if needed */ + pad = ((unsigned long)frame % BRCMF_SDALIGN); + if (pad) { + if (skb_headroom(pkt) < pad) { + brcmf_dbg(INFO, "insufficient headroom %d for %d pad\n", + skb_headroom(pkt), pad); + bus->drvr->tx_realloc++; + new = brcmu_pkt_buf_get_skb(pkt->len + BRCMF_SDALIGN); + if (!new) { + brcmf_dbg(ERROR, "couldn't allocate new %d-byte packet\n", + pkt->len + BRCMF_SDALIGN); + ret = -ENOMEM; + goto done; + } + + pkt_align(new, pkt->len, BRCMF_SDALIGN); + memcpy(new->data, pkt->data, pkt->len); + if (free_pkt) + brcmu_pkt_buf_free_skb(pkt); + /* free the pkt if canned one is not used */ + free_pkt = true; + pkt = new; + frame = (u8 *) (pkt->data); + /* precondition: (frame % BRCMF_SDALIGN) == 0) */ + pad = 0; + } else { + skb_push(pkt, pad); + frame = (u8 *) (pkt->data); + /* precondition: pad + SDPCM_HDRLEN <= pkt->len */ + memset(frame, 0, pad + SDPCM_HDRLEN); + } + } + /* precondition: pad < BRCMF_SDALIGN */ + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + len = (u16) (pkt->len); + *(__le16 *) frame = cpu_to_le16(len); + *(((__le16 *) frame) + 1) = cpu_to_le16(~len); + + /* Software tag: channel, sequence number, data offset */ + swheader = + ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) | bus->tx_seq | + (((pad + + SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK); + + put_unaligned_le32(swheader, frame + SDPCM_FRAMETAG_LEN); + put_unaligned_le32(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader)); + +#ifdef BCMDBG + tx_packets[pkt->priority]++; + if (BRCMF_BYTES_ON() && + (((BRCMF_CTL_ON() && (chan == SDPCM_CONTROL_CHANNEL)) || + (BRCMF_DATA_ON() && (chan != SDPCM_CONTROL_CHANNEL))))) { + printk(KERN_DEBUG "Tx Frame:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, frame, len); + } else if (BRCMF_HDRS_ON()) { + printk(KERN_DEBUG "TxHdr:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + frame, min_t(u16, len, 16)); + } +#endif + + /* Raise len to next SDIO block to eliminate tail command */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + u16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) + len += pad; + } else if (len % BRCMF_SDALIGN) { + len += BRCMF_SDALIGN - (len % BRCMF_SDALIGN); + } + + /* Some controllers have trouble with odd bytes -- round to even */ + if (len & (ALIGNMENT - 1)) + len = roundup(len, ALIGNMENT); + + ret = brcmf_sdbrcm_send_buf(bus, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, frame, + len, pkt); + bus->f2txdata++; + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + ret); + bus->tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM, + NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, + NULL); + lo = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, + NULL); + bus->f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + if (ret == 0) + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + +done: + /* restore pkt buffer pointer before calling tx complete routine */ + skb_pull(pkt, SDPCM_HDRLEN + pad); + up(&bus->sdsem); + brcmf_txcomplete(bus->drvr, pkt, ret != 0); + down(&bus->sdsem); + + if (free_pkt) + brcmu_pkt_buf_free_skb(pkt); + + return ret; +} + +static uint brcmf_sdbrcm_sendfromq(struct brcmf_bus *bus, uint maxframes) +{ + struct sk_buff *pkt; + u32 intstatus = 0; + uint retries = 0; + int ret = 0, prec_out; + uint cnt = 0; + uint datalen; + u8 tx_prec_map; + + struct brcmf_pub *drvr = bus->drvr; + + brcmf_dbg(TRACE, "Enter\n"); + + tx_prec_map = ~bus->flowcontrol; + + /* Send frames until the limit or some other event */ + for (cnt = 0; (cnt < maxframes) && data_ok(bus); cnt++) { + spin_lock_bh(&bus->txqlock); + pkt = brcmu_pktq_mdeq(&bus->txq, tx_prec_map, &prec_out); + if (pkt == NULL) { + spin_unlock_bh(&bus->txqlock); + break; + } + spin_unlock_bh(&bus->txqlock); + datalen = pkt->len - SDPCM_HDRLEN; + + ret = brcmf_sdbrcm_txpkt(bus, pkt, SDPCM_DATA_CHANNEL, true); + if (ret) + bus->drvr->tx_errors++; + else + bus->drvr->dstats.tx_bytes += datalen; + + /* In poll mode, need to check for other events */ + if (!bus->intr && cnt) { + /* Check device status, signal pending interrupt */ + r_sdreg32(bus, &intstatus, + offsetof(struct sdpcmd_regs, intstatus), + &retries); + bus->f2txdata++; + if (brcmf_sdcard_regfail(bus->sdiodev)) + break; + if (intstatus & bus->hostintmask) + bus->ipend = true; + } + } + + /* Deflow-control stack if needed */ + if (drvr->up && (drvr->busstate == BRCMF_BUS_DATA) && + drvr->txoff && (pktq_len(&bus->txq) < TXLOW)) + brcmf_txflowcontrol(drvr, 0, OFF); + + return cnt; +} + +static bool brcmf_sdbrcm_dpc(struct brcmf_bus *bus) +{ + u32 intstatus, newstatus = 0; + uint retries = 0; + uint rxlimit = bus->rxbound; /* Rx frames to read before resched */ + uint txlimit = bus->txbound; /* Tx frames to send before resched */ + uint framecnt = 0; /* Temporary counter of tx/rx frames */ + bool rxdone = true; /* Flag for no more read data */ + bool resched = false; /* Flag indicating resched wanted */ + + brcmf_dbg(TRACE, "Enter\n"); + + /* Start with leftover status bits */ + intstatus = bus->intstatus; + + down(&bus->sdsem); + + /* If waiting for HTAVAIL, check status */ + if (bus->clkstate == CLK_PENDING) { + int err; + u8 clkctl, devctl = 0; + +#ifdef BCMDBG + /* Check for inconsistent device control */ + devctl = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_dbg(ERROR, "error reading DEVCTL: %d\n", err); + bus->drvr->busstate = BRCMF_BUS_DOWN; + } +#endif /* BCMDBG */ + + /* Read CSR, if clock on switch to AVAIL, else ignore */ + clkctl = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + brcmf_dbg(ERROR, "error reading CSR: %d\n", + err); + bus->drvr->busstate = BRCMF_BUS_DOWN; + } + + brcmf_dbg(INFO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", + devctl, clkctl); + + if (SBSDIO_HTAV(clkctl)) { + devctl = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_dbg(ERROR, "error reading DEVCTL: %d\n", + err); + bus->drvr->busstate = BRCMF_BUS_DOWN; + } + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_DEVICE_CTL, devctl, &err); + if (err) { + brcmf_dbg(ERROR, "error writing DEVCTL: %d\n", + err); + bus->drvr->busstate = BRCMF_BUS_DOWN; + } + bus->clkstate = CLK_AVAIL; + } else { + goto clkwait; + } + } + + bus_wake(bus); + + /* Make sure backplane clock is on */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, true); + if (bus->clkstate == CLK_PENDING) + goto clkwait; + + /* Pending interrupt indicates new device status */ + if (bus->ipend) { + bus->ipend = false; + r_sdreg32(bus, &newstatus, + offsetof(struct sdpcmd_regs, intstatus), &retries); + bus->f1regdata++; + if (brcmf_sdcard_regfail(bus->sdiodev)) + newstatus = 0; + newstatus &= bus->hostintmask; + bus->fcstate = !!(newstatus & I_HMB_FC_STATE); + if (newstatus) { + w_sdreg32(bus, newstatus, + offsetof(struct sdpcmd_regs, intstatus), + &retries); + bus->f1regdata++; + } + } + + /* Merge new bits with previous */ + intstatus |= newstatus; + bus->intstatus = 0; + + /* Handle flow-control change: read new state in case our ack + * crossed another change interrupt. If change still set, assume + * FC ON for safety, let next loop through do the debounce. + */ + if (intstatus & I_HMB_FC_CHANGE) { + intstatus &= ~I_HMB_FC_CHANGE; + w_sdreg32(bus, I_HMB_FC_CHANGE, + offsetof(struct sdpcmd_regs, intstatus), &retries); + + r_sdreg32(bus, &newstatus, + offsetof(struct sdpcmd_regs, intstatus), &retries); + bus->f1regdata += 2; + bus->fcstate = + !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE)); + intstatus |= (newstatus & bus->hostintmask); + } + + /* Handle host mailbox indication */ + if (intstatus & I_HMB_HOST_INT) { + intstatus &= ~I_HMB_HOST_INT; + intstatus |= brcmf_sdbrcm_hostmail(bus); + } + + /* Generally don't ask for these, can get CRC errors... */ + if (intstatus & I_WR_OOSYNC) { + brcmf_dbg(ERROR, "Dongle reports WR_OOSYNC\n"); + intstatus &= ~I_WR_OOSYNC; + } + + if (intstatus & I_RD_OOSYNC) { + brcmf_dbg(ERROR, "Dongle reports RD_OOSYNC\n"); + intstatus &= ~I_RD_OOSYNC; + } + + if (intstatus & I_SBINT) { + brcmf_dbg(ERROR, "Dongle reports SBINT\n"); + intstatus &= ~I_SBINT; + } + + /* Would be active due to wake-wlan in gSPI */ + if (intstatus & I_CHIPACTIVE) { + brcmf_dbg(INFO, "Dongle reports CHIPACTIVE\n"); + intstatus &= ~I_CHIPACTIVE; + } + + /* Ignore frame indications if rxskip is set */ + if (bus->rxskip) + intstatus &= ~I_HMB_FRAME_IND; + + /* On frame indication, read available frames */ + if (PKT_AVAILABLE()) { + framecnt = brcmf_sdbrcm_readframes(bus, rxlimit, &rxdone); + if (rxdone || bus->rxskip) + intstatus &= ~I_HMB_FRAME_IND; + rxlimit -= min(framecnt, rxlimit); + } + + /* Keep still-pending events for next scheduling */ + bus->intstatus = intstatus; + +clkwait: + if (data_ok(bus) && bus->ctrl_frame_stat && + (bus->clkstate == CLK_AVAIL)) { + int ret, i; + + ret = brcmf_sdbrcm_send_buf(bus, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, (u8 *) bus->ctrl_frame_buf, + (u32) bus->ctrl_frame_len, NULL); + + if (ret < 0) { + /* On failure, abort the command and + terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + ret); + bus->tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM, + NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, + NULL); + lo = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, + NULL); + bus->f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + if (ret == 0) + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + + brcmf_dbg(INFO, "Return_dpc value is : %d\n", ret); + bus->ctrl_frame_stat = false; + brcmf_sdbrcm_wait_event_wakeup(bus); + } + /* Send queued frames (limit 1 if rx may still be pending) */ + else if ((bus->clkstate == CLK_AVAIL) && !bus->fcstate && + brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit + && data_ok(bus)) { + framecnt = rxdone ? txlimit : min(txlimit, bus->txminmax); + framecnt = brcmf_sdbrcm_sendfromq(bus, framecnt); + txlimit -= framecnt; + } + + /* Resched if events or tx frames are pending, + else await next interrupt */ + /* On failed register access, all bets are off: + no resched or interrupts */ + if ((bus->drvr->busstate == BRCMF_BUS_DOWN) || + brcmf_sdcard_regfail(bus->sdiodev)) { + brcmf_dbg(ERROR, "failed backplane access over SDIO, halting operation %d\n", + brcmf_sdcard_regfail(bus->sdiodev)); + bus->drvr->busstate = BRCMF_BUS_DOWN; + bus->intstatus = 0; + } else if (bus->clkstate == CLK_PENDING) { + brcmf_dbg(INFO, "rescheduled due to CLK_PENDING awaiting I_CHIPACTIVE interrupt\n"); + resched = true; + } else if (bus->intstatus || bus->ipend || + (!bus->fcstate && brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) + && data_ok(bus)) || PKT_AVAILABLE()) { + resched = true; + } + + bus->dpc_sched = resched; + + /* If we're done for now, turn off clock request. */ + if ((bus->clkstate != CLK_PENDING) + && bus->idletime == BRCMF_IDLE_IMMEDIATE) { + bus->activity = false; + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + } + + up(&bus->sdsem); + + return resched; +} + +static int brcmf_sdbrcm_dpc_thread(void *data) +{ + struct brcmf_bus *bus = (struct brcmf_bus *) data; + + allow_signal(SIGTERM); + /* Run until signal received */ + while (1) { + if (kthread_should_stop()) + break; + if (!wait_for_completion_interruptible(&bus->dpc_wait)) { + /* Call bus dpc unless it indicated down + (then clean stop) */ + if (bus->drvr->busstate != BRCMF_BUS_DOWN) { + if (brcmf_sdbrcm_dpc(bus)) + complete(&bus->dpc_wait); + } else { + /* after stopping the bus, exit thread */ + brcmf_sdbrcm_bus_stop(bus); + bus->dpc_tsk = NULL; + break; + } + } else + break; + } + return 0; +} + +int brcmf_sdbrcm_bus_txdata(struct brcmf_bus *bus, struct sk_buff *pkt) +{ + int ret = -EBADE; + uint datalen, prec; + + brcmf_dbg(TRACE, "Enter\n"); + + datalen = pkt->len; + + /* Add space for the header */ + skb_push(pkt, SDPCM_HDRLEN); + /* precondition: IS_ALIGNED((unsigned long)(pkt->data), 2) */ + + prec = prio2prec((pkt->priority & PRIOMASK)); + + /* Check for existing queue, current flow-control, + pending event, or pending clock */ + brcmf_dbg(TRACE, "deferring pktq len %d\n", pktq_len(&bus->txq)); + bus->fcqueued++; + + /* Priority based enq */ + spin_lock_bh(&bus->txqlock); + if (brcmf_c_prec_enq(bus->drvr, &bus->txq, pkt, prec) == false) { + skb_pull(pkt, SDPCM_HDRLEN); + brcmf_txcomplete(bus->drvr, pkt, false); + brcmu_pkt_buf_free_skb(pkt); + brcmf_dbg(ERROR, "out of bus->txq !!!\n"); + ret = -ENOSR; + } else { + ret = 0; + } + spin_unlock_bh(&bus->txqlock); + + if (pktq_len(&bus->txq) >= TXHI) + brcmf_txflowcontrol(bus->drvr, 0, ON); + +#ifdef BCMDBG + if (pktq_plen(&bus->txq, prec) > qcount[prec]) + qcount[prec] = pktq_plen(&bus->txq, prec); +#endif + /* Schedule DPC if needed to send queued packet(s) */ + if (!bus->dpc_sched) { + bus->dpc_sched = true; + if (bus->dpc_tsk) + complete(&bus->dpc_wait); + } + + return ret; +} + +static int +brcmf_sdbrcm_membytes(struct brcmf_bus *bus, bool write, u32 address, u8 *data, + uint size) +{ + int bcmerror = 0; + u32 sdaddr; + uint dsize; + + /* Determine initial transfer parameters */ + sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK; + if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK) + dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr); + else + dsize = size; + + /* Set the backplane window to include the start address */ + bcmerror = brcmf_sdcard_set_sbaddr_window(bus->sdiodev, address); + if (bcmerror) { + brcmf_dbg(ERROR, "window change failed\n"); + goto xfer_done; + } + + /* Do the transfer(s) */ + while (size) { + brcmf_dbg(INFO, "%s %d bytes at offset 0x%08x in window 0x%08x\n", + write ? "write" : "read", dsize, + sdaddr, address & SBSDIO_SBWINDOW_MASK); + bcmerror = brcmf_sdcard_rwdata(bus->sdiodev, write, + sdaddr, data, dsize); + if (bcmerror) { + brcmf_dbg(ERROR, "membytes transfer failed\n"); + break; + } + + /* Adjust for next transfer (if any) */ + size -= dsize; + if (size) { + data += dsize; + address += dsize; + bcmerror = brcmf_sdcard_set_sbaddr_window(bus->sdiodev, + address); + if (bcmerror) { + brcmf_dbg(ERROR, "window change failed\n"); + break; + } + sdaddr = 0; + dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size); + } + } + +xfer_done: + /* Return the window to backplane enumeration space for core access */ + if (brcmf_sdcard_set_sbaddr_window(bus->sdiodev, bus->sdiodev->sbwad)) + brcmf_dbg(ERROR, "FAILED to set window back to 0x%x\n", + bus->sdiodev->sbwad); + + return bcmerror; +} + +#ifdef BCMDBG +#define CONSOLE_LINE_MAX 192 + +static int brcmf_sdbrcm_readconsole(struct brcmf_bus *bus) +{ + struct brcmf_console *c = &bus->console; + u8 line[CONSOLE_LINE_MAX], ch; + u32 n, idx, addr; + int rv; + + /* Don't do anything until FWREADY updates console address */ + if (bus->console_addr == 0) + return 0; + + /* Read console log struct */ + addr = bus->console_addr + offsetof(struct rte_console, log_le); + rv = brcmf_sdbrcm_membytes(bus, false, addr, (u8 *)&c->log_le, + sizeof(c->log_le)); + if (rv < 0) + return rv; + + /* Allocate console buffer (one time only) */ + if (c->buf == NULL) { + c->bufsize = le32_to_cpu(c->log_le.buf_size); + c->buf = kmalloc(c->bufsize, GFP_ATOMIC); + if (c->buf == NULL) + return -ENOMEM; + } + + idx = le32_to_cpu(c->log_le.idx); + + /* Protect against corrupt value */ + if (idx > c->bufsize) + return -EBADE; + + /* Skip reading the console buffer if the index pointer + has not moved */ + if (idx == c->last) + return 0; + + /* Read the console buffer */ + addr = le32_to_cpu(c->log_le.buf); + rv = brcmf_sdbrcm_membytes(bus, false, addr, c->buf, c->bufsize); + if (rv < 0) + return rv; + + while (c->last != idx) { + for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) { + if (c->last == idx) { + /* This would output a partial line. + * Instead, back up + * the buffer pointer and output this + * line next time around. + */ + if (c->last >= n) + c->last -= n; + else + c->last = c->bufsize - n; + goto break2; + } + ch = c->buf[c->last]; + c->last = (c->last + 1) % c->bufsize; + if (ch == '\n') + break; + line[n] = ch; + } + + if (n > 0) { + if (line[n - 1] == '\r') + n--; + line[n] = 0; + printk(KERN_DEBUG "CONSOLE: %s\n", line); + } + } +break2: + + return 0; +} +#endif /* BCMDBG */ + +static int brcmf_tx_frame(struct brcmf_bus *bus, u8 *frame, u16 len) +{ + int i; + int ret; + + bus->ctrl_frame_stat = false; + ret = brcmf_sdbrcm_send_buf(bus, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, frame, len, NULL); + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + ret); + bus->tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, + NULL); + lo = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, + NULL); + bus->f1regdata += 2; + if (hi == 0 && lo == 0) + break; + } + return ret; + } + + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + + return ret; +} + +int +brcmf_sdbrcm_bus_txctl(struct brcmf_bus *bus, unsigned char *msg, uint msglen) +{ + u8 *frame; + u16 len; + u32 swheader; + uint retries = 0; + u8 doff = 0; + int ret = -1; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Back the pointer to make a room for bus header */ + frame = msg - SDPCM_HDRLEN; + len = (msglen += SDPCM_HDRLEN); + + /* Add alignment padding (optional for ctl frames) */ + doff = ((unsigned long)frame % BRCMF_SDALIGN); + if (doff) { + frame -= doff; + len += doff; + msglen += doff; + memset(frame, 0, doff + SDPCM_HDRLEN); + } + /* precondition: doff < BRCMF_SDALIGN */ + doff += SDPCM_HDRLEN; + + /* Round send length to next SDIO block */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + u16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) + len += pad; + } else if (len % BRCMF_SDALIGN) { + len += BRCMF_SDALIGN - (len % BRCMF_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (len & (ALIGNMENT - 1)) + len = roundup(len, ALIGNMENT); + + /* precondition: IS_ALIGNED((unsigned long)frame, 2) */ + + /* Need to lock here to protect txseq and SDIO tx calls */ + down(&bus->sdsem); + + bus_wake(bus); + + /* Make sure backplane clock is on */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + *(__le16 *) frame = cpu_to_le16((u16) msglen); + *(((__le16 *) frame) + 1) = cpu_to_le16(~msglen); + + /* Software tag: channel, sequence number, data offset */ + swheader = + ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) & + SDPCM_CHANNEL_MASK) + | bus->tx_seq | ((doff << SDPCM_DOFFSET_SHIFT) & + SDPCM_DOFFSET_MASK); + put_unaligned_le32(swheader, frame + SDPCM_FRAMETAG_LEN); + put_unaligned_le32(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader)); + + if (!data_ok(bus)) { + brcmf_dbg(INFO, "No bus credit bus->tx_max %d, bus->tx_seq %d\n", + bus->tx_max, bus->tx_seq); + bus->ctrl_frame_stat = true; + /* Send from dpc */ + bus->ctrl_frame_buf = frame; + bus->ctrl_frame_len = len; + + brcmf_sdbrcm_wait_for_event(bus, &bus->ctrl_frame_stat); + + if (bus->ctrl_frame_stat == false) { + brcmf_dbg(INFO, "ctrl_frame_stat == false\n"); + ret = 0; + } else { + brcmf_dbg(INFO, "ctrl_frame_stat == true\n"); + ret = -1; + } + } + + if (ret == -1) { +#ifdef BCMDBG + if (BRCMF_BYTES_ON() && BRCMF_CTL_ON()) { + printk(KERN_DEBUG "Tx Frame:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + frame, len); + } else if (BRCMF_HDRS_ON()) { + printk(KERN_DEBUG "TxHdr:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + frame, min_t(u16, len, 16)); + } +#endif + + do { + ret = brcmf_tx_frame(bus, frame, len); + } while (ret < 0 && retries++ < TXRETRIES); + } + + if ((bus->idletime == BRCMF_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = false; + brcmf_sdbrcm_clkctl(bus, CLK_NONE, true); + } + + up(&bus->sdsem); + + if (ret) + bus->drvr->tx_ctlerrs++; + else + bus->drvr->tx_ctlpkts++; + + return ret ? -EIO : 0; +} + +int +brcmf_sdbrcm_bus_rxctl(struct brcmf_bus *bus, unsigned char *msg, uint msglen) +{ + int timeleft; + uint rxlen = 0; + bool pending; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Wait until control frame is available */ + timeleft = brcmf_sdbrcm_dcmd_resp_wait(bus, &bus->rxlen, &pending); + + down(&bus->sdsem); + rxlen = bus->rxlen; + memcpy(msg, bus->rxctl, min(msglen, rxlen)); + bus->rxlen = 0; + up(&bus->sdsem); + + if (rxlen) { + brcmf_dbg(CTL, "resumed on rxctl frame, got %d expected %d\n", + rxlen, msglen); + } else if (timeleft == 0) { + brcmf_dbg(ERROR, "resumed on timeout\n"); + } else if (pending == true) { + brcmf_dbg(CTL, "cancelled\n"); + return -ERESTARTSYS; + } else { + brcmf_dbg(CTL, "resumed for unknown reason?\n"); + } + + if (rxlen) + bus->drvr->rx_ctlpkts++; + else + bus->drvr->rx_ctlerrs++; + + return rxlen ? (int)rxlen : -ETIMEDOUT; +} + +static int brcmf_sdbrcm_downloadvars(struct brcmf_bus *bus, void *arg, int len) +{ + int bcmerror = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Basic sanity checks */ + if (bus->drvr->up) { + bcmerror = -EISCONN; + goto err; + } + if (!len) { + bcmerror = -EOVERFLOW; + goto err; + } + + /* Free the old ones and replace with passed variables */ + kfree(bus->vars); + + bus->vars = kmalloc(len, GFP_ATOMIC); + bus->varsz = bus->vars ? len : 0; + if (bus->vars == NULL) { + bcmerror = -ENOMEM; + goto err; + } + + /* Copy the passed variables, which should include the + terminating double-null */ + memcpy(bus->vars, arg, bus->varsz); +err: + return bcmerror; +} + +static int brcmf_sdbrcm_write_vars(struct brcmf_bus *bus) +{ + int bcmerror = 0; + u32 varsize; + u32 varaddr; + u8 *vbuffer; + u32 varsizew; + __le32 varsizew_le; +#ifdef BCMDBG + char *nvram_ularray; +#endif /* BCMDBG */ + + /* Even if there are no vars are to be written, we still + need to set the ramsize. */ + varsize = bus->varsz ? roundup(bus->varsz, 4) : 0; + varaddr = (bus->ramsize - 4) - varsize; + + if (bus->vars) { + vbuffer = kzalloc(varsize, GFP_ATOMIC); + if (!vbuffer) + return -ENOMEM; + + memcpy(vbuffer, bus->vars, bus->varsz); + + /* Write the vars list */ + bcmerror = + brcmf_sdbrcm_membytes(bus, true, varaddr, vbuffer, varsize); +#ifdef BCMDBG + /* Verify NVRAM bytes */ + brcmf_dbg(INFO, "Compare NVRAM dl & ul; varsize=%d\n", varsize); + nvram_ularray = kmalloc(varsize, GFP_ATOMIC); + if (!nvram_ularray) + return -ENOMEM; + + /* Upload image to verify downloaded contents. */ + memset(nvram_ularray, 0xaa, varsize); + + /* Read the vars list to temp buffer for comparison */ + bcmerror = + brcmf_sdbrcm_membytes(bus, false, varaddr, nvram_ularray, + varsize); + if (bcmerror) { + brcmf_dbg(ERROR, "error %d on reading %d nvram bytes at 0x%08x\n", + bcmerror, varsize, varaddr); + } + /* Compare the org NVRAM with the one read from RAM */ + if (memcmp(vbuffer, nvram_ularray, varsize)) + brcmf_dbg(ERROR, "Downloaded NVRAM image is corrupted\n"); + else + brcmf_dbg(ERROR, "Download/Upload/Compare of NVRAM ok\n"); + + kfree(nvram_ularray); +#endif /* BCMDBG */ + + kfree(vbuffer); + } + + /* adjust to the user specified RAM */ + brcmf_dbg(INFO, "Physical memory size: %d\n", bus->ramsize); + brcmf_dbg(INFO, "Vars are at %d, orig varsize is %d\n", + varaddr, varsize); + varsize = ((bus->ramsize - 4) - varaddr); + + /* + * Determine the length token: + * Varsize, converted to words, in lower 16-bits, checksum + * in upper 16-bits. + */ + if (bcmerror) { + varsizew = 0; + varsizew_le = cpu_to_le32(0); + } else { + varsizew = varsize / 4; + varsizew = (~varsizew << 16) | (varsizew & 0x0000FFFF); + varsizew_le = cpu_to_le32(varsizew); + } + + brcmf_dbg(INFO, "New varsize is %d, length token=0x%08x\n", + varsize, varsizew); + + /* Write the length token to the last word */ + bcmerror = brcmf_sdbrcm_membytes(bus, true, (bus->ramsize - 4), + (u8 *)&varsizew_le, 4); + + return bcmerror; +} + +static void +brcmf_sdbrcm_chip_disablecore(struct brcmf_sdio_dev *sdiodev, u32 corebase) +{ + u32 regdata; + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4); + if (regdata & SBTML_RESET) + return; + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4); + if ((regdata & (SICF_CLOCK_EN << SBTML_SICF_SHIFT)) != 0) { + /* + * set target reject and spin until busy is clear + * (preserve core-specific bits) + */ + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4); + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbtmstatelow), + 4, regdata | SBTML_REJ); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4); + udelay(1); + SPINWAIT((brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatehigh), 4) & + SBTMH_BUSY), 100000); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatehigh), 4); + if (regdata & SBTMH_BUSY) + brcmf_dbg(ERROR, "ARM core still busy\n"); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbidlow), 4); + if (regdata & SBIDL_INIT) { + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbimstate), 4) | + SBIM_RJ; + brcmf_sdcard_reg_write(sdiodev, + CORE_SB(corebase, sbimstate), 4, + regdata); + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbimstate), 4); + udelay(1); + SPINWAIT((brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbimstate), 4) & + SBIM_BY), 100000); + } + + /* set reset and reject while enabling the clocks */ + brcmf_sdcard_reg_write(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4, + (((SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT) | + SBTML_REJ | SBTML_RESET)); + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatelow), 4); + udelay(10); + + /* clear the initiator reject bit */ + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbidlow), 4); + if (regdata & SBIDL_INIT) { + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbimstate), 4) & + ~SBIM_RJ; + brcmf_sdcard_reg_write(sdiodev, + CORE_SB(corebase, sbimstate), 4, + regdata); + } + } + + /* leave reset and reject asserted */ + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbtmstatelow), 4, + (SBTML_REJ | SBTML_RESET)); + udelay(1); +} + +static void +brcmf_sdbrcm_chip_resetcore(struct brcmf_sdio_dev *sdiodev, u32 corebase) +{ + u32 regdata; + + /* + * Must do the disable sequence first to work for + * arbitrary current core state. + */ + brcmf_sdbrcm_chip_disablecore(sdiodev, corebase); + + /* + * Now do the initialization sequence. + * set reset while enabling the clock and + * forcing them on throughout the core + */ + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbtmstatelow), 4, + ((SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT) | + SBTML_RESET); + udelay(1); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbtmstatehigh), 4); + if (regdata & SBTMH_SERR) + brcmf_sdcard_reg_write(sdiodev, + CORE_SB(corebase, sbtmstatehigh), 4, 0); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(corebase, sbimstate), 4); + if (regdata & (SBIM_IBE | SBIM_TO)) + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbimstate), 4, + regdata & ~(SBIM_IBE | SBIM_TO)); + + /* clear reset and allow it to propagate throughout the core */ + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbtmstatelow), 4, + (SICF_FGC << SBTML_SICF_SHIFT) | + (SICF_CLOCK_EN << SBTML_SICF_SHIFT)); + udelay(1); + + /* leave clock enabled */ + brcmf_sdcard_reg_write(sdiodev, CORE_SB(corebase, sbtmstatelow), 4, + (SICF_CLOCK_EN << SBTML_SICF_SHIFT)); + udelay(1); +} + +static int brcmf_sdbrcm_download_state(struct brcmf_bus *bus, bool enter) +{ + uint retries; + u32 regdata; + int bcmerror = 0; + + /* To enter download state, disable ARM and reset SOCRAM. + * To exit download state, simply reset ARM (default is RAM boot). + */ + if (enter) { + bus->alp_only = true; + + brcmf_sdbrcm_chip_disablecore(bus->sdiodev, + bus->ci->armcorebase); + + brcmf_sdbrcm_chip_resetcore(bus->sdiodev, bus->ci->ramcorebase); + + /* Clear the top bit of memory */ + if (bus->ramsize) { + u32 zeros = 0; + brcmf_sdbrcm_membytes(bus, true, bus->ramsize - 4, + (u8 *)&zeros, 4); + } + } else { + regdata = brcmf_sdcard_reg_read(bus->sdiodev, + CORE_SB(bus->ci->ramcorebase, sbtmstatelow), 4); + regdata &= (SBTML_RESET | SBTML_REJ_MASK | + (SICF_CLOCK_EN << SBTML_SICF_SHIFT)); + if ((SICF_CLOCK_EN << SBTML_SICF_SHIFT) != regdata) { + brcmf_dbg(ERROR, "SOCRAM core is down after reset?\n"); + bcmerror = -EBADE; + goto fail; + } + + bcmerror = brcmf_sdbrcm_write_vars(bus); + if (bcmerror) { + brcmf_dbg(ERROR, "no vars written to RAM\n"); + bcmerror = 0; + } + + w_sdreg32(bus, 0xFFFFFFFF, + offsetof(struct sdpcmd_regs, intstatus), &retries); + + brcmf_sdbrcm_chip_resetcore(bus->sdiodev, bus->ci->armcorebase); + + /* Allow HT Clock now that the ARM is running. */ + bus->alp_only = false; + + bus->drvr->busstate = BRCMF_BUS_LOAD; + } +fail: + return bcmerror; +} + +static int brcmf_sdbrcm_get_image(char *buf, int len, struct brcmf_bus *bus) +{ + if (bus->firmware->size < bus->fw_ptr + len) + len = bus->firmware->size - bus->fw_ptr; + + memcpy(buf, &bus->firmware->data[bus->fw_ptr], len); + bus->fw_ptr += len; + return len; +} + +MODULE_FIRMWARE(BCM4329_FW_NAME); +MODULE_FIRMWARE(BCM4329_NV_NAME); + +static int brcmf_sdbrcm_download_code_file(struct brcmf_bus *bus) +{ + int offset = 0; + uint len; + u8 *memblock = NULL, *memptr; + int ret; + + brcmf_dbg(INFO, "Enter\n"); + + bus->fw_name = BCM4329_FW_NAME; + ret = request_firmware(&bus->firmware, bus->fw_name, + &bus->sdiodev->func[2]->dev); + if (ret) { + brcmf_dbg(ERROR, "Fail to request firmware %d\n", ret); + return ret; + } + bus->fw_ptr = 0; + + memptr = memblock = kmalloc(MEMBLOCK + BRCMF_SDALIGN, GFP_ATOMIC); + if (memblock == NULL) { + ret = -ENOMEM; + goto err; + } + if ((u32)(unsigned long)memblock % BRCMF_SDALIGN) + memptr += (BRCMF_SDALIGN - + ((u32)(unsigned long)memblock % BRCMF_SDALIGN)); + + /* Download image */ + while ((len = + brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus))) { + ret = brcmf_sdbrcm_membytes(bus, true, offset, memptr, len); + if (ret) { + brcmf_dbg(ERROR, "error %d on writing %d membytes at 0x%08x\n", + ret, MEMBLOCK, offset); + goto err; + } + + offset += MEMBLOCK; + } + +err: + kfree(memblock); + + release_firmware(bus->firmware); + bus->fw_ptr = 0; + + return ret; +} + +/* + * ProcessVars:Takes a buffer of "<var>=<value>\n" lines read from a file + * and ending in a NUL. + * Removes carriage returns, empty lines, comment lines, and converts + * newlines to NULs. + * Shortens buffer as needed and pads with NULs. End of buffer is marked + * by two NULs. +*/ + +static uint brcmf_process_nvram_vars(char *varbuf, uint len) +{ + char *dp; + bool findNewline; + int column; + uint buf_len, n; + + dp = varbuf; + + findNewline = false; + column = 0; + + for (n = 0; n < len; n++) { + if (varbuf[n] == 0) + break; + if (varbuf[n] == '\r') + continue; + if (findNewline && varbuf[n] != '\n') + continue; + findNewline = false; + if (varbuf[n] == '#') { + findNewline = true; + continue; + } + if (varbuf[n] == '\n') { + if (column == 0) + continue; + *dp++ = 0; + column = 0; + continue; + } + *dp++ = varbuf[n]; + column++; + } + buf_len = dp - varbuf; + + while (dp < varbuf + n) + *dp++ = 0; + + return buf_len; +} + +static int brcmf_sdbrcm_download_nvram(struct brcmf_bus *bus) +{ + uint len; + char *memblock = NULL; + char *bufp; + int ret; + + bus->nv_name = BCM4329_NV_NAME; + ret = request_firmware(&bus->firmware, bus->nv_name, + &bus->sdiodev->func[2]->dev); + if (ret) { + brcmf_dbg(ERROR, "Fail to request nvram %d\n", ret); + return ret; + } + bus->fw_ptr = 0; + + memblock = kmalloc(MEMBLOCK, GFP_ATOMIC); + if (memblock == NULL) { + ret = -ENOMEM; + goto err; + } + + len = brcmf_sdbrcm_get_image(memblock, MEMBLOCK, bus); + + if (len > 0 && len < MEMBLOCK) { + bufp = (char *)memblock; + bufp[len] = 0; + len = brcmf_process_nvram_vars(bufp, len); + bufp += len; + *bufp++ = 0; + if (len) + ret = brcmf_sdbrcm_downloadvars(bus, memblock, len + 1); + if (ret) + brcmf_dbg(ERROR, "error downloading vars: %d\n", ret); + } else { + brcmf_dbg(ERROR, "error reading nvram file: %d\n", len); + ret = -EIO; + } + +err: + kfree(memblock); + + release_firmware(bus->firmware); + bus->fw_ptr = 0; + + return ret; +} + +static int _brcmf_sdbrcm_download_firmware(struct brcmf_bus *bus) +{ + int bcmerror = -1; + + /* Keep arm in reset */ + if (brcmf_sdbrcm_download_state(bus, true)) { + brcmf_dbg(ERROR, "error placing ARM core in reset\n"); + goto err; + } + + /* External image takes precedence if specified */ + if (brcmf_sdbrcm_download_code_file(bus)) { + brcmf_dbg(ERROR, "dongle image file download failed\n"); + goto err; + } + + /* External nvram takes precedence if specified */ + if (brcmf_sdbrcm_download_nvram(bus)) + brcmf_dbg(ERROR, "dongle nvram file download failed\n"); + + /* Take arm out of reset */ + if (brcmf_sdbrcm_download_state(bus, false)) { + brcmf_dbg(ERROR, "error getting out of ARM core reset\n"); + goto err; + } + + bcmerror = 0; + +err: + return bcmerror; +} + +static bool +brcmf_sdbrcm_download_firmware(struct brcmf_bus *bus) +{ + bool ret; + + /* Download the firmware */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + ret = _brcmf_sdbrcm_download_firmware(bus) == 0; + + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + + return ret; +} + +void brcmf_sdbrcm_bus_stop(struct brcmf_bus *bus) +{ + u32 local_hostintmask; + u8 saveclk; + uint retries; + int err; + + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->watchdog_tsk) { + send_sig(SIGTERM, bus->watchdog_tsk, 1); + kthread_stop(bus->watchdog_tsk); + bus->watchdog_tsk = NULL; + } + + if (bus->dpc_tsk && bus->dpc_tsk != current) { + send_sig(SIGTERM, bus->dpc_tsk, 1); + kthread_stop(bus->dpc_tsk); + bus->dpc_tsk = NULL; + } + + down(&bus->sdsem); + + bus_wake(bus); + + /* Enable clock for device interrupts */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + /* Disable and clear interrupts at the chip level also */ + w_sdreg32(bus, 0, offsetof(struct sdpcmd_regs, hostintmask), &retries); + local_hostintmask = bus->hostintmask; + bus->hostintmask = 0; + + /* Change our idea of bus state */ + bus->drvr->busstate = BRCMF_BUS_DOWN; + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) + brcmf_dbg(ERROR, "Failed to force clock for F2: err %d\n", err); + + /* Turn off the bus (F2), free any pending packets */ + brcmf_dbg(INTR, "disable SDIO interrupts\n"); + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_0, SDIO_CCCR_IOEx, + SDIO_FUNC_ENABLE_1, NULL); + + /* Clear any pending interrupts now that F2 is disabled */ + w_sdreg32(bus, local_hostintmask, + offsetof(struct sdpcmd_regs, intstatus), &retries); + + /* Turn off the backplane clock (only) */ + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + + /* Clear the data packet queues */ + brcmu_pktq_flush(&bus->txq, true, NULL, NULL); + + /* Clear any held glomming stuff */ + if (bus->glomd) + brcmu_pkt_buf_free_skb(bus->glomd); + + if (bus->glom) + brcmu_pkt_buf_free_skb(bus->glom); + + bus->glom = bus->glomd = NULL; + + /* Clear rx control and wake any waiters */ + bus->rxlen = 0; + brcmf_sdbrcm_dcmd_resp_wake(bus); + + /* Reset some F2 state stuff */ + bus->rxskip = false; + bus->tx_seq = bus->rx_seq = 0; + + up(&bus->sdsem); +} + +int brcmf_sdbrcm_bus_init(struct brcmf_pub *drvr) +{ + struct brcmf_bus *bus = drvr->bus; + unsigned long timeout; + uint retries = 0; + u8 ready, enable; + int err, ret = 0; + u8 saveclk; + + brcmf_dbg(TRACE, "Enter\n"); + + /* try to download image and nvram to the dongle */ + if (drvr->busstate == BRCMF_BUS_DOWN) { + if (!(brcmf_sdbrcm_download_firmware(bus))) + return -1; + } + + if (!bus->drvr) + return 0; + + /* Start the watchdog timer */ + bus->drvr->tickcnt = 0; + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + + down(&bus->sdsem); + + /* Make sure backplane clock is on, needed to generate F2 interrupt */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + if (bus->clkstate != CLK_AVAIL) + goto exit; + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = + brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) { + brcmf_dbg(ERROR, "Failed to force clock for F2: err %d\n", err); + goto exit; + } + + /* Enable function 2 (frame transfers) */ + w_sdreg32(bus, SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT, + offsetof(struct sdpcmd_regs, tosbmailboxdata), &retries); + enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_0, SDIO_CCCR_IOEx, + enable, NULL); + + timeout = jiffies + msecs_to_jiffies(BRCMF_WAIT_F2RDY); + ready = 0; + while (enable != ready) { + ready = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_0, + SDIO_CCCR_IORx, NULL); + if (time_after(jiffies, timeout)) + break; + else if (time_after(jiffies, timeout - BRCMF_WAIT_F2RDY + 50)) + /* prevent busy waiting if it takes too long */ + msleep_interruptible(20); + } + + brcmf_dbg(INFO, "enable 0x%02x, ready 0x%02x\n", enable, ready); + + /* If F2 successfully enabled, set core and enable interrupts */ + if (ready == enable) { + /* Set up the interrupt mask and enable interrupts */ + bus->hostintmask = HOSTINTMASK; + w_sdreg32(bus, bus->hostintmask, + offsetof(struct sdpcmd_regs, hostintmask), &retries); + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_WATERMARK, 8, &err); + + /* Set bus state according to enable result */ + drvr->busstate = BRCMF_BUS_DATA; + } + + else { + /* Disable F2 again */ + enable = SDIO_FUNC_ENABLE_1; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_0, + SDIO_CCCR_IOEx, enable, NULL); + } + + /* Restore previous clock setting */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err); + + /* If we didn't come up, turn off backplane clock */ + if (drvr->busstate != BRCMF_BUS_DATA) + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + +exit: + up(&bus->sdsem); + + return ret; +} + +void brcmf_sdbrcm_isr(void *arg) +{ + struct brcmf_bus *bus = (struct brcmf_bus *) arg; + + brcmf_dbg(TRACE, "Enter\n"); + + if (!bus) { + brcmf_dbg(ERROR, "bus is null pointer, exiting\n"); + return; + } + + if (bus->drvr->busstate == BRCMF_BUS_DOWN) { + brcmf_dbg(ERROR, "bus is down. we have nothing to do\n"); + return; + } + /* Count the interrupt call */ + bus->intrcount++; + bus->ipend = true; + + /* Shouldn't get this interrupt if we're sleeping? */ + if (bus->sleeping) { + brcmf_dbg(ERROR, "INTERRUPT WHILE SLEEPING??\n"); + return; + } + + /* Disable additional interrupts (is this needed now)? */ + if (!bus->intr) + brcmf_dbg(ERROR, "isr w/o interrupt configured!\n"); + + bus->dpc_sched = true; + if (bus->dpc_tsk) + complete(&bus->dpc_wait); +} + +static bool brcmf_sdbrcm_bus_watchdog(struct brcmf_pub *drvr) +{ + struct brcmf_bus *bus; + + brcmf_dbg(TIMER, "Enter\n"); + + bus = drvr->bus; + + /* Ignore the timer if simulating bus down */ + if (bus->sleeping) + return false; + + down(&bus->sdsem); + + /* Poll period: check device if appropriate. */ + if (bus->poll && (++bus->polltick >= bus->pollrate)) { + u32 intstatus = 0; + + /* Reset poll tick */ + bus->polltick = 0; + + /* Check device if no interrupts */ + if (!bus->intr || (bus->intrcount == bus->lastintrs)) { + + if (!bus->dpc_sched) { + u8 devpend; + devpend = brcmf_sdcard_cfg_read(bus->sdiodev, + SDIO_FUNC_0, SDIO_CCCR_INTx, + NULL); + intstatus = + devpend & (INTR_STATUS_FUNC1 | + INTR_STATUS_FUNC2); + } + + /* If there is something, make like the ISR and + schedule the DPC */ + if (intstatus) { + bus->pollcnt++; + bus->ipend = true; + + bus->dpc_sched = true; + if (bus->dpc_tsk) + complete(&bus->dpc_wait); + } + } + + /* Update interrupt tracking */ + bus->lastintrs = bus->intrcount; + } +#ifdef BCMDBG + /* Poll for console output periodically */ + if (drvr->busstate == BRCMF_BUS_DATA && bus->console_interval != 0) { + bus->console.count += BRCMF_WD_POLL_MS; + if (bus->console.count >= bus->console_interval) { + bus->console.count -= bus->console_interval; + /* Make sure backplane clock is on */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + if (brcmf_sdbrcm_readconsole(bus) < 0) + /* stop on error */ + bus->console_interval = 0; + } + } +#endif /* BCMDBG */ + + /* On idle timeout clear activity flag and/or turn off clock */ + if ((bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) { + if (++bus->idlecount >= bus->idletime) { + bus->idlecount = 0; + if (bus->activity) { + bus->activity = false; + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + } else { + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + } + } + } + + up(&bus->sdsem); + + return bus->ipend; +} + +static bool brcmf_sdbrcm_chipmatch(u16 chipid) +{ + if (chipid == BCM4329_CHIP_ID) + return true; + return false; +} + +static void brcmf_sdbrcm_release_malloc(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + kfree(bus->rxbuf); + bus->rxctl = bus->rxbuf = NULL; + bus->rxlen = 0; + + kfree(bus->databuf); + bus->databuf = NULL; +} + +static bool brcmf_sdbrcm_probe_malloc(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->drvr->maxctl) { + bus->rxblen = + roundup((bus->drvr->maxctl + SDPCM_HDRLEN), + ALIGNMENT) + BRCMF_SDALIGN; + bus->rxbuf = kmalloc(bus->rxblen, GFP_ATOMIC); + if (!(bus->rxbuf)) + goto fail; + } + + /* Allocate buffer to receive glomed packet */ + bus->databuf = kmalloc(MAX_DATA_BUF, GFP_ATOMIC); + if (!(bus->databuf)) { + /* release rxbuf which was already located as above */ + if (!bus->rxblen) + kfree(bus->rxbuf); + goto fail; + } + + /* Align the buffer */ + if ((unsigned long)bus->databuf % BRCMF_SDALIGN) + bus->dataptr = bus->databuf + (BRCMF_SDALIGN - + ((unsigned long)bus->databuf % BRCMF_SDALIGN)); + else + bus->dataptr = bus->databuf; + + return true; + +fail: + return false; +} + +/* SDIO Pad drive strength to select value mappings */ +struct sdiod_drive_str { + u8 strength; /* Pad Drive Strength in mA */ + u8 sel; /* Chip-specific select value */ +}; + +/* SDIO Drive Strength to sel value table for PMU Rev 1 */ +static const struct sdiod_drive_str sdiod_drive_strength_tab1[] = { + { + 4, 0x2}, { + 2, 0x3}, { + 1, 0x0}, { + 0, 0x0} + }; + +/* SDIO Drive Strength to sel value table for PMU Rev 2, 3 */ +static const struct sdiod_drive_str sdiod_drive_strength_tab2[] = { + { + 12, 0x7}, { + 10, 0x6}, { + 8, 0x5}, { + 6, 0x4}, { + 4, 0x2}, { + 2, 0x1}, { + 0, 0x0} + }; + +/* SDIO Drive Strength to sel value table for PMU Rev 8 (1.8V) */ +static const struct sdiod_drive_str sdiod_drive_strength_tab3[] = { + { + 32, 0x7}, { + 26, 0x6}, { + 22, 0x5}, { + 16, 0x4}, { + 12, 0x3}, { + 8, 0x2}, { + 4, 0x1}, { + 0, 0x0} + }; + +#define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) + +static void brcmf_sdbrcm_sdiod_drive_strength_init(struct brcmf_bus *bus, + u32 drivestrength) { + struct sdiod_drive_str *str_tab = NULL; + u32 str_mask = 0; + u32 str_shift = 0; + char chn[8]; + + if (!(bus->ci->cccaps & CC_CAP_PMU)) + return; + + switch (SDIOD_DRVSTR_KEY(bus->ci->chip, bus->ci->pmurev)) { + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 1): + str_tab = (struct sdiod_drive_str *)&sdiod_drive_strength_tab1; + str_mask = 0x30000000; + str_shift = 28; + break; + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 2): + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 3): + str_tab = (struct sdiod_drive_str *)&sdiod_drive_strength_tab2; + str_mask = 0x00003800; + str_shift = 11; + break; + case SDIOD_DRVSTR_KEY(BCM4336_CHIP_ID, 8): + str_tab = (struct sdiod_drive_str *)&sdiod_drive_strength_tab3; + str_mask = 0x00003800; + str_shift = 11; + break; + default: + brcmf_dbg(ERROR, "No SDIO Drive strength init done for chip %s rev %d pmurev %d\n", + brcmu_chipname(bus->ci->chip, chn, 8), + bus->ci->chiprev, bus->ci->pmurev); + break; + } + + if (str_tab != NULL) { + u32 drivestrength_sel = 0; + u32 cc_data_temp; + int i; + + for (i = 0; str_tab[i].strength != 0; i++) { + if (drivestrength >= str_tab[i].strength) { + drivestrength_sel = str_tab[i].sel; + break; + } + } + + brcmf_sdcard_reg_write(bus->sdiodev, + CORE_CC_REG(bus->ci->cccorebase, chipcontrol_addr), + 4, 1); + cc_data_temp = brcmf_sdcard_reg_read(bus->sdiodev, + CORE_CC_REG(bus->ci->cccorebase, chipcontrol_addr), 4); + cc_data_temp &= ~str_mask; + drivestrength_sel <<= str_shift; + cc_data_temp |= drivestrength_sel; + brcmf_sdcard_reg_write(bus->sdiodev, + CORE_CC_REG(bus->ci->cccorebase, chipcontrol_addr), + 4, cc_data_temp); + + brcmf_dbg(INFO, "SDIO: %dmA drive strength selected, set to 0x%08x\n", + drivestrength, cc_data_temp); + } +} + +static int +brcmf_sdbrcm_chip_recognition(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u32 regs) +{ + u32 regdata; + + /* + * Get CC core rev + * Chipid is assume to be at offset 0 from regs arg + * For different chiptypes or old sdio hosts w/o chipcommon, + * other ways of recognition should be added here. + */ + ci->cccorebase = regs; + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_CC_REG(ci->cccorebase, chipid), 4); + ci->chip = regdata & CID_ID_MASK; + ci->chiprev = (regdata & CID_REV_MASK) >> CID_REV_SHIFT; + + brcmf_dbg(INFO, "chipid=0x%x chiprev=%d\n", ci->chip, ci->chiprev); + + /* Address of cores for new chips should be added here */ + switch (ci->chip) { + case BCM4329_CHIP_ID: + ci->buscorebase = BCM4329_CORE_BUS_BASE; + ci->ramcorebase = BCM4329_CORE_SOCRAM_BASE; + ci->armcorebase = BCM4329_CORE_ARM_BASE; + ci->ramsize = BCM4329_RAMSIZE; + break; + default: + brcmf_dbg(ERROR, "chipid 0x%x is not supported\n", ci->chip); + return -ENODEV; + } + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(ci->cccorebase, sbidhigh), 4); + ci->ccrev = SBCOREREV(regdata); + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_CC_REG(ci->cccorebase, pmucapabilities), 4); + ci->pmurev = regdata & PCAP_REV_MASK; + + regdata = brcmf_sdcard_reg_read(sdiodev, + CORE_SB(ci->buscorebase, sbidhigh), 4); + ci->buscorerev = SBCOREREV(regdata); + ci->buscoretype = (regdata & SBIDH_CC_MASK) >> SBIDH_CC_SHIFT; + + brcmf_dbg(INFO, "ccrev=%d, pmurev=%d, buscore rev/type=%d/0x%x\n", + ci->ccrev, ci->pmurev, ci->buscorerev, ci->buscoretype); + + /* get chipcommon capabilites */ + ci->cccaps = brcmf_sdcard_reg_read(sdiodev, + CORE_CC_REG(ci->cccorebase, capabilities), 4); + + return 0; +} + +static int +brcmf_sdbrcm_chip_attach(struct brcmf_bus *bus, u32 regs) +{ + struct chip_info *ci; + int err; + u8 clkval, clkset; + + brcmf_dbg(TRACE, "Enter\n"); + + /* alloc chip_info_t */ + ci = kzalloc(sizeof(struct chip_info), GFP_ATOMIC); + if (NULL == ci) + return -ENOMEM; + + /* bus/core/clk setup for register access */ + /* Try forcing SDIO core to do ALPAvail request only */ + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); + if (err) { + brcmf_dbg(ERROR, "error writing for HT off\n"); + goto fail; + } + + /* If register supported, wait for ALPAvail and then force ALP */ + /* This may take up to 15 milliseconds */ + clkval = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, NULL); + if ((clkval & ~SBSDIO_AVBITS) == clkset) { + SPINWAIT(((clkval = + brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + NULL)), + !SBSDIO_ALPAV(clkval)), + PMU_MAX_TRANSITION_DLY); + if (!SBSDIO_ALPAV(clkval)) { + brcmf_dbg(ERROR, "timeout on ALPAV wait, clkval 0x%02x\n", + clkval); + err = -EBUSY; + goto fail; + } + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | + SBSDIO_FORCE_ALP; + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + clkset, &err); + udelay(65); + } else { + brcmf_dbg(ERROR, "ChipClkCSR access: wrote 0x%02x read 0x%02x\n", + clkset, clkval); + err = -EACCES; + goto fail; + } + + /* Also, disable the extra SDIO pull-ups */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_SDIOPULLUP, 0, NULL); + + err = brcmf_sdbrcm_chip_recognition(bus->sdiodev, ci, regs); + if (err) + goto fail; + + /* + * Make sure any on-chip ARM is off (in case strapping is wrong), + * or downloaded code was already running. + */ + brcmf_sdbrcm_chip_disablecore(bus->sdiodev, ci->armcorebase); + + brcmf_sdcard_reg_write(bus->sdiodev, + CORE_CC_REG(ci->cccorebase, gpiopullup), 4, 0); + brcmf_sdcard_reg_write(bus->sdiodev, + CORE_CC_REG(ci->cccorebase, gpiopulldown), 4, 0); + + /* Disable F2 to clear any intermediate frame state on the dongle */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_0, SDIO_CCCR_IOEx, + SDIO_FUNC_ENABLE_1, NULL); + + /* WAR: cmd52 backplane read so core HW will drop ALPReq */ + clkval = brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + 0, NULL); + + /* Done with backplane-dependent accesses, can drop clock... */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); + + bus->ci = ci; + return 0; +fail: + bus->ci = NULL; + kfree(ci); + return err; +} + +static bool +brcmf_sdbrcm_probe_attach(struct brcmf_bus *bus, u32 regsva) +{ + u8 clkctl = 0; + int err = 0; + int reg_addr; + u32 reg_val; + + bus->alp_only = true; + + /* Return the window to backplane enumeration space for core access */ + if (brcmf_sdcard_set_sbaddr_window(bus->sdiodev, SI_ENUM_BASE)) + brcmf_dbg(ERROR, "FAILED to return to SI_ENUM_BASE\n"); + +#ifdef BCMDBG + printk(KERN_DEBUG "F1 signature read @0x18000000=0x%4x\n", + brcmf_sdcard_reg_read(bus->sdiodev, SI_ENUM_BASE, 4)); + +#endif /* BCMDBG */ + + /* + * Force PLL off until brcmf_sdbrcm_chip_attach() + * programs PLL control regs + */ + + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, + BRCMF_INIT_CLKCTL1, &err); + if (!err) + clkctl = + brcmf_sdcard_cfg_read(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + + if (err || ((clkctl & ~SBSDIO_AVBITS) != BRCMF_INIT_CLKCTL1)) { + brcmf_dbg(ERROR, "ChipClkCSR access: err %d wrote 0x%02x read 0x%02x\n", + err, BRCMF_INIT_CLKCTL1, clkctl); + goto fail; + } + + if (brcmf_sdbrcm_chip_attach(bus, regsva)) { + brcmf_dbg(ERROR, "brcmf_sdbrcm_chip_attach failed!\n"); + goto fail; + } + + if (!brcmf_sdbrcm_chipmatch((u16) bus->ci->chip)) { + brcmf_dbg(ERROR, "unsupported chip: 0x%04x\n", bus->ci->chip); + goto fail; + } + + brcmf_sdbrcm_sdiod_drive_strength_init(bus, SDIO_DRIVE_STRENGTH); + + /* Get info on the ARM and SOCRAM cores... */ + brcmf_sdcard_reg_read(bus->sdiodev, + CORE_SB(bus->ci->armcorebase, sbidhigh), 4); + bus->ramsize = bus->ci->ramsize; + if (!(bus->ramsize)) { + brcmf_dbg(ERROR, "failed to find SOCRAM memory!\n"); + goto fail; + } + + /* Set core control so an SDIO reset does a backplane reset */ + reg_addr = bus->ci->buscorebase + + offsetof(struct sdpcmd_regs, corecontrol); + reg_val = brcmf_sdcard_reg_read(bus->sdiodev, reg_addr, sizeof(u32)); + brcmf_sdcard_reg_write(bus->sdiodev, reg_addr, sizeof(u32), + reg_val | CC_BPRESEN); + + brcmu_pktq_init(&bus->txq, (PRIOMASK + 1), TXQLEN); + + /* Locate an appropriately-aligned portion of hdrbuf */ + bus->rxhdr = (u8 *) roundup((unsigned long)&bus->hdrbuf[0], + BRCMF_SDALIGN); + + /* Set the poll and/or interrupt flags */ + bus->intr = true; + bus->poll = false; + if (bus->poll) + bus->pollrate = 1; + + return true; + +fail: + return false; +} + +static bool brcmf_sdbrcm_probe_init(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + /* Disable F2 to clear any intermediate frame state on the dongle */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_0, SDIO_CCCR_IOEx, + SDIO_FUNC_ENABLE_1, NULL); + + bus->drvr->busstate = BRCMF_BUS_DOWN; + bus->sleeping = false; + bus->rxflow = false; + + /* Done with backplane-dependent accesses, can drop clock... */ + brcmf_sdcard_cfg_write(bus->sdiodev, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); + + /* ...and initialize clock/power states */ + bus->clkstate = CLK_SDONLY; + bus->idletime = BRCMF_IDLE_INTERVAL; + bus->idleclock = BRCMF_IDLE_ACTIVE; + + /* Query the F2 block size, set roundup accordingly */ + bus->blocksize = bus->sdiodev->func[2]->cur_blksize; + bus->roundup = min(max_roundup, bus->blocksize); + + /* bus module does not support packet chaining */ + bus->use_rxchain = false; + bus->sd_rxchain = false; + + return true; +} + +static int +brcmf_sdbrcm_watchdog_thread(void *data) +{ + struct brcmf_bus *bus = (struct brcmf_bus *)data; + + allow_signal(SIGTERM); + /* Run until signal received */ + while (1) { + if (kthread_should_stop()) + break; + if (!wait_for_completion_interruptible(&bus->watchdog_wait)) { + brcmf_sdbrcm_bus_watchdog(bus->drvr); + /* Count the tick for reference */ + bus->drvr->tickcnt++; + } else + break; + } + return 0; +} + +static void +brcmf_sdbrcm_watchdog(unsigned long data) +{ + struct brcmf_bus *bus = (struct brcmf_bus *)data; + + if (bus->watchdog_tsk) { + complete(&bus->watchdog_wait); + /* Reschedule the watchdog */ + if (bus->wd_timer_valid) + mod_timer(&bus->timer, + jiffies + BRCMF_WD_POLL_MS * HZ / 1000); + } +} + +static void +brcmf_sdbrcm_chip_detach(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + kfree(bus->ci); + bus->ci = NULL; +} + +static void brcmf_sdbrcm_release_dongle(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->ci) { + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + brcmf_sdbrcm_chip_detach(bus); + if (bus->vars && bus->varsz) + kfree(bus->vars); + bus->vars = NULL; + } + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +/* Detach and free everything */ +static void brcmf_sdbrcm_release(struct brcmf_bus *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus) { + /* De-register interrupt handler */ + brcmf_sdcard_intr_dereg(bus->sdiodev); + + if (bus->drvr) { + brcmf_detach(bus->drvr); + brcmf_sdbrcm_release_dongle(bus); + bus->drvr = NULL; + } + + brcmf_sdbrcm_release_malloc(bus); + + kfree(bus); + } + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +void *brcmf_sdbrcm_probe(u16 bus_no, u16 slot, u16 func, uint bustype, + u32 regsva, struct brcmf_sdio_dev *sdiodev) +{ + int ret; + struct brcmf_bus *bus; + + /* Init global variables at run-time, not as part of the declaration. + * This is required to support init/de-init of the driver. + * Initialization + * of globals as part of the declaration results in non-deterministic + * behavior since the value of the globals may be different on the + * first time that the driver is initialized vs subsequent + * initializations. + */ + brcmf_c_init(); + + brcmf_dbg(TRACE, "Enter\n"); + + /* We make an assumption about address window mappings: + * regsva == SI_ENUM_BASE*/ + + /* Allocate private bus interface state */ + bus = kzalloc(sizeof(struct brcmf_bus), GFP_ATOMIC); + if (!bus) + goto fail; + + bus->sdiodev = sdiodev; + sdiodev->bus = bus; + bus->txbound = BRCMF_TXBOUND; + bus->rxbound = BRCMF_RXBOUND; + bus->txminmax = BRCMF_TXMINMAX; + bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1; + bus->usebufpool = false; /* Use bufpool if allocated, + else use locally malloced rxbuf */ + + /* attempt to attach to the dongle */ + if (!(brcmf_sdbrcm_probe_attach(bus, regsva))) { + brcmf_dbg(ERROR, "brcmf_sdbrcm_probe_attach failed\n"); + goto fail; + } + + spin_lock_init(&bus->txqlock); + init_waitqueue_head(&bus->ctrl_wait); + init_waitqueue_head(&bus->dcmd_resp_wait); + + /* Set up the watchdog timer */ + init_timer(&bus->timer); + bus->timer.data = (unsigned long)bus; + bus->timer.function = brcmf_sdbrcm_watchdog; + + /* Initialize thread based operation and lock */ + sema_init(&bus->sdsem, 1); + + /* Initialize watchdog thread */ + init_completion(&bus->watchdog_wait); + bus->watchdog_tsk = kthread_run(brcmf_sdbrcm_watchdog_thread, + bus, "brcmf_watchdog"); + if (IS_ERR(bus->watchdog_tsk)) { + printk(KERN_WARNING + "brcmf_watchdog thread failed to start\n"); + bus->watchdog_tsk = NULL; + } + /* Initialize DPC thread */ + init_completion(&bus->dpc_wait); + bus->dpc_tsk = kthread_run(brcmf_sdbrcm_dpc_thread, + bus, "brcmf_dpc"); + if (IS_ERR(bus->dpc_tsk)) { + printk(KERN_WARNING + "brcmf_dpc thread failed to start\n"); + bus->dpc_tsk = NULL; + } + + /* Attach to the brcmf/OS/network interface */ + bus->drvr = brcmf_attach(bus, SDPCM_RESERVE); + if (!bus->drvr) { + brcmf_dbg(ERROR, "brcmf_attach failed\n"); + goto fail; + } + + /* Allocate buffers */ + if (!(brcmf_sdbrcm_probe_malloc(bus))) { + brcmf_dbg(ERROR, "brcmf_sdbrcm_probe_malloc failed\n"); + goto fail; + } + + if (!(brcmf_sdbrcm_probe_init(bus))) { + brcmf_dbg(ERROR, "brcmf_sdbrcm_probe_init failed\n"); + goto fail; + } + + /* Register interrupt callback, but mask it (not operational yet). */ + brcmf_dbg(INTR, "disable SDIO interrupts (not interested yet)\n"); + ret = brcmf_sdcard_intr_reg(bus->sdiodev); + if (ret != 0) { + brcmf_dbg(ERROR, "FAILED: sdcard_intr_reg returned %d\n", ret); + goto fail; + } + brcmf_dbg(INTR, "registered SDIO interrupt function ok\n"); + + brcmf_dbg(INFO, "completed!!\n"); + + /* if firmware path present try to download and bring up bus */ + ret = brcmf_bus_start(bus->drvr); + if (ret != 0) { + if (ret == -ENOLINK) { + brcmf_dbg(ERROR, "dongle is not responding\n"); + goto fail; + } + } + /* Ok, have the per-port tell the stack we're open for business */ + if (brcmf_net_attach(bus->drvr, 0) != 0) { + brcmf_dbg(ERROR, "Net attach failed!!\n"); + goto fail; + } + + return bus; + +fail: + brcmf_sdbrcm_release(bus); + return NULL; +} + +void brcmf_sdbrcm_disconnect(void *ptr) +{ + struct brcmf_bus *bus = (struct brcmf_bus *)ptr; + + brcmf_dbg(TRACE, "Enter\n"); + + if (bus) + brcmf_sdbrcm_release(bus); + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +struct device *brcmf_bus_get_device(struct brcmf_bus *bus) +{ + return &bus->sdiodev->func[2]->dev; +} + +void +brcmf_sdbrcm_wd_timer(struct brcmf_bus *bus, uint wdtick) +{ + /* don't start the wd until fw is loaded */ + if (bus->drvr->busstate == BRCMF_BUS_DOWN) + return; + + /* Totally stop the timer */ + if (!wdtick && bus->wd_timer_valid == true) { + del_timer_sync(&bus->timer); + bus->wd_timer_valid = false; + bus->save_ms = wdtick; + return; + } + + if (wdtick) { + if (bus->save_ms != BRCMF_WD_POLL_MS) { + if (bus->wd_timer_valid == true) + /* Stop timer and restart at new value */ + del_timer_sync(&bus->timer); + + /* Create timer again when watchdog period is + dynamically changed or in the first instance + */ + bus->timer.expires = + jiffies + BRCMF_WD_POLL_MS * HZ / 1000; + add_timer(&bus->timer); + + } else { + /* Re arm the timer, at last watchdog period */ + mod_timer(&bus->timer, + jiffies + BRCMF_WD_POLL_MS * HZ / 1000); + } + + bus->wd_timer_valid = true; + bus->save_ms = wdtick; + } +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h new file mode 100644 index 000000000000..726fa8981113 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_SDH_H_ +#define _BRCM_SDH_H_ + +#include <linux/skbuff.h> + +#define SDIO_FUNC_0 0 +#define SDIO_FUNC_1 1 +#define SDIO_FUNC_2 2 + +#define SDIOD_FBR_SIZE 0x100 + +/* io_en */ +#define SDIO_FUNC_ENABLE_1 0x02 +#define SDIO_FUNC_ENABLE_2 0x04 + +/* io_rdys */ +#define SDIO_FUNC_READY_1 0x02 +#define SDIO_FUNC_READY_2 0x04 + +/* intr_status */ +#define INTR_STATUS_FUNC1 0x2 +#define INTR_STATUS_FUNC2 0x4 + +/* Maximum number of I/O funcs */ +#define SDIOD_MAX_IOFUNCS 7 + +/* as of sdiod rev 0, supports 3 functions */ +#define SBSDIO_NUM_FUNCTION 3 + +/* function 1 miscellaneous registers */ + +/* sprom command and status */ +#define SBSDIO_SPROM_CS 0x10000 +/* sprom info register */ +#define SBSDIO_SPROM_INFO 0x10001 +/* sprom indirect access data byte 0 */ +#define SBSDIO_SPROM_DATA_LOW 0x10002 +/* sprom indirect access data byte 1 */ +#define SBSDIO_SPROM_DATA_HIGH 0x10003 +/* sprom indirect access addr byte 0 */ +#define SBSDIO_SPROM_ADDR_LOW 0x10004 +/* sprom indirect access addr byte 0 */ +#define SBSDIO_SPROM_ADDR_HIGH 0x10005 +/* xtal_pu (gpio) output */ +#define SBSDIO_CHIP_CTRL_DATA 0x10006 +/* xtal_pu (gpio) enable */ +#define SBSDIO_CHIP_CTRL_EN 0x10007 +/* rev < 7, watermark for sdio device */ +#define SBSDIO_WATERMARK 0x10008 +/* control busy signal generation */ +#define SBSDIO_DEVICE_CTL 0x10009 + +/* SB Address Window Low (b15) */ +#define SBSDIO_FUNC1_SBADDRLOW 0x1000A +/* SB Address Window Mid (b23:b16) */ +#define SBSDIO_FUNC1_SBADDRMID 0x1000B +/* SB Address Window High (b31:b24) */ +#define SBSDIO_FUNC1_SBADDRHIGH 0x1000C +/* Frame Control (frame term/abort) */ +#define SBSDIO_FUNC1_FRAMECTRL 0x1000D +/* ChipClockCSR (ALP/HT ctl/status) */ +#define SBSDIO_FUNC1_CHIPCLKCSR 0x1000E +/* SdioPullUp (on cmd, d0-d2) */ +#define SBSDIO_FUNC1_SDIOPULLUP 0x1000F +/* Write Frame Byte Count Low */ +#define SBSDIO_FUNC1_WFRAMEBCLO 0x10019 +/* Write Frame Byte Count High */ +#define SBSDIO_FUNC1_WFRAMEBCHI 0x1001A +/* Read Frame Byte Count Low */ +#define SBSDIO_FUNC1_RFRAMEBCLO 0x1001B +/* Read Frame Byte Count High */ +#define SBSDIO_FUNC1_RFRAMEBCHI 0x1001C + +#define SBSDIO_FUNC1_MISC_REG_START 0x10000 /* f1 misc register start */ +#define SBSDIO_FUNC1_MISC_REG_LIMIT 0x1001C /* f1 misc register end */ + +/* function 1 OCP space */ + +/* sb offset addr is <= 15 bits, 32k */ +#define SBSDIO_SB_OFT_ADDR_MASK 0x07FFF +#define SBSDIO_SB_OFT_ADDR_LIMIT 0x08000 +/* with b15, maps to 32-bit SB access */ +#define SBSDIO_SB_ACCESS_2_4B_FLAG 0x08000 + +/* valid bits in SBSDIO_FUNC1_SBADDRxxx regs */ + +#define SBSDIO_SBADDRLOW_MASK 0x80 /* Valid bits in SBADDRLOW */ +#define SBSDIO_SBADDRMID_MASK 0xff /* Valid bits in SBADDRMID */ +#define SBSDIO_SBADDRHIGH_MASK 0xffU /* Valid bits in SBADDRHIGH */ +/* Address bits from SBADDR regs */ +#define SBSDIO_SBWINDOW_MASK 0xffff8000 + +#define SDIOH_READ 0 /* Read request */ +#define SDIOH_WRITE 1 /* Write request */ + +#define SDIOH_DATA_FIX 0 /* Fixed addressing */ +#define SDIOH_DATA_INC 1 /* Incremental addressing */ + +/* internal return code */ +#define SUCCESS 0 +#define ERROR 1 + +struct brcmf_sdreg { + int func; + int offset; + int value; +}; + +struct brcmf_sdio_dev { + struct sdio_func *func[SDIO_MAX_FUNCS]; + u8 num_funcs; /* Supported funcs on client */ + u32 func_cis_ptr[SDIOD_MAX_IOFUNCS]; + u32 sbwad; /* Save backplane window address */ + bool regfail; /* status of last reg_r/w call */ + void *bus; + atomic_t suspend; /* suspend flag */ + wait_queue_head_t request_byte_wait; + wait_queue_head_t request_word_wait; + wait_queue_head_t request_packet_wait; + wait_queue_head_t request_buffer_wait; + +}; + +/* 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); + +/* Access SDIO address space (e.g. CCCR) using CMD52 (single-byte interface). + * fn: function number + * addr: unmodified SDIO-space address + * data: data byte to write + * err: pointer to error code (or NULL) + */ +extern u8 brcmf_sdcard_cfg_read(struct brcmf_sdio_dev *sdiodev, uint func, + u32 addr, int *err); +extern void brcmf_sdcard_cfg_write(struct brcmf_sdio_dev *sdiodev, uint func, + u32 addr, u8 data, int *err); + +/* Synchronous access to device (client) core registers via CMD53 to F1. + * addr: backplane address (i.e. >= regsva from attach) + * size: register width in bytes (2 or 4) + * data: data for register write + */ +extern u32 +brcmf_sdcard_reg_read(struct brcmf_sdio_dev *sdiodev, u32 addr, uint size); + +extern u32 +brcmf_sdcard_reg_write(struct brcmf_sdio_dev *sdiodev, u32 addr, uint size, + u32 data); + +/* Indicate if last reg read/write failed */ +extern bool brcmf_sdcard_regfail(struct brcmf_sdio_dev *sdiodev); + +/* Buffer transfer to/from device (client) core via cmd53. + * fn: function number + * addr: backplane address (i.e. >= regsva from attach) + * flags: backplane width, address increment, sync/async + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * pkt: pointer to packet associated with buf (if any) + * complete: callback function for command completion (async only) + * handle: handle for completion callback (first arg in callback) + * Returns 0 or error code. + * NOTE: Async operation is not currently supported. + */ +extern int +brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes, struct sk_buff *pkt); +extern int +brcmf_sdcard_recv_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes, struct sk_buff *pkt); + +/* Flags bits */ + +/* Four-byte target (backplane) width (vs. two-byte) */ +#define SDIO_REQ_4BYTE 0x1 +/* Fixed address (FIFO) (vs. incrementing address) */ +#define SDIO_REQ_FIXED 0x2 +/* Async request (vs. sync request) */ +#define SDIO_REQ_ASYNC 0x4 + +/* Read/write to memory block (F1, no FIFO) via CMD53 (sync only). + * rw: read or write (0/1) + * addr: direct SDIO address + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * Returns 0 or error code. + */ +extern int brcmf_sdcard_rwdata(struct brcmf_sdio_dev *sdiodev, uint rw, + u32 addr, u8 *buf, uint nbytes); + +/* Issue an abort to the specified function */ +extern int brcmf_sdcard_abort(struct brcmf_sdio_dev *sdiodev, uint fn); + +/* platform specific/high level functions */ +extern int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev); +extern int brcmf_sdio_remove(struct brcmf_sdio_dev *sdiodev); + +extern int brcmf_sdcard_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, + u32 address); + +/* attach, return handler on success, NULL if failed. + * The handler shall be provided by all subsequent calls. No local cache + * cfghdl points to the starting address of pci device mapped memory + */ +extern int brcmf_sdioh_attach(struct brcmf_sdio_dev *sdiodev); +extern void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev); + +/* read or write one byte using cmd52 */ +extern int brcmf_sdioh_request_byte(struct brcmf_sdio_dev *sdiodev, uint rw, + uint fnc, uint addr, u8 *byte); + +/* read or write 2/4 bytes using cmd53 */ +extern int +brcmf_sdioh_request_word(struct brcmf_sdio_dev *sdiodev, + uint rw, uint fnc, uint addr, + u32 *word, uint nbyte); + +/* read or write any buffer using cmd53 */ +extern int +brcmf_sdioh_request_buffer(struct brcmf_sdio_dev *sdiodev, + uint fix_inc, uint rw, uint fnc_num, + u32 addr, uint regwidth, + u32 buflen, u8 *buffer, struct sk_buff *pkt); + +/* Watchdog timer interface for pm ops */ +extern void brcmf_sdio_wdtmr_enable(struct brcmf_sdio_dev *sdiodev, + bool enable); + +extern void *brcmf_sdbrcm_probe(u16 bus_no, u16 slot, u16 func, uint bustype, + u32 regsva, struct brcmf_sdio_dev *sdiodev); +extern void brcmf_sdbrcm_disconnect(void *ptr); +extern void brcmf_sdbrcm_isr(void *arg); +#endif /* _BRCM_SDH_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c new file mode 100644 index 000000000000..fc643c1eb59a --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c @@ -0,0 +1,3730 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Toplevel file. Relies on dhd_linux.c to send commands to the dongle. */ + +#include <linux/kernel.h> +#include <linux/if_arp.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/netdevice.h> +#include <linux/bitops.h> +#include <linux/etherdevice.h> +#include <linux/ieee80211.h> +#include <linux/uaccess.h> +#include <net/cfg80211.h> + +#include <brcmu_utils.h> +#include <defs.h> +#include <brcmu_wifi.h> +#include "dhd.h" +#include "wl_cfg80211.h" + +#define BRCMF_ASSOC_PARAMS_FIXED_SIZE \ + (sizeof(struct brcmf_assoc_params_le) - sizeof(u16)) + +static const u8 ether_bcast[ETH_ALEN] = {255, 255, 255, 255, 255, 255}; + +static u32 brcmf_dbg_level = WL_DBG_ERR; + +static void brcmf_set_drvdata(struct brcmf_cfg80211_dev *dev, void *data) +{ + dev->driver_data = data; +} + +static void *brcmf_get_drvdata(struct brcmf_cfg80211_dev *dev) +{ + void *data = NULL; + + if (dev) + data = dev->driver_data; + return data; +} + +static +struct brcmf_cfg80211_priv *brcmf_priv_get(struct brcmf_cfg80211_dev *cfg_dev) +{ + struct brcmf_cfg80211_iface *ci = brcmf_get_drvdata(cfg_dev); + return ci->cfg_priv; +} + +static bool check_sys_up(struct wiphy *wiphy) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + if (!test_bit(WL_STATUS_READY, &cfg_priv->status)) { + WL_INFO("device is not ready : status (%d)\n", + (int)cfg_priv->status); + return false; + } + return true; +} + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) +#define RATETAB_ENT(_rateid, _flags) \ + { \ + .bitrate = RATE_TO_BASE100KBPS(_rateid), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate __wl_rates[] = { + RATETAB_ENT(BRCM_RATE_1M, 0), + RATETAB_ENT(BRCM_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_6M, 0), + RATETAB_ENT(BRCM_RATE_9M, 0), + RATETAB_ENT(BRCM_RATE_12M, 0), + RATETAB_ENT(BRCM_RATE_18M, 0), + RATETAB_ENT(BRCM_RATE_24M, 0), + RATETAB_ENT(BRCM_RATE_36M, 0), + RATETAB_ENT(BRCM_RATE_48M, 0), + RATETAB_ENT(BRCM_RATE_54M, 0), +}; + +#define wl_a_rates (__wl_rates + 4) +#define wl_a_rates_size 8 +#define wl_g_rates (__wl_rates + 0) +#define wl_g_rates_size 12 + +static struct ieee80211_channel __wl_2ghz_channels[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static struct ieee80211_channel __wl_5ghz_a_channels[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; + +static struct ieee80211_channel __wl_5ghz_n_channels[] = { + CHAN5G(32, 0), CHAN5G(34, 0), + CHAN5G(36, 0), CHAN5G(38, 0), + CHAN5G(40, 0), CHAN5G(42, 0), + CHAN5G(44, 0), CHAN5G(46, 0), + CHAN5G(48, 0), CHAN5G(50, 0), + CHAN5G(52, 0), CHAN5G(54, 0), + CHAN5G(56, 0), CHAN5G(58, 0), + CHAN5G(60, 0), CHAN5G(62, 0), + CHAN5G(64, 0), CHAN5G(66, 0), + CHAN5G(68, 0), CHAN5G(70, 0), + CHAN5G(72, 0), CHAN5G(74, 0), + CHAN5G(76, 0), CHAN5G(78, 0), + CHAN5G(80, 0), CHAN5G(82, 0), + CHAN5G(84, 0), CHAN5G(86, 0), + CHAN5G(88, 0), CHAN5G(90, 0), + CHAN5G(92, 0), CHAN5G(94, 0), + CHAN5G(96, 0), CHAN5G(98, 0), + CHAN5G(100, 0), CHAN5G(102, 0), + CHAN5G(104, 0), CHAN5G(106, 0), + CHAN5G(108, 0), CHAN5G(110, 0), + CHAN5G(112, 0), CHAN5G(114, 0), + CHAN5G(116, 0), CHAN5G(118, 0), + CHAN5G(120, 0), CHAN5G(122, 0), + CHAN5G(124, 0), CHAN5G(126, 0), + CHAN5G(128, 0), CHAN5G(130, 0), + CHAN5G(132, 0), CHAN5G(134, 0), + CHAN5G(136, 0), CHAN5G(138, 0), + CHAN5G(140, 0), CHAN5G(142, 0), + CHAN5G(144, 0), CHAN5G(145, 0), + CHAN5G(146, 0), CHAN5G(147, 0), + CHAN5G(148, 0), CHAN5G(149, 0), + CHAN5G(150, 0), CHAN5G(151, 0), + CHAN5G(152, 0), CHAN5G(153, 0), + CHAN5G(154, 0), CHAN5G(155, 0), + CHAN5G(156, 0), CHAN5G(157, 0), + CHAN5G(158, 0), CHAN5G(159, 0), + CHAN5G(160, 0), CHAN5G(161, 0), + CHAN5G(162, 0), CHAN5G(163, 0), + CHAN5G(164, 0), CHAN5G(165, 0), + CHAN5G(166, 0), CHAN5G(168, 0), + CHAN5G(170, 0), CHAN5G(172, 0), + CHAN5G(174, 0), CHAN5G(176, 0), + CHAN5G(178, 0), CHAN5G(180, 0), + CHAN5G(182, 0), CHAN5G(184, 0), + CHAN5G(186, 0), CHAN5G(188, 0), + CHAN5G(190, 0), CHAN5G(192, 0), + CHAN5G(194, 0), CHAN5G(196, 0), + CHAN5G(198, 0), CHAN5G(200, 0), + CHAN5G(202, 0), CHAN5G(204, 0), + CHAN5G(206, 0), CHAN5G(208, 0), + CHAN5G(210, 0), CHAN5G(212, 0), + CHAN5G(214, 0), CHAN5G(216, 0), + CHAN5G(218, 0), CHAN5G(220, 0), + CHAN5G(222, 0), CHAN5G(224, 0), + CHAN5G(226, 0), CHAN5G(228, 0), +}; + +static struct ieee80211_supported_band __wl_band_2ghz = { + .band = IEEE80211_BAND_2GHZ, + .channels = __wl_2ghz_channels, + .n_channels = ARRAY_SIZE(__wl_2ghz_channels), + .bitrates = wl_g_rates, + .n_bitrates = wl_g_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_5ghz_a = { + .band = IEEE80211_BAND_5GHZ, + .channels = __wl_5ghz_a_channels, + .n_channels = ARRAY_SIZE(__wl_5ghz_a_channels), + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_5ghz_n = { + .band = IEEE80211_BAND_5GHZ, + .channels = __wl_5ghz_n_channels, + .n_channels = ARRAY_SIZE(__wl_5ghz_n_channels), + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +static const u32 __wl_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_AES_CMAC, +}; + +/* function for reading/writing a single u32 from/to the dongle */ +static int +brcmf_exec_dcmd_u32(struct net_device *ndev, u32 cmd, u32 *par) +{ + int err; + __le32 par_le = cpu_to_le32(*par); + + err = brcmf_exec_dcmd(ndev, cmd, &par_le, sizeof(__le32)); + *par = le32_to_cpu(par_le); + + return err; +} + +static void convert_key_from_CPU(struct brcmf_wsec_key *key, + struct brcmf_wsec_key_le *key_le) +{ + key_le->index = cpu_to_le32(key->index); + key_le->len = cpu_to_le32(key->len); + key_le->algo = cpu_to_le32(key->algo); + key_le->flags = cpu_to_le32(key->flags); + key_le->rxiv.hi = cpu_to_le32(key->rxiv.hi); + key_le->rxiv.lo = cpu_to_le16(key->rxiv.lo); + key_le->iv_initialized = cpu_to_le32(key->iv_initialized); + memcpy(key_le->data, key->data, sizeof(key->data)); + memcpy(key_le->ea, key->ea, sizeof(key->ea)); +} + +static int send_key_to_dongle(struct net_device *ndev, + struct brcmf_wsec_key *key) +{ + int err; + struct brcmf_wsec_key_le key_le; + + convert_key_from_CPU(key, &key_le); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_KEY, &key_le, sizeof(key_le)); + if (err) + WL_ERR("WLC_SET_KEY error (%d)\n", err); + return err; +} + +static s32 +brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct wireless_dev *wdev; + s32 infra = 0; + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + switch (type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + WL_ERR("type (%d) : currently we do not support this type\n", + type); + return -EOPNOTSUPP; + case NL80211_IFTYPE_ADHOC: + cfg_priv->conf->mode = WL_MODE_IBSS; + infra = 0; + break; + case NL80211_IFTYPE_STATION: + cfg_priv->conf->mode = WL_MODE_BSS; + infra = 1; + break; + default: + err = -EINVAL; + goto done; + } + + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_INFRA, &infra); + if (err) { + WL_ERR("WLC_SET_INFRA error (%d)\n", err); + err = -EAGAIN; + } else { + wdev = ndev->ieee80211_ptr; + wdev->iftype = type; + } + + WL_INFO("IF Type = %s\n", + (cfg_priv->conf->mode == WL_MODE_IBSS) ? "Adhoc" : "Infra"); + +done: + WL_TRACE("Exit\n"); + + return err; +} + +static s32 brcmf_dev_intvar_set(struct net_device *ndev, s8 *name, s32 val) +{ + s8 buf[BRCMF_DCMD_SMLEN]; + u32 len; + s32 err = 0; + __le32 val_le; + + val_le = cpu_to_le32(val); + len = brcmu_mkiovar(name, (char *)(&val_le), sizeof(val_le), buf, + sizeof(buf)); + BUG_ON(!len); + + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, buf, len); + if (err) + WL_ERR("error (%d)\n", err); + + return err; +} + +static s32 +brcmf_dev_intvar_get(struct net_device *ndev, s8 *name, s32 *retval) +{ + union { + s8 buf[BRCMF_DCMD_SMLEN]; + __le32 val; + } var; + u32 len; + u32 data_null; + s32 err = 0; + + len = + brcmu_mkiovar(name, (char *)(&data_null), 0, (char *)(&var), + sizeof(var.buf)); + BUG_ON(!len); + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_VAR, &var, len); + if (err) + WL_ERR("error (%d)\n", err); + + *retval = le32_to_cpu(var.val); + + return err; +} + +static void brcmf_set_mpc(struct net_device *ndev, int mpc) +{ + s32 err = 0; + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + + if (test_bit(WL_STATUS_READY, &cfg_priv->status)) { + err = brcmf_dev_intvar_set(ndev, "mpc", mpc); + if (err) { + WL_ERR("fail to set mpc\n"); + return; + } + WL_INFO("MPC : %d\n", mpc); + } +} + +static void wl_iscan_prep(struct brcmf_scan_params_le *params_le, + struct brcmf_ssid *ssid) +{ + memcpy(params_le->bssid, ether_bcast, ETH_ALEN); + params_le->bss_type = DOT11_BSSTYPE_ANY; + params_le->scan_type = 0; + params_le->channel_num = 0; + params_le->nprobes = cpu_to_le32(-1); + params_le->active_time = cpu_to_le32(-1); + params_le->passive_time = cpu_to_le32(-1); + params_le->home_time = cpu_to_le32(-1); + if (ssid && ssid->SSID_len) + memcpy(¶ms_le->ssid_le, ssid, sizeof(struct brcmf_ssid)); +} + +static s32 +brcmf_dev_iovar_setbuf(struct net_device *ndev, s8 * iovar, void *param, + s32 paramlen, void *bufptr, s32 buflen) +{ + s32 iolen; + + iolen = brcmu_mkiovar(iovar, param, paramlen, bufptr, buflen); + BUG_ON(!iolen); + + return brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, bufptr, iolen); +} + +static s32 +brcmf_dev_iovar_getbuf(struct net_device *ndev, s8 * iovar, void *param, + s32 paramlen, void *bufptr, s32 buflen) +{ + s32 iolen; + + iolen = brcmu_mkiovar(iovar, param, paramlen, bufptr, buflen); + BUG_ON(!iolen); + + return brcmf_exec_dcmd(ndev, BRCMF_C_GET_VAR, bufptr, buflen); +} + +static s32 +brcmf_run_iscan(struct brcmf_cfg80211_iscan_ctrl *iscan, + struct brcmf_ssid *ssid, u16 action) +{ + s32 params_size = BRCMF_SCAN_PARAMS_FIXED_SIZE + + offsetof(struct brcmf_iscan_params_le, params_le); + struct brcmf_iscan_params_le *params; + s32 err = 0; + + if (ssid && ssid->SSID_len) + params_size += sizeof(struct brcmf_ssid); + params = kzalloc(params_size, GFP_KERNEL); + if (!params) + return -ENOMEM; + BUG_ON(params_size >= BRCMF_DCMD_SMLEN); + + wl_iscan_prep(¶ms->params_le, ssid); + + params->version = cpu_to_le32(BRCMF_ISCAN_REQ_VERSION); + params->action = cpu_to_le16(action); + params->scan_duration = cpu_to_le16(0); + + err = brcmf_dev_iovar_setbuf(iscan->ndev, "iscan", params, params_size, + iscan->dcmd_buf, BRCMF_DCMD_SMLEN); + if (err) { + if (err == -EBUSY) + WL_INFO("system busy : iscan canceled\n"); + else + WL_ERR("error (%d)\n", err); + } + + kfree(params); + return err; +} + +static s32 brcmf_do_iscan(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_to_iscan(cfg_priv); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + struct brcmf_ssid ssid; + s32 passive_scan; + s32 err = 0; + + /* Broadcast scan by default */ + memset(&ssid, 0, sizeof(ssid)); + + iscan->state = WL_ISCAN_STATE_SCANING; + + passive_scan = cfg_priv->active_scan ? 0 : 1; + err = brcmf_exec_dcmd(cfg_to_ndev(cfg_priv), BRCMF_C_SET_PASSIVE_SCAN, + &passive_scan, sizeof(passive_scan)); + if (err) { + WL_ERR("error (%d)\n", err); + return err; + } + brcmf_set_mpc(ndev, 0); + cfg_priv->iscan_kickstart = true; + err = brcmf_run_iscan(iscan, &ssid, BRCMF_SCAN_ACTION_START); + if (err) { + brcmf_set_mpc(ndev, 1); + cfg_priv->iscan_kickstart = false; + return err; + } + mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000); + iscan->timer_on = 1; + return err; +} + +static s32 +__brcmf_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request, + struct cfg80211_ssid *this_ssid) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct cfg80211_ssid *ssids; + struct brcmf_cfg80211_scan_req *sr = cfg_priv->scan_req_int; + s32 passive_scan; + bool iscan_req; + bool spec_scan; + s32 err = 0; + u32 SSID_len; + + if (test_bit(WL_STATUS_SCANNING, &cfg_priv->status)) { + WL_ERR("Scanning already : status (%lu)\n", cfg_priv->status); + return -EAGAIN; + } + if (test_bit(WL_STATUS_SCAN_ABORTING, &cfg_priv->status)) { + WL_ERR("Scanning being aborted : status (%lu)\n", + cfg_priv->status); + return -EAGAIN; + } + if (test_bit(WL_STATUS_CONNECTING, &cfg_priv->status)) { + WL_ERR("Connecting : status (%lu)\n", + cfg_priv->status); + return -EAGAIN; + } + + iscan_req = false; + spec_scan = false; + if (request) { + /* scan bss */ + ssids = request->ssids; + if (cfg_priv->iscan_on && (!ssids || !ssids->ssid_len)) + iscan_req = true; + } else { + /* scan in ibss */ + /* we don't do iscan in ibss */ + ssids = this_ssid; + } + + cfg_priv->scan_request = request; + set_bit(WL_STATUS_SCANNING, &cfg_priv->status); + if (iscan_req) { + err = brcmf_do_iscan(cfg_priv); + if (!err) + return err; + else + goto scan_out; + } else { + WL_SCAN("ssid \"%s\", ssid_len (%d)\n", + ssids->ssid, ssids->ssid_len); + memset(&sr->ssid_le, 0, sizeof(sr->ssid_le)); + SSID_len = min_t(u8, sizeof(sr->ssid_le.SSID), ssids->ssid_len); + sr->ssid_le.SSID_len = cpu_to_le32(0); + if (SSID_len) { + memcpy(sr->ssid_le.SSID, ssids->ssid, SSID_len); + sr->ssid_le.SSID_len = cpu_to_le32(SSID_len); + spec_scan = true; + } else { + WL_SCAN("Broadcast scan\n"); + } + + passive_scan = cfg_priv->active_scan ? 0 : 1; + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_PASSIVE_SCAN, + &passive_scan, sizeof(passive_scan)); + if (err) { + WL_ERR("WLC_SET_PASSIVE_SCAN error (%d)\n", err); + goto scan_out; + } + brcmf_set_mpc(ndev, 0); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SCAN, &sr->ssid_le, + sizeof(sr->ssid_le)); + if (err) { + if (err == -EBUSY) + WL_INFO("system busy : scan for \"%s\" " + "canceled\n", sr->ssid_le.SSID); + else + WL_ERR("WLC_SCAN error (%d)\n", err); + + brcmf_set_mpc(ndev, 1); + goto scan_out; + } + } + + return 0; + +scan_out: + clear_bit(WL_STATUS_SCANNING, &cfg_priv->status); + cfg_priv->scan_request = NULL; + return err; +} + +static s32 +brcmf_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request) +{ + s32 err = 0; + + WL_TRACE("Enter\n"); + + if (!check_sys_up(wiphy)) + return -EIO; + + err = __brcmf_cfg80211_scan(wiphy, ndev, request, NULL); + if (err) + WL_ERR("scan error (%d)\n", err); + + WL_TRACE("Exit\n"); + return err; +} + +static s32 brcmf_set_rts(struct net_device *ndev, u32 rts_threshold) +{ + s32 err = 0; + + err = brcmf_dev_intvar_set(ndev, "rtsthresh", rts_threshold); + if (err) + WL_ERR("Error (%d)\n", err); + + return err; +} + +static s32 brcmf_set_frag(struct net_device *ndev, u32 frag_threshold) +{ + s32 err = 0; + + err = brcmf_dev_intvar_set(ndev, "fragthresh", frag_threshold); + if (err) + WL_ERR("Error (%d)\n", err); + + return err; +} + +static s32 brcmf_set_retry(struct net_device *ndev, u32 retry, bool l) +{ + s32 err = 0; + u32 cmd = (l ? BRCM_SET_LRL : BRCM_SET_SRL); + + err = brcmf_exec_dcmd_u32(ndev, cmd, &retry); + if (err) { + WL_ERR("cmd (%d) , error (%d)\n", cmd, err); + return err; + } + return err; +} + +static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + if (changed & WIPHY_PARAM_RTS_THRESHOLD && + (cfg_priv->conf->rts_threshold != wiphy->rts_threshold)) { + cfg_priv->conf->rts_threshold = wiphy->rts_threshold; + err = brcmf_set_rts(ndev, cfg_priv->conf->rts_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD && + (cfg_priv->conf->frag_threshold != wiphy->frag_threshold)) { + cfg_priv->conf->frag_threshold = wiphy->frag_threshold; + err = brcmf_set_frag(ndev, cfg_priv->conf->frag_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_LONG + && (cfg_priv->conf->retry_long != wiphy->retry_long)) { + cfg_priv->conf->retry_long = wiphy->retry_long; + err = brcmf_set_retry(ndev, cfg_priv->conf->retry_long, true); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_SHORT + && (cfg_priv->conf->retry_short != wiphy->retry_short)) { + cfg_priv->conf->retry_short = wiphy->retry_short; + err = brcmf_set_retry(ndev, cfg_priv->conf->retry_short, false); + if (!err) + goto done; + } + +done: + WL_TRACE("Exit\n"); + return err; +} + +static void *brcmf_read_prof(struct brcmf_cfg80211_priv *cfg_priv, s32 item) +{ + switch (item) { + case WL_PROF_SEC: + return &cfg_priv->profile->sec; + case WL_PROF_BSSID: + return &cfg_priv->profile->bssid; + case WL_PROF_SSID: + return &cfg_priv->profile->ssid; + } + WL_ERR("invalid item (%d)\n", item); + return NULL; +} + +static s32 +brcmf_update_prof(struct brcmf_cfg80211_priv *cfg_priv, + const struct brcmf_event_msg *e, void *data, s32 item) +{ + s32 err = 0; + struct brcmf_ssid *ssid; + + switch (item) { + case WL_PROF_SSID: + ssid = (struct brcmf_ssid *) data; + memset(cfg_priv->profile->ssid.SSID, 0, + sizeof(cfg_priv->profile->ssid.SSID)); + memcpy(cfg_priv->profile->ssid.SSID, + ssid->SSID, ssid->SSID_len); + cfg_priv->profile->ssid.SSID_len = ssid->SSID_len; + break; + case WL_PROF_BSSID: + if (data) + memcpy(cfg_priv->profile->bssid, data, ETH_ALEN); + else + memset(cfg_priv->profile->bssid, 0, ETH_ALEN); + break; + case WL_PROF_SEC: + memcpy(&cfg_priv->profile->sec, data, + sizeof(cfg_priv->profile->sec)); + break; + case WL_PROF_BEACONINT: + cfg_priv->profile->beacon_interval = *(u16 *)data; + break; + case WL_PROF_DTIMPERIOD: + cfg_priv->profile->dtim_period = *(u8 *)data; + break; + default: + WL_ERR("unsupported item (%d)\n", item); + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static void brcmf_init_prof(struct brcmf_cfg80211_profile *prof) +{ + memset(prof, 0, sizeof(*prof)); +} + +static void brcmf_ch_to_chanspec(int ch, struct brcmf_join_params *join_params, + size_t *join_params_size) +{ + u16 chanspec = 0; + + if (ch != 0) { + if (ch <= CH_MAX_2G_CHANNEL) + chanspec |= WL_CHANSPEC_BAND_2G; + else + chanspec |= WL_CHANSPEC_BAND_5G; + + chanspec |= WL_CHANSPEC_BW_20; + chanspec |= WL_CHANSPEC_CTL_SB_NONE; + + *join_params_size += BRCMF_ASSOC_PARAMS_FIXED_SIZE + + sizeof(u16); + + chanspec |= (ch & WL_CHANSPEC_CHAN_MASK); + join_params->params_le.chanspec_list[0] = cpu_to_le16(chanspec); + join_params->params_le.chanspec_num = cpu_to_le32(1); + + WL_CONN("join_params->params.chanspec_list[0]= %#X," + "channel %d, chanspec %#X\n", + chanspec, ch, chanspec); + } +} + +static void brcmf_link_down(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct net_device *ndev = NULL; + s32 err = 0; + + WL_TRACE("Enter\n"); + + if (cfg_priv->link_up) { + ndev = cfg_to_ndev(cfg_priv); + WL_INFO("Call WLC_DISASSOC to stop excess roaming\n "); + err = brcmf_exec_dcmd(ndev, BRCMF_C_DISASSOC, NULL, 0); + if (err) + WL_ERR("WLC_DISASSOC failed (%d)\n", err); + cfg_priv->link_up = false; + } + WL_TRACE("Exit\n"); +} + +static s32 +brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ibss_params *params) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct brcmf_join_params join_params; + size_t join_params_size = 0; + s32 err = 0; + s32 wsec = 0; + s32 bcnprd; + struct brcmf_ssid ssid; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + if (params->ssid) + WL_CONN("SSID: %s\n", params->ssid); + else { + WL_CONN("SSID: NULL, Not supported\n"); + return -EOPNOTSUPP; + } + + set_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + + if (params->bssid) + WL_CONN("BSSID: %02X %02X %02X %02X %02X %02X\n", + params->bssid[0], params->bssid[1], params->bssid[2], + params->bssid[3], params->bssid[4], params->bssid[5]); + else + WL_CONN("No BSSID specified\n"); + + if (params->channel) + WL_CONN("channel: %d\n", params->channel->center_freq); + else + WL_CONN("no channel specified\n"); + + if (params->channel_fixed) + WL_CONN("fixed channel required\n"); + else + WL_CONN("no fixed channel required\n"); + + if (params->ie && params->ie_len) + WL_CONN("ie len: %d\n", params->ie_len); + else + WL_CONN("no ie specified\n"); + + if (params->beacon_interval) + WL_CONN("beacon interval: %d\n", params->beacon_interval); + else + WL_CONN("no beacon interval specified\n"); + + if (params->basic_rates) + WL_CONN("basic rates: %08X\n", params->basic_rates); + else + WL_CONN("no basic rates specified\n"); + + if (params->privacy) + WL_CONN("privacy required\n"); + else + WL_CONN("no privacy required\n"); + + /* Configure Privacy for starter */ + if (params->privacy) + wsec |= WEP_ENABLED; + + err = brcmf_dev_intvar_set(ndev, "wsec", wsec); + if (err) { + WL_ERR("wsec failed (%d)\n", err); + goto done; + } + + /* Configure Beacon Interval for starter */ + if (params->beacon_interval) + bcnprd = params->beacon_interval; + else + bcnprd = 100; + + err = brcmf_exec_dcmd_u32(ndev, BRCM_SET_BCNPRD, &bcnprd); + if (err) { + WL_ERR("WLC_SET_BCNPRD failed (%d)\n", err); + goto done; + } + + /* Configure required join parameter */ + memset(&join_params, 0, sizeof(struct brcmf_join_params)); + + /* SSID */ + ssid.SSID_len = min_t(u32, params->ssid_len, 32); + memcpy(ssid.SSID, params->ssid, ssid.SSID_len); + memcpy(join_params.ssid_le.SSID, params->ssid, ssid.SSID_len); + join_params.ssid_le.SSID_len = cpu_to_le32(ssid.SSID_len); + join_params_size = sizeof(join_params.ssid_le); + brcmf_update_prof(cfg_priv, NULL, &ssid, WL_PROF_SSID); + + /* BSSID */ + if (params->bssid) { + memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN); + join_params_size = sizeof(join_params.ssid_le) + + BRCMF_ASSOC_PARAMS_FIXED_SIZE; + } else { + memcpy(join_params.params_le.bssid, ether_bcast, ETH_ALEN); + } + + brcmf_update_prof(cfg_priv, NULL, + &join_params.params_le.bssid, WL_PROF_BSSID); + + /* Channel */ + if (params->channel) { + u32 target_channel; + + cfg_priv->channel = + ieee80211_frequency_to_channel( + params->channel->center_freq); + if (params->channel_fixed) { + /* adding chanspec */ + brcmf_ch_to_chanspec(cfg_priv->channel, + &join_params, &join_params_size); + } + + /* set channel for starter */ + target_channel = cfg_priv->channel; + err = brcmf_exec_dcmd_u32(ndev, BRCM_SET_CHANNEL, + &target_channel); + if (err) { + WL_ERR("WLC_SET_CHANNEL failed (%d)\n", err); + goto done; + } + } else + cfg_priv->channel = 0; + + cfg_priv->ibss_starter = false; + + + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_SSID, + &join_params, join_params_size); + if (err) { + WL_ERR("WLC_SET_SSID failed (%d)\n", err); + goto done; + } + +done: + if (err) + clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *ndev) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + brcmf_link_down(cfg_priv); + + WL_TRACE("Exit\n"); + + return err; +} + +static s32 brcmf_set_wpa_version(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; + else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + else + val = WPA_AUTH_DISABLED; + WL_CONN("setting wpa_auth to 0x%0x\n", val); + err = brcmf_dev_intvar_set(ndev, "wpa_auth", val); + if (err) { + WL_ERR("set wpa_auth failed (%d)\n", err); + return err; + } + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + sec->wpa_versions = sme->crypto.wpa_versions; + return err; +} + +static s32 brcmf_set_auth_type(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + switch (sme->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + val = 0; + WL_CONN("open system\n"); + break; + case NL80211_AUTHTYPE_SHARED_KEY: + val = 1; + WL_CONN("shared key\n"); + break; + case NL80211_AUTHTYPE_AUTOMATIC: + val = 2; + WL_CONN("automatic\n"); + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + WL_CONN("network eap\n"); + default: + val = 2; + WL_ERR("invalid auth type (%d)\n", sme->auth_type); + break; + } + + err = brcmf_dev_intvar_set(ndev, "auth", val); + if (err) { + WL_ERR("set auth failed (%d)\n", err); + return err; + } + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + sec->auth_type = sme->auth_type; + return err; +} + +static s32 +brcmf_set_set_cipher(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct brcmf_cfg80211_security *sec; + s32 pval = 0; + s32 gval = 0; + s32 err = 0; + + if (sme->crypto.n_ciphers_pairwise) { + switch (sme->crypto.ciphers_pairwise[0]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + pval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + pval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + pval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + pval = AES_ENABLED; + break; + default: + WL_ERR("invalid cipher pairwise (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + } + if (sme->crypto.cipher_group) { + switch (sme->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + gval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + gval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + gval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + gval = AES_ENABLED; + break; + default: + WL_ERR("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } + + WL_CONN("pval (%d) gval (%d)\n", pval, gval); + err = brcmf_dev_intvar_set(ndev, "wsec", pval | gval); + if (err) { + WL_ERR("error (%d)\n", err); + return err; + } + + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; + sec->cipher_group = sme->crypto.cipher_group; + + return err; +} + +static s32 +brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.n_akm_suites) { + err = brcmf_dev_intvar_get(ndev, "wpa_auth", &val); + if (err) { + WL_ERR("could not get wpa_auth (%d)\n", err); + return err; + } + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA_AUTH_PSK; + break; + default: + WL_ERR("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA2_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA2_AUTH_PSK; + break; + default: + WL_ERR("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } + + WL_CONN("setting wpa_auth to %d\n", val); + err = brcmf_dev_intvar_set(ndev, "wpa_auth", val); + if (err) { + WL_ERR("could not set wpa_auth (%d)\n", err); + return err; + } + } + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + sec->wpa_auth = sme->crypto.akm_suites[0]; + + return err; +} + +static s32 +brcmf_set_set_sharedkey(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + struct brcmf_cfg80211_security *sec; + struct brcmf_wsec_key key; + s32 val; + s32 err = 0; + + WL_CONN("key len (%d)\n", sme->key_len); + if (sme->key_len) { + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + WL_CONN("wpa_versions 0x%x cipher_pairwise 0x%x\n", + sec->wpa_versions, sec->cipher_pairwise); + if (! + (sec->wpa_versions & (NL80211_WPA_VERSION_1 | + NL80211_WPA_VERSION_2)) +&& (sec->cipher_pairwise & (WLAN_CIPHER_SUITE_WEP40 | + WLAN_CIPHER_SUITE_WEP104))) { + memset(&key, 0, sizeof(key)); + key.len = (u32) sme->key_len; + key.index = (u32) sme->key_idx; + if (key.len > sizeof(key.data)) { + WL_ERR("Too long key length (%u)\n", key.len); + return -EINVAL; + } + memcpy(key.data, sme->key, key.len); + key.flags = BRCMF_PRIMARY_KEY; + switch (sec->cipher_pairwise) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + break; + default: + WL_ERR("Invalid algorithm (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + /* Set the new key/index */ + WL_CONN("key length (%d) key index (%d) algo (%d)\n", + key.len, key.index, key.algo); + WL_CONN("key \"%s\"\n", key.data); + err = send_key_to_dongle(ndev, &key); + if (err) + return err; + + if (sec->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) { + WL_CONN("set auth_type to shared key\n"); + val = 1; /* shared key */ + err = brcmf_dev_intvar_set(ndev, "auth", val); + if (err) { + WL_ERR("set auth failed (%d)\n", err); + return err; + } + } + } + } + return err; +} + +static s32 +brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct ieee80211_channel *chan = sme->channel; + struct brcmf_join_params join_params; + size_t join_params_size; + struct brcmf_ssid ssid; + + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + if (!sme->ssid) { + WL_ERR("Invalid ssid\n"); + return -EOPNOTSUPP; + } + + set_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + + if (chan) { + cfg_priv->channel = + ieee80211_frequency_to_channel(chan->center_freq); + WL_CONN("channel (%d), center_req (%d)\n", + cfg_priv->channel, chan->center_freq); + } else + cfg_priv->channel = 0; + + WL_INFO("ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len); + + err = brcmf_set_wpa_version(ndev, sme); + if (err) { + WL_ERR("wl_set_wpa_version failed (%d)\n", err); + goto done; + } + + err = brcmf_set_auth_type(ndev, sme); + if (err) { + WL_ERR("wl_set_auth_type failed (%d)\n", err); + goto done; + } + + err = brcmf_set_set_cipher(ndev, sme); + if (err) { + WL_ERR("wl_set_set_cipher failed (%d)\n", err); + goto done; + } + + err = brcmf_set_key_mgmt(ndev, sme); + if (err) { + WL_ERR("wl_set_key_mgmt failed (%d)\n", err); + goto done; + } + + err = brcmf_set_set_sharedkey(ndev, sme); + if (err) { + WL_ERR("wl_set_set_sharedkey failed (%d)\n", err); + goto done; + } + + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid_le); + + ssid.SSID_len = min_t(u32, sizeof(ssid.SSID), sme->ssid_len); + memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid.SSID_len); + memcpy(&ssid.SSID, sme->ssid, ssid.SSID_len); + join_params.ssid_le.SSID_len = cpu_to_le32(ssid.SSID_len); + brcmf_update_prof(cfg_priv, NULL, &ssid, WL_PROF_SSID); + + memcpy(join_params.params_le.bssid, ether_bcast, ETH_ALEN); + + if (ssid.SSID_len < IEEE80211_MAX_SSID_LEN) + WL_CONN("ssid \"%s\", len (%d)\n", + ssid.SSID, ssid.SSID_len); + + brcmf_ch_to_chanspec(cfg_priv->channel, + &join_params, &join_params_size); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_SSID, + &join_params, join_params_size); + if (err) + WL_ERR("WLC_SET_SSID failed (%d)\n", err); + +done: + if (err) + clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, + u16 reason_code) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct brcmf_scb_val_le scbval; + s32 err = 0; + + WL_TRACE("Enter. Reason code = %d\n", reason_code); + if (!check_sys_up(wiphy)) + return -EIO; + + clear_bit(WL_STATUS_CONNECTED, &cfg_priv->status); + + memcpy(&scbval.ea, brcmf_read_prof(cfg_priv, WL_PROF_BSSID), ETH_ALEN); + scbval.val = cpu_to_le32(reason_code); + err = brcmf_exec_dcmd(ndev, BRCMF_C_DISASSOC, &scbval, + sizeof(struct brcmf_scb_val_le)); + if (err) + WL_ERR("error (%d)\n", err); + + cfg_priv->link_up = false; + + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, + enum nl80211_tx_power_setting type, s32 dbm) +{ + + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + u16 txpwrmw; + s32 err = 0; + s32 disable = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + break; + case NL80211_TX_POWER_LIMITED: + if (dbm < 0) { + WL_ERR("TX_POWER_LIMITED - dbm is negative\n"); + err = -EINVAL; + goto done; + } + break; + case NL80211_TX_POWER_FIXED: + if (dbm < 0) { + WL_ERR("TX_POWER_FIXED - dbm is negative\n"); + err = -EINVAL; + goto done; + } + break; + } + /* Make sure radio is off or on as far as software is concerned */ + disable = WL_RADIO_SW_DISABLE << 16; + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_RADIO, &disable); + if (err) + WL_ERR("WLC_SET_RADIO error (%d)\n", err); + + if (dbm > 0xffff) + txpwrmw = 0xffff; + else + txpwrmw = (u16) dbm; + err = brcmf_dev_intvar_set(ndev, "qtxpower", + (s32) (brcmu_mw_to_qdbm(txpwrmw))); + if (err) + WL_ERR("qtxpower error (%d)\n", err); + cfg_priv->conf->tx_power = dbm; + +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, s32 *dbm) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + s32 txpwrdbm; + u8 result; + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + err = brcmf_dev_intvar_get(ndev, "qtxpower", &txpwrdbm); + if (err) { + WL_ERR("error (%d)\n", err); + goto done; + } + + result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE); + *dbm = (s32) brcmu_qdbm_to_mw(result); + +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool unicast, bool multicast) +{ + u32 index; + u32 wsec; + s32 err = 0; + + WL_TRACE("Enter\n"); + WL_CONN("key index (%d)\n", key_idx); + if (!check_sys_up(wiphy)) + return -EIO; + + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_GET_WSEC, &wsec); + if (err) { + WL_ERR("WLC_GET_WSEC error (%d)\n", err); + goto done; + } + + if (wsec & WEP_ENABLED) { + /* Just select a new current key */ + index = key_idx; + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_KEY_PRIMARY, + &index); + if (err) + WL_ERR("error (%d)\n", err); + } +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_add_keyext(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, const u8 *mac_addr, struct key_params *params) +{ + struct brcmf_wsec_key key; + struct brcmf_wsec_key_le key_le; + s32 err = 0; + + memset(&key, 0, sizeof(key)); + key.index = (u32) key_idx; + /* Instead of bcast for ea address for default wep keys, + driver needs it to be Null */ + if (!is_multicast_ether_addr(mac_addr)) + memcpy((char *)&key.ea, (void *)mac_addr, ETH_ALEN); + key.len = (u32) params->key_len; + /* check for key index change */ + if (key.len == 0) { + /* key delete */ + err = send_key_to_dongle(ndev, &key); + if (err) + return err; + } else { + if (key.len > sizeof(key.data)) { + WL_ERR("Invalid key length (%d)\n", key.len); + return -EINVAL; + } + + WL_CONN("Setting the key index %d\n", key.index); + memcpy(key.data, params->key, key.len); + + if (params->cipher == WLAN_CIPHER_SUITE_TKIP) { + u8 keybuf[8]; + memcpy(keybuf, &key.data[24], sizeof(keybuf)); + memcpy(&key.data[24], &key.data[16], sizeof(keybuf)); + memcpy(&key.data[16], keybuf, sizeof(keybuf)); + } + + /* if IW_ENCODE_EXT_RX_SEQ_VALID set */ + if (params->seq && params->seq_len == 6) { + /* rx iv */ + u8 *ivptr; + ivptr = (u8 *) params->seq; + key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | + (ivptr[3] << 8) | ivptr[2]; + key.rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key.iv_initialized = true; + } + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + WL_CONN("WLAN_CIPHER_SUITE_WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + WL_CONN("WLAN_CIPHER_SUITE_WEP104\n"); + break; + case WLAN_CIPHER_SUITE_TKIP: + key.algo = CRYPTO_ALGO_TKIP; + WL_CONN("WLAN_CIPHER_SUITE_TKIP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key.algo = CRYPTO_ALGO_AES_CCM; + WL_CONN("WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + WL_CONN("WLAN_CIPHER_SUITE_CCMP\n"); + break; + default: + WL_ERR("Invalid cipher (0x%x)\n", params->cipher); + return -EINVAL; + } + convert_key_from_CPU(&key, &key_le); + + brcmf_netdev_wait_pend8021x(ndev); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_KEY, &key_le, + sizeof(key_le)); + if (err) { + WL_ERR("WLC_SET_KEY error (%d)\n", err); + return err; + } + } + return err; +} + +static s32 +brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct brcmf_wsec_key key; + s32 val; + s32 wsec; + s32 err = 0; + u8 keybuf[8]; + + WL_TRACE("Enter\n"); + WL_CONN("key index (%d)\n", key_idx); + if (!check_sys_up(wiphy)) + return -EIO; + + if (mac_addr) { + WL_TRACE("Exit"); + return brcmf_add_keyext(wiphy, ndev, key_idx, mac_addr, params); + } + memset(&key, 0, sizeof(key)); + + key.len = (u32) params->key_len; + key.index = (u32) key_idx; + + if (key.len > sizeof(key.data)) { + WL_ERR("Too long key length (%u)\n", key.len); + err = -EINVAL; + goto done; + } + memcpy(key.data, params->key, key.len); + + key.flags = BRCMF_PRIMARY_KEY; + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + WL_CONN("WLAN_CIPHER_SUITE_WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + WL_CONN("WLAN_CIPHER_SUITE_WEP104\n"); + break; + case WLAN_CIPHER_SUITE_TKIP: + memcpy(keybuf, &key.data[24], sizeof(keybuf)); + memcpy(&key.data[24], &key.data[16], sizeof(keybuf)); + memcpy(&key.data[16], keybuf, sizeof(keybuf)); + key.algo = CRYPTO_ALGO_TKIP; + WL_CONN("WLAN_CIPHER_SUITE_TKIP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key.algo = CRYPTO_ALGO_AES_CCM; + WL_CONN("WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + WL_CONN("WLAN_CIPHER_SUITE_CCMP\n"); + break; + default: + WL_ERR("Invalid cipher (0x%x)\n", params->cipher); + err = -EINVAL; + goto done; + } + + err = send_key_to_dongle(ndev, &key); /* Set the new key/index */ + if (err) + goto done; + + val = WEP_ENABLED; + err = brcmf_dev_intvar_get(ndev, "wsec", &wsec); + if (err) { + WL_ERR("get wsec error (%d)\n", err); + goto done; + } + wsec &= ~(WEP_ENABLED); + wsec |= val; + err = brcmf_dev_intvar_set(ndev, "wsec", wsec); + if (err) { + WL_ERR("set wsec error (%d)\n", err); + goto done; + } + + val = 1; /* assume shared key. otherwise 0 */ + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_AUTH, &val); + if (err) + WL_ERR("WLC_SET_AUTH error (%d)\n", err); +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr) +{ + struct brcmf_wsec_key key; + s32 err = 0; + s32 val; + s32 wsec; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + memset(&key, 0, sizeof(key)); + + key.index = (u32) key_idx; + key.flags = BRCMF_PRIMARY_KEY; + key.algo = CRYPTO_ALGO_OFF; + + WL_CONN("key index (%d)\n", key_idx); + + /* Set the new key/index */ + err = send_key_to_dongle(ndev, &key); + if (err) { + if (err == -EINVAL) { + if (key.index >= DOT11_MAX_DEFAULT_KEYS) + /* we ignore this key index in this case */ + WL_ERR("invalid key index (%d)\n", key_idx); + } + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + + val = 0; + err = brcmf_dev_intvar_get(ndev, "wsec", &wsec); + if (err) { + WL_ERR("get wsec error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + wsec &= ~(WEP_ENABLED); + wsec |= val; + err = brcmf_dev_intvar_set(ndev, "wsec", wsec); + if (err) { + WL_ERR("set wsec error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + + val = 0; /* assume open key. otherwise 1 */ + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_AUTH, &val); + if (err) { + WL_ERR("WLC_SET_AUTH error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + } +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie, + void (*callback) (void *cookie, struct key_params * params)) +{ + struct key_params params; + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct brcmf_cfg80211_security *sec; + s32 wsec; + s32 err = 0; + + WL_TRACE("Enter\n"); + WL_CONN("key index (%d)\n", key_idx); + if (!check_sys_up(wiphy)) + return -EIO; + + memset(¶ms, 0, sizeof(params)); + + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_GET_WSEC, &wsec); + if (err) { + WL_ERR("WLC_GET_WSEC error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + switch (wsec) { + case WEP_ENABLED: + sec = brcmf_read_prof(cfg_priv, WL_PROF_SEC); + if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) { + params.cipher = WLAN_CIPHER_SUITE_WEP40; + WL_CONN("WLAN_CIPHER_SUITE_WEP40\n"); + } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) { + params.cipher = WLAN_CIPHER_SUITE_WEP104; + WL_CONN("WLAN_CIPHER_SUITE_WEP104\n"); + } + break; + case TKIP_ENABLED: + params.cipher = WLAN_CIPHER_SUITE_TKIP; + WL_CONN("WLAN_CIPHER_SUITE_TKIP\n"); + break; + case AES_ENABLED: + params.cipher = WLAN_CIPHER_SUITE_AES_CMAC; + WL_CONN("WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + default: + WL_ERR("Invalid algo (0x%x)\n", wsec); + err = -EINVAL; + goto done; + } + callback(cookie, ¶ms); + +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *ndev, u8 key_idx) +{ + WL_INFO("Not supported\n"); + + return -EOPNOTSUPP; +} + +static s32 +brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, + u8 *mac, struct station_info *sinfo) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct brcmf_scb_val_le scb_val; + int rssi; + s32 rate; + s32 err = 0; + u8 *bssid = brcmf_read_prof(cfg_priv, WL_PROF_BSSID); + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + if (memcmp(mac, bssid, ETH_ALEN)) { + WL_ERR("Wrong Mac address cfg_mac-%X:%X:%X:%X:%X:%X" + "wl_bssid-%X:%X:%X:%X:%X:%X\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + bssid[0], bssid[1], bssid[2], bssid[3], + bssid[4], bssid[5]); + err = -ENOENT; + goto done; + } + + /* Report the current tx rate */ + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_GET_RATE, &rate); + if (err) { + WL_ERR("Could not get rate (%d)\n", err); + } else { + sinfo->filled |= STATION_INFO_TX_BITRATE; + sinfo->txrate.legacy = rate * 5; + WL_CONN("Rate %d Mbps\n", rate / 2); + } + + if (test_bit(WL_STATUS_CONNECTED, &cfg_priv->status)) { + scb_val.val = cpu_to_le32(0); + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_RSSI, &scb_val, + sizeof(struct brcmf_scb_val_le)); + if (err) + WL_ERR("Could not get rssi (%d)\n", err); + + rssi = le32_to_cpu(scb_val.val); + sinfo->filled |= STATION_INFO_SIGNAL; + sinfo->signal = rssi; + WL_CONN("RSSI %d dBm\n", rssi); + } + +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, + bool enabled, s32 timeout) +{ + s32 pm; + s32 err = 0; + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + + WL_TRACE("Enter\n"); + + /* + * Powersave enable/disable request is coming from the + * cfg80211 even before the interface is up. In that + * scenario, driver will be storing the power save + * preference in cfg_priv struct to apply this to + * FW later while initializing the dongle + */ + cfg_priv->pwr_save = enabled; + if (!test_bit(WL_STATUS_READY, &cfg_priv->status)) { + + WL_INFO("Device is not ready," + "storing the value in cfg_priv struct\n"); + goto done; + } + + pm = enabled ? PM_FAST : PM_OFF; + WL_INFO("power save %s\n", (pm ? "enabled" : "disabled")); + + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_PM, &pm); + if (err) { + if (err == -ENODEV) + WL_ERR("net_device is not ready yet\n"); + else + WL_ERR("error (%d)\n", err); + } +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_set_bitrate_mask(struct wiphy *wiphy, struct net_device *ndev, + const u8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct brcm_rateset_le rateset_le; + s32 rate; + s32 val; + s32 err_bg; + s32 err_a; + u32 legacy; + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + /* addr param is always NULL. ignore it */ + /* Get current rateset */ + err = brcmf_exec_dcmd(ndev, BRCM_GET_CURR_RATESET, &rateset_le, + sizeof(rateset_le)); + if (err) { + WL_ERR("could not get current rateset (%d)\n", err); + goto done; + } + + legacy = ffs(mask->control[IEEE80211_BAND_2GHZ].legacy & 0xFFFF); + if (!legacy) + legacy = ffs(mask->control[IEEE80211_BAND_5GHZ].legacy & + 0xFFFF); + + val = wl_g_rates[legacy - 1].bitrate * 100000; + + if (val < le32_to_cpu(rateset_le.count)) + /* Select rate by rateset index */ + rate = rateset_le.rates[val] & 0x7f; + else + /* Specified rate in bps */ + rate = val / 500000; + + WL_CONN("rate %d mbps\n", rate / 2); + + /* + * + * Set rate override, + * Since the is a/b/g-blind, both a/bg_rate are enforced. + */ + err_bg = brcmf_dev_intvar_set(ndev, "bg_rate", rate); + err_a = brcmf_dev_intvar_set(ndev, "a_rate", rate); + if (err_bg && err_a) { + WL_ERR("could not set fixed rate (%d) (%d)\n", err_bg, err_a); + err = err_bg | err_a; + } + +done: + WL_TRACE("Exit\n"); + return err; +} + +static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_priv *cfg_priv, + struct brcmf_bss_info *bi) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg_priv); + struct ieee80211_channel *notify_channel; + struct cfg80211_bss *bss; + struct ieee80211_supported_band *band; + s32 err = 0; + u16 channel; + u32 freq; + u64 notify_timestamp; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + s32 notify_signal; + + if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) { + WL_ERR("Bss info is larger than buffer. Discarding\n"); + return 0; + } + + channel = bi->ctl_ch ? bi->ctl_ch : + CHSPEC_CHANNEL(le16_to_cpu(bi->chanspec)); + + if (channel <= CH_MAX_2G_CHANNEL) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(channel, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + + notify_timestamp = jiffies_to_msecs(jiffies)*1000; /* uSec */ + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + WL_CONN("bssid: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + bi->BSSID[0], bi->BSSID[1], bi->BSSID[2], + bi->BSSID[3], bi->BSSID[4], bi->BSSID[5]); + WL_CONN("Channel: %d(%d)\n", channel, freq); + WL_CONN("Capability: %X\n", notify_capability); + WL_CONN("Beacon interval: %d\n", notify_interval); + WL_CONN("Signal: %d\n", notify_signal); + WL_CONN("notify_timestamp: %#018llx\n", notify_timestamp); + + bss = cfg80211_inform_bss(wiphy, notify_channel, (const u8 *)bi->BSSID, + notify_timestamp, notify_capability, notify_interval, notify_ie, + notify_ielen, notify_signal, GFP_KERNEL); + + if (!bss) { + WL_ERR("cfg80211_inform_bss_frame error\n"); + return -EINVAL; + } + + return err; +} + +static s32 brcmf_inform_bss(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_scan_results *bss_list; + struct brcmf_bss_info *bi = NULL; /* must be initialized */ + s32 err = 0; + int i; + + bss_list = cfg_priv->bss_list; + if (bss_list->version != BRCMF_BSS_INFO_VERSION) { + WL_ERR("Version %d != WL_BSS_INFO_VERSION\n", + bss_list->version); + return -EOPNOTSUPP; + } + WL_SCAN("scanned AP count (%d)\n", bss_list->count); + for (i = 0; i < bss_list->count && i < WL_AP_MAX; i++) { + bi = next_bss(bss_list, bi); + err = brcmf_inform_single_bss(cfg_priv, bi); + if (err) + break; + } + return err; +} + +static s32 wl_inform_ibss(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, const u8 *bssid) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg_priv); + struct ieee80211_channel *notify_channel; + struct brcmf_bss_info *bi = NULL; + struct ieee80211_supported_band *band; + u8 *buf = NULL; + s32 err = 0; + u16 channel; + u32 freq; + u64 notify_timestamp; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + s32 notify_signal; + + WL_TRACE("Enter\n"); + + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + goto CleanUp; + } + + *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); + + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_BSS_INFO, buf, WL_BSS_INFO_MAX); + if (err) { + WL_ERR("WLC_GET_BSS_INFO failed: %d\n", err); + goto CleanUp; + } + + bi = (struct brcmf_bss_info *)(buf + 4); + + channel = bi->ctl_ch ? bi->ctl_ch : + CHSPEC_CHANNEL(le16_to_cpu(bi->chanspec)); + + if (channel <= CH_MAX_2G_CHANNEL) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(channel, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + + notify_timestamp = jiffies_to_msecs(jiffies)*1000; /* uSec */ + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + WL_CONN("channel: %d(%d)\n", channel, freq); + WL_CONN("capability: %X\n", notify_capability); + WL_CONN("beacon interval: %d\n", notify_interval); + WL_CONN("signal: %d\n", notify_signal); + WL_CONN("notify_timestamp: %#018llx\n", notify_timestamp); + + cfg80211_inform_bss(wiphy, notify_channel, bssid, + notify_timestamp, notify_capability, notify_interval, + notify_ie, notify_ielen, notify_signal, GFP_KERNEL); + +CleanUp: + + kfree(buf); + + WL_TRACE("Exit\n"); + + return err; +} + +static bool brcmf_is_ibssmode(struct brcmf_cfg80211_priv *cfg_priv) +{ + return cfg_priv->conf->mode == WL_MODE_IBSS; +} + +static s32 brcmf_update_bss_info(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_bss_info *bi; + struct brcmf_ssid *ssid; + struct brcmu_tlv *tim; + u16 beacon_interval; + u8 dtim_period; + size_t ie_len; + u8 *ie; + s32 err = 0; + + WL_TRACE("Enter\n"); + if (brcmf_is_ibssmode(cfg_priv)) + return err; + + ssid = (struct brcmf_ssid *)brcmf_read_prof(cfg_priv, WL_PROF_SSID); + + *(__le32 *)cfg_priv->extra_buf = cpu_to_le32(WL_EXTRA_BUF_MAX); + err = brcmf_exec_dcmd(cfg_to_ndev(cfg_priv), BRCMF_C_GET_BSS_INFO, + cfg_priv->extra_buf, WL_EXTRA_BUF_MAX); + if (err) { + WL_ERR("Could not get bss info %d\n", err); + goto update_bss_info_out; + } + + bi = (struct brcmf_bss_info *)(cfg_priv->extra_buf + 4); + err = brcmf_inform_single_bss(cfg_priv, bi); + if (err) + goto update_bss_info_out; + + ie = ((u8 *)bi) + le16_to_cpu(bi->ie_offset); + ie_len = le32_to_cpu(bi->ie_length); + beacon_interval = le16_to_cpu(bi->beacon_period); + + tim = brcmu_parse_tlvs(ie, ie_len, WLAN_EID_TIM); + if (tim) + dtim_period = tim->data[1]; + else { + /* + * active scan was done so we could not get dtim + * information out of probe response. + * so we speficially query dtim information to dongle. + */ + u32 var; + err = brcmf_dev_intvar_get(cfg_to_ndev(cfg_priv), + "dtim_assoc", &var); + if (err) { + WL_ERR("wl dtim_assoc failed (%d)\n", err); + goto update_bss_info_out; + } + dtim_period = (u8)var; + } + + brcmf_update_prof(cfg_priv, NULL, &beacon_interval, WL_PROF_BEACONINT); + brcmf_update_prof(cfg_priv, NULL, &dtim_period, WL_PROF_DTIMPERIOD); + +update_bss_info_out: + WL_TRACE("Exit"); + return err; +} + +static void brcmf_term_iscan(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_to_iscan(cfg_priv); + struct brcmf_ssid ssid; + + if (cfg_priv->iscan_on) { + iscan->state = WL_ISCAN_STATE_IDLE; + + if (iscan->timer_on) { + del_timer_sync(&iscan->timer); + iscan->timer_on = 0; + } + + cancel_work_sync(&iscan->work); + + /* Abort iscan running in FW */ + memset(&ssid, 0, sizeof(ssid)); + brcmf_run_iscan(iscan, &ssid, WL_SCAN_ACTION_ABORT); + } +} + +static void brcmf_notify_iscan_complete(struct brcmf_cfg80211_iscan_ctrl *iscan, + bool aborted) +{ + struct brcmf_cfg80211_priv *cfg_priv = iscan_to_cfg(iscan); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + + if (!test_and_clear_bit(WL_STATUS_SCANNING, &cfg_priv->status)) { + WL_ERR("Scan complete while device not scanning\n"); + return; + } + if (cfg_priv->scan_request) { + WL_SCAN("ISCAN Completed scan: %s\n", + aborted ? "Aborted" : "Done"); + cfg80211_scan_done(cfg_priv->scan_request, aborted); + brcmf_set_mpc(ndev, 1); + cfg_priv->scan_request = NULL; + } + cfg_priv->iscan_kickstart = false; +} + +static s32 brcmf_wakeup_iscan(struct brcmf_cfg80211_iscan_ctrl *iscan) +{ + if (iscan->state != WL_ISCAN_STATE_IDLE) { + WL_SCAN("wake up iscan\n"); + schedule_work(&iscan->work); + return 0; + } + + return -EIO; +} + +static s32 +brcmf_get_iscan_results(struct brcmf_cfg80211_iscan_ctrl *iscan, u32 *status, + struct brcmf_scan_results **bss_list) +{ + struct brcmf_iscan_results list; + struct brcmf_scan_results *results; + struct brcmf_scan_results_le *results_le; + struct brcmf_iscan_results *list_buf; + s32 err = 0; + + memset(iscan->scan_buf, 0, WL_ISCAN_BUF_MAX); + list_buf = (struct brcmf_iscan_results *)iscan->scan_buf; + results = &list_buf->results; + results_le = &list_buf->results_le; + results->buflen = BRCMF_ISCAN_RESULTS_FIXED_SIZE; + results->version = 0; + results->count = 0; + + memset(&list, 0, sizeof(list)); + list.results_le.buflen = cpu_to_le32(WL_ISCAN_BUF_MAX); + err = brcmf_dev_iovar_getbuf(iscan->ndev, "iscanresults", &list, + BRCMF_ISCAN_RESULTS_FIXED_SIZE, + iscan->scan_buf, WL_ISCAN_BUF_MAX); + if (err) { + WL_ERR("error (%d)\n", err); + return err; + } + results->buflen = le32_to_cpu(results_le->buflen); + results->version = le32_to_cpu(results_le->version); + results->count = le32_to_cpu(results_le->count); + WL_SCAN("results->count = %d\n", results_le->count); + WL_SCAN("results->buflen = %d\n", results_le->buflen); + *status = le32_to_cpu(list_buf->status_le); + WL_SCAN("status = %d\n", *status); + *bss_list = results; + + return err; +} + +static s32 brcmf_iscan_done(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_priv->iscan; + s32 err = 0; + + iscan->state = WL_ISCAN_STATE_IDLE; + brcmf_inform_bss(cfg_priv); + brcmf_notify_iscan_complete(iscan, false); + + return err; +} + +static s32 brcmf_iscan_pending(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_priv->iscan; + s32 err = 0; + + /* Reschedule the timer */ + mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000); + iscan->timer_on = 1; + + return err; +} + +static s32 brcmf_iscan_inprogress(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_priv->iscan; + s32 err = 0; + + brcmf_inform_bss(cfg_priv); + brcmf_run_iscan(iscan, NULL, BRCMF_SCAN_ACTION_CONTINUE); + /* Reschedule the timer */ + mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000); + iscan->timer_on = 1; + + return err; +} + +static s32 brcmf_iscan_aborted(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_priv->iscan; + s32 err = 0; + + iscan->state = WL_ISCAN_STATE_IDLE; + brcmf_notify_iscan_complete(iscan, true); + + return err; +} + +static void brcmf_cfg80211_iscan_handler(struct work_struct *work) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = + container_of(work, struct brcmf_cfg80211_iscan_ctrl, + work); + struct brcmf_cfg80211_priv *cfg_priv = iscan_to_cfg(iscan); + struct brcmf_cfg80211_iscan_eloop *el = &iscan->el; + u32 status = BRCMF_SCAN_RESULTS_PARTIAL; + + if (iscan->timer_on) { + del_timer_sync(&iscan->timer); + iscan->timer_on = 0; + } + + if (brcmf_get_iscan_results(iscan, &status, &cfg_priv->bss_list)) { + status = BRCMF_SCAN_RESULTS_ABORTED; + WL_ERR("Abort iscan\n"); + } + + el->handler[status](cfg_priv); +} + +static void brcmf_iscan_timer(unsigned long data) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = + (struct brcmf_cfg80211_iscan_ctrl *)data; + + if (iscan) { + iscan->timer_on = 0; + WL_SCAN("timer expired\n"); + brcmf_wakeup_iscan(iscan); + } +} + +static s32 brcmf_invoke_iscan(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_to_iscan(cfg_priv); + + if (cfg_priv->iscan_on) { + iscan->state = WL_ISCAN_STATE_IDLE; + INIT_WORK(&iscan->work, brcmf_cfg80211_iscan_handler); + } + + return 0; +} + +static void brcmf_init_iscan_eloop(struct brcmf_cfg80211_iscan_eloop *el) +{ + memset(el, 0, sizeof(*el)); + el->handler[BRCMF_SCAN_RESULTS_SUCCESS] = brcmf_iscan_done; + el->handler[BRCMF_SCAN_RESULTS_PARTIAL] = brcmf_iscan_inprogress; + el->handler[BRCMF_SCAN_RESULTS_PENDING] = brcmf_iscan_pending; + el->handler[BRCMF_SCAN_RESULTS_ABORTED] = brcmf_iscan_aborted; + el->handler[BRCMF_SCAN_RESULTS_NO_MEM] = brcmf_iscan_aborted; +} + +static s32 brcmf_init_iscan(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_iscan_ctrl *iscan = cfg_to_iscan(cfg_priv); + int err = 0; + + if (cfg_priv->iscan_on) { + iscan->ndev = cfg_to_ndev(cfg_priv); + brcmf_init_iscan_eloop(&iscan->el); + iscan->timer_ms = WL_ISCAN_TIMER_INTERVAL_MS; + init_timer(&iscan->timer); + iscan->timer.data = (unsigned long) iscan; + iscan->timer.function = brcmf_iscan_timer; + err = brcmf_invoke_iscan(cfg_priv); + if (!err) + iscan->data = cfg_priv; + } + + return err; +} + +static void brcmf_delay(u32 ms) +{ + if (ms < 1000 / HZ) { + cond_resched(); + mdelay(ms); + } else { + msleep(ms); + } +} + +static s32 brcmf_cfg80211_resume(struct wiphy *wiphy) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + + /* + * Check for WL_STATUS_READY before any function call which + * could result is bus access. Don't block the resume for + * any driver error conditions + */ + WL_TRACE("Enter\n"); + + if (test_bit(WL_STATUS_READY, &cfg_priv->status)) + brcmf_invoke_iscan(wiphy_to_cfg(wiphy)); + + WL_TRACE("Exit\n"); + return 0; +} + +static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wow) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg_priv); + + WL_TRACE("Enter\n"); + + /* + * Check for WL_STATUS_READY before any function call which + * could result is bus access. Don't block the suspend for + * any driver error conditions + */ + + /* + * While going to suspend if associated with AP disassociate + * from AP to save power while system is in suspended state + */ + if ((test_bit(WL_STATUS_CONNECTED, &cfg_priv->status) || + test_bit(WL_STATUS_CONNECTING, &cfg_priv->status)) && + test_bit(WL_STATUS_READY, &cfg_priv->status)) { + WL_INFO("Disassociating from AP" + " while entering suspend state\n"); + brcmf_link_down(cfg_priv); + + /* + * Make sure WPA_Supplicant receives all the event + * generated due to DISASSOC call to the fw to keep + * the state fw and WPA_Supplicant state consistent + */ + brcmf_delay(500); + } + + set_bit(WL_STATUS_SCAN_ABORTING, &cfg_priv->status); + if (test_bit(WL_STATUS_READY, &cfg_priv->status)) + brcmf_term_iscan(cfg_priv); + + if (cfg_priv->scan_request) { + /* Indidate scan abort to cfg80211 layer */ + WL_INFO("Terminating scan in progress\n"); + cfg80211_scan_done(cfg_priv->scan_request, true); + cfg_priv->scan_request = NULL; + } + clear_bit(WL_STATUS_SCANNING, &cfg_priv->status); + clear_bit(WL_STATUS_SCAN_ABORTING, &cfg_priv->status); + + /* Turn off watchdog timer */ + if (test_bit(WL_STATUS_READY, &cfg_priv->status)) { + WL_INFO("Enable MPC\n"); + brcmf_set_mpc(ndev, 1); + } + + WL_TRACE("Exit\n"); + + return 0; +} + +static __used s32 +brcmf_dev_bufvar_set(struct net_device *ndev, s8 *name, s8 *buf, s32 len) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + u32 buflen; + + buflen = brcmu_mkiovar(name, buf, len, cfg_priv->dcmd_buf, + WL_DCMD_LEN_MAX); + BUG_ON(!buflen); + + return brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, cfg_priv->dcmd_buf, + buflen); +} + +static s32 +brcmf_dev_bufvar_get(struct net_device *ndev, s8 *name, s8 *buf, + s32 buf_len) +{ + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + u32 len; + s32 err = 0; + + len = brcmu_mkiovar(name, NULL, 0, cfg_priv->dcmd_buf, + WL_DCMD_LEN_MAX); + BUG_ON(!len); + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_VAR, cfg_priv->dcmd_buf, + WL_DCMD_LEN_MAX); + if (err) { + WL_ERR("error (%d)\n", err); + return err; + } + memcpy(buf, cfg_priv->dcmd_buf, buf_len); + + return err; +} + +static __used s32 +brcmf_update_pmklist(struct net_device *ndev, + struct brcmf_cfg80211_pmk_list *pmk_list, s32 err) +{ + int i, j; + + WL_CONN("No of elements %d\n", pmk_list->pmkids.npmkid); + for (i = 0; i < pmk_list->pmkids.npmkid; i++) { + WL_CONN("PMKID[%d]: %pM =\n", i, + &pmk_list->pmkids.pmkid[i].BSSID); + for (j = 0; j < WLAN_PMKID_LEN; j++) + WL_CONN("%02x\n", pmk_list->pmkids.pmkid[i].PMKID[j]); + } + + if (!err) + brcmf_dev_bufvar_set(ndev, "pmkid_info", (char *)pmk_list, + sizeof(*pmk_list)); + + return err; +} + +static s32 +brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct pmkid_list *pmkids = &cfg_priv->pmk_list->pmkids; + s32 err = 0; + int i; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + for (i = 0; i < pmkids->npmkid; i++) + if (!memcmp(pmksa->bssid, pmkids->pmkid[i].BSSID, ETH_ALEN)) + break; + if (i < WL_NUM_PMKIDS_MAX) { + memcpy(pmkids->pmkid[i].BSSID, pmksa->bssid, ETH_ALEN); + memcpy(pmkids->pmkid[i].PMKID, pmksa->pmkid, WLAN_PMKID_LEN); + if (i == pmkids->npmkid) + pmkids->npmkid++; + } else + err = -EINVAL; + + WL_CONN("set_pmksa,IW_PMKSA_ADD - PMKID: %pM =\n", + pmkids->pmkid[pmkids->npmkid].BSSID); + for (i = 0; i < WLAN_PMKID_LEN; i++) + WL_CONN("%02x\n", pmkids->pmkid[pmkids->npmkid].PMKID[i]); + + err = brcmf_update_pmklist(ndev, cfg_priv->pmk_list, err); + + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + struct pmkid_list pmkid; + s32 err = 0; + int i; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + memcpy(&pmkid.pmkid[0].BSSID, pmksa->bssid, ETH_ALEN); + memcpy(&pmkid.pmkid[0].PMKID, pmksa->pmkid, WLAN_PMKID_LEN); + + WL_CONN("del_pmksa,IW_PMKSA_REMOVE - PMKID: %pM =\n", + &pmkid.pmkid[0].BSSID); + for (i = 0; i < WLAN_PMKID_LEN; i++) + WL_CONN("%02x\n", pmkid.pmkid[0].PMKID[i]); + + for (i = 0; i < cfg_priv->pmk_list->pmkids.npmkid; i++) + if (!memcmp + (pmksa->bssid, &cfg_priv->pmk_list->pmkids.pmkid[i].BSSID, + ETH_ALEN)) + break; + + if ((cfg_priv->pmk_list->pmkids.npmkid > 0) + && (i < cfg_priv->pmk_list->pmkids.npmkid)) { + memset(&cfg_priv->pmk_list->pmkids.pmkid[i], 0, + sizeof(struct pmkid)); + for (; i < (cfg_priv->pmk_list->pmkids.npmkid - 1); i++) { + memcpy(&cfg_priv->pmk_list->pmkids.pmkid[i].BSSID, + &cfg_priv->pmk_list->pmkids.pmkid[i + 1].BSSID, + ETH_ALEN); + memcpy(&cfg_priv->pmk_list->pmkids.pmkid[i].PMKID, + &cfg_priv->pmk_list->pmkids.pmkid[i + 1].PMKID, + WLAN_PMKID_LEN); + } + cfg_priv->pmk_list->pmkids.npmkid--; + } else + err = -EINVAL; + + err = brcmf_update_pmklist(ndev, cfg_priv->pmk_list, err); + + WL_TRACE("Exit\n"); + return err; + +} + +static s32 +brcmf_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev) +{ + struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy); + s32 err = 0; + + WL_TRACE("Enter\n"); + if (!check_sys_up(wiphy)) + return -EIO; + + memset(cfg_priv->pmk_list, 0, sizeof(*cfg_priv->pmk_list)); + err = brcmf_update_pmklist(ndev, cfg_priv->pmk_list, err); + + WL_TRACE("Exit\n"); + return err; + +} + +static struct cfg80211_ops wl_cfg80211_ops = { + .change_virtual_intf = brcmf_cfg80211_change_iface, + .scan = brcmf_cfg80211_scan, + .set_wiphy_params = brcmf_cfg80211_set_wiphy_params, + .join_ibss = brcmf_cfg80211_join_ibss, + .leave_ibss = brcmf_cfg80211_leave_ibss, + .get_station = brcmf_cfg80211_get_station, + .set_tx_power = brcmf_cfg80211_set_tx_power, + .get_tx_power = brcmf_cfg80211_get_tx_power, + .add_key = brcmf_cfg80211_add_key, + .del_key = brcmf_cfg80211_del_key, + .get_key = brcmf_cfg80211_get_key, + .set_default_key = brcmf_cfg80211_config_default_key, + .set_default_mgmt_key = brcmf_cfg80211_config_default_mgmt_key, + .set_power_mgmt = brcmf_cfg80211_set_power_mgmt, + .set_bitrate_mask = brcmf_cfg80211_set_bitrate_mask, + .connect = brcmf_cfg80211_connect, + .disconnect = brcmf_cfg80211_disconnect, + .suspend = brcmf_cfg80211_suspend, + .resume = brcmf_cfg80211_resume, + .set_pmksa = brcmf_cfg80211_set_pmksa, + .del_pmksa = brcmf_cfg80211_del_pmksa, + .flush_pmksa = brcmf_cfg80211_flush_pmksa +}; + +static s32 brcmf_mode_to_nl80211_iftype(s32 mode) +{ + s32 err = 0; + + switch (mode) { + case WL_MODE_BSS: + return NL80211_IFTYPE_STATION; + case WL_MODE_IBSS: + return NL80211_IFTYPE_ADHOC; + default: + return NL80211_IFTYPE_UNSPECIFIED; + } + + return err; +} + +static struct wireless_dev *brcmf_alloc_wdev(s32 sizeof_iface, + struct device *ndev) +{ + struct wireless_dev *wdev; + s32 err = 0; + + wdev = kzalloc(sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + wdev->wiphy = + wiphy_new(&wl_cfg80211_ops, + sizeof(struct brcmf_cfg80211_priv) + sizeof_iface); + if (!wdev->wiphy) { + WL_ERR("Couldn not allocate wiphy device\n"); + err = -ENOMEM; + goto wiphy_new_out; + } + set_wiphy_dev(wdev->wiphy, ndev); + wdev->wiphy->max_scan_ssids = WL_NUM_SCAN_MAX; + wdev->wiphy->max_num_pmkids = WL_NUM_PMKIDS_MAX; + wdev->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC); + wdev->wiphy->bands[IEEE80211_BAND_2GHZ] = &__wl_band_2ghz; + wdev->wiphy->bands[IEEE80211_BAND_5GHZ] = &__wl_band_5ghz_a; /* Set + * it as 11a by default. + * This will be updated with + * 11n phy tables in + * "ifconfig up" + * if phy has 11n capability + */ + wdev->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wdev->wiphy->cipher_suites = __wl_cipher_suites; + wdev->wiphy->n_cipher_suites = ARRAY_SIZE(__wl_cipher_suites); + wdev->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; /* enable power + * save mode + * by default + */ + err = wiphy_register(wdev->wiphy); + if (err < 0) { + WL_ERR("Couldn not register wiphy device (%d)\n", err); + goto wiphy_register_out; + } + return wdev; + +wiphy_register_out: + wiphy_free(wdev->wiphy); + +wiphy_new_out: + kfree(wdev); + + return ERR_PTR(err); +} + +static void brcmf_free_wdev(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct wireless_dev *wdev = cfg_priv->wdev; + + if (!wdev) { + WL_ERR("wdev is invalid\n"); + return; + } + wiphy_unregister(wdev->wiphy); + wiphy_free(wdev->wiphy); + kfree(wdev); + cfg_priv->wdev = NULL; +} + +static bool brcmf_is_linkup(struct brcmf_cfg80211_priv *cfg_priv, + const struct brcmf_event_msg *e) +{ + u32 event = be32_to_cpu(e->event_type); + u32 status = be32_to_cpu(e->status); + + if (event == BRCMF_E_SET_SSID && status == BRCMF_E_STATUS_SUCCESS) { + WL_CONN("Processing set ssid\n"); + cfg_priv->link_up = true; + return true; + } + + return false; +} + +static bool brcmf_is_linkdown(struct brcmf_cfg80211_priv *cfg_priv, + const struct brcmf_event_msg *e) +{ + u32 event = be32_to_cpu(e->event_type); + u16 flags = be16_to_cpu(e->flags); + + if (event == BRCMF_E_LINK && (!(flags & BRCMF_EVENT_MSG_LINK))) { + WL_CONN("Processing link down\n"); + return true; + } + return false; +} + +static bool brcmf_is_nonetwork(struct brcmf_cfg80211_priv *cfg_priv, + const struct brcmf_event_msg *e) +{ + u32 event = be32_to_cpu(e->event_type); + u32 status = be32_to_cpu(e->status); + + if (event == BRCMF_E_LINK && status == BRCMF_E_STATUS_NO_NETWORKS) { + WL_CONN("Processing Link %s & no network found\n", + be16_to_cpu(e->flags) & BRCMF_EVENT_MSG_LINK ? + "up" : "down"); + return true; + } + + if (event == BRCMF_E_SET_SSID && status != BRCMF_E_STATUS_SUCCESS) { + WL_CONN("Processing connecting & no network found\n"); + return true; + } + + return false; +} + +static void brcmf_clear_assoc_ies(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg_priv); + + kfree(conn_info->req_ie); + conn_info->req_ie = NULL; + conn_info->req_ie_len = 0; + kfree(conn_info->resp_ie); + conn_info->resp_ie = NULL; + conn_info->resp_ie_len = 0; +} + +static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct net_device *ndev = cfg_to_ndev(cfg_priv); + struct brcmf_cfg80211_assoc_ielen *assoc_info; + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg_priv); + u32 req_len; + u32 resp_len; + s32 err = 0; + + brcmf_clear_assoc_ies(cfg_priv); + + err = brcmf_dev_bufvar_get(ndev, "assoc_info", cfg_priv->extra_buf, + WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR("could not get assoc info (%d)\n", err); + return err; + } + assoc_info = (struct brcmf_cfg80211_assoc_ielen *)cfg_priv->extra_buf; + req_len = assoc_info->req_len; + resp_len = assoc_info->resp_len; + if (req_len) { + err = brcmf_dev_bufvar_get(ndev, "assoc_req_ies", + cfg_priv->extra_buf, + WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR("could not get assoc req (%d)\n", err); + return err; + } + conn_info->req_ie_len = req_len; + conn_info->req_ie = + kmemdup(cfg_priv->extra_buf, conn_info->req_ie_len, + GFP_KERNEL); + } else { + conn_info->req_ie_len = 0; + conn_info->req_ie = NULL; + } + if (resp_len) { + err = brcmf_dev_bufvar_get(ndev, "assoc_resp_ies", + cfg_priv->extra_buf, + WL_ASSOC_INFO_MAX); + if (err) { + WL_ERR("could not get assoc resp (%d)\n", err); + return err; + } + conn_info->resp_ie_len = resp_len; + conn_info->resp_ie = + kmemdup(cfg_priv->extra_buf, conn_info->resp_ie_len, + GFP_KERNEL); + } else { + conn_info->resp_ie_len = 0; + conn_info->resp_ie = NULL; + } + WL_CONN("req len (%d) resp len (%d)\n", + conn_info->req_ie_len, conn_info->resp_ie_len); + + return err; +} + +static s32 +brcmf_bss_roaming_done(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e) +{ + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg_priv); + struct wiphy *wiphy = cfg_to_wiphy(cfg_priv); + struct brcmf_channel_info_le channel_le; + struct ieee80211_channel *notify_channel; + struct ieee80211_supported_band *band; + u32 freq; + s32 err = 0; + u32 target_channel; + + WL_TRACE("Enter\n"); + + brcmf_get_assoc_ies(cfg_priv); + brcmf_update_prof(cfg_priv, NULL, &e->addr, WL_PROF_BSSID); + brcmf_update_bss_info(cfg_priv); + + brcmf_exec_dcmd(ndev, BRCMF_C_GET_CHANNEL, &channel_le, + sizeof(channel_le)); + + target_channel = le32_to_cpu(channel_le.target_channel); + WL_CONN("Roamed to channel %d\n", target_channel); + + if (target_channel <= CH_MAX_2G_CHANNEL) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(target_channel, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + + cfg80211_roamed(ndev, notify_channel, + (u8 *)brcmf_read_prof(cfg_priv, WL_PROF_BSSID), + conn_info->req_ie, conn_info->req_ie_len, + conn_info->resp_ie, conn_info->resp_ie_len, GFP_KERNEL); + WL_CONN("Report roaming result\n"); + + set_bit(WL_STATUS_CONNECTED, &cfg_priv->status); + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_bss_connect_done(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, const struct brcmf_event_msg *e, + bool completed) +{ + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg_priv); + s32 err = 0; + + WL_TRACE("Enter\n"); + + if (test_and_clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status)) { + if (completed) { + brcmf_get_assoc_ies(cfg_priv); + brcmf_update_prof(cfg_priv, NULL, &e->addr, + WL_PROF_BSSID); + brcmf_update_bss_info(cfg_priv); + } + cfg80211_connect_result(ndev, + (u8 *)brcmf_read_prof(cfg_priv, + WL_PROF_BSSID), + conn_info->req_ie, + conn_info->req_ie_len, + conn_info->resp_ie, + conn_info->resp_ie_len, + completed ? WLAN_STATUS_SUCCESS : + WLAN_STATUS_AUTH_TIMEOUT, + GFP_KERNEL); + if (completed) + set_bit(WL_STATUS_CONNECTED, &cfg_priv->status); + WL_CONN("Report connect result - connection %s\n", + completed ? "succeeded" : "failed"); + } + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_notify_connect_status(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + s32 err = 0; + + if (brcmf_is_linkup(cfg_priv, e)) { + WL_CONN("Linkup\n"); + if (brcmf_is_ibssmode(cfg_priv)) { + brcmf_update_prof(cfg_priv, NULL, (void *)e->addr, + WL_PROF_BSSID); + wl_inform_ibss(cfg_priv, ndev, e->addr); + cfg80211_ibss_joined(ndev, e->addr, GFP_KERNEL); + clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + set_bit(WL_STATUS_CONNECTED, &cfg_priv->status); + } else + brcmf_bss_connect_done(cfg_priv, ndev, e, true); + } else if (brcmf_is_linkdown(cfg_priv, e)) { + WL_CONN("Linkdown\n"); + if (brcmf_is_ibssmode(cfg_priv)) { + clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + if (test_and_clear_bit(WL_STATUS_CONNECTED, + &cfg_priv->status)) + brcmf_link_down(cfg_priv); + } else { + brcmf_bss_connect_done(cfg_priv, ndev, e, false); + if (test_and_clear_bit(WL_STATUS_CONNECTED, + &cfg_priv->status)) { + cfg80211_disconnected(ndev, 0, NULL, 0, + GFP_KERNEL); + brcmf_link_down(cfg_priv); + } + } + brcmf_init_prof(cfg_priv->profile); + } else if (brcmf_is_nonetwork(cfg_priv, e)) { + if (brcmf_is_ibssmode(cfg_priv)) + clear_bit(WL_STATUS_CONNECTING, &cfg_priv->status); + else + brcmf_bss_connect_done(cfg_priv, ndev, e, false); + } + + return err; +} + +static s32 +brcmf_notify_roaming_status(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + s32 err = 0; + u32 event = be32_to_cpu(e->event_type); + u32 status = be32_to_cpu(e->status); + + if (event == BRCMF_E_ROAM && status == BRCMF_E_STATUS_SUCCESS) { + if (test_bit(WL_STATUS_CONNECTED, &cfg_priv->status)) + brcmf_bss_roaming_done(cfg_priv, ndev, e); + else + brcmf_bss_connect_done(cfg_priv, ndev, e, true); + } + + return err; +} + +static s32 +brcmf_notify_mic_status(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + u16 flags = be16_to_cpu(e->flags); + enum nl80211_key_type key_type; + + if (flags & BRCMF_EVENT_MSG_GROUP) + key_type = NL80211_KEYTYPE_GROUP; + else + key_type = NL80211_KEYTYPE_PAIRWISE; + + cfg80211_michael_mic_failure(ndev, (u8 *)&e->addr, key_type, -1, + NULL, GFP_KERNEL); + + return 0; +} + +static s32 +brcmf_notify_scan_status(struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_channel_info_le channel_inform_le; + struct brcmf_scan_results_le *bss_list_le; + u32 len = WL_SCAN_BUF_MAX; + s32 err = 0; + bool scan_abort = false; + u32 scan_channel; + + WL_TRACE("Enter\n"); + + if (cfg_priv->iscan_on && cfg_priv->iscan_kickstart) { + WL_TRACE("Exit\n"); + return brcmf_wakeup_iscan(cfg_to_iscan(cfg_priv)); + } + + if (!test_and_clear_bit(WL_STATUS_SCANNING, &cfg_priv->status)) { + WL_ERR("Scan complete while device not scanning\n"); + scan_abort = true; + err = -EINVAL; + goto scan_done_out; + } + + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_CHANNEL, &channel_inform_le, + sizeof(channel_inform_le)); + if (err) { + WL_ERR("scan busy (%d)\n", err); + scan_abort = true; + goto scan_done_out; + } + scan_channel = le32_to_cpu(channel_inform_le.scan_channel); + if (scan_channel) + WL_CONN("channel_inform.scan_channel (%d)\n", scan_channel); + cfg_priv->bss_list = cfg_priv->scan_results; + bss_list_le = (struct brcmf_scan_results_le *) cfg_priv->bss_list; + + memset(cfg_priv->scan_results, 0, len); + bss_list_le->buflen = cpu_to_le32(len); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SCAN_RESULTS, + cfg_priv->scan_results, len); + if (err) { + WL_ERR("%s Scan_results error (%d)\n", ndev->name, err); + err = -EINVAL; + scan_abort = true; + goto scan_done_out; + } + cfg_priv->scan_results->buflen = le32_to_cpu(bss_list_le->buflen); + cfg_priv->scan_results->version = le32_to_cpu(bss_list_le->version); + cfg_priv->scan_results->count = le32_to_cpu(bss_list_le->count); + + err = brcmf_inform_bss(cfg_priv); + if (err) { + scan_abort = true; + goto scan_done_out; + } + +scan_done_out: + if (cfg_priv->scan_request) { + WL_SCAN("calling cfg80211_scan_done\n"); + cfg80211_scan_done(cfg_priv->scan_request, scan_abort); + brcmf_set_mpc(ndev, 1); + cfg_priv->scan_request = NULL; + } + + WL_TRACE("Exit\n"); + + return err; +} + +static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) +{ + conf->mode = (u32)-1; + conf->frag_threshold = (u32)-1; + conf->rts_threshold = (u32)-1; + conf->retry_short = (u32)-1; + conf->retry_long = (u32)-1; + conf->tx_power = -1; +} + +static void brcmf_init_eloop_handler(struct brcmf_cfg80211_event_loop *el) +{ + memset(el, 0, sizeof(*el)); + el->handler[BRCMF_E_SCAN_COMPLETE] = brcmf_notify_scan_status; + el->handler[BRCMF_E_LINK] = brcmf_notify_connect_status; + el->handler[BRCMF_E_ROAM] = brcmf_notify_roaming_status; + el->handler[BRCMF_E_MIC_ERROR] = brcmf_notify_mic_status; + el->handler[BRCMF_E_SET_SSID] = brcmf_notify_connect_status; +} + +static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_priv *cfg_priv) +{ + kfree(cfg_priv->scan_results); + cfg_priv->scan_results = NULL; + kfree(cfg_priv->bss_info); + cfg_priv->bss_info = NULL; + kfree(cfg_priv->conf); + cfg_priv->conf = NULL; + kfree(cfg_priv->profile); + cfg_priv->profile = NULL; + kfree(cfg_priv->scan_req_int); + cfg_priv->scan_req_int = NULL; + kfree(cfg_priv->dcmd_buf); + cfg_priv->dcmd_buf = NULL; + kfree(cfg_priv->extra_buf); + cfg_priv->extra_buf = NULL; + kfree(cfg_priv->iscan); + cfg_priv->iscan = NULL; + kfree(cfg_priv->pmk_list); + cfg_priv->pmk_list = NULL; +} + +static s32 brcmf_init_priv_mem(struct brcmf_cfg80211_priv *cfg_priv) +{ + cfg_priv->scan_results = kzalloc(WL_SCAN_BUF_MAX, GFP_KERNEL); + if (!cfg_priv->scan_results) + goto init_priv_mem_out; + cfg_priv->conf = kzalloc(sizeof(*cfg_priv->conf), GFP_KERNEL); + if (!cfg_priv->conf) + goto init_priv_mem_out; + cfg_priv->profile = kzalloc(sizeof(*cfg_priv->profile), GFP_KERNEL); + if (!cfg_priv->profile) + goto init_priv_mem_out; + cfg_priv->bss_info = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (!cfg_priv->bss_info) + goto init_priv_mem_out; + cfg_priv->scan_req_int = kzalloc(sizeof(*cfg_priv->scan_req_int), + GFP_KERNEL); + if (!cfg_priv->scan_req_int) + goto init_priv_mem_out; + cfg_priv->dcmd_buf = kzalloc(WL_DCMD_LEN_MAX, GFP_KERNEL); + if (!cfg_priv->dcmd_buf) + goto init_priv_mem_out; + cfg_priv->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!cfg_priv->extra_buf) + goto init_priv_mem_out; + cfg_priv->iscan = kzalloc(sizeof(*cfg_priv->iscan), GFP_KERNEL); + if (!cfg_priv->iscan) + goto init_priv_mem_out; + cfg_priv->pmk_list = kzalloc(sizeof(*cfg_priv->pmk_list), GFP_KERNEL); + if (!cfg_priv->pmk_list) + goto init_priv_mem_out; + + return 0; + +init_priv_mem_out: + brcmf_deinit_priv_mem(cfg_priv); + + return -ENOMEM; +} + +/* +* retrieve first queued event from head +*/ + +static struct brcmf_cfg80211_event_q *brcmf_deq_event( + struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_event_q *e = NULL; + + spin_lock_irq(&cfg_priv->evt_q_lock); + if (!list_empty(&cfg_priv->evt_q_list)) { + e = list_first_entry(&cfg_priv->evt_q_list, + struct brcmf_cfg80211_event_q, evt_q_list); + list_del(&e->evt_q_list); + } + spin_unlock_irq(&cfg_priv->evt_q_lock); + + return e; +} + +/* +** push event to tail of the queue +*/ + +static s32 +brcmf_enq_event(struct brcmf_cfg80211_priv *cfg_priv, u32 event, + const struct brcmf_event_msg *msg) +{ + struct brcmf_cfg80211_event_q *e; + s32 err = 0; + + e = kzalloc(sizeof(struct brcmf_cfg80211_event_q), GFP_KERNEL); + if (!e) + return -ENOMEM; + + e->etype = event; + memcpy(&e->emsg, msg, sizeof(struct brcmf_event_msg)); + + spin_lock_irq(&cfg_priv->evt_q_lock); + list_add_tail(&e->evt_q_list, &cfg_priv->evt_q_list); + spin_unlock_irq(&cfg_priv->evt_q_lock); + + return err; +} + +static void brcmf_put_event(struct brcmf_cfg80211_event_q *e) +{ + kfree(e); +} + +static void brcmf_cfg80211_event_handler(struct work_struct *work) +{ + struct brcmf_cfg80211_priv *cfg_priv = + container_of(work, struct brcmf_cfg80211_priv, + event_work); + struct brcmf_cfg80211_event_q *e; + + e = brcmf_deq_event(cfg_priv); + if (unlikely(!e)) { + WL_ERR("event queue empty...\n"); + return; + } + + do { + WL_INFO("event type (%d)\n", e->etype); + if (cfg_priv->el.handler[e->etype]) + cfg_priv->el.handler[e->etype](cfg_priv, + cfg_to_ndev(cfg_priv), + &e->emsg, e->edata); + else + WL_INFO("Unknown Event (%d): ignoring\n", e->etype); + brcmf_put_event(e); + } while ((e = brcmf_deq_event(cfg_priv))); + +} + +static void brcmf_init_eq(struct brcmf_cfg80211_priv *cfg_priv) +{ + spin_lock_init(&cfg_priv->evt_q_lock); + INIT_LIST_HEAD(&cfg_priv->evt_q_list); +} + +static void brcmf_flush_eq(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct brcmf_cfg80211_event_q *e; + + spin_lock_irq(&cfg_priv->evt_q_lock); + while (!list_empty(&cfg_priv->evt_q_list)) { + e = list_first_entry(&cfg_priv->evt_q_list, + struct brcmf_cfg80211_event_q, evt_q_list); + list_del(&e->evt_q_list); + kfree(e); + } + spin_unlock_irq(&cfg_priv->evt_q_lock); +} + +static s32 wl_init_priv(struct brcmf_cfg80211_priv *cfg_priv) +{ + s32 err = 0; + + cfg_priv->scan_request = NULL; + cfg_priv->pwr_save = true; + cfg_priv->iscan_on = true; /* iscan on & off switch. + we enable iscan per default */ + cfg_priv->roam_on = true; /* roam on & off switch. + we enable roam per default */ + + cfg_priv->iscan_kickstart = false; + cfg_priv->active_scan = true; /* we do active scan for + specific scan per default */ + cfg_priv->dongle_up = false; /* dongle is not up yet */ + brcmf_init_eq(cfg_priv); + err = brcmf_init_priv_mem(cfg_priv); + if (err) + return err; + INIT_WORK(&cfg_priv->event_work, brcmf_cfg80211_event_handler); + brcmf_init_eloop_handler(&cfg_priv->el); + mutex_init(&cfg_priv->usr_sync); + err = brcmf_init_iscan(cfg_priv); + if (err) + return err; + brcmf_init_conf(cfg_priv->conf); + brcmf_init_prof(cfg_priv->profile); + brcmf_link_down(cfg_priv); + + return err; +} + +static void wl_deinit_priv(struct brcmf_cfg80211_priv *cfg_priv) +{ + cancel_work_sync(&cfg_priv->event_work); + cfg_priv->dongle_up = false; /* dongle down */ + brcmf_flush_eq(cfg_priv); + brcmf_link_down(cfg_priv); + brcmf_term_iscan(cfg_priv); + brcmf_deinit_priv_mem(cfg_priv); +} + +struct brcmf_cfg80211_dev *brcmf_cfg80211_attach(struct net_device *ndev, + struct device *busdev, + void *data) +{ + struct wireless_dev *wdev; + struct brcmf_cfg80211_priv *cfg_priv; + struct brcmf_cfg80211_iface *ci; + struct brcmf_cfg80211_dev *cfg_dev; + s32 err = 0; + + if (!ndev) { + WL_ERR("ndev is invalid\n"); + return NULL; + } + cfg_dev = kzalloc(sizeof(struct brcmf_cfg80211_dev), GFP_KERNEL); + if (!cfg_dev) + return NULL; + + wdev = brcmf_alloc_wdev(sizeof(struct brcmf_cfg80211_iface), busdev); + if (IS_ERR(wdev)) { + kfree(cfg_dev); + return NULL; + } + + wdev->iftype = brcmf_mode_to_nl80211_iftype(WL_MODE_BSS); + cfg_priv = wdev_to_cfg(wdev); + cfg_priv->wdev = wdev; + cfg_priv->pub = data; + ci = (struct brcmf_cfg80211_iface *)&cfg_priv->ci; + ci->cfg_priv = cfg_priv; + ndev->ieee80211_ptr = wdev; + SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy)); + wdev->netdev = ndev; + err = wl_init_priv(cfg_priv); + if (err) { + WL_ERR("Failed to init iwm_priv (%d)\n", err); + goto cfg80211_attach_out; + } + brcmf_set_drvdata(cfg_dev, ci); + + return cfg_dev; + +cfg80211_attach_out: + brcmf_free_wdev(cfg_priv); + kfree(cfg_dev); + return NULL; +} + +void brcmf_cfg80211_detach(struct brcmf_cfg80211_dev *cfg_dev) +{ + struct brcmf_cfg80211_priv *cfg_priv; + + cfg_priv = brcmf_priv_get(cfg_dev); + + wl_deinit_priv(cfg_priv); + brcmf_free_wdev(cfg_priv); + brcmf_set_drvdata(cfg_dev, NULL); + kfree(cfg_dev); +} + +void +brcmf_cfg80211_event(struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + u32 event_type = be32_to_cpu(e->event_type); + struct brcmf_cfg80211_priv *cfg_priv = ndev_to_cfg(ndev); + + if (!brcmf_enq_event(cfg_priv, event_type, e)) + schedule_work(&cfg_priv->event_work); +} + +static s32 brcmf_dongle_mode(struct net_device *ndev, s32 iftype) +{ + s32 infra = 0; + s32 err = 0; + + switch (iftype) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + WL_ERR("type (%d) : currently we do not support this mode\n", + iftype); + err = -EINVAL; + return err; + case NL80211_IFTYPE_ADHOC: + infra = 0; + break; + case NL80211_IFTYPE_STATION: + infra = 1; + break; + default: + err = -EINVAL; + WL_ERR("invalid type (%d)\n", iftype); + return err; + } + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_INFRA, &infra); + if (err) { + WL_ERR("WLC_SET_INFRA error (%d)\n", err); + return err; + } + + return 0; +} + +static s32 brcmf_dongle_eventmsg(struct net_device *ndev) +{ + /* Room for "event_msgs" + '\0' + bitvec */ + s8 iovbuf[BRCMF_EVENTING_MASK_LEN + 12]; + s8 eventmask[BRCMF_EVENTING_MASK_LEN]; + s32 err = 0; + + WL_TRACE("Enter\n"); + + /* Setup event_msgs */ + brcmu_mkiovar("event_msgs", eventmask, BRCMF_EVENTING_MASK_LEN, iovbuf, + sizeof(iovbuf)); + err = brcmf_exec_dcmd(ndev, BRCMF_C_GET_VAR, iovbuf, sizeof(iovbuf)); + if (err) { + WL_ERR("Get event_msgs error (%d)\n", err); + goto dongle_eventmsg_out; + } + memcpy(eventmask, iovbuf, BRCMF_EVENTING_MASK_LEN); + + setbit(eventmask, BRCMF_E_SET_SSID); + setbit(eventmask, BRCMF_E_ROAM); + setbit(eventmask, BRCMF_E_PRUNE); + setbit(eventmask, BRCMF_E_AUTH); + setbit(eventmask, BRCMF_E_REASSOC); + setbit(eventmask, BRCMF_E_REASSOC_IND); + setbit(eventmask, BRCMF_E_DEAUTH_IND); + setbit(eventmask, BRCMF_E_DISASSOC_IND); + setbit(eventmask, BRCMF_E_DISASSOC); + setbit(eventmask, BRCMF_E_JOIN); + setbit(eventmask, BRCMF_E_ASSOC_IND); + setbit(eventmask, BRCMF_E_PSK_SUP); + setbit(eventmask, BRCMF_E_LINK); + setbit(eventmask, BRCMF_E_NDIS_LINK); + setbit(eventmask, BRCMF_E_MIC_ERROR); + setbit(eventmask, BRCMF_E_PMKID_CACHE); + setbit(eventmask, BRCMF_E_TXFAIL); + setbit(eventmask, BRCMF_E_JOIN_START); + setbit(eventmask, BRCMF_E_SCAN_COMPLETE); + + brcmu_mkiovar("event_msgs", eventmask, BRCMF_EVENTING_MASK_LEN, iovbuf, + sizeof(iovbuf)); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, iovbuf, sizeof(iovbuf)); + if (err) { + WL_ERR("Set event_msgs error (%d)\n", err); + goto dongle_eventmsg_out; + } + +dongle_eventmsg_out: + WL_TRACE("Exit\n"); + return err; +} + +static s32 +brcmf_dongle_roam(struct net_device *ndev, u32 roamvar, u32 bcn_timeout) +{ + s8 iovbuf[32]; + s32 roamtrigger[2]; + s32 roam_delta[2]; + s32 err = 0; + + /* + * Setup timeout if Beacons are lost and roam is + * off to report link down + */ + if (roamvar) { + brcmu_mkiovar("bcn_timeout", (char *)&bcn_timeout, + sizeof(bcn_timeout), iovbuf, sizeof(iovbuf)); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, + iovbuf, sizeof(iovbuf)); + if (err) { + WL_ERR("bcn_timeout error (%d)\n", err); + goto dongle_rom_out; + } + } + + /* + * Enable/Disable built-in roaming to allow supplicant + * to take care of roaming + */ + WL_INFO("Internal Roaming = %s\n", roamvar ? "Off" : "On"); + brcmu_mkiovar("roam_off", (char *)&roamvar, + sizeof(roamvar), iovbuf, sizeof(iovbuf)); + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_VAR, iovbuf, sizeof(iovbuf)); + if (err) { + WL_ERR("roam_off error (%d)\n", err); + goto dongle_rom_out; + } + + roamtrigger[0] = WL_ROAM_TRIGGER_LEVEL; + roamtrigger[1] = BRCM_BAND_ALL; + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_ROAM_TRIGGER, + (void *)roamtrigger, sizeof(roamtrigger)); + if (err) { + WL_ERR("WLC_SET_ROAM_TRIGGER error (%d)\n", err); + goto dongle_rom_out; + } + + roam_delta[0] = WL_ROAM_DELTA; + roam_delta[1] = BRCM_BAND_ALL; + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_ROAM_DELTA, + (void *)roam_delta, sizeof(roam_delta)); + if (err) { + WL_ERR("WLC_SET_ROAM_DELTA error (%d)\n", err); + goto dongle_rom_out; + } + +dongle_rom_out: + return err; +} + +static s32 +brcmf_dongle_scantime(struct net_device *ndev, s32 scan_assoc_time, + s32 scan_unassoc_time, s32 scan_passive_time) +{ + s32 err = 0; + + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_SCAN_CHANNEL_TIME, + &scan_assoc_time, sizeof(scan_assoc_time)); + if (err) { + if (err == -EOPNOTSUPP) + WL_INFO("Scan assoc time is not supported\n"); + else + WL_ERR("Scan assoc time error (%d)\n", err); + goto dongle_scantime_out; + } + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_SCAN_UNASSOC_TIME, + &scan_unassoc_time, sizeof(scan_unassoc_time)); + if (err) { + if (err == -EOPNOTSUPP) + WL_INFO("Scan unassoc time is not supported\n"); + else + WL_ERR("Scan unassoc time error (%d)\n", err); + goto dongle_scantime_out; + } + + err = brcmf_exec_dcmd(ndev, BRCMF_C_SET_SCAN_PASSIVE_TIME, + &scan_passive_time, sizeof(scan_passive_time)); + if (err) { + if (err == -EOPNOTSUPP) + WL_INFO("Scan passive time is not supported\n"); + else + WL_ERR("Scan passive time error (%d)\n", err); + goto dongle_scantime_out; + } + +dongle_scantime_out: + return err; +} + +static s32 wl_update_wiphybands(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct wiphy *wiphy; + s32 phy_list; + s8 phy; + s32 err = 0; + + err = brcmf_exec_dcmd(cfg_to_ndev(cfg_priv), BRCM_GET_PHYLIST, + &phy_list, sizeof(phy_list)); + if (err) { + WL_ERR("error (%d)\n", err); + return err; + } + + phy = ((char *)&phy_list)[1]; + WL_INFO("%c phy\n", phy); + if (phy == 'n' || phy == 'a') { + wiphy = cfg_to_wiphy(cfg_priv); + wiphy->bands[IEEE80211_BAND_5GHZ] = &__wl_band_5ghz_n; + } + + return err; +} + +static s32 brcmf_dongle_probecap(struct brcmf_cfg80211_priv *cfg_priv) +{ + return wl_update_wiphybands(cfg_priv); +} + +static s32 brcmf_config_dongle(struct brcmf_cfg80211_priv *cfg_priv) +{ + struct net_device *ndev; + struct wireless_dev *wdev; + s32 power_mode; + s32 err = 0; + + if (cfg_priv->dongle_up) + return err; + + ndev = cfg_to_ndev(cfg_priv); + wdev = ndev->ieee80211_ptr; + + brcmf_dongle_scantime(ndev, WL_SCAN_CHANNEL_TIME, + WL_SCAN_UNASSOC_TIME, WL_SCAN_PASSIVE_TIME); + + err = brcmf_dongle_eventmsg(ndev); + if (err) + goto default_conf_out; + + power_mode = cfg_priv->pwr_save ? PM_FAST : PM_OFF; + err = brcmf_exec_dcmd_u32(ndev, BRCMF_C_SET_PM, &power_mode); + if (err) + goto default_conf_out; + WL_INFO("power save set to %s\n", + (power_mode ? "enabled" : "disabled")); + + err = brcmf_dongle_roam(ndev, (cfg_priv->roam_on ? 0 : 1), + WL_BEACON_TIMEOUT); + if (err) + goto default_conf_out; + err = brcmf_dongle_mode(ndev, wdev->iftype); + if (err && err != -EINPROGRESS) + goto default_conf_out; + err = brcmf_dongle_probecap(cfg_priv); + if (err) + goto default_conf_out; + + /* -EINPROGRESS: Call commit handler */ + +default_conf_out: + + cfg_priv->dongle_up = true; + + return err; + +} + +static int brcmf_debugfs_add_netdev_params(struct brcmf_cfg80211_priv *cfg_priv) +{ + char buf[10+IFNAMSIZ]; + struct dentry *fd; + s32 err = 0; + + sprintf(buf, "netdev:%s", cfg_to_ndev(cfg_priv)->name); + cfg_priv->debugfsdir = debugfs_create_dir(buf, + cfg_to_wiphy(cfg_priv)->debugfsdir); + + fd = debugfs_create_u16("beacon_int", S_IRUGO, cfg_priv->debugfsdir, + (u16 *)&cfg_priv->profile->beacon_interval); + if (!fd) { + err = -ENOMEM; + goto err_out; + } + + fd = debugfs_create_u8("dtim_period", S_IRUGO, cfg_priv->debugfsdir, + (u8 *)&cfg_priv->profile->dtim_period); + if (!fd) { + err = -ENOMEM; + goto err_out; + } + +err_out: + return err; +} + +static void brcmf_debugfs_remove_netdev(struct brcmf_cfg80211_priv *cfg_priv) +{ + debugfs_remove_recursive(cfg_priv->debugfsdir); + cfg_priv->debugfsdir = NULL; +} + +static s32 __brcmf_cfg80211_up(struct brcmf_cfg80211_priv *cfg_priv) +{ + s32 err = 0; + + set_bit(WL_STATUS_READY, &cfg_priv->status); + + brcmf_debugfs_add_netdev_params(cfg_priv); + + err = brcmf_config_dongle(cfg_priv); + if (err) + return err; + + brcmf_invoke_iscan(cfg_priv); + + return err; +} + +static s32 __brcmf_cfg80211_down(struct brcmf_cfg80211_priv *cfg_priv) +{ + /* + * While going down, if associated with AP disassociate + * from AP to save power + */ + if ((test_bit(WL_STATUS_CONNECTED, &cfg_priv->status) || + test_bit(WL_STATUS_CONNECTING, &cfg_priv->status)) && + test_bit(WL_STATUS_READY, &cfg_priv->status)) { + WL_INFO("Disassociating from AP"); + brcmf_link_down(cfg_priv); + + /* Make sure WPA_Supplicant receives all the event + generated due to DISASSOC call to the fw to keep + the state fw and WPA_Supplicant state consistent + */ + brcmf_delay(500); + } + + set_bit(WL_STATUS_SCAN_ABORTING, &cfg_priv->status); + brcmf_term_iscan(cfg_priv); + if (cfg_priv->scan_request) { + cfg80211_scan_done(cfg_priv->scan_request, true); + /* May need to perform this to cover rmmod */ + /* wl_set_mpc(cfg_to_ndev(wl), 1); */ + cfg_priv->scan_request = NULL; + } + clear_bit(WL_STATUS_READY, &cfg_priv->status); + clear_bit(WL_STATUS_SCANNING, &cfg_priv->status); + clear_bit(WL_STATUS_SCAN_ABORTING, &cfg_priv->status); + + brcmf_debugfs_remove_netdev(cfg_priv); + + return 0; +} + +s32 brcmf_cfg80211_up(struct brcmf_cfg80211_dev *cfg_dev) +{ + struct brcmf_cfg80211_priv *cfg_priv; + s32 err = 0; + + cfg_priv = brcmf_priv_get(cfg_dev); + mutex_lock(&cfg_priv->usr_sync); + err = __brcmf_cfg80211_up(cfg_priv); + mutex_unlock(&cfg_priv->usr_sync); + + return err; +} + +s32 brcmf_cfg80211_down(struct brcmf_cfg80211_dev *cfg_dev) +{ + struct brcmf_cfg80211_priv *cfg_priv; + s32 err = 0; + + cfg_priv = brcmf_priv_get(cfg_dev); + mutex_lock(&cfg_priv->usr_sync); + err = __brcmf_cfg80211_down(cfg_priv); + mutex_unlock(&cfg_priv->usr_sync); + + return err; +} + +static __used s32 brcmf_add_ie(struct brcmf_cfg80211_priv *cfg_priv, + u8 t, u8 l, u8 *v) +{ + struct brcmf_cfg80211_ie *ie = &cfg_priv->ie; + s32 err = 0; + + if (ie->offset + l + 2 > WL_TLV_INFO_MAX) { + WL_ERR("ei crosses buffer boundary\n"); + return -ENOSPC; + } + ie->buf[ie->offset] = t; + ie->buf[ie->offset + 1] = l; + memcpy(&ie->buf[ie->offset + 2], v, l); + ie->offset += l + 2; + + return err; +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h new file mode 100644 index 000000000000..e69f4f6bf946 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _wl_cfg80211_h_ +#define _wl_cfg80211_h_ + +struct brcmf_cfg80211_conf; +struct brcmf_cfg80211_iface; +struct brcmf_cfg80211_priv; +struct brcmf_cfg80211_security; +struct brcmf_cfg80211_ibss; + +#define WL_DBG_NONE 0 +#define WL_DBG_CONN (1 << 5) +#define WL_DBG_SCAN (1 << 4) +#define WL_DBG_TRACE (1 << 3) +#define WL_DBG_INFO (1 << 1) +#define WL_DBG_ERR (1 << 0) +#define WL_DBG_MASK ((WL_DBG_INFO | WL_DBG_ERR | WL_DBG_TRACE) | \ + (WL_DBG_SCAN) | (WL_DBG_CONN)) + +#define WL_ERR(fmt, args...) \ +do { \ + if (brcmf_dbg_level & WL_DBG_ERR) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "ERROR @%s : " fmt, \ + __func__, ##args); \ + } \ + } \ +} while (0) + +#if (defined BCMDBG) +#define WL_INFO(fmt, args...) \ +do { \ + if (brcmf_dbg_level & WL_DBG_INFO) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "INFO @%s : " fmt, \ + __func__, ##args); \ + } \ + } \ +} while (0) + +#define WL_TRACE(fmt, args...) \ +do { \ + if (brcmf_dbg_level & WL_DBG_TRACE) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "TRACE @%s : " fmt, \ + __func__, ##args); \ + } \ + } \ +} while (0) + +#define WL_SCAN(fmt, args...) \ +do { \ + if (brcmf_dbg_level & WL_DBG_SCAN) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "SCAN @%s : " fmt, \ + __func__, ##args); \ + } \ + } \ +} while (0) + +#define WL_CONN(fmt, args...) \ +do { \ + if (brcmf_dbg_level & WL_DBG_CONN) { \ + if (net_ratelimit()) { \ + printk(KERN_ERR "CONN @%s : " fmt, \ + __func__, ##args); \ + } \ + } \ +} while (0) + +#else /* (defined BCMDBG) */ +#define WL_INFO(fmt, args...) +#define WL_TRACE(fmt, args...) +#define WL_SCAN(fmt, args...) +#define WL_CONN(fmt, args...) +#endif /* (defined BCMDBG) */ + +#define WL_NUM_SCAN_MAX 1 +#define WL_NUM_PMKIDS_MAX MAXPMKID /* will be used + * for 2.6.33 kernel + * or later + */ +#define WL_SCAN_BUF_MAX (1024 * 8) +#define WL_TLV_INFO_MAX 1024 +#define WL_BSS_INFO_MAX 2048 +#define WL_ASSOC_INFO_MAX 512 /* + * needs to grab assoc info from dongle to + * report it to cfg80211 through "connect" + * event + */ +#define WL_DCMD_LEN_MAX 1024 +#define WL_EXTRA_BUF_MAX 2048 +#define WL_ISCAN_BUF_MAX 2048 /* + * the buf length can be BRCMF_DCMD_MAXLEN + * to reduce iteration + */ +#define WL_ISCAN_TIMER_INTERVAL_MS 3000 +#define WL_SCAN_ERSULTS_LAST (BRCMF_SCAN_RESULTS_NO_MEM+1) +#define WL_AP_MAX 256 /* virtually unlimitted as long + * as kernel memory allows + */ + +#define WL_ROAM_TRIGGER_LEVEL -75 +#define WL_ROAM_DELTA 20 +#define WL_BEACON_TIMEOUT 3 + +#define WL_SCAN_CHANNEL_TIME 40 +#define WL_SCAN_UNASSOC_TIME 40 +#define WL_SCAN_PASSIVE_TIME 120 + +/* dongle status */ +enum wl_status { + WL_STATUS_READY, + WL_STATUS_SCANNING, + WL_STATUS_SCAN_ABORTING, + WL_STATUS_CONNECTING, + WL_STATUS_CONNECTED +}; + +/* wi-fi mode */ +enum wl_mode { + WL_MODE_BSS, + WL_MODE_IBSS, + WL_MODE_AP +}; + +/* dongle profile list */ +enum wl_prof_list { + WL_PROF_MODE, + WL_PROF_SSID, + WL_PROF_SEC, + WL_PROF_IBSS, + WL_PROF_BAND, + WL_PROF_BSSID, + WL_PROF_ACT, + WL_PROF_BEACONINT, + WL_PROF_DTIMPERIOD +}; + +/* dongle iscan state */ +enum wl_iscan_state { + WL_ISCAN_STATE_IDLE, + WL_ISCAN_STATE_SCANING +}; + +/* dongle configuration */ +struct brcmf_cfg80211_conf { + u32 mode; /* adhoc , infrastructure or ap */ + u32 frag_threshold; + u32 rts_threshold; + u32 retry_short; + u32 retry_long; + s32 tx_power; + struct ieee80211_channel channel; +}; + +/* cfg80211 main event loop */ +struct brcmf_cfg80211_event_loop { + s32(*handler[BRCMF_E_LAST]) (struct brcmf_cfg80211_priv *cfg_priv, + struct net_device *ndev, + const struct brcmf_event_msg *e, + void *data); +}; + +/* representing interface of cfg80211 plane */ +struct brcmf_cfg80211_iface { + struct brcmf_cfg80211_priv *cfg_priv; +}; + +struct brcmf_cfg80211_dev { + void *driver_data; /* to store cfg80211 object information */ +}; + +/* basic structure of scan request */ +struct brcmf_cfg80211_scan_req { + struct brcmf_ssid_le ssid_le; +}; + +/* basic structure of information element */ +struct brcmf_cfg80211_ie { + u16 offset; + u8 buf[WL_TLV_INFO_MAX]; +}; + +/* event queue for cfg80211 main event */ +struct brcmf_cfg80211_event_q { + struct list_head evt_q_list; + u32 etype; + struct brcmf_event_msg emsg; + s8 edata[1]; +}; + +/* security information with currently associated ap */ +struct brcmf_cfg80211_security { + u32 wpa_versions; + u32 auth_type; + u32 cipher_pairwise; + u32 cipher_group; + u32 wpa_auth; +}; + +/* ibss information for currently joined ibss network */ +struct brcmf_cfg80211_ibss { + u8 beacon_interval; /* in millisecond */ + u8 atim; /* in millisecond */ + s8 join_only; + u8 band; + u8 channel; +}; + +/* dongle profile */ +struct brcmf_cfg80211_profile { + u32 mode; + struct brcmf_ssid ssid; + u8 bssid[ETH_ALEN]; + u16 beacon_interval; + u8 dtim_period; + struct brcmf_cfg80211_security sec; + struct brcmf_cfg80211_ibss ibss; + s32 band; +}; + +/* dongle iscan event loop */ +struct brcmf_cfg80211_iscan_eloop { + s32 (*handler[WL_SCAN_ERSULTS_LAST]) + (struct brcmf_cfg80211_priv *cfg_priv); +}; + +/* dongle iscan controller */ +struct brcmf_cfg80211_iscan_ctrl { + struct net_device *ndev; + struct timer_list timer; + u32 timer_ms; + u32 timer_on; + s32 state; + struct work_struct work; + struct brcmf_cfg80211_iscan_eloop el; + void *data; + s8 dcmd_buf[BRCMF_DCMD_SMLEN]; + s8 scan_buf[WL_ISCAN_BUF_MAX]; +}; + +/* association inform */ +struct brcmf_cfg80211_connect_info { + u8 *req_ie; + s32 req_ie_len; + u8 *resp_ie; + s32 resp_ie_len; +}; + +/* assoc ie length */ +struct brcmf_cfg80211_assoc_ielen { + u32 req_len; + u32 resp_len; +}; + +/* wpa2 pmk list */ +struct brcmf_cfg80211_pmk_list { + struct pmkid_list pmkids; + struct pmkid foo[MAXPMKID - 1]; +}; + +/* dongle private data of cfg80211 interface */ +struct brcmf_cfg80211_priv { + struct wireless_dev *wdev; /* representing wl cfg80211 device */ + struct brcmf_cfg80211_conf *conf; /* dongle configuration */ + struct cfg80211_scan_request *scan_request; /* scan request + object */ + struct brcmf_cfg80211_event_loop el; /* main event loop */ + struct list_head evt_q_list; /* used for event queue */ + spinlock_t evt_q_lock; /* for event queue synchronization */ + struct mutex usr_sync; /* maily for dongle up/down synchronization */ + struct brcmf_scan_results *bss_list; /* bss_list holding scanned + ap information */ + struct brcmf_scan_results *scan_results; + struct brcmf_cfg80211_scan_req *scan_req_int; /* scan request object + for internal purpose */ + struct wl_cfg80211_bss_info *bss_info; /* bss information for + cfg80211 layer */ + struct brcmf_cfg80211_ie ie; /* information element object for + internal purpose */ + struct brcmf_cfg80211_profile *profile; /* holding dongle profile */ + struct brcmf_cfg80211_iscan_ctrl *iscan; /* iscan controller */ + struct brcmf_cfg80211_connect_info conn_info; /* association info */ + struct brcmf_cfg80211_pmk_list *pmk_list; /* wpa2 pmk list */ + struct work_struct event_work; /* event handler work struct */ + unsigned long status; /* current dongle status */ + void *pub; + u32 channel; /* current channel */ + bool iscan_on; /* iscan on/off switch */ + bool iscan_kickstart; /* indicate iscan already started */ + bool active_scan; /* current scan mode */ + bool ibss_starter; /* indicates this sta is ibss starter */ + bool link_up; /* link/connection up flag */ + bool pwr_save; /* indicate whether dongle to support + power save mode */ + bool dongle_up; /* indicate whether dongle up or not */ + bool roam_on; /* on/off switch for dongle self-roaming */ + bool scan_tried; /* indicates if first scan attempted */ + u8 *dcmd_buf; /* dcmd buffer */ + u8 *extra_buf; /* maily to grab assoc information */ + struct dentry *debugfsdir; + u8 ci[0] __aligned(NETDEV_ALIGN); +}; + +static inline struct wiphy *cfg_to_wiphy(struct brcmf_cfg80211_priv *w) +{ + return w->wdev->wiphy; +} + +static inline struct brcmf_cfg80211_priv *wiphy_to_cfg(struct wiphy *w) +{ + return (struct brcmf_cfg80211_priv *)(wiphy_priv(w)); +} + +static inline struct brcmf_cfg80211_priv *wdev_to_cfg(struct wireless_dev *wd) +{ + return (struct brcmf_cfg80211_priv *)(wdev_priv(wd)); +} + +static inline struct net_device *cfg_to_ndev(struct brcmf_cfg80211_priv *cfg) +{ + return cfg->wdev->netdev; +} + +static inline struct brcmf_cfg80211_priv *ndev_to_cfg(struct net_device *ndev) +{ + return wdev_to_cfg(ndev->ieee80211_ptr); +} + +#define iscan_to_cfg(i) ((struct brcmf_cfg80211_priv *)(i->data)) +#define cfg_to_iscan(w) (w->iscan) + +static inline struct +brcmf_cfg80211_connect_info *cfg_to_conn(struct brcmf_cfg80211_priv *cfg) +{ + return &cfg->conn_info; +} + +static inline struct brcmf_bss_info *next_bss(struct brcmf_scan_results *list, + struct brcmf_bss_info *bss) +{ + return bss = bss ? + (struct brcmf_bss_info *)((unsigned long)bss + + le32_to_cpu(bss->length)) : + list->bss_info; +} + +extern struct brcmf_cfg80211_dev *brcmf_cfg80211_attach(struct net_device *ndev, + struct device *busdev, + void *data); +extern void brcmf_cfg80211_detach(struct brcmf_cfg80211_dev *cfg); + +/* event handler from dongle */ +extern void brcmf_cfg80211_event(struct net_device *ndev, + const struct brcmf_event_msg *e, void *data); +extern s32 brcmf_cfg80211_up(struct brcmf_cfg80211_dev *cfg_dev); +extern s32 brcmf_cfg80211_down(struct brcmf_cfg80211_dev *cfg_dev); + +#endif /* _wl_cfg80211_h_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/Makefile b/drivers/net/wireless/brcm80211/brcmsmac/Makefile new file mode 100644 index 000000000000..c2eb2d0af386 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/Makefile @@ -0,0 +1,51 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ccflags-y := \ + -D__CHECK_ENDIAN__ \ + -Idrivers/net/wireless/brcm80211/brcmsmac \ + -Idrivers/net/wireless/brcm80211/brcmsmac/phy \ + -Idrivers/net/wireless/brcm80211/include + +BRCMSMAC_OFILES := \ + mac80211_if.o \ + ucode_loader.o \ + ampdu.o \ + antsel.o \ + channel.o \ + main.o \ + phy_shim.o \ + pmu.o \ + rate.o \ + stf.o \ + aiutils.o \ + phy/phy_cmn.o \ + phy/phy_lcn.o \ + phy/phy_n.o \ + phy/phytbl_lcn.o \ + phy/phytbl_n.o \ + phy/phy_qmath.o \ + otp.o \ + srom.o \ + dma.o \ + nicpci.o \ + brcms_trace_events.o + +MODULEPFX := brcmsmac + +obj-$(CONFIG_BRCMSMAC) += $(MODULEPFX).o +$(MODULEPFX)-objs = $(BRCMSMAC_OFILES) diff --git a/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c new file mode 100644 index 000000000000..025fa0eb6f47 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c @@ -0,0 +1,2079 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * File contents: support functions for PCI/PCIe + */ + +#include <linux/delay.h> +#include <linux/pci.h> + +#include <defs.h> +#include <chipcommon.h> +#include <brcmu_utils.h> +#include <brcm_hw_ids.h> +#include <soc.h> +#include "types.h" +#include "pub.h" +#include "pmu.h" +#include "srom.h" +#include "nicpci.h" +#include "aiutils.h" + +/* slow_clk_ctl */ + /* slow clock source mask */ +#define SCC_SS_MASK 0x00000007 + /* source of slow clock is LPO */ +#define SCC_SS_LPO 0x00000000 + /* source of slow clock is crystal */ +#define SCC_SS_XTAL 0x00000001 + /* source of slow clock is PCI */ +#define SCC_SS_PCI 0x00000002 + /* LPOFreqSel, 1: 160Khz, 0: 32KHz */ +#define SCC_LF 0x00000200 + /* LPOPowerDown, 1: LPO is disabled, 0: LPO is enabled */ +#define SCC_LP 0x00000400 + /* ForceSlowClk, 1: sb/cores running on slow clock, 0: power logic control */ +#define SCC_FS 0x00000800 + /* IgnorePllOffReq, 1/0: + * power logic ignores/honors PLL clock disable requests from core + */ +#define SCC_IP 0x00001000 + /* XtalControlEn, 1/0: + * power logic does/doesn't disable crystal when appropriate + */ +#define SCC_XC 0x00002000 + /* XtalPU (RO), 1/0: crystal running/disabled */ +#define SCC_XP 0x00004000 + /* ClockDivider (SlowClk = 1/(4+divisor)) */ +#define SCC_CD_MASK 0xffff0000 +#define SCC_CD_SHIFT 16 + +/* system_clk_ctl */ + /* ILPen: Enable Idle Low Power */ +#define SYCC_IE 0x00000001 + /* ALPen: Enable Active Low Power */ +#define SYCC_AE 0x00000002 + /* ForcePLLOn */ +#define SYCC_FP 0x00000004 + /* Force ALP (or HT if ALPen is not set */ +#define SYCC_AR 0x00000008 + /* Force HT */ +#define SYCC_HR 0x00000010 + /* ClkDiv (ILP = 1/(4 * (divisor + 1)) */ +#define SYCC_CD_MASK 0xffff0000 +#define SYCC_CD_SHIFT 16 + +#define CST4329_SPROM_OTP_SEL_MASK 0x00000003 + /* OTP is powered up, use def. CIS, no SPROM */ +#define CST4329_DEFCIS_SEL 0 + /* OTP is powered up, SPROM is present */ +#define CST4329_SPROM_SEL 1 + /* OTP is powered up, no SPROM */ +#define CST4329_OTP_SEL 2 + /* OTP is powered down, SPROM is present */ +#define CST4329_OTP_PWRDN 3 + +#define CST4329_SPI_SDIO_MODE_MASK 0x00000004 +#define CST4329_SPI_SDIO_MODE_SHIFT 2 + +/* 43224 chip-specific ChipControl register bits */ +#define CCTRL43224_GPIO_TOGGLE 0x8000 + /* 12 mA drive strength */ +#define CCTRL_43224A0_12MA_LED_DRIVE 0x00F000F0 + /* 12 mA drive strength for later 43224s */ +#define CCTRL_43224B0_12MA_LED_DRIVE 0xF0 + +/* 43236 Chip specific ChipStatus register bits */ +#define CST43236_SFLASH_MASK 0x00000040 +#define CST43236_OTP_MASK 0x00000080 +#define CST43236_HSIC_MASK 0x00000100 /* USB/HSIC */ +#define CST43236_BP_CLK 0x00000200 /* 120/96Mbps */ +#define CST43236_BOOT_MASK 0x00001800 +#define CST43236_BOOT_SHIFT 11 +#define CST43236_BOOT_FROM_SRAM 0 /* boot from SRAM, ARM in reset */ +#define CST43236_BOOT_FROM_ROM 1 /* boot from ROM */ +#define CST43236_BOOT_FROM_FLASH 2 /* boot from FLASH */ +#define CST43236_BOOT_FROM_INVALID 3 + +/* 4331 chip-specific ChipControl register bits */ + /* 0 disable */ +#define CCTRL4331_BT_COEXIST (1<<0) + /* 0 SECI is disabled (JTAG functional) */ +#define CCTRL4331_SECI (1<<1) + /* 0 disable */ +#define CCTRL4331_EXT_LNA (1<<2) + /* sprom/gpio13-15 mux */ +#define CCTRL4331_SPROM_GPIO13_15 (1<<3) + /* 0 ext pa disable, 1 ext pa enabled */ +#define CCTRL4331_EXTPA_EN (1<<4) + /* set drive out GPIO_CLK on sprom_cs pin */ +#define CCTRL4331_GPIOCLK_ON_SPROMCS (1<<5) + /* use sprom_cs pin as PCIE mdio interface */ +#define CCTRL4331_PCIE_MDIO_ON_SPROMCS (1<<6) + /* aband extpa will be at gpio2/5 and sprom_dout */ +#define CCTRL4331_EXTPA_ON_GPIO2_5 (1<<7) + /* override core control on pipe_AuxClkEnable */ +#define CCTRL4331_OVR_PIPEAUXCLKEN (1<<8) + /* override core control on pipe_AuxPowerDown */ +#define CCTRL4331_OVR_PIPEAUXPWRDOWN (1<<9) + /* pcie_auxclkenable */ +#define CCTRL4331_PCIE_AUXCLKEN (1<<10) + /* pcie_pipe_pllpowerdown */ +#define CCTRL4331_PCIE_PIPE_PLLDOWN (1<<11) + /* enable bt_shd0 at gpio4 */ +#define CCTRL4331_BT_SHD0_ON_GPIO4 (1<<16) + /* enable bt_shd1 at gpio5 */ +#define CCTRL4331_BT_SHD1_ON_GPIO5 (1<<17) + +/* 4331 Chip specific ChipStatus register bits */ + /* crystal frequency 20/40Mhz */ +#define CST4331_XTAL_FREQ 0x00000001 +#define CST4331_SPROM_PRESENT 0x00000002 +#define CST4331_OTP_PRESENT 0x00000004 +#define CST4331_LDO_RF 0x00000008 +#define CST4331_LDO_PAR 0x00000010 + +/* 4319 chip-specific ChipStatus register bits */ +#define CST4319_SPI_CPULESSUSB 0x00000001 +#define CST4319_SPI_CLK_POL 0x00000002 +#define CST4319_SPI_CLK_PH 0x00000008 + /* gpio [7:6], SDIO CIS selection */ +#define CST4319_SPROM_OTP_SEL_MASK 0x000000c0 +#define CST4319_SPROM_OTP_SEL_SHIFT 6 + /* use default CIS, OTP is powered up */ +#define CST4319_DEFCIS_SEL 0x00000000 + /* use SPROM, OTP is powered up */ +#define CST4319_SPROM_SEL 0x00000040 + /* use OTP, OTP is powered up */ +#define CST4319_OTP_SEL 0x00000080 + /* use SPROM, OTP is powered down */ +#define CST4319_OTP_PWRDN 0x000000c0 + /* gpio [8], sdio/usb mode */ +#define CST4319_SDIO_USB_MODE 0x00000100 +#define CST4319_REMAP_SEL_MASK 0x00000600 +#define CST4319_ILPDIV_EN 0x00000800 +#define CST4319_XTAL_PD_POL 0x00001000 +#define CST4319_LPO_SEL 0x00002000 +#define CST4319_RES_INIT_MODE 0x0000c000 + /* PALDO is configured with external PNP */ +#define CST4319_PALDO_EXTPNP 0x00010000 +#define CST4319_CBUCK_MODE_MASK 0x00060000 +#define CST4319_CBUCK_MODE_BURST 0x00020000 +#define CST4319_CBUCK_MODE_LPBURST 0x00060000 +#define CST4319_RCAL_VALID 0x01000000 +#define CST4319_RCAL_VALUE_MASK 0x3e000000 +#define CST4319_RCAL_VALUE_SHIFT 25 + +/* 4336 chip-specific ChipStatus register bits */ +#define CST4336_SPI_MODE_MASK 0x00000001 +#define CST4336_SPROM_PRESENT 0x00000002 +#define CST4336_OTP_PRESENT 0x00000004 +#define CST4336_ARMREMAP_0 0x00000008 +#define CST4336_ILPDIV_EN_MASK 0x00000010 +#define CST4336_ILPDIV_EN_SHIFT 4 +#define CST4336_XTAL_PD_POL_MASK 0x00000020 +#define CST4336_XTAL_PD_POL_SHIFT 5 +#define CST4336_LPO_SEL_MASK 0x00000040 +#define CST4336_LPO_SEL_SHIFT 6 +#define CST4336_RES_INIT_MODE_MASK 0x00000180 +#define CST4336_RES_INIT_MODE_SHIFT 7 +#define CST4336_CBUCK_MODE_MASK 0x00000600 +#define CST4336_CBUCK_MODE_SHIFT 9 + +/* 4313 chip-specific ChipStatus register bits */ +#define CST4313_SPROM_PRESENT 1 +#define CST4313_OTP_PRESENT 2 +#define CST4313_SPROM_OTP_SEL_MASK 0x00000002 +#define CST4313_SPROM_OTP_SEL_SHIFT 0 + +/* 4313 Chip specific ChipControl register bits */ + /* 12 mA drive strengh for later 4313 */ +#define CCTRL_4313_12MA_LED_DRIVE 0x00000007 + +/* Manufacturer Ids */ +#define MFGID_ARM 0x43b +#define MFGID_BRCM 0x4bf +#define MFGID_MIPS 0x4a7 + +/* Enumeration ROM registers */ +#define ER_EROMENTRY 0x000 +#define ER_REMAPCONTROL 0xe00 +#define ER_REMAPSELECT 0xe04 +#define ER_MASTERSELECT 0xe10 +#define ER_ITCR 0xf00 +#define ER_ITIP 0xf04 + +/* Erom entries */ +#define ER_TAG 0xe +#define ER_TAG1 0x6 +#define ER_VALID 1 +#define ER_CI 0 +#define ER_MP 2 +#define ER_ADD 4 +#define ER_END 0xe +#define ER_BAD 0xffffffff + +/* EROM CompIdentA */ +#define CIA_MFG_MASK 0xfff00000 +#define CIA_MFG_SHIFT 20 +#define CIA_CID_MASK 0x000fff00 +#define CIA_CID_SHIFT 8 +#define CIA_CCL_MASK 0x000000f0 +#define CIA_CCL_SHIFT 4 + +/* EROM CompIdentB */ +#define CIB_REV_MASK 0xff000000 +#define CIB_REV_SHIFT 24 +#define CIB_NSW_MASK 0x00f80000 +#define CIB_NSW_SHIFT 19 +#define CIB_NMW_MASK 0x0007c000 +#define CIB_NMW_SHIFT 14 +#define CIB_NSP_MASK 0x00003e00 +#define CIB_NSP_SHIFT 9 +#define CIB_NMP_MASK 0x000001f0 +#define CIB_NMP_SHIFT 4 + +/* EROM AddrDesc */ +#define AD_ADDR_MASK 0xfffff000 +#define AD_SP_MASK 0x00000f00 +#define AD_SP_SHIFT 8 +#define AD_ST_MASK 0x000000c0 +#define AD_ST_SHIFT 6 +#define AD_ST_SLAVE 0x00000000 +#define AD_ST_BRIDGE 0x00000040 +#define AD_ST_SWRAP 0x00000080 +#define AD_ST_MWRAP 0x000000c0 +#define AD_SZ_MASK 0x00000030 +#define AD_SZ_SHIFT 4 +#define AD_SZ_4K 0x00000000 +#define AD_SZ_8K 0x00000010 +#define AD_SZ_16K 0x00000020 +#define AD_SZ_SZD 0x00000030 +#define AD_AG32 0x00000008 +#define AD_ADDR_ALIGN 0x00000fff +#define AD_SZ_BASE 0x00001000 /* 4KB */ + +/* EROM SizeDesc */ +#define SD_SZ_MASK 0xfffff000 +#define SD_SG32 0x00000008 +#define SD_SZ_ALIGN 0x00000fff + +/* PCI config space bit 4 for 4306c0 slow clock source */ +#define PCI_CFG_GPIO_SCS 0x10 +/* PCI config space GPIO 14 for Xtal power-up */ +#define PCI_CFG_GPIO_XTAL 0x40 +/* PCI config space GPIO 15 for PLL power-down */ +#define PCI_CFG_GPIO_PLL 0x80 + +/* power control defines */ +#define PLL_DELAY 150 /* us pll on delay */ +#define FREF_DELAY 200 /* us fref change delay */ +#define XTAL_ON_DELAY 1000 /* us crystal power-on delay */ + +/* resetctrl */ +#define AIRC_RESET 1 + +#define NOREV -1 /* Invalid rev */ + +/* GPIO Based LED powersave defines */ +#define DEFAULT_GPIO_ONTIME 10 /* Default: 10% on */ +#define DEFAULT_GPIO_OFFTIME 90 /* Default: 10% on */ + +/* When Srom support present, fields in sromcontrol */ +#define SRC_START 0x80000000 +#define SRC_BUSY 0x80000000 +#define SRC_OPCODE 0x60000000 +#define SRC_OP_READ 0x00000000 +#define SRC_OP_WRITE 0x20000000 +#define SRC_OP_WRDIS 0x40000000 +#define SRC_OP_WREN 0x60000000 +#define SRC_OTPSEL 0x00000010 +#define SRC_LOCK 0x00000008 +#define SRC_SIZE_MASK 0x00000006 +#define SRC_SIZE_1K 0x00000000 +#define SRC_SIZE_4K 0x00000002 +#define SRC_SIZE_16K 0x00000004 +#define SRC_SIZE_SHIFT 1 +#define SRC_PRESENT 0x00000001 + +/* External PA enable mask */ +#define GPIO_CTRL_EPA_EN_MASK 0x40 + +#define DEFAULT_GPIOTIMERVAL \ + ((DEFAULT_GPIO_ONTIME << GPIO_ONTIME_SHIFT) | DEFAULT_GPIO_OFFTIME) + +#define BADIDX (SI_MAXCORES + 1) + +/* Newer chips can access PCI/PCIE and CC core without requiring to change + * PCI BAR0 WIN + */ +#define SI_FAST(si) (((si)->pub.buscoretype == PCIE_CORE_ID) || \ + (((si)->pub.buscoretype == PCI_CORE_ID) && \ + (si)->pub.buscorerev >= 13)) + +#define CCREGS_FAST(si) (((char __iomem *)((si)->curmap) + \ + PCI_16KB0_CCREGS_OFFSET)) + +#define IS_SIM(chippkg) \ + ((chippkg == HDLSIM_PKG_ID) || (chippkg == HWSIM_PKG_ID)) + +/* + * Macros to disable/restore function core(D11, ENET, ILINE20, etc) interrupts + * before after core switching to avoid invalid register accesss inside ISR. + */ +#define INTR_OFF(si, intr_val) \ + if ((si)->intrsoff_fn && \ + (si)->coreid[(si)->curidx] == (si)->dev_coreid) \ + intr_val = (*(si)->intrsoff_fn)((si)->intr_arg) + +#define INTR_RESTORE(si, intr_val) \ + if ((si)->intrsrestore_fn && \ + (si)->coreid[(si)->curidx] == (si)->dev_coreid) \ + (*(si)->intrsrestore_fn)((si)->intr_arg, intr_val) + +#define PCI(si) ((si)->pub.buscoretype == PCI_CORE_ID) +#define PCIE(si) ((si)->pub.buscoretype == PCIE_CORE_ID) + +#define PCI_FORCEHT(si) (PCIE(si) && (si->pub.chip == BCM4716_CHIP_ID)) + +#ifdef BCMDBG +#define SI_MSG(args) printk args +#else +#define SI_MSG(args) +#endif /* BCMDBG */ + +#define GOODCOREADDR(x, b) \ + (((x) >= (b)) && ((x) < ((b) + SI_MAXCORES * SI_CORE_SIZE)) && \ + IS_ALIGNED((x), SI_CORE_SIZE)) + +#define PCIEREGS(si) ((__iomem char *)((si)->curmap) + \ + PCI_16KB0_PCIREGS_OFFSET) + +struct aidmp { + u32 oobselina30; /* 0x000 */ + u32 oobselina74; /* 0x004 */ + u32 PAD[6]; + u32 oobselinb30; /* 0x020 */ + u32 oobselinb74; /* 0x024 */ + u32 PAD[6]; + u32 oobselinc30; /* 0x040 */ + u32 oobselinc74; /* 0x044 */ + u32 PAD[6]; + u32 oobselind30; /* 0x060 */ + u32 oobselind74; /* 0x064 */ + u32 PAD[38]; + u32 oobselouta30; /* 0x100 */ + u32 oobselouta74; /* 0x104 */ + u32 PAD[6]; + u32 oobseloutb30; /* 0x120 */ + u32 oobseloutb74; /* 0x124 */ + u32 PAD[6]; + u32 oobseloutc30; /* 0x140 */ + u32 oobseloutc74; /* 0x144 */ + u32 PAD[6]; + u32 oobseloutd30; /* 0x160 */ + u32 oobseloutd74; /* 0x164 */ + u32 PAD[38]; + u32 oobsynca; /* 0x200 */ + u32 oobseloutaen; /* 0x204 */ + u32 PAD[6]; + u32 oobsyncb; /* 0x220 */ + u32 oobseloutben; /* 0x224 */ + u32 PAD[6]; + u32 oobsyncc; /* 0x240 */ + u32 oobseloutcen; /* 0x244 */ + u32 PAD[6]; + u32 oobsyncd; /* 0x260 */ + u32 oobseloutden; /* 0x264 */ + u32 PAD[38]; + u32 oobaextwidth; /* 0x300 */ + u32 oobainwidth; /* 0x304 */ + u32 oobaoutwidth; /* 0x308 */ + u32 PAD[5]; + u32 oobbextwidth; /* 0x320 */ + u32 oobbinwidth; /* 0x324 */ + u32 oobboutwidth; /* 0x328 */ + u32 PAD[5]; + u32 oobcextwidth; /* 0x340 */ + u32 oobcinwidth; /* 0x344 */ + u32 oobcoutwidth; /* 0x348 */ + u32 PAD[5]; + u32 oobdextwidth; /* 0x360 */ + u32 oobdinwidth; /* 0x364 */ + u32 oobdoutwidth; /* 0x368 */ + u32 PAD[37]; + u32 ioctrlset; /* 0x400 */ + u32 ioctrlclear; /* 0x404 */ + u32 ioctrl; /* 0x408 */ + u32 PAD[61]; + u32 iostatus; /* 0x500 */ + u32 PAD[127]; + u32 ioctrlwidth; /* 0x700 */ + u32 iostatuswidth; /* 0x704 */ + u32 PAD[62]; + u32 resetctrl; /* 0x800 */ + u32 resetstatus; /* 0x804 */ + u32 resetreadid; /* 0x808 */ + u32 resetwriteid; /* 0x80c */ + u32 PAD[60]; + u32 errlogctrl; /* 0x900 */ + u32 errlogdone; /* 0x904 */ + u32 errlogstatus; /* 0x908 */ + u32 errlogaddrlo; /* 0x90c */ + u32 errlogaddrhi; /* 0x910 */ + u32 errlogid; /* 0x914 */ + u32 errloguser; /* 0x918 */ + u32 errlogflags; /* 0x91c */ + u32 PAD[56]; + u32 intstatus; /* 0xa00 */ + u32 PAD[127]; + u32 config; /* 0xe00 */ + u32 PAD[63]; + u32 itcr; /* 0xf00 */ + u32 PAD[3]; + u32 itipooba; /* 0xf10 */ + u32 itipoobb; /* 0xf14 */ + u32 itipoobc; /* 0xf18 */ + u32 itipoobd; /* 0xf1c */ + u32 PAD[4]; + u32 itipoobaout; /* 0xf30 */ + u32 itipoobbout; /* 0xf34 */ + u32 itipoobcout; /* 0xf38 */ + u32 itipoobdout; /* 0xf3c */ + u32 PAD[4]; + u32 itopooba; /* 0xf50 */ + u32 itopoobb; /* 0xf54 */ + u32 itopoobc; /* 0xf58 */ + u32 itopoobd; /* 0xf5c */ + u32 PAD[4]; + u32 itopoobain; /* 0xf70 */ + u32 itopoobbin; /* 0xf74 */ + u32 itopoobcin; /* 0xf78 */ + u32 itopoobdin; /* 0xf7c */ + u32 PAD[4]; + u32 itopreset; /* 0xf90 */ + u32 PAD[15]; + u32 peripherialid4; /* 0xfd0 */ + u32 peripherialid5; /* 0xfd4 */ + u32 peripherialid6; /* 0xfd8 */ + u32 peripherialid7; /* 0xfdc */ + u32 peripherialid0; /* 0xfe0 */ + u32 peripherialid1; /* 0xfe4 */ + u32 peripherialid2; /* 0xfe8 */ + u32 peripherialid3; /* 0xfec */ + u32 componentid0; /* 0xff0 */ + u32 componentid1; /* 0xff4 */ + u32 componentid2; /* 0xff8 */ + u32 componentid3; /* 0xffc */ +}; + +/* EROM parsing */ + +static u32 +get_erom_ent(struct si_pub *sih, u32 __iomem **eromptr, u32 mask, u32 match) +{ + u32 ent; + uint inv = 0, nom = 0; + + while (true) { + ent = R_REG(*eromptr); + (*eromptr)++; + + if (mask == 0) + break; + + if ((ent & ER_VALID) == 0) { + inv++; + continue; + } + + if (ent == (ER_END | ER_VALID)) + break; + + if ((ent & mask) == match) + break; + + nom++; + } + + return ent; +} + +static u32 +get_asd(struct si_pub *sih, u32 __iomem **eromptr, uint sp, uint ad, uint st, + u32 *addrl, u32 *addrh, u32 *sizel, u32 *sizeh) +{ + u32 asd, sz, szd; + + asd = get_erom_ent(sih, eromptr, ER_VALID, ER_VALID); + if (((asd & ER_TAG1) != ER_ADD) || + (((asd & AD_SP_MASK) >> AD_SP_SHIFT) != sp) || + ((asd & AD_ST_MASK) != st)) { + /* This is not what we want, "push" it back */ + (*eromptr)--; + return 0; + } + *addrl = asd & AD_ADDR_MASK; + if (asd & AD_AG32) + *addrh = get_erom_ent(sih, eromptr, 0, 0); + else + *addrh = 0; + *sizeh = 0; + sz = asd & AD_SZ_MASK; + if (sz == AD_SZ_SZD) { + szd = get_erom_ent(sih, eromptr, 0, 0); + *sizel = szd & SD_SZ_MASK; + if (szd & SD_SG32) + *sizeh = get_erom_ent(sih, eromptr, 0, 0); + } else + *sizel = AD_SZ_BASE << (sz >> AD_SZ_SHIFT); + + return asd; +} + +static void ai_hwfixup(struct si_info *sii) +{ +} + +/* parse the enumeration rom to identify all cores */ +static void ai_scan(struct si_pub *sih, struct chipcregs __iomem *cc) +{ + struct si_info *sii = (struct si_info *)sih; + + u32 erombase; + u32 __iomem *eromptr, *eromlim; + void __iomem *regs = cc; + + erombase = R_REG(&cc->eromptr); + + /* Set wrappers address */ + sii->curwrap = (void *)((unsigned long)cc + SI_CORE_SIZE); + + /* Now point the window at the erom */ + pci_write_config_dword(sii->pbus, PCI_BAR0_WIN, erombase); + eromptr = regs; + eromlim = eromptr + (ER_REMAPCONTROL / sizeof(u32)); + + while (eromptr < eromlim) { + u32 cia, cib, cid, mfg, crev, nmw, nsw, nmp, nsp; + u32 mpd, asd, addrl, addrh, sizel, sizeh; + u32 __iomem *base; + uint i, j, idx; + bool br; + + br = false; + + /* Grok a component */ + cia = get_erom_ent(sih, &eromptr, ER_TAG, ER_CI); + if (cia == (ER_END | ER_VALID)) { + /* Found END of erom */ + ai_hwfixup(sii); + return; + } + base = eromptr - 1; + cib = get_erom_ent(sih, &eromptr, 0, 0); + + if ((cib & ER_TAG) != ER_CI) { + /* CIA not followed by CIB */ + goto error; + } + + cid = (cia & CIA_CID_MASK) >> CIA_CID_SHIFT; + mfg = (cia & CIA_MFG_MASK) >> CIA_MFG_SHIFT; + crev = (cib & CIB_REV_MASK) >> CIB_REV_SHIFT; + nmw = (cib & CIB_NMW_MASK) >> CIB_NMW_SHIFT; + nsw = (cib & CIB_NSW_MASK) >> CIB_NSW_SHIFT; + nmp = (cib & CIB_NMP_MASK) >> CIB_NMP_SHIFT; + nsp = (cib & CIB_NSP_MASK) >> CIB_NSP_SHIFT; + + if (((mfg == MFGID_ARM) && (cid == DEF_AI_COMP)) || (nsp == 0)) + continue; + if ((nmw + nsw == 0)) { + /* A component which is not a core */ + if (cid == OOB_ROUTER_CORE_ID) { + asd = get_asd(sih, &eromptr, 0, 0, AD_ST_SLAVE, + &addrl, &addrh, &sizel, &sizeh); + if (asd != 0) + sii->oob_router = addrl; + } + continue; + } + + idx = sii->numcores; +/* sii->eromptr[idx] = base; */ + sii->cia[idx] = cia; + sii->cib[idx] = cib; + sii->coreid[idx] = cid; + + for (i = 0; i < nmp; i++) { + mpd = get_erom_ent(sih, &eromptr, ER_VALID, ER_VALID); + if ((mpd & ER_TAG) != ER_MP) { + /* Not enough MP entries for component */ + goto error; + } + } + + /* First Slave Address Descriptor should be port 0: + * the main register space for the core + */ + asd = + get_asd(sih, &eromptr, 0, 0, AD_ST_SLAVE, &addrl, &addrh, + &sizel, &sizeh); + if (asd == 0) { + /* Try again to see if it is a bridge */ + asd = + get_asd(sih, &eromptr, 0, 0, AD_ST_BRIDGE, &addrl, + &addrh, &sizel, &sizeh); + if (asd != 0) + br = true; + else if ((addrh != 0) || (sizeh != 0) + || (sizel != SI_CORE_SIZE)) { + /* First Slave ASD for core malformed */ + goto error; + } + } + sii->coresba[idx] = addrl; + sii->coresba_size[idx] = sizel; + /* Get any more ASDs in port 0 */ + j = 1; + do { + asd = + get_asd(sih, &eromptr, 0, j, AD_ST_SLAVE, &addrl, + &addrh, &sizel, &sizeh); + if ((asd != 0) && (j == 1) && (sizel == SI_CORE_SIZE)) { + sii->coresba2[idx] = addrl; + sii->coresba2_size[idx] = sizel; + } + j++; + } while (asd != 0); + + /* Go through the ASDs for other slave ports */ + for (i = 1; i < nsp; i++) { + j = 0; + do { + asd = + get_asd(sih, &eromptr, i, j++, AD_ST_SLAVE, + &addrl, &addrh, &sizel, &sizeh); + } while (asd != 0); + if (j == 0) { + /* SP has no address descriptors */ + goto error; + } + } + + /* Now get master wrappers */ + for (i = 0; i < nmw; i++) { + asd = + get_asd(sih, &eromptr, i, 0, AD_ST_MWRAP, &addrl, + &addrh, &sizel, &sizeh); + if (asd == 0) { + /* Missing descriptor for MW */ + goto error; + } + if ((sizeh != 0) || (sizel != SI_CORE_SIZE)) { + /* Master wrapper %d is not 4KB */ + goto error; + } + if (i == 0) + sii->wrapba[idx] = addrl; + } + + /* And finally slave wrappers */ + for (i = 0; i < nsw; i++) { + uint fwp = (nsp == 1) ? 0 : 1; + asd = + get_asd(sih, &eromptr, fwp + i, 0, AD_ST_SWRAP, + &addrl, &addrh, &sizel, &sizeh); + if (asd == 0) { + /* Missing descriptor for SW */ + goto error; + } + if ((sizeh != 0) || (sizel != SI_CORE_SIZE)) { + /* Slave wrapper is not 4KB */ + goto error; + } + if ((nmw == 0) && (i == 0)) + sii->wrapba[idx] = addrl; + } + + /* Don't record bridges */ + if (br) + continue; + + /* Done with core */ + sii->numcores++; + } + + error: + /* Reached end of erom without finding END */ + sii->numcores = 0; + return; +} + +/* + * This function changes the logical "focus" to the indicated core. + * Return the current core's virtual address. Since each core starts with the + * same set of registers (BIST, clock control, etc), the returned address + * contains the first register of this 'common' register block (not to be + * confused with 'common core'). + */ +void __iomem *ai_setcoreidx(struct si_pub *sih, uint coreidx) +{ + struct si_info *sii = (struct si_info *)sih; + u32 addr = sii->coresba[coreidx]; + u32 wrap = sii->wrapba[coreidx]; + + if (coreidx >= sii->numcores) + return NULL; + + /* point bar0 window */ + pci_write_config_dword(sii->pbus, PCI_BAR0_WIN, addr); + /* point bar0 2nd 4KB window */ + pci_write_config_dword(sii->pbus, PCI_BAR0_WIN2, wrap); + sii->curidx = coreidx; + + return sii->curmap; +} + +/* Return the number of address spaces in current core */ +int ai_numaddrspaces(struct si_pub *sih) +{ + return 2; +} + +/* Return the address of the nth address space in the current core */ +u32 ai_addrspace(struct si_pub *sih, uint asidx) +{ + struct si_info *sii; + uint cidx; + + sii = (struct si_info *)sih; + cidx = sii->curidx; + + if (asidx == 0) + return sii->coresba[cidx]; + else if (asidx == 1) + return sii->coresba2[cidx]; + else { + /* Need to parse the erom again to find addr space */ + return 0; + } +} + +/* Return the size of the nth address space in the current core */ +u32 ai_addrspacesize(struct si_pub *sih, uint asidx) +{ + struct si_info *sii; + uint cidx; + + sii = (struct si_info *)sih; + cidx = sii->curidx; + + if (asidx == 0) + return sii->coresba_size[cidx]; + else if (asidx == 1) + return sii->coresba2_size[cidx]; + else { + /* Need to parse the erom again to find addr */ + return 0; + } +} + +uint ai_flag(struct si_pub *sih) +{ + struct si_info *sii; + struct aidmp *ai; + + sii = (struct si_info *)sih; + ai = sii->curwrap; + + return R_REG(&ai->oobselouta30) & 0x1f; +} + +void ai_setint(struct si_pub *sih, int siflag) +{ +} + +uint ai_corevendor(struct si_pub *sih) +{ + struct si_info *sii; + u32 cia; + + sii = (struct si_info *)sih; + cia = sii->cia[sii->curidx]; + return (cia & CIA_MFG_MASK) >> CIA_MFG_SHIFT; +} + +uint ai_corerev(struct si_pub *sih) +{ + struct si_info *sii; + u32 cib; + + sii = (struct si_info *)sih; + cib = sii->cib[sii->curidx]; + return (cib & CIB_REV_MASK) >> CIB_REV_SHIFT; +} + +bool ai_iscoreup(struct si_pub *sih) +{ + struct si_info *sii; + struct aidmp *ai; + + sii = (struct si_info *)sih; + ai = sii->curwrap; + + return (((R_REG(&ai->ioctrl) & (SICF_FGC | SICF_CLOCK_EN)) == + SICF_CLOCK_EN) + && ((R_REG(&ai->resetctrl) & AIRC_RESET) == 0)); +} + +void ai_core_cflags_wo(struct si_pub *sih, u32 mask, u32 val) +{ + struct si_info *sii; + struct aidmp *ai; + u32 w; + + sii = (struct si_info *)sih; + + ai = sii->curwrap; + + if (mask || val) { + w = ((R_REG(&ai->ioctrl) & ~mask) | val); + W_REG(&ai->ioctrl, w); + } +} + +u32 ai_core_cflags(struct si_pub *sih, u32 mask, u32 val) +{ + struct si_info *sii; + struct aidmp *ai; + u32 w; + + sii = (struct si_info *)sih; + ai = sii->curwrap; + + if (mask || val) { + w = ((R_REG(&ai->ioctrl) & ~mask) | val); + W_REG(&ai->ioctrl, w); + } + + return R_REG(&ai->ioctrl); +} + +/* return true if PCIE capability exists in the pci config space */ +static bool ai_ispcie(struct si_info *sii) +{ + u8 cap_ptr; + + cap_ptr = + pcicore_find_pci_capability(sii->pbus, PCI_CAP_ID_EXP, NULL, + NULL); + if (!cap_ptr) + return false; + + return true; +} + +static bool ai_buscore_prep(struct si_info *sii) +{ + /* kludge to enable the clock on the 4306 which lacks a slowclock */ + if (!ai_ispcie(sii)) + ai_clkctl_xtal(&sii->pub, XTAL | PLL, ON); + return true; +} + +u32 ai_core_sflags(struct si_pub *sih, u32 mask, u32 val) +{ + struct si_info *sii; + struct aidmp *ai; + u32 w; + + sii = (struct si_info *)sih; + ai = sii->curwrap; + + if (mask || val) { + w = ((R_REG(&ai->iostatus) & ~mask) | val); + W_REG(&ai->iostatus, w); + } + + return R_REG(&ai->iostatus); +} + +static bool +ai_buscore_setup(struct si_info *sii, u32 savewin, uint *origidx) +{ + bool pci, pcie; + uint i; + uint pciidx, pcieidx, pcirev, pcierev; + struct chipcregs __iomem *cc; + + cc = ai_setcoreidx(&sii->pub, SI_CC_IDX); + + /* get chipcommon rev */ + sii->pub.ccrev = (int)ai_corerev(&sii->pub); + + /* get chipcommon chipstatus */ + if (sii->pub.ccrev >= 11) + sii->pub.chipst = R_REG(&cc->chipstatus); + + /* get chipcommon capabilites */ + sii->pub.cccaps = R_REG(&cc->capabilities); + /* get chipcommon extended capabilities */ + + if (sii->pub.ccrev >= 35) + sii->pub.cccaps_ext = R_REG(&cc->capabilities_ext); + + /* get pmu rev and caps */ + if (sii->pub.cccaps & CC_CAP_PMU) { + sii->pub.pmucaps = R_REG(&cc->pmucapabilities); + sii->pub.pmurev = sii->pub.pmucaps & PCAP_REV_MASK; + } + + /* figure out bus/orignal core idx */ + sii->pub.buscoretype = NODEV_CORE_ID; + sii->pub.buscorerev = NOREV; + sii->pub.buscoreidx = BADIDX; + + pci = pcie = false; + pcirev = pcierev = NOREV; + pciidx = pcieidx = BADIDX; + + for (i = 0; i < sii->numcores; i++) { + uint cid, crev; + + ai_setcoreidx(&sii->pub, i); + cid = ai_coreid(&sii->pub); + crev = ai_corerev(&sii->pub); + + if (cid == PCI_CORE_ID) { + pciidx = i; + pcirev = crev; + pci = true; + } else if (cid == PCIE_CORE_ID) { + pcieidx = i; + pcierev = crev; + pcie = true; + } + + /* find the core idx before entering this func. */ + if ((savewin && (savewin == sii->coresba[i])) || + (cc == sii->regs[i])) + *origidx = i; + } + + if (pci && pcie) { + if (ai_ispcie(sii)) + pci = false; + else + pcie = false; + } + if (pci) { + sii->pub.buscoretype = PCI_CORE_ID; + sii->pub.buscorerev = pcirev; + sii->pub.buscoreidx = pciidx; + } else if (pcie) { + sii->pub.buscoretype = PCIE_CORE_ID; + sii->pub.buscorerev = pcierev; + sii->pub.buscoreidx = pcieidx; + } + + /* fixup necessary chip/core configurations */ + if (SI_FAST(sii)) { + if (!sii->pch) { + sii->pch = pcicore_init(&sii->pub, sii->pbus, + (__iomem void *)PCIEREGS(sii)); + if (sii->pch == NULL) + return false; + } + } + if (ai_pci_fixcfg(&sii->pub)) { + /* si_doattach: si_pci_fixcfg failed */ + return false; + } + + /* return to the original core */ + ai_setcoreidx(&sii->pub, *origidx); + + return true; +} + +/* + * get boardtype and boardrev + */ +static __used void ai_nvram_process(struct si_info *sii) +{ + uint w = 0; + + /* do a pci config read to get subsystem id and subvendor id */ + pci_read_config_dword(sii->pbus, PCI_SUBSYSTEM_VENDOR_ID, &w); + + sii->pub.boardvendor = w & 0xffff; + sii->pub.boardtype = (w >> 16) & 0xffff; + sii->pub.boardflags = getintvar(&sii->pub, BRCMS_SROM_BOARDFLAGS); +} + +static struct si_info *ai_doattach(struct si_info *sii, + void __iomem *regs, struct pci_dev *pbus) +{ + struct si_pub *sih = &sii->pub; + u32 w, savewin; + struct chipcregs __iomem *cc; + uint socitype; + uint origidx; + + memset((unsigned char *) sii, 0, sizeof(struct si_info)); + + savewin = 0; + + sih->buscoreidx = BADIDX; + + sii->curmap = regs; + sii->pbus = pbus; + + /* find Chipcommon address */ + pci_read_config_dword(sii->pbus, PCI_BAR0_WIN, &savewin); + if (!GOODCOREADDR(savewin, SI_ENUM_BASE)) + savewin = SI_ENUM_BASE; + + pci_write_config_dword(sii->pbus, PCI_BAR0_WIN, + SI_ENUM_BASE); + cc = (struct chipcregs __iomem *) regs; + + /* bus/core/clk setup for register access */ + if (!ai_buscore_prep(sii)) + return NULL; + + /* + * ChipID recognition. + * We assume we can read chipid at offset 0 from the regs arg. + * If we add other chiptypes (or if we need to support old sdio + * hosts w/o chipcommon), some way of recognizing them needs to + * be added here. + */ + w = R_REG(&cc->chipid); + socitype = (w & CID_TYPE_MASK) >> CID_TYPE_SHIFT; + /* Might as wll fill in chip id rev & pkg */ + sih->chip = w & CID_ID_MASK; + sih->chiprev = (w & CID_REV_MASK) >> CID_REV_SHIFT; + sih->chippkg = (w & CID_PKG_MASK) >> CID_PKG_SHIFT; + + sih->issim = false; + + /* scan for cores */ + if (socitype == SOCI_AI) { + SI_MSG(("Found chip type AI (0x%08x)\n", w)); + /* pass chipc address instead of original core base */ + ai_scan(&sii->pub, cc); + } else { + /* Found chip of unknown type */ + return NULL; + } + /* no cores found, bail out */ + if (sii->numcores == 0) + return NULL; + + /* bus/core/clk setup */ + origidx = SI_CC_IDX; + if (!ai_buscore_setup(sii, savewin, &origidx)) + goto exit; + + /* Init nvram from sprom/otp if they exist */ + if (srom_var_init(&sii->pub, cc)) + goto exit; + + ai_nvram_process(sii); + + /* === NVRAM, clock is ready === */ + cc = (struct chipcregs __iomem *) ai_setcore(sih, CC_CORE_ID, 0); + W_REG(&cc->gpiopullup, 0); + W_REG(&cc->gpiopulldown, 0); + ai_setcoreidx(sih, origidx); + + /* PMU specific initializations */ + if (sih->cccaps & CC_CAP_PMU) { + u32 xtalfreq; + si_pmu_init(sih); + si_pmu_chip_init(sih); + + xtalfreq = si_pmu_measure_alpclk(sih); + si_pmu_pll_init(sih, xtalfreq); + si_pmu_res_init(sih); + si_pmu_swreg_init(sih); + } + + /* setup the GPIO based LED powersave register */ + w = getintvar(sih, BRCMS_SROM_LEDDC); + if (w == 0) + w = DEFAULT_GPIOTIMERVAL; + ai_corereg(sih, SI_CC_IDX, offsetof(struct chipcregs, gpiotimerval), + ~0, w); + + if (PCIE(sii)) + pcicore_attach(sii->pch, SI_DOATTACH); + + if (sih->chip == BCM43224_CHIP_ID) { + /* + * enable 12 mA drive strenth for 43224 and + * set chipControl register bit 15 + */ + if (sih->chiprev == 0) { + SI_MSG(("Applying 43224A0 WARs\n")); + ai_corereg(sih, SI_CC_IDX, + offsetof(struct chipcregs, chipcontrol), + CCTRL43224_GPIO_TOGGLE, + CCTRL43224_GPIO_TOGGLE); + si_pmu_chipcontrol(sih, 0, CCTRL_43224A0_12MA_LED_DRIVE, + CCTRL_43224A0_12MA_LED_DRIVE); + } + if (sih->chiprev >= 1) { + SI_MSG(("Applying 43224B0+ WARs\n")); + si_pmu_chipcontrol(sih, 0, CCTRL_43224B0_12MA_LED_DRIVE, + CCTRL_43224B0_12MA_LED_DRIVE); + } + } + + if (sih->chip == BCM4313_CHIP_ID) { + /* + * enable 12 mA drive strenth for 4313 and + * set chipControl register bit 1 + */ + SI_MSG(("Applying 4313 WARs\n")); + si_pmu_chipcontrol(sih, 0, CCTRL_4313_12MA_LED_DRIVE, + CCTRL_4313_12MA_LED_DRIVE); + } + + return sii; + + exit: + if (sii->pch) + pcicore_deinit(sii->pch); + sii->pch = NULL; + + return NULL; +} + +/* + * Allocate a si handle. + * devid - pci device id (used to determine chip#) + * osh - opaque OS handle + * regs - virtual address of initial core registers + */ +struct si_pub * +ai_attach(void __iomem *regs, struct pci_dev *sdh) +{ + struct si_info *sii; + + /* alloc struct si_info */ + sii = kmalloc(sizeof(struct si_info), GFP_ATOMIC); + if (sii == NULL) + return NULL; + + if (ai_doattach(sii, regs, sdh) == NULL) { + kfree(sii); + return NULL; + } + + return (struct si_pub *) sii; +} + +/* may be called with core in reset */ +void ai_detach(struct si_pub *sih) +{ + struct si_info *sii; + + struct si_pub *si_local = NULL; + memcpy(&si_local, &sih, sizeof(struct si_pub **)); + + sii = (struct si_info *)sih; + + if (sii == NULL) + return; + + if (sii->pch) + pcicore_deinit(sii->pch); + sii->pch = NULL; + + srom_free_vars(sih); + kfree(sii); +} + +/* register driver interrupt disabling and restoring callback functions */ +void +ai_register_intr_callback(struct si_pub *sih, void *intrsoff_fn, + void *intrsrestore_fn, + void *intrsenabled_fn, void *intr_arg) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + sii->intr_arg = intr_arg; + sii->intrsoff_fn = (u32 (*)(void *)) intrsoff_fn; + sii->intrsrestore_fn = (void (*) (void *, u32)) intrsrestore_fn; + sii->intrsenabled_fn = (bool (*)(void *)) intrsenabled_fn; + /* save current core id. when this function called, the current core + * must be the core which provides driver functions(il, et, wl, etc.) + */ + sii->dev_coreid = sii->coreid[sii->curidx]; +} + +void ai_deregister_intr_callback(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + sii->intrsoff_fn = NULL; +} + +uint ai_coreid(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + return sii->coreid[sii->curidx]; +} + +uint ai_coreidx(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + return sii->curidx; +} + +bool ai_backplane64(struct si_pub *sih) +{ + return (sih->cccaps & CC_CAP_BKPLN64) != 0; +} + +/* return index of coreid or BADIDX if not found */ +uint ai_findcoreidx(struct si_pub *sih, uint coreid, uint coreunit) +{ + struct si_info *sii; + uint found; + uint i; + + sii = (struct si_info *)sih; + + found = 0; + + for (i = 0; i < sii->numcores; i++) + if (sii->coreid[i] == coreid) { + if (found == coreunit) + return i; + found++; + } + + return BADIDX; +} + +/* + * This function changes logical "focus" to the indicated core; + * must be called with interrupts off. + * Moreover, callers should keep interrupts off during switching + * out of and back to d11 core. + */ +void __iomem *ai_setcore(struct si_pub *sih, uint coreid, uint coreunit) +{ + uint idx; + + idx = ai_findcoreidx(sih, coreid, coreunit); + if (idx >= SI_MAXCORES) + return NULL; + + return ai_setcoreidx(sih, idx); +} + +/* Turn off interrupt as required by ai_setcore, before switch core */ +void __iomem *ai_switch_core(struct si_pub *sih, uint coreid, uint *origidx, + uint *intr_val) +{ + void __iomem *cc; + struct si_info *sii; + + sii = (struct si_info *)sih; + + if (SI_FAST(sii)) { + /* Overloading the origidx variable to remember the coreid, + * this works because the core ids cannot be confused with + * core indices. + */ + *origidx = coreid; + if (coreid == CC_CORE_ID) + return CCREGS_FAST(sii); + else if (coreid == sih->buscoretype) + return PCIEREGS(sii); + } + INTR_OFF(sii, *intr_val); + *origidx = sii->curidx; + cc = ai_setcore(sih, coreid, 0); + return cc; +} + +/* restore coreidx and restore interrupt */ +void ai_restore_core(struct si_pub *sih, uint coreid, uint intr_val) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + if (SI_FAST(sii) + && ((coreid == CC_CORE_ID) || (coreid == sih->buscoretype))) + return; + + ai_setcoreidx(sih, coreid); + INTR_RESTORE(sii, intr_val); +} + +void ai_write_wrapperreg(struct si_pub *sih, u32 offset, u32 val) +{ + struct si_info *sii = (struct si_info *)sih; + u32 *w = (u32 *) sii->curwrap; + W_REG(w + (offset / 4), val); + return; +} + +/* + * Switch to 'coreidx', issue a single arbitrary 32bit register mask&set + * operation, switch back to the original core, and return the new value. + * + * When using the silicon backplane, no fiddling with interrupts or core + * switches is needed. + * + * Also, when using pci/pcie, we can optimize away the core switching for pci + * registers and (on newer pci cores) chipcommon registers. + */ +uint ai_corereg(struct si_pub *sih, uint coreidx, uint regoff, uint mask, + uint val) +{ + uint origidx = 0; + u32 __iomem *r = NULL; + uint w; + uint intr_val = 0; + bool fast = false; + struct si_info *sii; + + sii = (struct si_info *)sih; + + if (coreidx >= SI_MAXCORES) + return 0; + + /* + * If pci/pcie, we can get at pci/pcie regs + * and on newer cores to chipc + */ + if ((sii->coreid[coreidx] == CC_CORE_ID) && SI_FAST(sii)) { + /* Chipc registers are mapped at 12KB */ + fast = true; + r = (u32 __iomem *)((__iomem char *)sii->curmap + + PCI_16KB0_CCREGS_OFFSET + regoff); + } else if (sii->pub.buscoreidx == coreidx) { + /* + * pci registers are at either in the last 2KB of + * an 8KB window or, in pcie and pci rev 13 at 8KB + */ + fast = true; + if (SI_FAST(sii)) + r = (u32 __iomem *)((__iomem char *)sii->curmap + + PCI_16KB0_PCIREGS_OFFSET + regoff); + else + r = (u32 __iomem *)((__iomem char *)sii->curmap + + ((regoff >= SBCONFIGOFF) ? + PCI_BAR0_PCISBR_OFFSET : + PCI_BAR0_PCIREGS_OFFSET) + regoff); + } + + if (!fast) { + INTR_OFF(sii, intr_val); + + /* save current core index */ + origidx = ai_coreidx(&sii->pub); + + /* switch core */ + r = (u32 __iomem *) ((unsigned char __iomem *) + ai_setcoreidx(&sii->pub, coreidx) + regoff); + } + + /* mask and set */ + if (mask || val) { + w = (R_REG(r) & ~mask) | val; + W_REG(r, w); + } + + /* readback */ + w = R_REG(r); + + if (!fast) { + /* restore core index */ + if (origidx != coreidx) + ai_setcoreidx(&sii->pub, origidx); + + INTR_RESTORE(sii, intr_val); + } + + return w; +} + +void ai_core_disable(struct si_pub *sih, u32 bits) +{ + struct si_info *sii; + u32 dummy; + struct aidmp *ai; + + sii = (struct si_info *)sih; + + ai = sii->curwrap; + + /* if core is already in reset, just return */ + if (R_REG(&ai->resetctrl) & AIRC_RESET) + return; + + W_REG(&ai->ioctrl, bits); + dummy = R_REG(&ai->ioctrl); + udelay(10); + + W_REG(&ai->resetctrl, AIRC_RESET); + udelay(1); +} + +/* reset and re-enable a core + * inputs: + * bits - core specific bits that are set during and after reset sequence + * resetbits - core specific bits that are set only during reset sequence + */ +void ai_core_reset(struct si_pub *sih, u32 bits, u32 resetbits) +{ + struct si_info *sii; + struct aidmp *ai; + u32 dummy; + + sii = (struct si_info *)sih; + ai = sii->curwrap; + + /* + * Must do the disable sequence first to work + * for arbitrary current core state. + */ + ai_core_disable(sih, (bits | resetbits)); + + /* + * Now do the initialization sequence. + */ + W_REG(&ai->ioctrl, (bits | SICF_FGC | SICF_CLOCK_EN)); + dummy = R_REG(&ai->ioctrl); + W_REG(&ai->resetctrl, 0); + udelay(1); + + W_REG(&ai->ioctrl, (bits | SICF_CLOCK_EN)); + dummy = R_REG(&ai->ioctrl); + udelay(1); +} + +/* return the slow clock source - LPO, XTAL, or PCI */ +static uint ai_slowclk_src(struct si_info *sii) +{ + struct chipcregs __iomem *cc; + u32 val; + + if (sii->pub.ccrev < 6) { + pci_read_config_dword(sii->pbus, PCI_GPIO_OUT, + &val); + if (val & PCI_CFG_GPIO_SCS) + return SCC_SS_PCI; + return SCC_SS_XTAL; + } else if (sii->pub.ccrev < 10) { + cc = (struct chipcregs __iomem *) + ai_setcoreidx(&sii->pub, sii->curidx); + return R_REG(&cc->slow_clk_ctl) & SCC_SS_MASK; + } else /* Insta-clock */ + return SCC_SS_XTAL; +} + +/* +* return the ILP (slowclock) min or max frequency +* precondition: we've established the chip has dynamic clk control +*/ +static uint ai_slowclk_freq(struct si_info *sii, bool max_freq, + struct chipcregs __iomem *cc) +{ + u32 slowclk; + uint div; + + slowclk = ai_slowclk_src(sii); + if (sii->pub.ccrev < 6) { + if (slowclk == SCC_SS_PCI) + return max_freq ? (PCIMAXFREQ / 64) + : (PCIMINFREQ / 64); + else + return max_freq ? (XTALMAXFREQ / 32) + : (XTALMINFREQ / 32); + } else if (sii->pub.ccrev < 10) { + div = 4 * + (((R_REG(&cc->slow_clk_ctl) & SCC_CD_MASK) >> + SCC_CD_SHIFT) + 1); + if (slowclk == SCC_SS_LPO) + return max_freq ? LPOMAXFREQ : LPOMINFREQ; + else if (slowclk == SCC_SS_XTAL) + return max_freq ? (XTALMAXFREQ / div) + : (XTALMINFREQ / div); + else if (slowclk == SCC_SS_PCI) + return max_freq ? (PCIMAXFREQ / div) + : (PCIMINFREQ / div); + } else { + /* Chipc rev 10 is InstaClock */ + div = R_REG(&cc->system_clk_ctl) >> SYCC_CD_SHIFT; + div = 4 * (div + 1); + return max_freq ? XTALMAXFREQ : (XTALMINFREQ / div); + } + return 0; +} + +static void +ai_clkctl_setdelay(struct si_info *sii, struct chipcregs __iomem *cc) +{ + uint slowmaxfreq, pll_delay, slowclk; + uint pll_on_delay, fref_sel_delay; + + pll_delay = PLL_DELAY; + + /* + * If the slow clock is not sourced by the xtal then + * add the xtal_on_delay since the xtal will also be + * powered down by dynamic clk control logic. + */ + + slowclk = ai_slowclk_src(sii); + if (slowclk != SCC_SS_XTAL) + pll_delay += XTAL_ON_DELAY; + + /* Starting with 4318 it is ILP that is used for the delays */ + slowmaxfreq = + ai_slowclk_freq(sii, (sii->pub.ccrev >= 10) ? false : true, cc); + + pll_on_delay = ((slowmaxfreq * pll_delay) + 999999) / 1000000; + fref_sel_delay = ((slowmaxfreq * FREF_DELAY) + 999999) / 1000000; + + W_REG(&cc->pll_on_delay, pll_on_delay); + W_REG(&cc->fref_sel_delay, fref_sel_delay); +} + +/* initialize power control delay registers */ +void ai_clkctl_init(struct si_pub *sih) +{ + struct si_info *sii; + uint origidx = 0; + struct chipcregs __iomem *cc; + bool fast; + + if (!(sih->cccaps & CC_CAP_PWR_CTL)) + return; + + sii = (struct si_info *)sih; + fast = SI_FAST(sii); + if (!fast) { + origidx = sii->curidx; + cc = (struct chipcregs __iomem *) + ai_setcore(sih, CC_CORE_ID, 0); + if (cc == NULL) + return; + } else { + cc = (struct chipcregs __iomem *) CCREGS_FAST(sii); + if (cc == NULL) + return; + } + + /* set all Instaclk chip ILP to 1 MHz */ + if (sih->ccrev >= 10) + SET_REG(&cc->system_clk_ctl, SYCC_CD_MASK, + (ILP_DIV_1MHZ << SYCC_CD_SHIFT)); + + ai_clkctl_setdelay(sii, cc); + + if (!fast) + ai_setcoreidx(sih, origidx); +} + +/* + * return the value suitable for writing to the + * dot11 core FAST_PWRUP_DELAY register + */ +u16 ai_clkctl_fast_pwrup_delay(struct si_pub *sih) +{ + struct si_info *sii; + uint origidx = 0; + struct chipcregs __iomem *cc; + uint slowminfreq; + u16 fpdelay; + uint intr_val = 0; + bool fast; + + sii = (struct si_info *)sih; + if (sih->cccaps & CC_CAP_PMU) { + INTR_OFF(sii, intr_val); + fpdelay = si_pmu_fast_pwrup_delay(sih); + INTR_RESTORE(sii, intr_val); + return fpdelay; + } + + if (!(sih->cccaps & CC_CAP_PWR_CTL)) + return 0; + + fast = SI_FAST(sii); + fpdelay = 0; + if (!fast) { + origidx = sii->curidx; + INTR_OFF(sii, intr_val); + cc = (struct chipcregs __iomem *) + ai_setcore(sih, CC_CORE_ID, 0); + if (cc == NULL) + goto done; + } else { + cc = (struct chipcregs __iomem *) CCREGS_FAST(sii); + if (cc == NULL) + goto done; + } + + slowminfreq = ai_slowclk_freq(sii, false, cc); + fpdelay = (((R_REG(&cc->pll_on_delay) + 2) * 1000000) + + (slowminfreq - 1)) / slowminfreq; + + done: + if (!fast) { + ai_setcoreidx(sih, origidx); + INTR_RESTORE(sii, intr_val); + } + return fpdelay; +} + +/* turn primary xtal and/or pll off/on */ +int ai_clkctl_xtal(struct si_pub *sih, uint what, bool on) +{ + struct si_info *sii; + u32 in, out, outen; + + sii = (struct si_info *)sih; + + /* pcie core doesn't have any mapping to control the xtal pu */ + if (PCIE(sii)) + return -1; + + pci_read_config_dword(sii->pbus, PCI_GPIO_IN, &in); + pci_read_config_dword(sii->pbus, PCI_GPIO_OUT, &out); + pci_read_config_dword(sii->pbus, PCI_GPIO_OUTEN, &outen); + + /* + * Avoid glitching the clock if GPRS is already using it. + * We can't actually read the state of the PLLPD so we infer it + * by the value of XTAL_PU which *is* readable via gpioin. + */ + if (on && (in & PCI_CFG_GPIO_XTAL)) + return 0; + + if (what & XTAL) + outen |= PCI_CFG_GPIO_XTAL; + if (what & PLL) + outen |= PCI_CFG_GPIO_PLL; + + if (on) { + /* turn primary xtal on */ + if (what & XTAL) { + out |= PCI_CFG_GPIO_XTAL; + if (what & PLL) + out |= PCI_CFG_GPIO_PLL; + pci_write_config_dword(sii->pbus, + PCI_GPIO_OUT, out); + pci_write_config_dword(sii->pbus, + PCI_GPIO_OUTEN, outen); + udelay(XTAL_ON_DELAY); + } + + /* turn pll on */ + if (what & PLL) { + out &= ~PCI_CFG_GPIO_PLL; + pci_write_config_dword(sii->pbus, + PCI_GPIO_OUT, out); + mdelay(2); + } + } else { + if (what & XTAL) + out &= ~PCI_CFG_GPIO_XTAL; + if (what & PLL) + out |= PCI_CFG_GPIO_PLL; + pci_write_config_dword(sii->pbus, + PCI_GPIO_OUT, out); + pci_write_config_dword(sii->pbus, + PCI_GPIO_OUTEN, outen); + } + + return 0; +} + +/* clk control mechanism through chipcommon, no policy checking */ +static bool _ai_clkctl_cc(struct si_info *sii, uint mode) +{ + uint origidx = 0; + struct chipcregs __iomem *cc; + u32 scc; + uint intr_val = 0; + bool fast = SI_FAST(sii); + + /* chipcommon cores prior to rev6 don't support dynamic clock control */ + if (sii->pub.ccrev < 6) + return false; + + if (!fast) { + INTR_OFF(sii, intr_val); + origidx = sii->curidx; + cc = (struct chipcregs __iomem *) + ai_setcore(&sii->pub, CC_CORE_ID, 0); + } else { + cc = (struct chipcregs __iomem *) CCREGS_FAST(sii); + if (cc == NULL) + goto done; + } + + if (!(sii->pub.cccaps & CC_CAP_PWR_CTL) && (sii->pub.ccrev < 20)) + goto done; + + switch (mode) { + case CLK_FAST: /* FORCEHT, fast (pll) clock */ + if (sii->pub.ccrev < 10) { + /* + * don't forget to force xtal back + * on before we clear SCC_DYN_XTAL.. + */ + ai_clkctl_xtal(&sii->pub, XTAL, ON); + SET_REG(&cc->slow_clk_ctl, + (SCC_XC | SCC_FS | SCC_IP), SCC_IP); + } else if (sii->pub.ccrev < 20) { + OR_REG(&cc->system_clk_ctl, SYCC_HR); + } else { + OR_REG(&cc->clk_ctl_st, CCS_FORCEHT); + } + + /* wait for the PLL */ + if (sii->pub.cccaps & CC_CAP_PMU) { + u32 htavail = CCS_HTAVAIL; + SPINWAIT(((R_REG(&cc->clk_ctl_st) & htavail) + == 0), PMU_MAX_TRANSITION_DLY); + } else { + udelay(PLL_DELAY); + } + break; + + case CLK_DYNAMIC: /* enable dynamic clock control */ + if (sii->pub.ccrev < 10) { + scc = R_REG(&cc->slow_clk_ctl); + scc &= ~(SCC_FS | SCC_IP | SCC_XC); + if ((scc & SCC_SS_MASK) != SCC_SS_XTAL) + scc |= SCC_XC; + W_REG(&cc->slow_clk_ctl, scc); + + /* + * for dynamic control, we have to + * release our xtal_pu "force on" + */ + if (scc & SCC_XC) + ai_clkctl_xtal(&sii->pub, XTAL, OFF); + } else if (sii->pub.ccrev < 20) { + /* Instaclock */ + AND_REG(&cc->system_clk_ctl, ~SYCC_HR); + } else { + AND_REG(&cc->clk_ctl_st, ~CCS_FORCEHT); + } + break; + + default: + break; + } + + done: + if (!fast) { + ai_setcoreidx(&sii->pub, origidx); + INTR_RESTORE(sii, intr_val); + } + return mode == CLK_FAST; +} + +/* + * clock control policy function throught chipcommon + * + * set dynamic clk control mode (forceslow, forcefast, dynamic) + * returns true if we are forcing fast clock + * this is a wrapper over the next internal function + * to allow flexible policy settings for outside caller + */ +bool ai_clkctl_cc(struct si_pub *sih, uint mode) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + + /* chipcommon cores prior to rev6 don't support dynamic clock control */ + if (sih->ccrev < 6) + return false; + + if (PCI_FORCEHT(sii)) + return mode == CLK_FAST; + + return _ai_clkctl_cc(sii, mode); +} + +/* Build device path */ +int ai_devpath(struct si_pub *sih, char *path, int size) +{ + int slen; + + if (!path || size <= 0) + return -1; + + slen = snprintf(path, (size_t) size, "pci/%u/%u/", + ((struct si_info *)sih)->pbus->bus->number, + PCI_SLOT(((struct pci_dev *) + (((struct si_info *)(sih))->pbus))->devfn)); + + if (slen < 0 || slen >= size) { + path[0] = '\0'; + return -1; + } + + return 0; +} + +void ai_pci_up(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + + if (PCI_FORCEHT(sii)) + _ai_clkctl_cc(sii, CLK_FAST); + + if (PCIE(sii)) + pcicore_up(sii->pch, SI_PCIUP); + +} + +/* Unconfigure and/or apply various WARs when system is going to sleep mode */ +void ai_pci_sleep(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + + pcicore_sleep(sii->pch); +} + +/* Unconfigure and/or apply various WARs when going down */ +void ai_pci_down(struct si_pub *sih) +{ + struct si_info *sii; + + sii = (struct si_info *)sih; + + /* release FORCEHT since chip is going to "down" state */ + if (PCI_FORCEHT(sii)) + _ai_clkctl_cc(sii, CLK_DYNAMIC); + + pcicore_down(sii->pch, SI_PCIDOWN); +} + +/* + * Configure the pci core for pci client (NIC) action + * coremask is the bitvec of cores by index to be enabled. + */ +void ai_pci_setup(struct si_pub *sih, uint coremask) +{ + struct si_info *sii; + struct sbpciregs __iomem *regs = NULL; + u32 siflag = 0, w; + uint idx = 0; + + sii = (struct si_info *)sih; + + if (PCI(sii)) { + /* get current core index */ + idx = sii->curidx; + + /* we interrupt on this backplane flag number */ + siflag = ai_flag(sih); + + /* switch over to pci core */ + regs = ai_setcoreidx(sih, sii->pub.buscoreidx); + } + + /* + * Enable sb->pci interrupts. Assume + * PCI rev 2.3 support was added in pci core rev 6 and things changed.. + */ + if (PCIE(sii) || (PCI(sii) && ((sii->pub.buscorerev) >= 6))) { + /* pci config write to set this core bit in PCIIntMask */ + pci_read_config_dword(sii->pbus, PCI_INT_MASK, &w); + w |= (coremask << PCI_SBIM_SHIFT); + pci_write_config_dword(sii->pbus, PCI_INT_MASK, w); + } else { + /* set sbintvec bit for our flag number */ + ai_setint(sih, siflag); + } + + if (PCI(sii)) { + pcicore_pci_setup(sii->pch, regs); + + /* switch back to previous core */ + ai_setcoreidx(sih, idx); + } +} + +/* + * Fixup SROMless PCI device's configuration. + * The current core may be changed upon return. + */ +int ai_pci_fixcfg(struct si_pub *sih) +{ + uint origidx; + void __iomem *regs = NULL; + struct si_info *sii = (struct si_info *)sih; + + /* Fixup PI in SROM shadow area to enable the correct PCI core access */ + /* save the current index */ + origidx = ai_coreidx(&sii->pub); + + /* check 'pi' is correct and fix it if not */ + regs = ai_setcore(&sii->pub, sii->pub.buscoretype, 0); + if (sii->pub.buscoretype == PCIE_CORE_ID) + pcicore_fixcfg_pcie(sii->pch, + (struct sbpcieregs __iomem *)regs); + else if (sii->pub.buscoretype == PCI_CORE_ID) + pcicore_fixcfg_pci(sii->pch, (struct sbpciregs __iomem *)regs); + + /* restore the original index */ + ai_setcoreidx(&sii->pub, origidx); + + pcicore_hwup(sii->pch); + return 0; +} + +/* mask&set gpiocontrol bits */ +u32 ai_gpiocontrol(struct si_pub *sih, u32 mask, u32 val, u8 priority) +{ + uint regoff; + + regoff = offsetof(struct chipcregs, gpiocontrol); + return ai_corereg(sih, SI_CC_IDX, regoff, mask, val); +} + +void ai_chipcontrl_epa4331(struct si_pub *sih, bool on) +{ + struct si_info *sii; + struct chipcregs __iomem *cc; + uint origidx; + u32 val; + + sii = (struct si_info *)sih; + origidx = ai_coreidx(sih); + + cc = (struct chipcregs __iomem *) ai_setcore(sih, CC_CORE_ID, 0); + + val = R_REG(&cc->chipcontrol); + + if (on) { + if (sih->chippkg == 9 || sih->chippkg == 0xb) + /* Ext PA Controls for 4331 12x9 Package */ + W_REG(&cc->chipcontrol, val | + CCTRL4331_EXTPA_EN | + CCTRL4331_EXTPA_ON_GPIO2_5); + else + /* Ext PA Controls for 4331 12x12 Package */ + W_REG(&cc->chipcontrol, + val | CCTRL4331_EXTPA_EN); + } else { + val &= ~(CCTRL4331_EXTPA_EN | CCTRL4331_EXTPA_ON_GPIO2_5); + W_REG(&cc->chipcontrol, val); + } + + ai_setcoreidx(sih, origidx); +} + +/* Enable BT-COEX & Ex-PA for 4313 */ +void ai_epa_4313war(struct si_pub *sih) +{ + struct si_info *sii; + struct chipcregs __iomem *cc; + uint origidx; + + sii = (struct si_info *)sih; + origidx = ai_coreidx(sih); + + cc = ai_setcore(sih, CC_CORE_ID, 0); + + /* EPA Fix */ + W_REG(&cc->gpiocontrol, + R_REG(&cc->gpiocontrol) | GPIO_CTRL_EPA_EN_MASK); + + ai_setcoreidx(sih, origidx); +} + +/* check if the device is removed */ +bool ai_deviceremoved(struct si_pub *sih) +{ + u32 w; + struct si_info *sii; + + sii = (struct si_info *)sih; + + pci_read_config_dword(sii->pbus, PCI_VENDOR_ID, &w); + if ((w & 0xFFFF) != PCI_VENDOR_ID_BROADCOM) + return true; + + return false; +} + +bool ai_is_sprom_available(struct si_pub *sih) +{ + if (sih->ccrev >= 31) { + struct si_info *sii; + uint origidx; + struct chipcregs __iomem *cc; + u32 sromctrl; + + if ((sih->cccaps & CC_CAP_SROM) == 0) + return false; + + sii = (struct si_info *)sih; + origidx = sii->curidx; + cc = ai_setcoreidx(sih, SI_CC_IDX); + sromctrl = R_REG(&cc->sromcontrol); + ai_setcoreidx(sih, origidx); + return sromctrl & SRC_PRESENT; + } + + switch (sih->chip) { + case BCM4313_CHIP_ID: + return (sih->chipst & CST4313_SPROM_PRESENT) != 0; + default: + return true; + } +} + +bool ai_is_otp_disabled(struct si_pub *sih) +{ + switch (sih->chip) { + case BCM4313_CHIP_ID: + return (sih->chipst & CST4313_OTP_PRESENT) == 0; + /* These chips always have their OTP on */ + case BCM43224_CHIP_ID: + case BCM43225_CHIP_ID: + default: + return false; + } +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h new file mode 100644 index 000000000000..106a7424a7cd --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_AIUTILS_H_ +#define _BRCM_AIUTILS_H_ + +#include "types.h" + +/* + * SOC Interconnect Address Map. + * All regions may not exist on all chips. + */ +/* each core gets 4Kbytes for registers */ +#define SI_CORE_SIZE 0x1000 +/* + * Max cores (this is arbitrary, for software + * convenience and could be changed if we + * make any larger chips + */ +#define SI_MAXCORES 16 + +/* Client Mode sb2pcitranslation2 size in bytes */ +#define SI_PCI_DMA_SZ 0x40000000 + +/* PCIE Client Mode sb2pcitranslation2 (2 ZettaBytes), high 32 bits */ +#define SI_PCIE_DMA_H32 0x80000000 + +/* core codes */ +#define NODEV_CORE_ID 0x700 /* Invalid coreid */ +#define CC_CORE_ID 0x800 /* chipcommon core */ +#define ILINE20_CORE_ID 0x801 /* iline20 core */ +#define SRAM_CORE_ID 0x802 /* sram core */ +#define SDRAM_CORE_ID 0x803 /* sdram core */ +#define PCI_CORE_ID 0x804 /* pci core */ +#define MIPS_CORE_ID 0x805 /* mips core */ +#define ENET_CORE_ID 0x806 /* enet mac core */ +#define CODEC_CORE_ID 0x807 /* v90 codec core */ +#define USB_CORE_ID 0x808 /* usb 1.1 host/device core */ +#define ADSL_CORE_ID 0x809 /* ADSL core */ +#define ILINE100_CORE_ID 0x80a /* iline100 core */ +#define IPSEC_CORE_ID 0x80b /* ipsec core */ +#define UTOPIA_CORE_ID 0x80c /* utopia core */ +#define PCMCIA_CORE_ID 0x80d /* pcmcia core */ +#define SOCRAM_CORE_ID 0x80e /* internal memory core */ +#define MEMC_CORE_ID 0x80f /* memc sdram core */ +#define OFDM_CORE_ID 0x810 /* OFDM phy core */ +#define EXTIF_CORE_ID 0x811 /* external interface core */ +#define D11_CORE_ID 0x812 /* 802.11 MAC core */ +#define APHY_CORE_ID 0x813 /* 802.11a phy core */ +#define BPHY_CORE_ID 0x814 /* 802.11b phy core */ +#define GPHY_CORE_ID 0x815 /* 802.11g phy core */ +#define MIPS33_CORE_ID 0x816 /* mips3302 core */ +#define USB11H_CORE_ID 0x817 /* usb 1.1 host core */ +#define USB11D_CORE_ID 0x818 /* usb 1.1 device core */ +#define USB20H_CORE_ID 0x819 /* usb 2.0 host core */ +#define USB20D_CORE_ID 0x81a /* usb 2.0 device core */ +#define SDIOH_CORE_ID 0x81b /* sdio host core */ +#define ROBO_CORE_ID 0x81c /* roboswitch core */ +#define ATA100_CORE_ID 0x81d /* parallel ATA core */ +#define SATAXOR_CORE_ID 0x81e /* serial ATA & XOR DMA core */ +#define GIGETH_CORE_ID 0x81f /* gigabit ethernet core */ +#define PCIE_CORE_ID 0x820 /* pci express core */ +#define NPHY_CORE_ID 0x821 /* 802.11n 2x2 phy core */ +#define SRAMC_CORE_ID 0x822 /* SRAM controller core */ +#define MINIMAC_CORE_ID 0x823 /* MINI MAC/phy core */ +#define ARM11_CORE_ID 0x824 /* ARM 1176 core */ +#define ARM7S_CORE_ID 0x825 /* ARM7tdmi-s core */ +#define LPPHY_CORE_ID 0x826 /* 802.11a/b/g phy core */ +#define PMU_CORE_ID 0x827 /* PMU core */ +#define SSNPHY_CORE_ID 0x828 /* 802.11n single-stream phy core */ +#define SDIOD_CORE_ID 0x829 /* SDIO device core */ +#define ARMCM3_CORE_ID 0x82a /* ARM Cortex M3 core */ +#define HTPHY_CORE_ID 0x82b /* 802.11n 4x4 phy core */ +#define MIPS74K_CORE_ID 0x82c /* mips 74k core */ +#define GMAC_CORE_ID 0x82d /* Gigabit MAC core */ +#define DMEMC_CORE_ID 0x82e /* DDR1/2 memory controller core */ +#define PCIERC_CORE_ID 0x82f /* PCIE Root Complex core */ +#define OCP_CORE_ID 0x830 /* OCP2OCP bridge core */ +#define SC_CORE_ID 0x831 /* shared common core */ +#define AHB_CORE_ID 0x832 /* OCP2AHB bridge core */ +#define SPIH_CORE_ID 0x833 /* SPI host core */ +#define I2S_CORE_ID 0x834 /* I2S core */ +#define DMEMS_CORE_ID 0x835 /* SDR/DDR1 memory controller core */ +#define DEF_SHIM_COMP 0x837 /* SHIM component in ubus/6362 */ +#define OOB_ROUTER_CORE_ID 0x367 /* OOB router core ID */ +#define DEF_AI_COMP 0xfff /* Default component, in ai chips it + * maps all unused address ranges + */ + +/* chipcommon being the first core: */ +#define SI_CC_IDX 0 + +/* SOC Interconnect types (aka chip types) */ +#define SOCI_AI 1 + +/* Common core control flags */ +#define SICF_BIST_EN 0x8000 +#define SICF_PME_EN 0x4000 +#define SICF_CORE_BITS 0x3ffc +#define SICF_FGC 0x0002 +#define SICF_CLOCK_EN 0x0001 + +/* Common core status flags */ +#define SISF_BIST_DONE 0x8000 +#define SISF_BIST_ERROR 0x4000 +#define SISF_GATED_CLK 0x2000 +#define SISF_DMA64 0x1000 +#define SISF_CORE_BITS 0x0fff + +/* A register that is common to all cores to + * communicate w/PMU regarding clock control. + */ +#define SI_CLK_CTL_ST 0x1e0 /* clock control and status */ + +/* clk_ctl_st register */ +#define CCS_FORCEALP 0x00000001 /* force ALP request */ +#define CCS_FORCEHT 0x00000002 /* force HT request */ +#define CCS_FORCEILP 0x00000004 /* force ILP request */ +#define CCS_ALPAREQ 0x00000008 /* ALP Avail Request */ +#define CCS_HTAREQ 0x00000010 /* HT Avail Request */ +#define CCS_FORCEHWREQOFF 0x00000020 /* Force HW Clock Request Off */ +#define CCS_ERSRC_REQ_MASK 0x00000700 /* external resource requests */ +#define CCS_ERSRC_REQ_SHIFT 8 +#define CCS_ALPAVAIL 0x00010000 /* ALP is available */ +#define CCS_HTAVAIL 0x00020000 /* HT is available */ +#define CCS_BP_ON_APL 0x00040000 /* RO: running on ALP clock */ +#define CCS_BP_ON_HT 0x00080000 /* RO: running on HT clock */ +#define CCS_ERSRC_STS_MASK 0x07000000 /* external resource status */ +#define CCS_ERSRC_STS_SHIFT 24 + +/* HT avail in chipc and pcmcia on 4328a0 */ +#define CCS0_HTAVAIL 0x00010000 +/* ALP avail in chipc and pcmcia on 4328a0 */ +#define CCS0_ALPAVAIL 0x00020000 + +/* Not really related to SOC Interconnect, but a couple of software + * conventions for the use the flash space: + */ + +/* Minumum amount of flash we support */ +#define FLASH_MIN 0x00020000 /* Minimum flash size */ + +#define CC_SROM_OTP 0x800 /* SROM/OTP address space */ + +/* gpiotimerval */ +#define GPIO_ONTIME_SHIFT 16 + +/* Fields in clkdiv */ +#define CLKD_OTP 0x000f0000 +#define CLKD_OTP_SHIFT 16 + +/* Package IDs */ +#define BCM4717_PKG_ID 9 /* 4717 package id */ +#define BCM4718_PKG_ID 10 /* 4718 package id */ +#define BCM43224_FAB_SMIC 0xa /* the chip is manufactured by SMIC */ + +/* these are router chips */ +#define BCM4716_CHIP_ID 0x4716 /* 4716 chipcommon chipid */ +#define BCM47162_CHIP_ID 47162 /* 47162 chipcommon chipid */ +#define BCM4748_CHIP_ID 0x4748 /* 4716 chipcommon chipid (OTP, RBBU) */ + +/* dynamic clock control defines */ +#define LPOMINFREQ 25000 /* low power oscillator min */ +#define LPOMAXFREQ 43000 /* low power oscillator max */ +#define XTALMINFREQ 19800000 /* 20 MHz - 1% */ +#define XTALMAXFREQ 20200000 /* 20 MHz + 1% */ +#define PCIMINFREQ 25000000 /* 25 MHz */ +#define PCIMAXFREQ 34000000 /* 33 MHz + fudge */ + +#define ILP_DIV_5MHZ 0 /* ILP = 5 MHz */ +#define ILP_DIV_1MHZ 4 /* ILP = 1 MHz */ + +/* clkctl xtal what flags */ +#define XTAL 0x1 /* primary crystal oscillator (2050) */ +#define PLL 0x2 /* main chip pll */ + +/* clkctl clk mode */ +#define CLK_FAST 0 /* force fast (pll) clock */ +#define CLK_DYNAMIC 2 /* enable dynamic clock control */ + +/* GPIO usage priorities */ +#define GPIO_DRV_PRIORITY 0 /* Driver */ +#define GPIO_APP_PRIORITY 1 /* Application */ +#define GPIO_HI_PRIORITY 2 /* Highest priority. Ignore GPIO + * reservation + */ + +/* GPIO pull up/down */ +#define GPIO_PULLUP 0 +#define GPIO_PULLDN 1 + +/* GPIO event regtype */ +#define GPIO_REGEVT 0 /* GPIO register event */ +#define GPIO_REGEVT_INTMSK 1 /* GPIO register event int mask */ +#define GPIO_REGEVT_INTPOL 2 /* GPIO register event int polarity */ + +/* device path */ +#define SI_DEVPATH_BUFSZ 16 /* min buffer size in bytes */ + +/* SI routine enumeration: to be used by update function with multiple hooks */ +#define SI_DOATTACH 1 +#define SI_PCIDOWN 2 +#define SI_PCIUP 3 + +/* + * Data structure to export all chip specific common variables + * public (read-only) portion of aiutils handle returned by si_attach() + */ +struct si_pub { + uint buscoretype; /* PCI_CORE_ID, PCIE_CORE_ID, PCMCIA_CORE_ID */ + uint buscorerev; /* buscore rev */ + uint buscoreidx; /* buscore index */ + int ccrev; /* chip common core rev */ + u32 cccaps; /* chip common capabilities */ + u32 cccaps_ext; /* chip common capabilities extension */ + int pmurev; /* pmu core rev */ + u32 pmucaps; /* pmu capabilities */ + uint boardtype; /* board type */ + uint boardvendor; /* board vendor */ + uint boardflags; /* board flags */ + uint boardflags2; /* board flags2 */ + uint chip; /* chip number */ + uint chiprev; /* chip revision */ + uint chippkg; /* chip package option */ + u32 chipst; /* chip status */ + bool issim; /* chip is in simulation or emulation */ + uint socirev; /* SOC interconnect rev */ + bool pci_pr32414; + +}; + +struct pci_dev; + +struct gpioh_item { + void *arg; + bool level; + void (*handler) (u32 stat, void *arg); + u32 event; + struct gpioh_item *next; +}; + +/* misc si info needed by some of the routines */ +struct si_info { + struct si_pub pub; /* back plane public state (must be first) */ + struct pci_dev *pbus; /* handle to pci bus */ + uint dev_coreid; /* the core provides driver functions */ + void *intr_arg; /* interrupt callback function arg */ + u32 (*intrsoff_fn) (void *intr_arg); /* turns chip interrupts off */ + /* restore chip interrupts */ + void (*intrsrestore_fn) (void *intr_arg, u32 arg); + /* check if interrupts are enabled */ + bool (*intrsenabled_fn) (void *intr_arg); + + struct pcicore_info *pch; /* PCI/E core handle */ + + struct list_head var_list; /* list of srom variables */ + + void __iomem *curmap; /* current regs va */ + void __iomem *regs[SI_MAXCORES]; /* other regs va */ + + uint curidx; /* current core index */ + uint numcores; /* # discovered cores */ + uint coreid[SI_MAXCORES]; /* id of each core */ + u32 coresba[SI_MAXCORES]; /* backplane address of each core */ + void *regs2[SI_MAXCORES]; /* 2nd virtual address per core (usbh20) */ + u32 coresba2[SI_MAXCORES]; /* 2nd phys address per core (usbh20) */ + u32 coresba_size[SI_MAXCORES]; /* backplane address space size */ + u32 coresba2_size[SI_MAXCORES]; /* second address space size */ + + void *curwrap; /* current wrapper va */ + void *wrappers[SI_MAXCORES]; /* other cores wrapper va */ + u32 wrapba[SI_MAXCORES]; /* address of controlling wrapper */ + + u32 cia[SI_MAXCORES]; /* erom cia entry for each core */ + u32 cib[SI_MAXCORES]; /* erom cia entry for each core */ + u32 oob_router; /* oob router registers for axi */ +}; + +/* + * Many of the routines below take an 'sih' handle as their first arg. + * Allocate this by calling si_attach(). Free it by calling si_detach(). + * At any one time, the sih is logically focused on one particular si core + * (the "current core"). + * Use si_setcore() or si_setcoreidx() to change the association to another core + */ + + +/* AMBA Interconnect exported externs */ +extern uint ai_flag(struct si_pub *sih); +extern void ai_setint(struct si_pub *sih, int siflag); +extern uint ai_coreidx(struct si_pub *sih); +extern uint ai_corevendor(struct si_pub *sih); +extern uint ai_corerev(struct si_pub *sih); +extern bool ai_iscoreup(struct si_pub *sih); +extern u32 ai_core_cflags(struct si_pub *sih, u32 mask, u32 val); +extern void ai_core_cflags_wo(struct si_pub *sih, u32 mask, u32 val); +extern u32 ai_core_sflags(struct si_pub *sih, u32 mask, u32 val); +extern uint ai_corereg(struct si_pub *sih, uint coreidx, uint regoff, uint mask, + uint val); +extern void ai_core_reset(struct si_pub *sih, u32 bits, u32 resetbits); +extern void ai_core_disable(struct si_pub *sih, u32 bits); +extern int ai_numaddrspaces(struct si_pub *sih); +extern u32 ai_addrspace(struct si_pub *sih, uint asidx); +extern u32 ai_addrspacesize(struct si_pub *sih, uint asidx); +extern void ai_write_wrap_reg(struct si_pub *sih, u32 offset, u32 val); + +/* === exported functions === */ +extern struct si_pub *ai_attach(void __iomem *regs, struct pci_dev *sdh); +extern void ai_detach(struct si_pub *sih); +extern uint ai_coreid(struct si_pub *sih); +extern uint ai_corerev(struct si_pub *sih); +extern uint ai_corereg(struct si_pub *sih, uint coreidx, uint regoff, uint mask, + uint val); +extern void ai_write_wrapperreg(struct si_pub *sih, u32 offset, u32 val); +extern u32 ai_core_cflags(struct si_pub *sih, u32 mask, u32 val); +extern u32 ai_core_sflags(struct si_pub *sih, u32 mask, u32 val); +extern bool ai_iscoreup(struct si_pub *sih); +extern uint ai_findcoreidx(struct si_pub *sih, uint coreid, uint coreunit); +extern void __iomem *ai_setcoreidx(struct si_pub *sih, uint coreidx); +extern void __iomem *ai_setcore(struct si_pub *sih, uint coreid, uint coreunit); +extern void __iomem *ai_switch_core(struct si_pub *sih, uint coreid, + uint *origidx, uint *intr_val); +extern void ai_restore_core(struct si_pub *sih, uint coreid, uint intr_val); +extern void ai_core_reset(struct si_pub *sih, u32 bits, u32 resetbits); +extern void ai_core_disable(struct si_pub *sih, u32 bits); +extern u32 ai_alp_clock(struct si_pub *sih); +extern u32 ai_ilp_clock(struct si_pub *sih); +extern void ai_pci_setup(struct si_pub *sih, uint coremask); +extern void ai_setint(struct si_pub *sih, int siflag); +extern bool ai_backplane64(struct si_pub *sih); +extern void ai_register_intr_callback(struct si_pub *sih, void *intrsoff_fn, + void *intrsrestore_fn, + void *intrsenabled_fn, void *intr_arg); +extern void ai_deregister_intr_callback(struct si_pub *sih); +extern void ai_clkctl_init(struct si_pub *sih); +extern u16 ai_clkctl_fast_pwrup_delay(struct si_pub *sih); +extern bool ai_clkctl_cc(struct si_pub *sih, uint mode); +extern int ai_clkctl_xtal(struct si_pub *sih, uint what, bool on); +extern bool ai_deviceremoved(struct si_pub *sih); +extern u32 ai_gpiocontrol(struct si_pub *sih, u32 mask, u32 val, + u8 priority); + +/* OTP status */ +extern bool ai_is_otp_disabled(struct si_pub *sih); + +/* SPROM availability */ +extern bool ai_is_sprom_available(struct si_pub *sih); + +/* + * Build device path. Path size must be >= SI_DEVPATH_BUFSZ. + * The returned path is NULL terminated and has trailing '/'. + * Return 0 on success, nonzero otherwise. + */ +extern int ai_devpath(struct si_pub *sih, char *path, int size); + +extern void ai_pci_sleep(struct si_pub *sih); +extern void ai_pci_down(struct si_pub *sih); +extern void ai_pci_up(struct si_pub *sih); +extern int ai_pci_fixcfg(struct si_pub *sih); + +extern void ai_chipcontrl_epa4331(struct si_pub *sih, bool on); +/* Enable Ex-PA for 4313 */ +extern void ai_epa_4313war(struct si_pub *sih); + +#endif /* _BRCM_AIUTILS_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c new file mode 100644 index 000000000000..7f27dbdb6b60 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c @@ -0,0 +1,1241 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <net/mac80211.h> + +#include "rate.h" +#include "scb.h" +#include "phy/phy_hal.h" +#include "antsel.h" +#include "main.h" +#include "ampdu.h" + +/* max number of mpdus in an ampdu */ +#define AMPDU_MAX_MPDU 32 +/* max number of mpdus in an ampdu to a legacy */ +#define AMPDU_NUM_MPDU_LEGACY 16 +/* max Tx ba window size (in pdu) */ +#define AMPDU_TX_BA_MAX_WSIZE 64 +/* default Tx ba window size (in pdu) */ +#define AMPDU_TX_BA_DEF_WSIZE 64 +/* default Rx ba window size (in pdu) */ +#define AMPDU_RX_BA_DEF_WSIZE 64 +/* max Rx ba window size (in pdu) */ +#define AMPDU_RX_BA_MAX_WSIZE 64 +/* max dur of tx ampdu (in msec) */ +#define AMPDU_MAX_DUR 5 +/* default tx retry limit */ +#define AMPDU_DEF_RETRY_LIMIT 5 +/* default tx retry limit at reg rate */ +#define AMPDU_DEF_RR_RETRY_LIMIT 2 +/* default weight of ampdu in txfifo */ +#define AMPDU_DEF_TXPKT_WEIGHT 2 +/* default ffpld reserved bytes */ +#define AMPDU_DEF_FFPLD_RSVD 2048 +/* # of inis to be freed on detach */ +#define AMPDU_INI_FREE 10 +/* max # of mpdus released at a time */ +#define AMPDU_SCB_MAX_RELEASE 20 + +#define NUM_FFPLD_FIFO 4 /* number of fifo concerned by pre-loading */ +#define FFPLD_TX_MAX_UNFL 200 /* default value of the average number of ampdu + * without underflows + */ +#define FFPLD_MPDU_SIZE 1800 /* estimate of maximum mpdu size */ +#define FFPLD_MAX_MCS 23 /* we don't deal with mcs 32 */ +#define FFPLD_PLD_INCR 1000 /* increments in bytes */ +#define FFPLD_MAX_AMPDU_CNT 5000 /* maximum number of ampdu we + * accumulate between resets. + */ + +#define AMPDU_DELIMITER_LEN 4 + +/* max allowed number of mpdus in an ampdu (2 streams) */ +#define AMPDU_NUM_MPDU 16 + +#define TX_SEQ_TO_INDEX(seq) ((seq) % AMPDU_TX_BA_MAX_WSIZE) + +/* max possible overhead per mpdu in the ampdu; 3 is for roundup if needed */ +#define AMPDU_MAX_MPDU_OVERHEAD (FCS_LEN + DOT11_ICV_AES_LEN +\ + AMPDU_DELIMITER_LEN + 3\ + + DOT11_A4_HDR_LEN + DOT11_QOS_LEN + DOT11_IV_MAX_LEN) + +/* modulo add/sub, bound = 2^k */ +#define MODADD_POW2(x, y, bound) (((x) + (y)) & ((bound) - 1)) +#define MODSUB_POW2(x, y, bound) (((x) - (y)) & ((bound) - 1)) + +/* structure to hold tx fifo information and pre-loading state + * counters specific to tx underflows of ampdus + * some counters might be redundant with the ones in wlc or ampdu structures. + * This allows to maintain a specific state independently of + * how often and/or when the wlc counters are updated. + * + * ampdu_pld_size: number of bytes to be pre-loaded + * mcs2ampdu_table: per-mcs max # of mpdus in an ampdu + * prev_txfunfl: num of underflows last read from the HW macstats counter + * accum_txfunfl: num of underflows since we modified pld params + * accum_txampdu: num of tx ampdu since we modified pld params + * prev_txampdu: previous reading of tx ampdu + * dmaxferrate: estimated dma avg xfer rate in kbits/sec + */ +struct brcms_fifo_info { + u16 ampdu_pld_size; + u8 mcs2ampdu_table[FFPLD_MAX_MCS + 1]; + u16 prev_txfunfl; + u32 accum_txfunfl; + u32 accum_txampdu; + u32 prev_txampdu; + u32 dmaxferrate; +}; + +/* AMPDU module specific state + * + * wlc: pointer to main wlc structure + * scb_handle: scb cubby handle to retrieve data from scb + * ini_enable: per-tid initiator enable/disable of ampdu + * ba_tx_wsize: Tx ba window size (in pdu) + * ba_rx_wsize: Rx ba window size (in pdu) + * retry_limit: mpdu transmit retry limit + * rr_retry_limit: mpdu transmit retry limit at regular rate + * retry_limit_tid: per-tid mpdu transmit retry limit + * rr_retry_limit_tid: per-tid mpdu transmit retry limit at regular rate + * mpdu_density: min mpdu spacing (0-7) ==> 2^(x-1)/8 usec + * max_pdu: max pdus allowed in ampdu + * dur: max duration of an ampdu (in msec) + * txpkt_weight: weight of ampdu in txfifo; reduces rate lag + * rx_factor: maximum rx ampdu factor (0-3) ==> 2^(13+x) bytes + * ffpld_rsvd: number of bytes to reserve for preload + * max_txlen: max size of ampdu per mcs, bw and sgi + * mfbr: enable multiple fallback rate + * tx_max_funl: underflows should be kept such that + * (tx_max_funfl*underflows) < tx frames + * fifo_tb: table of fifo infos + */ +struct ampdu_info { + struct brcms_c_info *wlc; + int scb_handle; + u8 ini_enable[AMPDU_MAX_SCB_TID]; + u8 ba_tx_wsize; + u8 ba_rx_wsize; + u8 retry_limit; + u8 rr_retry_limit; + u8 retry_limit_tid[AMPDU_MAX_SCB_TID]; + u8 rr_retry_limit_tid[AMPDU_MAX_SCB_TID]; + u8 mpdu_density; + s8 max_pdu; + u8 dur; + u8 txpkt_weight; + u8 rx_factor; + u32 ffpld_rsvd; + u32 max_txlen[MCS_TABLE_SIZE][2][2]; + bool mfbr; + u32 tx_max_funl; + struct brcms_fifo_info fifo_tb[NUM_FFPLD_FIFO]; +}; + +/* used for flushing ampdu packets */ +struct cb_del_ampdu_pars { + struct ieee80211_sta *sta; + u16 tid; +}; + +static void brcms_c_scb_ampdu_update_max_txlen(struct ampdu_info *ampdu, u8 dur) +{ + u32 rate, mcs; + + for (mcs = 0; mcs < MCS_TABLE_SIZE; mcs++) { + /* rate is in Kbps; dur is in msec ==> len = (rate * dur) / 8 */ + /* 20MHz, No SGI */ + rate = mcs_2_rate(mcs, false, false); + ampdu->max_txlen[mcs][0][0] = (rate * dur) >> 3; + /* 40 MHz, No SGI */ + rate = mcs_2_rate(mcs, true, false); + ampdu->max_txlen[mcs][1][0] = (rate * dur) >> 3; + /* 20MHz, SGI */ + rate = mcs_2_rate(mcs, false, true); + ampdu->max_txlen[mcs][0][1] = (rate * dur) >> 3; + /* 40 MHz, SGI */ + rate = mcs_2_rate(mcs, true, true); + ampdu->max_txlen[mcs][1][1] = (rate * dur) >> 3; + } +} + +static bool brcms_c_ampdu_cap(struct ampdu_info *ampdu) +{ + if (BRCMS_PHY_11N_CAP(ampdu->wlc->band)) + return true; + else + return false; +} + +static int brcms_c_ampdu_set(struct ampdu_info *ampdu, bool on) +{ + struct brcms_c_info *wlc = ampdu->wlc; + + wlc->pub->_ampdu = false; + + if (on) { + if (!(wlc->pub->_n_enab & SUPPORT_11N)) { + wiphy_err(ampdu->wlc->wiphy, "wl%d: driver not " + "nmode enabled\n", wlc->pub->unit); + return -ENOTSUPP; + } + if (!brcms_c_ampdu_cap(ampdu)) { + wiphy_err(ampdu->wlc->wiphy, "wl%d: device not " + "ampdu capable\n", wlc->pub->unit); + return -ENOTSUPP; + } + wlc->pub->_ampdu = on; + } + + return 0; +} + +static void brcms_c_ffpld_init(struct ampdu_info *ampdu) +{ + int i, j; + struct brcms_fifo_info *fifo; + + for (j = 0; j < NUM_FFPLD_FIFO; j++) { + fifo = (ampdu->fifo_tb + j); + fifo->ampdu_pld_size = 0; + for (i = 0; i <= FFPLD_MAX_MCS; i++) + fifo->mcs2ampdu_table[i] = 255; + fifo->dmaxferrate = 0; + fifo->accum_txampdu = 0; + fifo->prev_txfunfl = 0; + fifo->accum_txfunfl = 0; + + } +} + +struct ampdu_info *brcms_c_ampdu_attach(struct brcms_c_info *wlc) +{ + struct ampdu_info *ampdu; + int i; + + ampdu = kzalloc(sizeof(struct ampdu_info), GFP_ATOMIC); + if (!ampdu) + return NULL; + + ampdu->wlc = wlc; + + for (i = 0; i < AMPDU_MAX_SCB_TID; i++) + ampdu->ini_enable[i] = true; + /* Disable ampdu for VO by default */ + ampdu->ini_enable[PRIO_8021D_VO] = false; + ampdu->ini_enable[PRIO_8021D_NC] = false; + + /* Disable ampdu for BK by default since not enough fifo space */ + ampdu->ini_enable[PRIO_8021D_NONE] = false; + ampdu->ini_enable[PRIO_8021D_BK] = false; + + ampdu->ba_tx_wsize = AMPDU_TX_BA_DEF_WSIZE; + ampdu->ba_rx_wsize = AMPDU_RX_BA_DEF_WSIZE; + ampdu->mpdu_density = AMPDU_DEF_MPDU_DENSITY; + ampdu->max_pdu = AUTO; + ampdu->dur = AMPDU_MAX_DUR; + ampdu->txpkt_weight = AMPDU_DEF_TXPKT_WEIGHT; + + ampdu->ffpld_rsvd = AMPDU_DEF_FFPLD_RSVD; + /* + * bump max ampdu rcv size to 64k for all 11n + * devices except 4321A0 and 4321A1 + */ + if (BRCMS_ISNPHY(wlc->band) && NREV_LT(wlc->band->phyrev, 2)) + ampdu->rx_factor = IEEE80211_HT_MAX_AMPDU_32K; + else + ampdu->rx_factor = IEEE80211_HT_MAX_AMPDU_64K; + ampdu->retry_limit = AMPDU_DEF_RETRY_LIMIT; + ampdu->rr_retry_limit = AMPDU_DEF_RR_RETRY_LIMIT; + + for (i = 0; i < AMPDU_MAX_SCB_TID; i++) { + ampdu->retry_limit_tid[i] = ampdu->retry_limit; + ampdu->rr_retry_limit_tid[i] = ampdu->rr_retry_limit; + } + + brcms_c_scb_ampdu_update_max_txlen(ampdu, ampdu->dur); + ampdu->mfbr = false; + /* try to set ampdu to the default value */ + brcms_c_ampdu_set(ampdu, wlc->pub->_ampdu); + + ampdu->tx_max_funl = FFPLD_TX_MAX_UNFL; + brcms_c_ffpld_init(ampdu); + + return ampdu; +} + +void brcms_c_ampdu_detach(struct ampdu_info *ampdu) +{ + kfree(ampdu); +} + +static void brcms_c_scb_ampdu_update_config(struct ampdu_info *ampdu, + struct scb *scb) +{ + struct scb_ampdu *scb_ampdu = &scb->scb_ampdu; + int i; + + scb_ampdu->max_pdu = AMPDU_NUM_MPDU; + + /* go back to legacy size if some preloading is occurring */ + for (i = 0; i < NUM_FFPLD_FIFO; i++) { + if (ampdu->fifo_tb[i].ampdu_pld_size > FFPLD_PLD_INCR) + scb_ampdu->max_pdu = AMPDU_NUM_MPDU_LEGACY; + } + + /* apply user override */ + if (ampdu->max_pdu != AUTO) + scb_ampdu->max_pdu = (u8) ampdu->max_pdu; + + scb_ampdu->release = min_t(u8, scb_ampdu->max_pdu, + AMPDU_SCB_MAX_RELEASE); + + if (scb_ampdu->max_rx_ampdu_bytes) + scb_ampdu->release = min_t(u8, scb_ampdu->release, + scb_ampdu->max_rx_ampdu_bytes / 1600); + + scb_ampdu->release = min(scb_ampdu->release, + ampdu->fifo_tb[TX_AC_BE_FIFO]. + mcs2ampdu_table[FFPLD_MAX_MCS]); +} + +static void brcms_c_scb_ampdu_update_config_all(struct ampdu_info *ampdu) +{ + brcms_c_scb_ampdu_update_config(ampdu, &du->wlc->pri_scb); +} + +static void brcms_c_ffpld_calc_mcs2ampdu_table(struct ampdu_info *ampdu, int f) +{ + int i; + u32 phy_rate, dma_rate, tmp; + u8 max_mpdu; + struct brcms_fifo_info *fifo = (ampdu->fifo_tb + f); + + /* recompute the dma rate */ + /* note : we divide/multiply by 100 to avoid integer overflows */ + max_mpdu = min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], + AMPDU_NUM_MPDU_LEGACY); + phy_rate = mcs_2_rate(FFPLD_MAX_MCS, true, false); + dma_rate = + (((phy_rate / 100) * + (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size)) + / (max_mpdu * FFPLD_MPDU_SIZE)) * 100; + fifo->dmaxferrate = dma_rate; + + /* fill up the mcs2ampdu table; do not recalc the last mcs */ + dma_rate = dma_rate >> 7; + for (i = 0; i < FFPLD_MAX_MCS; i++) { + /* shifting to keep it within integer range */ + phy_rate = mcs_2_rate(i, true, false) >> 7; + if (phy_rate > dma_rate) { + tmp = ((fifo->ampdu_pld_size * phy_rate) / + ((phy_rate - dma_rate) * FFPLD_MPDU_SIZE)) + 1; + tmp = min_t(u32, tmp, 255); + fifo->mcs2ampdu_table[i] = (u8) tmp; + } + } +} + +/* evaluate the dma transfer rate using the tx underflows as feedback. + * If necessary, increase tx fifo preloading. If not enough, + * decrease maximum ampdu size for each mcs till underflows stop + * Return 1 if pre-loading not active, -1 if not an underflow event, + * 0 if pre-loading module took care of the event. + */ +static int brcms_c_ffpld_check_txfunfl(struct brcms_c_info *wlc, int fid) +{ + struct ampdu_info *ampdu = wlc->ampdu; + u32 phy_rate = mcs_2_rate(FFPLD_MAX_MCS, true, false); + u32 txunfl_ratio; + u8 max_mpdu; + u32 current_ampdu_cnt = 0; + u16 max_pld_size; + u32 new_txunfl; + struct brcms_fifo_info *fifo = (ampdu->fifo_tb + fid); + uint xmtfifo_sz; + u16 cur_txunfl; + + /* return if we got here for a different reason than underflows */ + cur_txunfl = brcms_b_read_shm(wlc->hw, + M_UCODE_MACSTAT + + offsetof(struct macstat, txfunfl[fid])); + new_txunfl = (u16) (cur_txunfl - fifo->prev_txfunfl); + if (new_txunfl == 0) { + BCMMSG(wlc->wiphy, "TX status FRAG set but no tx underflows\n"); + return -1; + } + fifo->prev_txfunfl = cur_txunfl; + + if (!ampdu->tx_max_funl) + return 1; + + /* check if fifo is big enough */ + if (brcms_b_xmtfifo_sz_get(wlc->hw, fid, &xmtfifo_sz)) + return -1; + + if ((TXFIFO_SIZE_UNIT * (u32) xmtfifo_sz) <= ampdu->ffpld_rsvd) + return 1; + + max_pld_size = TXFIFO_SIZE_UNIT * xmtfifo_sz - ampdu->ffpld_rsvd; + fifo->accum_txfunfl += new_txunfl; + + /* we need to wait for at least 10 underflows */ + if (fifo->accum_txfunfl < 10) + return 0; + + BCMMSG(wlc->wiphy, "ampdu_count %d tx_underflows %d\n", + current_ampdu_cnt, fifo->accum_txfunfl); + + /* + compute the current ratio of tx unfl per ampdu. + When the current ampdu count becomes too + big while the ratio remains small, we reset + the current count in order to not + introduce too big of a latency in detecting a + large amount of tx underflows later. + */ + + txunfl_ratio = current_ampdu_cnt / fifo->accum_txfunfl; + + if (txunfl_ratio > ampdu->tx_max_funl) { + if (current_ampdu_cnt >= FFPLD_MAX_AMPDU_CNT) + fifo->accum_txfunfl = 0; + + return 0; + } + max_mpdu = min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], + AMPDU_NUM_MPDU_LEGACY); + + /* In case max value max_pdu is already lower than + the fifo depth, there is nothing more we can do. + */ + + if (fifo->ampdu_pld_size >= max_mpdu * FFPLD_MPDU_SIZE) { + fifo->accum_txfunfl = 0; + return 0; + } + + if (fifo->ampdu_pld_size < max_pld_size) { + + /* increment by TX_FIFO_PLD_INC bytes */ + fifo->ampdu_pld_size += FFPLD_PLD_INCR; + if (fifo->ampdu_pld_size > max_pld_size) + fifo->ampdu_pld_size = max_pld_size; + + /* update scb release size */ + brcms_c_scb_ampdu_update_config_all(ampdu); + + /* + * compute a new dma xfer rate for max_mpdu @ max mcs. + * This is the minimum dma rate that can achieve no + * underflow condition for the current mpdu size. + * + * note : we divide/multiply by 100 to avoid integer overflows + */ + fifo->dmaxferrate = + (((phy_rate / 100) * + (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size)) + / (max_mpdu * FFPLD_MPDU_SIZE)) * 100; + + BCMMSG(wlc->wiphy, "DMA estimated transfer rate %d; " + "pre-load size %d\n", + fifo->dmaxferrate, fifo->ampdu_pld_size); + } else { + + /* decrease ampdu size */ + if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] > 1) { + if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] == 255) + fifo->mcs2ampdu_table[FFPLD_MAX_MCS] = + AMPDU_NUM_MPDU_LEGACY - 1; + else + fifo->mcs2ampdu_table[FFPLD_MAX_MCS] -= 1; + + /* recompute the table */ + brcms_c_ffpld_calc_mcs2ampdu_table(ampdu, fid); + + /* update scb release size */ + brcms_c_scb_ampdu_update_config_all(ampdu); + } + } + fifo->accum_txfunfl = 0; + return 0; +} + +void +brcms_c_ampdu_tx_operational(struct brcms_c_info *wlc, u8 tid, + u8 ba_wsize, /* negotiated ba window size (in pdu) */ + uint max_rx_ampdu_bytes) /* from ht_cap in beacon */ +{ + struct scb_ampdu *scb_ampdu; + struct scb_ampdu_tid_ini *ini; + struct ampdu_info *ampdu = wlc->ampdu; + struct scb *scb = &wlc->pri_scb; + scb_ampdu = &scb->scb_ampdu; + + if (!ampdu->ini_enable[tid]) { + wiphy_err(ampdu->wlc->wiphy, "%s: Rejecting tid %d\n", + __func__, tid); + return; + } + + ini = &scb_ampdu->ini[tid]; + ini->tid = tid; + ini->scb = scb_ampdu->scb; + ini->ba_wsize = ba_wsize; + scb_ampdu->max_rx_ampdu_bytes = max_rx_ampdu_bytes; +} + +int +brcms_c_sendampdu(struct ampdu_info *ampdu, struct brcms_txq_info *qi, + struct sk_buff **pdu, int prec) +{ + struct brcms_c_info *wlc; + struct sk_buff *p, *pkt[AMPDU_MAX_MPDU]; + u8 tid, ndelim; + int err = 0; + u8 preamble_type = BRCMS_GF_PREAMBLE; + u8 fbr_preamble_type = BRCMS_GF_PREAMBLE; + u8 rts_preamble_type = BRCMS_LONG_PREAMBLE; + u8 rts_fbr_preamble_type = BRCMS_LONG_PREAMBLE; + + bool rr = true, fbr = false; + uint i, count = 0, fifo, seg_cnt = 0; + u16 plen, len, seq = 0, mcl, mch, index, frameid, dma_len = 0; + u32 ampdu_len, max_ampdu_bytes = 0; + struct d11txh *txh = NULL; + u8 *plcp; + struct ieee80211_hdr *h; + struct scb *scb; + struct scb_ampdu *scb_ampdu; + struct scb_ampdu_tid_ini *ini; + u8 mcs = 0; + bool use_rts = false, use_cts = false; + u32 rspec = 0, rspec_fallback = 0; + u32 rts_rspec = 0, rts_rspec_fallback = 0; + u16 mimo_ctlchbw = PHY_TXC1_BW_20MHZ; + struct ieee80211_rts *rts; + u8 rr_retry_limit; + struct brcms_fifo_info *f; + bool fbr_iscck; + struct ieee80211_tx_info *tx_info; + u16 qlen; + struct wiphy *wiphy; + + wlc = ampdu->wlc; + wiphy = wlc->wiphy; + p = *pdu; + + tid = (u8) (p->priority); + + f = ampdu->fifo_tb + prio2fifo[tid]; + + scb = &wlc->pri_scb; + scb_ampdu = &scb->scb_ampdu; + ini = &scb_ampdu->ini[tid]; + + /* Let pressure continue to build ... */ + qlen = pktq_plen(&qi->q, prec); + if (ini->tx_in_transit > 0 && + qlen < min(scb_ampdu->max_pdu, ini->ba_wsize)) + /* Collect multiple MPDU's to be sent in the next AMPDU */ + return -EBUSY; + + /* at this point we intend to transmit an AMPDU */ + rr_retry_limit = ampdu->rr_retry_limit_tid[tid]; + ampdu_len = 0; + dma_len = 0; + while (p) { + struct ieee80211_tx_rate *txrate; + + tx_info = IEEE80211_SKB_CB(p); + txrate = tx_info->status.rates; + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + err = brcms_c_prep_pdu(wlc, p, &fifo); + } else { + wiphy_err(wiphy, "%s: AMPDU flag is off!\n", __func__); + *pdu = NULL; + err = 0; + break; + } + + if (err) { + if (err == -EBUSY) { + wiphy_err(wiphy, "wl%d: sendampdu: " + "prep_xdu retry; seq 0x%x\n", + wlc->pub->unit, seq); + *pdu = p; + break; + } + + /* error in the packet; reject it */ + wiphy_err(wiphy, "wl%d: sendampdu: prep_xdu " + "rejected; seq 0x%x\n", wlc->pub->unit, seq); + *pdu = NULL; + break; + } + + /* pkt is good to be aggregated */ + txh = (struct d11txh *) p->data; + plcp = (u8 *) (txh + 1); + h = (struct ieee80211_hdr *)(plcp + D11_PHY_HDR_LEN); + seq = le16_to_cpu(h->seq_ctrl) >> SEQNUM_SHIFT; + index = TX_SEQ_TO_INDEX(seq); + + /* check mcl fields and test whether it can be agg'd */ + mcl = le16_to_cpu(txh->MacTxControlLow); + mcl &= ~TXC_AMPDU_MASK; + fbr_iscck = !(le16_to_cpu(txh->XtraFrameTypes) & 0x3); + txh->PreloadSize = 0; /* always default to 0 */ + + /* Handle retry limits */ + if (txrate[0].count <= rr_retry_limit) { + txrate[0].count++; + rr = true; + fbr = false; + } else { + fbr = true; + rr = false; + txrate[1].count++; + } + + /* extract the length info */ + len = fbr_iscck ? BRCMS_GET_CCK_PLCP_LEN(txh->FragPLCPFallback) + : BRCMS_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback); + + /* retrieve null delimiter count */ + ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM]; + seg_cnt += 1; + + BCMMSG(wlc->wiphy, "wl%d: mpdu %d plcp_len %d\n", + wlc->pub->unit, count, len); + + /* + * aggregateable mpdu. For ucode/hw agg, + * test whether need to break or change the epoch + */ + if (count == 0) { + mcl |= (TXC_AMPDU_FIRST << TXC_AMPDU_SHIFT); + /* refill the bits since might be a retx mpdu */ + mcl |= TXC_STARTMSDU; + rts = (struct ieee80211_rts *)&txh->rts_frame; + + if (ieee80211_is_rts(rts->frame_control)) { + mcl |= TXC_SENDRTS; + use_rts = true; + } + if (ieee80211_is_cts(rts->frame_control)) { + mcl |= TXC_SENDCTS; + use_cts = true; + } + } else { + mcl |= (TXC_AMPDU_MIDDLE << TXC_AMPDU_SHIFT); + mcl &= ~(TXC_STARTMSDU | TXC_SENDRTS | TXC_SENDCTS); + } + + len = roundup(len, 4); + ampdu_len += (len + (ndelim + 1) * AMPDU_DELIMITER_LEN); + + dma_len += (u16) brcmu_pkttotlen(p); + + BCMMSG(wlc->wiphy, "wl%d: ampdu_len %d" + " seg_cnt %d null delim %d\n", + wlc->pub->unit, ampdu_len, seg_cnt, ndelim); + + txh->MacTxControlLow = cpu_to_le16(mcl); + + /* this packet is added */ + pkt[count++] = p; + + /* patch the first MPDU */ + if (count == 1) { + u8 plcp0, plcp3, is40, sgi; + struct ieee80211_sta *sta; + + sta = tx_info->control.sta; + + if (rr) { + plcp0 = plcp[0]; + plcp3 = plcp[3]; + } else { + plcp0 = txh->FragPLCPFallback[0]; + plcp3 = txh->FragPLCPFallback[3]; + + } + is40 = (plcp0 & MIMO_PLCP_40MHZ) ? 1 : 0; + sgi = plcp3_issgi(plcp3) ? 1 : 0; + mcs = plcp0 & ~MIMO_PLCP_40MHZ; + max_ampdu_bytes = + min(scb_ampdu->max_rx_ampdu_bytes, + ampdu->max_txlen[mcs][is40][sgi]); + + if (is40) + mimo_ctlchbw = + CHSPEC_SB_UPPER(wlc_phy_chanspec_get( + wlc->band->pi)) + ? PHY_TXC1_BW_20MHZ_UP : PHY_TXC1_BW_20MHZ; + + /* rebuild the rspec and rspec_fallback */ + rspec = RSPEC_MIMORATE; + rspec |= plcp[0] & ~MIMO_PLCP_40MHZ; + if (plcp[0] & MIMO_PLCP_40MHZ) + rspec |= (PHY_TXC1_BW_40MHZ << RSPEC_BW_SHIFT); + + if (fbr_iscck) /* CCK */ + rspec_fallback = cck_rspec(cck_phy2mac_rate + (txh->FragPLCPFallback[0])); + else { /* MIMO */ + rspec_fallback = RSPEC_MIMORATE; + rspec_fallback |= + txh->FragPLCPFallback[0] & ~MIMO_PLCP_40MHZ; + if (txh->FragPLCPFallback[0] & MIMO_PLCP_40MHZ) + rspec_fallback |= + (PHY_TXC1_BW_40MHZ << + RSPEC_BW_SHIFT); + } + + if (use_rts || use_cts) { + rts_rspec = + brcms_c_rspec_to_rts_rspec(wlc, + rspec, false, mimo_ctlchbw); + rts_rspec_fallback = + brcms_c_rspec_to_rts_rspec(wlc, + rspec_fallback, false, mimo_ctlchbw); + } + } + + /* if (first mpdu for host agg) */ + /* test whether to add more */ + if ((mcs_2_rate(mcs, true, false) >= f->dmaxferrate) && + (count == f->mcs2ampdu_table[mcs])) { + BCMMSG(wlc->wiphy, "wl%d: PR 37644: stopping" + " ampdu at %d for mcs %d\n", + wlc->pub->unit, count, mcs); + break; + } + + if (count == scb_ampdu->max_pdu) + break; + + /* + * check to see if the next pkt is + * a candidate for aggregation + */ + p = pktq_ppeek(&qi->q, prec); + /* tx_info must be checked with current p */ + tx_info = IEEE80211_SKB_CB(p); + + if (p) { + if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) && + ((u8) (p->priority) == tid)) { + + plen = brcmu_pkttotlen(p) + + AMPDU_MAX_MPDU_OVERHEAD; + plen = max(scb_ampdu->min_len, plen); + + if ((plen + ampdu_len) > max_ampdu_bytes) { + p = NULL; + continue; + } + + /* + * check if there are enough + * descriptors available + */ + if (*wlc->core->txavail[fifo] <= seg_cnt + 1) { + wiphy_err(wiphy, "%s: No fifo space " + "!!\n", __func__); + p = NULL; + continue; + } + p = brcmu_pktq_pdeq(&qi->q, prec); + } else { + p = NULL; + } + } + } /* end while(p) */ + + ini->tx_in_transit += count; + + if (count) { + /* patch up the last txh */ + txh = (struct d11txh *) pkt[count - 1]->data; + mcl = le16_to_cpu(txh->MacTxControlLow); + mcl &= ~TXC_AMPDU_MASK; + mcl |= (TXC_AMPDU_LAST << TXC_AMPDU_SHIFT); + txh->MacTxControlLow = cpu_to_le16(mcl); + + /* remove the null delimiter after last mpdu */ + ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM]; + txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM] = 0; + ampdu_len -= ndelim * AMPDU_DELIMITER_LEN; + + /* remove the pad len from last mpdu */ + fbr_iscck = ((le16_to_cpu(txh->XtraFrameTypes) & 0x3) == 0); + len = fbr_iscck ? BRCMS_GET_CCK_PLCP_LEN(txh->FragPLCPFallback) + : BRCMS_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback); + ampdu_len -= roundup(len, 4) - len; + + /* patch up the first txh & plcp */ + txh = (struct d11txh *) pkt[0]->data; + plcp = (u8 *) (txh + 1); + + BRCMS_SET_MIMO_PLCP_LEN(plcp, ampdu_len); + /* mark plcp to indicate ampdu */ + BRCMS_SET_MIMO_PLCP_AMPDU(plcp); + + /* reset the mixed mode header durations */ + if (txh->MModeLen) { + u16 mmodelen = + brcms_c_calc_lsig_len(wlc, rspec, ampdu_len); + txh->MModeLen = cpu_to_le16(mmodelen); + preamble_type = BRCMS_MM_PREAMBLE; + } + if (txh->MModeFbrLen) { + u16 mmfbrlen = + brcms_c_calc_lsig_len(wlc, rspec_fallback, + ampdu_len); + txh->MModeFbrLen = cpu_to_le16(mmfbrlen); + fbr_preamble_type = BRCMS_MM_PREAMBLE; + } + + /* set the preload length */ + if (mcs_2_rate(mcs, true, false) >= f->dmaxferrate) { + dma_len = min(dma_len, f->ampdu_pld_size); + txh->PreloadSize = cpu_to_le16(dma_len); + } else + txh->PreloadSize = 0; + + mch = le16_to_cpu(txh->MacTxControlHigh); + + /* update RTS dur fields */ + if (use_rts || use_cts) { + u16 durid; + rts = (struct ieee80211_rts *)&txh->rts_frame; + if ((mch & TXC_PREAMBLE_RTS_MAIN_SHORT) == + TXC_PREAMBLE_RTS_MAIN_SHORT) + rts_preamble_type = BRCMS_SHORT_PREAMBLE; + + if ((mch & TXC_PREAMBLE_RTS_FB_SHORT) == + TXC_PREAMBLE_RTS_FB_SHORT) + rts_fbr_preamble_type = BRCMS_SHORT_PREAMBLE; + + durid = + brcms_c_compute_rtscts_dur(wlc, use_cts, rts_rspec, + rspec, rts_preamble_type, + preamble_type, ampdu_len, + true); + rts->duration = cpu_to_le16(durid); + durid = brcms_c_compute_rtscts_dur(wlc, use_cts, + rts_rspec_fallback, + rspec_fallback, + rts_fbr_preamble_type, + fbr_preamble_type, + ampdu_len, true); + txh->RTSDurFallback = cpu_to_le16(durid); + /* set TxFesTimeNormal */ + txh->TxFesTimeNormal = rts->duration; + /* set fallback rate version of TxFesTimeNormal */ + txh->TxFesTimeFallback = txh->RTSDurFallback; + } + + /* set flag and plcp for fallback rate */ + if (fbr) { + mch |= TXC_AMPDU_FBR; + txh->MacTxControlHigh = cpu_to_le16(mch); + BRCMS_SET_MIMO_PLCP_AMPDU(plcp); + BRCMS_SET_MIMO_PLCP_AMPDU(txh->FragPLCPFallback); + } + + BCMMSG(wlc->wiphy, "wl%d: count %d ampdu_len %d\n", + wlc->pub->unit, count, ampdu_len); + + /* inform rate_sel if it this is a rate probe pkt */ + frameid = le16_to_cpu(txh->TxFrameID); + if (frameid & TXFID_RATE_PROBE_MASK) + wiphy_err(wiphy, "%s: XXX what to do with " + "TXFID_RATE_PROBE_MASK!?\n", __func__); + + for (i = 0; i < count; i++) + brcms_c_txfifo(wlc, fifo, pkt[i], i == (count - 1), + ampdu->txpkt_weight); + + } + /* endif (count) */ + return err; +} + +static void +brcms_c_ampdu_rate_status(struct brcms_c_info *wlc, + struct ieee80211_tx_info *tx_info, + struct tx_status *txs, u8 mcs) +{ + struct ieee80211_tx_rate *txrate = tx_info->status.rates; + int i; + + /* clear the rest of the rates */ + for (i = 2; i < IEEE80211_TX_MAX_RATES; i++) { + txrate[i].idx = -1; + txrate[i].count = 0; + } +} + +static void +brcms_c_ampdu_dotxstatus_complete(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs, + u32 s1, u32 s2) +{ + struct scb_ampdu *scb_ampdu; + struct brcms_c_info *wlc = ampdu->wlc; + struct scb_ampdu_tid_ini *ini; + u8 bitmap[8], queue, tid; + struct d11txh *txh; + u8 *plcp; + struct ieee80211_hdr *h; + u16 seq, start_seq = 0, bindex, index, mcl; + u8 mcs = 0; + bool ba_recd = false, ack_recd = false; + u8 suc_mpdu = 0, tot_mpdu = 0; + uint supr_status; + bool update_rate = true, retry = true, tx_error = false; + u16 mimoantsel = 0; + u8 antselid = 0; + u8 retry_limit, rr_retry_limit; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(p); + struct wiphy *wiphy = wlc->wiphy; + +#ifdef BCMDBG + u8 hole[AMPDU_MAX_MPDU]; + memset(hole, 0, sizeof(hole)); +#endif + + scb_ampdu = &scb->scb_ampdu; + tid = (u8) (p->priority); + + ini = &scb_ampdu->ini[tid]; + retry_limit = ampdu->retry_limit_tid[tid]; + rr_retry_limit = ampdu->rr_retry_limit_tid[tid]; + memset(bitmap, 0, sizeof(bitmap)); + queue = txs->frameid & TXFID_QUEUE_MASK; + supr_status = txs->status & TX_STATUS_SUPR_MASK; + + if (txs->status & TX_STATUS_ACK_RCV) { + if (TX_STATUS_SUPR_UF == supr_status) + update_rate = false; + + WARN_ON(!(txs->status & TX_STATUS_INTERMEDIATE)); + start_seq = txs->sequence >> SEQNUM_SHIFT; + bitmap[0] = (txs->status & TX_STATUS_BA_BMAP03_MASK) >> + TX_STATUS_BA_BMAP03_SHIFT; + + WARN_ON(s1 & TX_STATUS_INTERMEDIATE); + WARN_ON(!(s1 & TX_STATUS_AMPDU)); + + bitmap[0] |= + (s1 & TX_STATUS_BA_BMAP47_MASK) << + TX_STATUS_BA_BMAP47_SHIFT; + bitmap[1] = (s1 >> 8) & 0xff; + bitmap[2] = (s1 >> 16) & 0xff; + bitmap[3] = (s1 >> 24) & 0xff; + + bitmap[4] = s2 & 0xff; + bitmap[5] = (s2 >> 8) & 0xff; + bitmap[6] = (s2 >> 16) & 0xff; + bitmap[7] = (s2 >> 24) & 0xff; + + ba_recd = true; + } else { + if (supr_status) { + update_rate = false; + if (supr_status == TX_STATUS_SUPR_BADCH) { + wiphy_err(wiphy, "%s: Pkt tx suppressed, " + "illegal channel possibly %d\n", + __func__, CHSPEC_CHANNEL( + wlc->default_bss->chanspec)); + } else { + if (supr_status != TX_STATUS_SUPR_FRAG) + wiphy_err(wiphy, "%s:" + "supr_status 0x%x\n", + __func__, supr_status); + } + /* no need to retry for badch; will fail again */ + if (supr_status == TX_STATUS_SUPR_BADCH || + supr_status == TX_STATUS_SUPR_EXPTIME) { + retry = false; + } else if (supr_status == TX_STATUS_SUPR_EXPTIME) { + /* TX underflow: + * try tuning pre-loading or ampdu size + */ + } else if (supr_status == TX_STATUS_SUPR_FRAG) { + /* + * if there were underflows, but pre-loading + * is not active, notify rate adaptation. + */ + if (brcms_c_ffpld_check_txfunfl(wlc, + prio2fifo[tid]) > 0) + tx_error = true; + } + } else if (txs->phyerr) { + update_rate = false; + wiphy_err(wiphy, "wl%d: ampdu tx phy " + "error (0x%x)\n", wlc->pub->unit, + txs->phyerr); + + if (brcm_msg_level & LOG_ERROR_VAL) { + brcmu_prpkt("txpkt (AMPDU)", p); + brcms_c_print_txdesc((struct d11txh *) p->data); + } + brcms_c_print_txstatus(txs); + } + } + + /* loop through all pkts and retry if not acked */ + while (p) { + tx_info = IEEE80211_SKB_CB(p); + txh = (struct d11txh *) p->data; + mcl = le16_to_cpu(txh->MacTxControlLow); + plcp = (u8 *) (txh + 1); + h = (struct ieee80211_hdr *)(plcp + D11_PHY_HDR_LEN); + seq = le16_to_cpu(h->seq_ctrl) >> SEQNUM_SHIFT; + + if (tot_mpdu == 0) { + mcs = plcp[0] & MIMO_PLCP_MCS_MASK; + mimoantsel = le16_to_cpu(txh->ABI_MimoAntSel); + } + + index = TX_SEQ_TO_INDEX(seq); + ack_recd = false; + if (ba_recd) { + bindex = MODSUB_POW2(seq, start_seq, SEQNUM_MAX); + BCMMSG(wlc->wiphy, "tid %d seq %d," + " start_seq %d, bindex %d set %d, index %d\n", + tid, seq, start_seq, bindex, + isset(bitmap, bindex), index); + /* if acked then clear bit and free packet */ + if ((bindex < AMPDU_TX_BA_MAX_WSIZE) + && isset(bitmap, bindex)) { + ini->tx_in_transit--; + ini->txretry[index] = 0; + + /* + * ampdu_ack_len: + * number of acked aggregated frames + */ + /* ampdu_len: number of aggregated frames */ + brcms_c_ampdu_rate_status(wlc, tx_info, txs, + mcs); + tx_info->flags |= IEEE80211_TX_STAT_ACK; + tx_info->flags |= IEEE80211_TX_STAT_AMPDU; + tx_info->status.ampdu_ack_len = + tx_info->status.ampdu_len = 1; + + skb_pull(p, D11_PHY_HDR_LEN); + skb_pull(p, D11_TXH_LEN); + + ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw, + p); + ack_recd = true; + suc_mpdu++; + } + } + /* either retransmit or send bar if ack not recd */ + if (!ack_recd) { + struct ieee80211_tx_rate *txrate = + tx_info->status.rates; + if (retry && (txrate[0].count < (int)retry_limit)) { + ini->txretry[index]++; + ini->tx_in_transit--; + /* + * Use high prededence for retransmit to + * give some punch + */ + /* brcms_c_txq_enq(wlc, scb, p, + * BRCMS_PRIO_TO_PREC(tid)); */ + brcms_c_txq_enq(wlc, scb, p, + BRCMS_PRIO_TO_HI_PREC(tid)); + } else { + /* Retry timeout */ + ini->tx_in_transit--; + ieee80211_tx_info_clear_status(tx_info); + tx_info->status.ampdu_ack_len = 0; + tx_info->status.ampdu_len = 1; + tx_info->flags |= + IEEE80211_TX_STAT_AMPDU_NO_BACK; + skb_pull(p, D11_PHY_HDR_LEN); + skb_pull(p, D11_TXH_LEN); + wiphy_err(wiphy, "%s: BA Timeout, seq %d, in_" + "transit %d\n", "AMPDU status", seq, + ini->tx_in_transit); + ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw, + p); + } + } + tot_mpdu++; + + /* break out if last packet of ampdu */ + if (((mcl & TXC_AMPDU_MASK) >> TXC_AMPDU_SHIFT) == + TXC_AMPDU_LAST) + break; + + p = dma_getnexttxp(wlc->hw->di[queue], DMA_RANGE_TRANSMITTED); + } + brcms_c_send_q(wlc); + + /* update rate state */ + antselid = brcms_c_antsel_antsel2id(wlc->asi, mimoantsel); + + brcms_c_txfifo_complete(wlc, queue, ampdu->txpkt_weight); +} + +void +brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs) +{ + struct scb_ampdu *scb_ampdu; + struct brcms_c_info *wlc = ampdu->wlc; + struct scb_ampdu_tid_ini *ini; + u32 s1 = 0, s2 = 0; + struct ieee80211_tx_info *tx_info; + + tx_info = IEEE80211_SKB_CB(p); + + /* BMAC_NOTE: For the split driver, second level txstatus comes later + * So if the ACK was received then wait for the second level else just + * call the first one + */ + if (txs->status & TX_STATUS_ACK_RCV) { + u8 status_delay = 0; + + /* wait till the next 8 bytes of txstatus is available */ + while (((s1 = R_REG(&wlc->regs->frmtxstatus)) & TXS_V) == 0) { + udelay(1); + status_delay++; + if (status_delay > 10) + return; /* error condition */ + } + + s2 = R_REG(&wlc->regs->frmtxstatus2); + } + + if (scb) { + scb_ampdu = &scb->scb_ampdu; + ini = &scb_ampdu->ini[p->priority]; + brcms_c_ampdu_dotxstatus_complete(ampdu, scb, p, txs, s1, s2); + } else { + /* loop through all pkts and free */ + u8 queue = txs->frameid & TXFID_QUEUE_MASK; + struct d11txh *txh; + u16 mcl; + while (p) { + tx_info = IEEE80211_SKB_CB(p); + txh = (struct d11txh *) p->data; + mcl = le16_to_cpu(txh->MacTxControlLow); + brcmu_pkt_buf_free_skb(p); + /* break out if last packet of ampdu */ + if (((mcl & TXC_AMPDU_MASK) >> TXC_AMPDU_SHIFT) == + TXC_AMPDU_LAST) + break; + p = dma_getnexttxp(wlc->hw->di[queue], + DMA_RANGE_TRANSMITTED); + } + brcms_c_txfifo_complete(wlc, queue, ampdu->txpkt_weight); + } +} + +void brcms_c_ampdu_macaddr_upd(struct brcms_c_info *wlc) +{ + char template[T_RAM_ACCESS_SZ * 2]; + + /* driver needs to write the ta in the template; ta is at offset 16 */ + memset(template, 0, sizeof(template)); + memcpy(template, wlc->pub->cur_etheraddr, ETH_ALEN); + brcms_b_write_template_ram(wlc->hw, (T_BA_TPL_BASE + 16), + (T_RAM_ACCESS_SZ * 2), + template); +} + +bool brcms_c_aggregatable(struct brcms_c_info *wlc, u8 tid) +{ + return wlc->ampdu->ini_enable[tid]; +} + +void brcms_c_ampdu_shm_upd(struct ampdu_info *ampdu) +{ + struct brcms_c_info *wlc = ampdu->wlc; + + /* + * Extend ucode internal watchdog timer to + * match larger received frames + */ + if ((ampdu->rx_factor & IEEE80211_HT_AMPDU_PARM_FACTOR) == + IEEE80211_HT_MAX_AMPDU_64K) { + brcms_b_write_shm(wlc->hw, M_MIMO_MAXSYM, MIMO_MAXSYM_MAX); + brcms_b_write_shm(wlc->hw, M_WATCHDOG_8TU, WATCHDOG_8TU_MAX); + } else { + brcms_b_write_shm(wlc->hw, M_MIMO_MAXSYM, MIMO_MAXSYM_DEF); + brcms_b_write_shm(wlc->hw, M_WATCHDOG_8TU, WATCHDOG_8TU_DEF); + } +} + +/* + * callback function that helps flushing ampdu packets from a priority queue + */ +static bool cb_del_ampdu_pkt(struct sk_buff *mpdu, void *arg_a) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(mpdu); + struct cb_del_ampdu_pars *ampdu_pars = + (struct cb_del_ampdu_pars *)arg_a; + bool rc; + + rc = tx_info->flags & IEEE80211_TX_CTL_AMPDU ? true : false; + rc = rc && (tx_info->control.sta == NULL || ampdu_pars->sta == NULL || + tx_info->control.sta == ampdu_pars->sta); + rc = rc && ((u8)(mpdu->priority) == ampdu_pars->tid); + return rc; +} + +/* + * callback function that helps invalidating ampdu packets in a DMA queue + */ +static void dma_cb_fn_ampdu(void *txi, void *arg_a) +{ + struct ieee80211_sta *sta = arg_a; + struct ieee80211_tx_info *tx_info = (struct ieee80211_tx_info *)txi; + + if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) && + (tx_info->control.sta == sta || sta == NULL)) + tx_info->control.sta = NULL; +} + +/* + * When a remote party is no longer available for ampdu communication, any + * pending tx ampdu packets in the driver have to be flushed. + */ +void brcms_c_ampdu_flush(struct brcms_c_info *wlc, + struct ieee80211_sta *sta, u16 tid) +{ + struct brcms_txq_info *qi = wlc->pkt_queue; + struct pktq *pq = &qi->q; + int prec; + struct cb_del_ampdu_pars ampdu_pars; + + ampdu_pars.sta = sta; + ampdu_pars.tid = tid; + for (prec = 0; prec < pq->num_prec; prec++) + brcmu_pktq_pflush(pq, prec, true, cb_del_ampdu_pkt, + (void *)&du_pars); + brcms_c_inval_dma_pkts(wlc->hw, sta, dma_cb_fn_ampdu); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h new file mode 100644 index 000000000000..421f4ba7c63c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_AMPDU_H_ +#define _BRCM_AMPDU_H_ + +extern struct ampdu_info *brcms_c_ampdu_attach(struct brcms_c_info *wlc); +extern void brcms_c_ampdu_detach(struct ampdu_info *ampdu); +extern int brcms_c_sendampdu(struct ampdu_info *ampdu, + struct brcms_txq_info *qi, + struct sk_buff **aggp, int prec); +extern void brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs); +extern void brcms_c_ampdu_macaddr_upd(struct brcms_c_info *wlc); +extern void brcms_c_ampdu_shm_upd(struct ampdu_info *ampdu); + +#endif /* _BRCM_AMPDU_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/antsel.c b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c new file mode 100644 index 000000000000..a47ce25cb9a2 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/slab.h> +#include <net/mac80211.h> + +#include "types.h" +#include "main.h" +#include "phy_shim.h" +#include "antsel.h" + +#define ANT_SELCFG_AUTO 0x80 /* bit indicates antenna sel AUTO */ +#define ANT_SELCFG_MASK 0x33 /* antenna configuration mask */ +#define ANT_SELCFG_TX_UNICAST 0 /* unicast tx antenna configuration */ +#define ANT_SELCFG_RX_UNICAST 1 /* unicast rx antenna configuration */ +#define ANT_SELCFG_TX_DEF 2 /* default tx antenna configuration */ +#define ANT_SELCFG_RX_DEF 3 /* default rx antenna configuration */ + +/* useful macros */ +#define BRCMS_ANTSEL_11N_0(ant) ((((ant) & ANT_SELCFG_MASK) >> 4) & 0xf) +#define BRCMS_ANTSEL_11N_1(ant) (((ant) & ANT_SELCFG_MASK) & 0xf) +#define BRCMS_ANTIDX_11N(ant) (((BRCMS_ANTSEL_11N_0(ant)) << 2) +\ + (BRCMS_ANTSEL_11N_1(ant))) +#define BRCMS_ANT_ISAUTO_11N(ant) (((ant) & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) +#define BRCMS_ANTSEL_11N(ant) ((ant) & ANT_SELCFG_MASK) + +/* antenna switch */ +/* defines for no boardlevel antenna diversity */ +#define ANT_SELCFG_DEF_2x2 0x01 /* default antenna configuration */ + +/* 2x3 antdiv defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x3 3 +#define ANT_SELCFG_DEF_2x3 0x01 /* default antenna configuration */ + +/* 2x4 antdiv rev4 defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x4 4 +#define ANT_SELCFG_DEF_2x4 0x02 /* default antenna configuration */ + +static const u16 mimo_2x4_div_antselpat_tbl[] = { + 0, 0, 0x9, 0xa, /* ant0: 0 ant1: 2,3 */ + 0, 0, 0x5, 0x6, /* ant0: 1 ant1: 2,3 */ + 0, 0, 0, 0, /* n.a. */ + 0, 0, 0, 0 /* n.a. */ +}; + +static const u8 mimo_2x4_div_antselid_tbl[16] = { + 0, 0, 0, 0, 0, 2, 3, 0, + 0, 0, 1, 0, 0, 0, 0, 0 /* pat to antselid */ +}; + +static const u16 mimo_2x3_div_antselpat_tbl[] = { + 16, 0, 1, 16, /* ant0: 0 ant1: 1,2 */ + 16, 16, 16, 16, /* n.a. */ + 16, 2, 16, 16, /* ant0: 2 ant1: 1 */ + 16, 16, 16, 16 /* n.a. */ +}; + +static const u8 mimo_2x3_div_antselid_tbl[16] = { + 0, 1, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 /* pat to antselid */ +}; + +/* boardlevel antenna selection: init antenna selection structure */ +static void +brcms_c_antsel_init_cfg(struct antsel_info *asi, struct brcms_antselcfg *antsel, + bool auto_sel) +{ + if (asi->antsel_type == ANTSEL_2x3) { + u8 antcfg_def = ANT_SELCFG_DEF_2x3 | + ((asi->antsel_avail && auto_sel) ? ANT_SELCFG_AUTO : 0); + antsel->ant_config[ANT_SELCFG_TX_DEF] = antcfg_def; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = antcfg_def; + antsel->ant_config[ANT_SELCFG_RX_DEF] = antcfg_def; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = antcfg_def; + antsel->num_antcfg = ANT_SELCFG_NUM_2x3; + + } else if (asi->antsel_type == ANTSEL_2x4) { + + antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x4; + antsel->num_antcfg = ANT_SELCFG_NUM_2x4; + + } else { /* no antenna selection available */ + + antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x2; + antsel->num_antcfg = 0; + } +} + +struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc) +{ + struct antsel_info *asi; + struct si_pub *sih = wlc->hw->sih; + + asi = kzalloc(sizeof(struct antsel_info), GFP_ATOMIC); + if (!asi) + return NULL; + + asi->wlc = wlc; + asi->pub = wlc->pub; + asi->antsel_type = ANTSEL_NA; + asi->antsel_avail = false; + asi->antsel_antswitch = (u8) getintvar(sih, BRCMS_SROM_ANTSWITCH); + + if ((asi->pub->sromrev >= 4) && (asi->antsel_antswitch != 0)) { + switch (asi->antsel_antswitch) { + case ANTSWITCH_TYPE_1: + case ANTSWITCH_TYPE_2: + case ANTSWITCH_TYPE_3: + /* 4321/2 board with 2x3 switch logic */ + asi->antsel_type = ANTSEL_2x3; + /* Antenna selection availability */ + if (((u16) getintvar(sih, BRCMS_SROM_AA2G) == 7) || + ((u16) getintvar(sih, BRCMS_SROM_AA5G) == 7)) { + asi->antsel_avail = true; + } else if ( + (u16) getintvar(sih, BRCMS_SROM_AA2G) == 3 || + (u16) getintvar(sih, BRCMS_SROM_AA5G) == 3) { + asi->antsel_avail = false; + } else { + asi->antsel_avail = false; + wiphy_err(wlc->wiphy, "antsel_attach: 2o3 " + "board cfg invalid\n"); + } + + break; + default: + break; + } + } else if ((asi->pub->sromrev == 4) && + ((u16) getintvar(sih, BRCMS_SROM_AA2G) == 7) && + ((u16) getintvar(sih, BRCMS_SROM_AA5G) == 0)) { + /* hack to match old 4321CB2 cards with 2of3 antenna switch */ + asi->antsel_type = ANTSEL_2x3; + asi->antsel_avail = true; + } else if (asi->pub->boardflags2 & BFL2_2X4_DIV) { + asi->antsel_type = ANTSEL_2x4; + asi->antsel_avail = true; + } + + /* Set the antenna selection type for the low driver */ + brcms_b_antsel_type_set(wlc->hw, asi->antsel_type); + + /* Init (auto/manual) antenna selection */ + brcms_c_antsel_init_cfg(asi, &asi->antcfg_11n, true); + brcms_c_antsel_init_cfg(asi, &asi->antcfg_cur, true); + + return asi; +} + +void brcms_c_antsel_detach(struct antsel_info *asi) +{ + kfree(asi); +} + +/* + * boardlevel antenna selection: + * convert ant_cfg to mimo_antsel (ucode interface) + */ +static u16 brcms_c_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg) +{ + u8 idx = BRCMS_ANTIDX_11N(BRCMS_ANTSEL_11N(ant_cfg)); + u16 mimo_antsel = 0; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + mimo_antsel = (mimo_2x4_div_antselpat_tbl[idx] & 0xf); + return mimo_antsel; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + mimo_antsel = (mimo_2x3_div_antselpat_tbl[idx] & 0xf); + return mimo_antsel; + } + + return mimo_antsel; +} + +/* boardlevel antenna selection: ucode interface control */ +static int brcms_c_antsel_cfgupd(struct antsel_info *asi, + struct brcms_antselcfg *antsel) +{ + struct brcms_c_info *wlc = asi->wlc; + u8 ant_cfg; + u16 mimo_antsel; + + /* 1) Update TX antconfig for all frames that are not unicast data + * (aka default TX) + */ + ant_cfg = antsel->ant_config[ANT_SELCFG_TX_DEF]; + mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); + brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_TXDFLT, mimo_antsel); + /* + * Update driver stats for currently selected + * default tx/rx antenna config + */ + asi->antcfg_cur.ant_config[ANT_SELCFG_TX_DEF] = ant_cfg; + + /* 2) Update RX antconfig for all frames that are not unicast data + * (aka default RX) + */ + ant_cfg = antsel->ant_config[ANT_SELCFG_RX_DEF]; + mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); + brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_RXDFLT, mimo_antsel); + /* + * Update driver stats for currently selected + * default tx/rx antenna config + */ + asi->antcfg_cur.ant_config[ANT_SELCFG_RX_DEF] = ant_cfg; + + return 0; +} + +void brcms_c_antsel_init(struct antsel_info *asi) +{ + if ((asi->antsel_type == ANTSEL_2x3) || + (asi->antsel_type == ANTSEL_2x4)) + brcms_c_antsel_cfgupd(asi, &asi->antcfg_11n); +} + +/* boardlevel antenna selection: convert id to ant_cfg */ +static u8 brcms_c_antsel_id2antcfg(struct antsel_info *asi, u8 id) +{ + u8 antcfg = ANT_SELCFG_DEF_2x2; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + antcfg = (((id & 0x2) << 3) | ((id & 0x1) + 2)); + return antcfg; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + antcfg = (((id & 0x02) << 4) | ((id & 0x1) + 1)); + return antcfg; + } + + return antcfg; +} + +void +brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, bool sel, + u8 antselid, u8 fbantselid, u8 *antcfg, + u8 *fbantcfg) +{ + u8 ant; + + /* if use default, assign it and return */ + if (usedef) { + *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_DEF]; + *fbantcfg = *antcfg; + return; + } + + if (!sel) { + *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + *fbantcfg = *antcfg; + + } else { + ant = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + if ((ant & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) { + *antcfg = brcms_c_antsel_id2antcfg(asi, antselid); + *fbantcfg = brcms_c_antsel_id2antcfg(asi, fbantselid); + } else { + *antcfg = + asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + *fbantcfg = *antcfg; + } + } + return; +} + +/* boardlevel antenna selection: convert mimo_antsel (ucode interface) to id */ +u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel) +{ + u8 antselid = 0; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + antselid = mimo_2x4_div_antselid_tbl[(antsel & 0xf)]; + return antselid; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + antselid = mimo_2x3_div_antselid_tbl[(antsel & 0xf)]; + return antselid; + } + + return antselid; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/antsel.h b/drivers/net/wireless/brcm80211/brcmsmac/antsel.h new file mode 100644 index 000000000000..97ea3881a8ec --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/antsel.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_ANTSEL_H_ +#define _BRCM_ANTSEL_H_ + +extern struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc); +extern void brcms_c_antsel_detach(struct antsel_info *asi); +extern void brcms_c_antsel_init(struct antsel_info *asi); +extern void brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, + bool sel, + u8 id, u8 fbid, u8 *antcfg, + u8 *fbantcfg); +extern u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel); + +#endif /* _BRCM_ANTSEL_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c new file mode 100644 index 000000000000..52fc9eeb5fa5 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/module.h> /* bug in tracepoint.h, it should include this */ + +#ifndef __CHECKER__ +#include "mac80211_if.h" +#define CREATE_TRACE_POINTS +#include "brcms_trace_events.h" +#endif diff --git a/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h new file mode 100644 index 000000000000..27dd73eef56d --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM brcmsmac + +#if !defined(__TRACE_BRCMSMAC_H) || defined(TRACE_HEADER_MULTI_READ) + +#define __TRACE_BRCMSMAC_H + +#include <linux/tracepoint.h> +#include "mac80211_if.h" + +#ifndef CONFIG_BRCMDBG +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif + +/* + * We define a tracepoint, its arguments, its printk format and its + * 'fast binary record' layout. + */ +TRACE_EVENT(brcms_timer, + /* TPPROTO is the prototype of the function called by this tracepoint */ + TP_PROTO(struct brcms_timer *t), + /* + * TPARGS(firstarg, p) are the parameters names, same as found in the + * prototype. + */ + TP_ARGS(t), + /* + * Fast binary tracing: define the trace record via TP_STRUCT__entry(). + * You can think about it like a regular C structure local variable + * definition. + */ + TP_STRUCT__entry( + __field(uint, ms) + __field(uint, set) + __field(uint, periodic) + ), + TP_fast_assign( + __entry->ms = t->ms; + __entry->set = t->set; + __entry->periodic = t->periodic; + ), + TP_printk( + "ms=%u set=%u periodic=%u", + __entry->ms, __entry->set, __entry->periodic + ) +); + +TRACE_EVENT(brcms_dpc, + TP_PROTO(unsigned long data), + TP_ARGS(data), + TP_STRUCT__entry( + __field(unsigned long, data) + ), + TP_fast_assign( + __entry->data = data; + ), + TP_printk( + "data=%p", + (void *)__entry->data + ) +); + +#endif /* __TRACE_BRCMSMAC_H */ + +#ifdef CONFIG_BRCMDBG + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE brcms_trace_events + +#include <trace/define_trace.h> + +#endif /* CONFIG_BRCMDBG */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/channel.c b/drivers/net/wireless/brcm80211/brcmsmac/channel.c new file mode 100644 index 000000000000..a1b415da6c3a --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/channel.c @@ -0,0 +1,1565 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/types.h> +#include <net/mac80211.h> + +#include <defs.h> +#include "pub.h" +#include "phy/phy_hal.h" +#include "main.h" +#include "stf.h" +#include "channel.h" + +/* QDB() macro takes a dB value and converts to a quarter dB value */ +#define QDB(n) ((n) * BRCMS_TXPWR_DB_FACTOR) + +#define LOCALE_CHAN_01_11 (1<<0) +#define LOCALE_CHAN_12_13 (1<<1) +#define LOCALE_CHAN_14 (1<<2) +#define LOCALE_SET_5G_LOW_JP1 (1<<3) /* 34-48, step 2 */ +#define LOCALE_SET_5G_LOW_JP2 (1<<4) /* 34-46, step 4 */ +#define LOCALE_SET_5G_LOW1 (1<<5) /* 36-48, step 4 */ +#define LOCALE_SET_5G_LOW2 (1<<6) /* 52 */ +#define LOCALE_SET_5G_LOW3 (1<<7) /* 56-64, step 4 */ +#define LOCALE_SET_5G_MID1 (1<<8) /* 100-116, step 4 */ +#define LOCALE_SET_5G_MID2 (1<<9) /* 120-124, step 4 */ +#define LOCALE_SET_5G_MID3 (1<<10) /* 128 */ +#define LOCALE_SET_5G_HIGH1 (1<<11) /* 132-140, step 4 */ +#define LOCALE_SET_5G_HIGH2 (1<<12) /* 149-161, step 4 */ +#define LOCALE_SET_5G_HIGH3 (1<<13) /* 165 */ +#define LOCALE_CHAN_52_140_ALL (1<<14) +#define LOCALE_SET_5G_HIGH4 (1<<15) /* 184-216 */ + +#define LOCALE_CHAN_36_64 (LOCALE_SET_5G_LOW1 | \ + LOCALE_SET_5G_LOW2 | \ + LOCALE_SET_5G_LOW3) +#define LOCALE_CHAN_52_64 (LOCALE_SET_5G_LOW2 | LOCALE_SET_5G_LOW3) +#define LOCALE_CHAN_100_124 (LOCALE_SET_5G_MID1 | LOCALE_SET_5G_MID2) +#define LOCALE_CHAN_100_140 (LOCALE_SET_5G_MID1 | LOCALE_SET_5G_MID2 | \ + LOCALE_SET_5G_MID3 | LOCALE_SET_5G_HIGH1) +#define LOCALE_CHAN_149_165 (LOCALE_SET_5G_HIGH2 | LOCALE_SET_5G_HIGH3) +#define LOCALE_CHAN_184_216 LOCALE_SET_5G_HIGH4 + +#define LOCALE_CHAN_01_14 (LOCALE_CHAN_01_11 | \ + LOCALE_CHAN_12_13 | \ + LOCALE_CHAN_14) + +#define LOCALE_RADAR_SET_NONE 0 +#define LOCALE_RADAR_SET_1 1 + +#define LOCALE_RESTRICTED_NONE 0 +#define LOCALE_RESTRICTED_SET_2G_SHORT 1 +#define LOCALE_RESTRICTED_CHAN_165 2 +#define LOCALE_CHAN_ALL_5G 3 +#define LOCALE_RESTRICTED_JAPAN_LEGACY 4 +#define LOCALE_RESTRICTED_11D_2G 5 +#define LOCALE_RESTRICTED_11D_5G 6 +#define LOCALE_RESTRICTED_LOW_HI 7 +#define LOCALE_RESTRICTED_12_13_14 8 + +#define LOCALE_2G_IDX_i 0 +#define LOCALE_5G_IDX_11 0 +#define LOCALE_MIMO_IDX_bn 0 +#define LOCALE_MIMO_IDX_11n 0 + +/* max of BAND_5G_PWR_LVLS and 6 for 2.4 GHz */ +#define BRCMS_MAXPWR_TBL_SIZE 6 +/* max of BAND_5G_PWR_LVLS and 14 for 2.4 GHz */ +#define BRCMS_MAXPWR_MIMO_TBL_SIZE 14 + +/* power level in group of 2.4GHz band channels: + * maxpwr[0] - CCK channels [1] + * maxpwr[1] - CCK channels [2-10] + * maxpwr[2] - CCK channels [11-14] + * maxpwr[3] - OFDM channels [1] + * maxpwr[4] - OFDM channels [2-10] + * maxpwr[5] - OFDM channels [11-14] + */ + +/* maxpwr mapping to 5GHz band channels: + * maxpwr[0] - channels [34-48] + * maxpwr[1] - channels [52-60] + * maxpwr[2] - channels [62-64] + * maxpwr[3] - channels [100-140] + * maxpwr[4] - channels [149-165] + */ +#define BAND_5G_PWR_LVLS 5 /* 5 power levels for 5G */ + +#define LC(id) LOCALE_MIMO_IDX_ ## id + +#define LC_2G(id) LOCALE_2G_IDX_ ## id + +#define LC_5G(id) LOCALE_5G_IDX_ ## id + +#define LOCALES(band2, band5, mimo2, mimo5) \ + {LC_2G(band2), LC_5G(band5), LC(mimo2), LC(mimo5)} + +/* macro to get 2.4 GHz channel group index for tx power */ +#define CHANNEL_POWER_IDX_2G_CCK(c) (((c) < 2) ? 0 : (((c) < 11) ? 1 : 2)) +#define CHANNEL_POWER_IDX_2G_OFDM(c) (((c) < 2) ? 3 : (((c) < 11) ? 4 : 5)) + +/* macro to get 5 GHz channel group index for tx power */ +#define CHANNEL_POWER_IDX_5G(c) (((c) < 52) ? 0 : \ + (((c) < 62) ? 1 : \ + (((c) < 100) ? 2 : \ + (((c) < 149) ? 3 : 4)))) + +#define ISDFS_EU(fl) (((fl) & BRCMS_DFS_EU) == BRCMS_DFS_EU) + +struct brcms_cm_band { + /* struct locale_info flags */ + u8 locale_flags; + /* List of valid channels in the country */ + struct brcms_chanvec valid_channels; + /* List of restricted use channels */ + const struct brcms_chanvec *restricted_channels; + /* List of radar sensitive channels */ + const struct brcms_chanvec *radar_channels; + u8 PAD[8]; +}; + + /* locale per-channel tx power limits for MIMO frames + * maxpwr arrays are index by channel for 2.4 GHz limits, and + * by sub-band for 5 GHz limits using CHANNEL_POWER_IDX_5G(channel) + */ +struct locale_mimo_info { + /* tx 20 MHz power limits, qdBm units */ + s8 maxpwr20[BRCMS_MAXPWR_MIMO_TBL_SIZE]; + /* tx 40 MHz power limits, qdBm units */ + s8 maxpwr40[BRCMS_MAXPWR_MIMO_TBL_SIZE]; + u8 flags; +}; + +/* Country names and abbreviations with locale defined from ISO 3166 */ +struct country_info { + const u8 locale_2G; /* 2.4G band locale */ + const u8 locale_5G; /* 5G band locale */ + const u8 locale_mimo_2G; /* 2.4G mimo info */ + const u8 locale_mimo_5G; /* 5G mimo info */ +}; + +struct brcms_cm_info { + struct brcms_pub *pub; + struct brcms_c_info *wlc; + char srom_ccode[BRCM_CNTRY_BUF_SZ]; /* Country Code in SROM */ + uint srom_regrev; /* Regulatory Rev for the SROM ccode */ + const struct country_info *country; /* current country def */ + char ccode[BRCM_CNTRY_BUF_SZ]; /* current internal Country Code */ + uint regrev; /* current Regulatory Revision */ + char country_abbrev[BRCM_CNTRY_BUF_SZ]; /* current advertised ccode */ + /* per-band state (one per phy/radio) */ + struct brcms_cm_band bandstate[MAXBANDS]; + /* quiet channels currently for radar sensitivity or 11h support */ + /* channels on which we cannot transmit */ + struct brcms_chanvec quiet_channels; +}; + +/* locale channel and power info. */ +struct locale_info { + u32 valid_channels; + /* List of radar sensitive channels */ + u8 radar_channels; + /* List of channels used only if APs are detected */ + u8 restricted_channels; + /* Max tx pwr in qdBm for each sub-band */ + s8 maxpwr[BRCMS_MAXPWR_TBL_SIZE]; + /* Country IE advertised max tx pwr in dBm per sub-band */ + s8 pub_maxpwr[BAND_5G_PWR_LVLS]; + u8 flags; +}; + +/* Regulatory Matrix Spreadsheet (CLM) MIMO v3.7.9 */ + +/* + * Some common channel sets + */ + +/* No channels */ +static const struct brcms_chanvec chanvec_none = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* All 2.4 GHz HW channels */ +static const struct brcms_chanvec chanvec_all_2G = { + {0xfe, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* All 5 GHz HW channels */ +static const struct brcms_chanvec chanvec_all_5G = { + {0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0x11, 0x11, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x20, 0x22, 0x22, 0x00, 0x00, 0x11, + 0x11, 0x11, 0x11, 0x01} +}; + +/* + * Radar channel sets + */ + +/* Channels 52 - 64, 100 - 140 */ +static const struct brcms_chanvec radar_set1 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, /* 52 - 60 */ + 0x01, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x11, /* 64, 100 - 124 */ + 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 128 - 140 */ + 0x00, 0x00, 0x00, 0x00} +}; + +/* + * Restricted channel sets + */ + +/* Channels 34, 38, 42, 46 */ +static const struct brcms_chanvec restricted_set_japan_legacy = { + {0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* Channels 12, 13 */ +static const struct brcms_chanvec restricted_set_2g_short = { + {0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* Channel 165 */ +static const struct brcms_chanvec restricted_chan_165 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* Channels 36 - 48 & 149 - 165 */ +static const struct brcms_chanvec restricted_low_hi = { + {0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x22, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* Channels 12 - 14 */ +static const struct brcms_chanvec restricted_set_12_13_14 = { + {0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +/* global memory to provide working buffer for expanded locale */ + +static const struct brcms_chanvec *g_table_radar_set[] = { + &chanvec_none, + &radar_set1 +}; + +static const struct brcms_chanvec *g_table_restricted_chan[] = { + &chanvec_none, /* restricted_set_none */ + &restricted_set_2g_short, + &restricted_chan_165, + &chanvec_all_5G, + &restricted_set_japan_legacy, + &chanvec_all_2G, /* restricted_set_11d_2G */ + &chanvec_all_5G, /* restricted_set_11d_5G */ + &restricted_low_hi, + &restricted_set_12_13_14 +}; + +static const struct brcms_chanvec locale_2g_01_11 = { + {0xfe, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_2g_12_13 = { + {0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_2g_14 = { + {0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_LOW_JP1 = { + {0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_LOW_JP2 = { + {0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_LOW1 = { + {0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_LOW2 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_LOW3 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_MID1 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_MID2 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_MID3 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_HIGH1 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_HIGH2 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x22, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_HIGH3 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_52_140_ALL = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_chanvec locale_5g_HIGH4 = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x11, 0x11, 0x11, 0x11} +}; + +static const struct brcms_chanvec *g_table_locale_base[] = { + &locale_2g_01_11, + &locale_2g_12_13, + &locale_2g_14, + &locale_5g_LOW_JP1, + &locale_5g_LOW_JP2, + &locale_5g_LOW1, + &locale_5g_LOW2, + &locale_5g_LOW3, + &locale_5g_MID1, + &locale_5g_MID2, + &locale_5g_MID3, + &locale_5g_HIGH1, + &locale_5g_HIGH2, + &locale_5g_HIGH3, + &locale_5g_52_140_ALL, + &locale_5g_HIGH4 +}; + +static void brcms_c_locale_add_channels(struct brcms_chanvec *target, + const struct brcms_chanvec *channels) +{ + u8 i; + for (i = 0; i < sizeof(struct brcms_chanvec); i++) + target->vec[i] |= channels->vec[i]; +} + +static void brcms_c_locale_get_channels(const struct locale_info *locale, + struct brcms_chanvec *channels) +{ + u8 i; + + memset(channels, 0, sizeof(struct brcms_chanvec)); + + for (i = 0; i < ARRAY_SIZE(g_table_locale_base); i++) { + if (locale->valid_channels & (1 << i)) + brcms_c_locale_add_channels(channels, + g_table_locale_base[i]); + } +} + +/* + * Locale Definitions - 2.4 GHz + */ +static const struct locale_info locale_i = { /* locale i. channel 1 - 13 */ + LOCALE_CHAN_01_11 | LOCALE_CHAN_12_13, + LOCALE_RADAR_SET_NONE, + LOCALE_RESTRICTED_SET_2G_SHORT, + {QDB(19), QDB(19), QDB(19), + QDB(19), QDB(19), QDB(19)}, + {20, 20, 20, 0}, + BRCMS_EIRP +}; + +/* + * Locale Definitions - 5 GHz + */ +static const struct locale_info locale_11 = { + /* locale 11. channel 36 - 48, 52 - 64, 100 - 140, 149 - 165 */ + LOCALE_CHAN_36_64 | LOCALE_CHAN_100_140 | LOCALE_CHAN_149_165, + LOCALE_RADAR_SET_1, + LOCALE_RESTRICTED_NONE, + {QDB(21), QDB(21), QDB(21), QDB(21), QDB(21)}, + {23, 23, 23, 30, 30}, + BRCMS_EIRP | BRCMS_DFS_EU +}; + +static const struct locale_info *g_locale_2g_table[] = { + &locale_i +}; + +static const struct locale_info *g_locale_5g_table[] = { + &locale_11 +}; + +/* + * MIMO Locale Definitions - 2.4 GHz + */ +static const struct locale_mimo_info locale_bn = { + {QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13)}, + {0, 0, QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), 0, 0}, + 0 +}; + +static const struct locale_mimo_info *g_mimo_2g_table[] = { + &locale_bn +}; + +/* + * MIMO Locale Definitions - 5 GHz + */ +static const struct locale_mimo_info locale_11n = { + { /* 12.5 dBm */ 50, 50, 50, QDB(15), QDB(15)}, + {QDB(14), QDB(15), QDB(15), QDB(15), QDB(15)}, + 0 +}; + +static const struct locale_mimo_info *g_mimo_5g_table[] = { + &locale_11n +}; + +static const struct { + char abbrev[BRCM_CNTRY_BUF_SZ]; /* country abbreviation */ + struct country_info country; +} cntry_locales[] = { + { + "X2", LOCALES(i, 11, bn, 11n)}, /* Worldwide RoW 2 */ +}; + +#ifdef SUPPORT_40MHZ +/* 20MHz channel info for 40MHz pairing support */ +struct chan20_info { + u8 sb; + u8 adj_sbs; +}; + +/* indicates adjacent channels that are allowed for a 40 Mhz channel and + * those that permitted by the HT + */ +struct chan20_info chan20_info[] = { + /* 11b/11g */ +/* 0 */ {1, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 1 */ {2, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 2 */ {3, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 3 */ {4, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 4 */ {5, (CH_UPPER_SB | CH_LOWER_SB | CH_EWA_VALID)}, +/* 5 */ {6, (CH_UPPER_SB | CH_LOWER_SB | CH_EWA_VALID)}, +/* 6 */ {7, (CH_UPPER_SB | CH_LOWER_SB | CH_EWA_VALID)}, +/* 7 */ {8, (CH_UPPER_SB | CH_LOWER_SB | CH_EWA_VALID)}, +/* 8 */ {9, (CH_UPPER_SB | CH_LOWER_SB | CH_EWA_VALID)}, +/* 9 */ {10, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 10 */ {11, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 11 */ {12, (CH_LOWER_SB)}, +/* 12 */ {13, (CH_LOWER_SB)}, +/* 13 */ {14, (CH_LOWER_SB)}, + +/* 11a japan high */ +/* 14 */ {34, (CH_UPPER_SB)}, +/* 15 */ {38, (CH_LOWER_SB)}, +/* 16 */ {42, (CH_LOWER_SB)}, +/* 17 */ {46, (CH_LOWER_SB)}, + +/* 11a usa low */ +/* 18 */ {36, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 19 */ {40, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 20 */ {44, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 21 */ {48, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 22 */ {52, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 23 */ {56, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 24 */ {60, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 25 */ {64, (CH_LOWER_SB | CH_EWA_VALID)}, + +/* 11a Europe */ +/* 26 */ {100, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 27 */ {104, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 28 */ {108, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 29 */ {112, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 30 */ {116, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 31 */ {120, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 32 */ {124, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 33 */ {128, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 34 */ {132, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 35 */ {136, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 36 */ {140, (CH_LOWER_SB)}, + +/* 11a usa high, ref5 only */ +/* The 0x80 bit in pdiv means these are REF5, other entries are REF20 */ +/* 37 */ {149, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 38 */ {153, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 39 */ {157, (CH_UPPER_SB | CH_EWA_VALID)}, +/* 40 */ {161, (CH_LOWER_SB | CH_EWA_VALID)}, +/* 41 */ {165, (CH_LOWER_SB)}, + +/* 11a japan */ +/* 42 */ {184, (CH_UPPER_SB)}, +/* 43 */ {188, (CH_LOWER_SB)}, +/* 44 */ {192, (CH_UPPER_SB)}, +/* 45 */ {196, (CH_LOWER_SB)}, +/* 46 */ {200, (CH_UPPER_SB)}, +/* 47 */ {204, (CH_LOWER_SB)}, +/* 48 */ {208, (CH_UPPER_SB)}, +/* 49 */ {212, (CH_LOWER_SB)}, +/* 50 */ {216, (CH_LOWER_SB)} +}; +#endif /* SUPPORT_40MHZ */ + +static const struct locale_info *brcms_c_get_locale_2g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_locale_2g_table)) + return NULL; /* error condition */ + + return g_locale_2g_table[locale_idx]; +} + +static const struct locale_info *brcms_c_get_locale_5g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_locale_5g_table)) + return NULL; /* error condition */ + + return g_locale_5g_table[locale_idx]; +} + +static const struct locale_mimo_info *brcms_c_get_mimo_2g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_mimo_2g_table)) + return NULL; + + return g_mimo_2g_table[locale_idx]; +} + +static const struct locale_mimo_info *brcms_c_get_mimo_5g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_mimo_5g_table)) + return NULL; + + return g_mimo_5g_table[locale_idx]; +} + +static int +brcms_c_country_aggregate_map(struct brcms_cm_info *wlc_cm, const char *ccode, + char *mapped_ccode, uint *mapped_regrev) +{ + return false; +} + +/* Lookup a country info structure from a null terminated country + * abbreviation and regrev directly with no translation. + */ +static const struct country_info * +brcms_c_country_lookup_direct(const char *ccode, uint regrev) +{ + uint size, i; + + /* Should just return 0 for single locale driver. */ + /* Keep it this way in case we add more locales. (for now anyway) */ + + /* + * all other country def arrays are for regrev == 0, so if + * regrev is non-zero, fail + */ + if (regrev > 0) + return NULL; + + /* find matched table entry from country code */ + size = ARRAY_SIZE(cntry_locales); + for (i = 0; i < size; i++) { + if (strcmp(ccode, cntry_locales[i].abbrev) == 0) + return &cntry_locales[i].country; + } + return NULL; +} + +static const struct country_info * +brcms_c_countrycode_map(struct brcms_cm_info *wlc_cm, const char *ccode, + char *mapped_ccode, uint *mapped_regrev) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + const struct country_info *country; + uint srom_regrev = wlc_cm->srom_regrev; + const char *srom_ccode = wlc_cm->srom_ccode; + int mapped; + + /* check for currently supported ccode size */ + if (strlen(ccode) > (BRCM_CNTRY_BUF_SZ - 1)) { + wiphy_err(wlc->wiphy, "wl%d: %s: ccode \"%s\" too long for " + "match\n", wlc->pub->unit, __func__, ccode); + return NULL; + } + + /* default mapping is the given ccode and regrev 0 */ + strncpy(mapped_ccode, ccode, BRCM_CNTRY_BUF_SZ); + *mapped_regrev = 0; + + /* If the desired country code matches the srom country code, + * then the mapped country is the srom regulatory rev. + * Otherwise look for an aggregate mapping. + */ + if (!strcmp(srom_ccode, ccode)) { + *mapped_regrev = srom_regrev; + mapped = 0; + wiphy_err(wlc->wiphy, "srom_code == ccode %s\n", __func__); + } else { + mapped = + brcms_c_country_aggregate_map(wlc_cm, ccode, mapped_ccode, + mapped_regrev); + } + + /* find the matching built-in country definition */ + country = brcms_c_country_lookup_direct(mapped_ccode, *mapped_regrev); + + /* if there is not an exact rev match, default to rev zero */ + if (country == NULL && *mapped_regrev != 0) { + *mapped_regrev = 0; + country = + brcms_c_country_lookup_direct(mapped_ccode, *mapped_regrev); + } + + return country; +} + +/* Lookup a country info structure from a null terminated country code + * The lookup is case sensitive. + */ +static const struct country_info * +brcms_c_country_lookup(struct brcms_c_info *wlc, const char *ccode) +{ + const struct country_info *country; + char mapped_ccode[BRCM_CNTRY_BUF_SZ]; + uint mapped_regrev; + + /* + * map the country code to a built-in country code, regrev, and + * country_info struct + */ + country = brcms_c_countrycode_map(wlc->cmi, ccode, mapped_ccode, + &mapped_regrev); + + return country; +} + +/* + * reset the quiet channels vector to the union + * of the restricted and radar channel sets + */ +static void brcms_c_quiet_channels_reset(struct brcms_cm_info *wlc_cm) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + uint i, j; + struct brcms_band *band; + const struct brcms_chanvec *chanvec; + + memset(&wlc_cm->quiet_channels, 0, sizeof(struct brcms_chanvec)); + + band = wlc->band; + for (i = 0; i < wlc->pub->_nbands; + i++, band = wlc->bandstate[OTHERBANDUNIT(wlc)]) { + + /* initialize quiet channels for restricted channels */ + chanvec = wlc_cm->bandstate[band->bandunit].restricted_channels; + for (j = 0; j < sizeof(struct brcms_chanvec); j++) + wlc_cm->quiet_channels.vec[j] |= chanvec->vec[j]; + + } +} + +/* Is the channel valid for the current locale and current band? */ +static bool brcms_c_valid_channel20(struct brcms_cm_info *wlc_cm, uint val) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + + return ((val < MAXCHANNEL) && + isset(wlc_cm->bandstate[wlc->band->bandunit].valid_channels.vec, + val)); +} + +/* Is the channel valid for the current locale and specified band? */ +static bool brcms_c_valid_channel20_in_band(struct brcms_cm_info *wlc_cm, + uint bandunit, uint val) +{ + return ((val < MAXCHANNEL) + && isset(wlc_cm->bandstate[bandunit].valid_channels.vec, val)); +} + +/* Is the channel valid for the current locale? (but don't consider channels not + * available due to bandlocking) + */ +static bool brcms_c_valid_channel20_db(struct brcms_cm_info *wlc_cm, uint val) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + + return brcms_c_valid_channel20(wlc->cmi, val) || + (!wlc->bandlocked + && brcms_c_valid_channel20_in_band(wlc->cmi, + OTHERBANDUNIT(wlc), val)); +} + +/* JP, J1 - J10 are Japan ccodes */ +static bool brcms_c_japan_ccode(const char *ccode) +{ + return (ccode[0] == 'J' && + (ccode[1] == 'P' || (ccode[1] >= '1' && ccode[1] <= '9'))); +} + +/* Returns true if currently set country is Japan or variant */ +static bool brcms_c_japan(struct brcms_c_info *wlc) +{ + return brcms_c_japan_ccode(wlc->cmi->country_abbrev); +} + +static void +brcms_c_channel_min_txpower_limits_with_local_constraint( + struct brcms_cm_info *wlc_cm, struct txpwr_limits *txpwr, + u8 local_constraint_qdbm) +{ + int j; + + /* CCK Rates */ + for (j = 0; j < WL_TX_POWER_CCK_NUM; j++) + txpwr->cck[j] = min(txpwr->cck[j], local_constraint_qdbm); + + /* 20 MHz Legacy OFDM SISO */ + for (j = 0; j < WL_TX_POWER_OFDM_NUM; j++) + txpwr->ofdm[j] = min(txpwr->ofdm[j], local_constraint_qdbm); + + /* 20 MHz Legacy OFDM CDD */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_cdd[j] = + min(txpwr->ofdm_cdd[j], local_constraint_qdbm); + + /* 40 MHz Legacy OFDM SISO */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_40_siso[j] = + min(txpwr->ofdm_40_siso[j], local_constraint_qdbm); + + /* 40 MHz Legacy OFDM CDD */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_40_cdd[j] = + min(txpwr->ofdm_40_cdd[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 SISO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_siso[j] = + min(txpwr->mcs_20_siso[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 CDD */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_cdd[j] = + min(txpwr->mcs_20_cdd[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 STBC */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_stbc[j] = + min(txpwr->mcs_20_stbc[j], local_constraint_qdbm); + + /* 20MHz MCS 8-15 MIMO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_2_STREAM; j++) + txpwr->mcs_20_mimo[j] = + min(txpwr->mcs_20_mimo[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 SISO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_siso[j] = + min(txpwr->mcs_40_siso[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 CDD */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_cdd[j] = + min(txpwr->mcs_40_cdd[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 STBC */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_stbc[j] = + min(txpwr->mcs_40_stbc[j], local_constraint_qdbm); + + /* 40MHz MCS 8-15 MIMO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_2_STREAM; j++) + txpwr->mcs_40_mimo[j] = + min(txpwr->mcs_40_mimo[j], local_constraint_qdbm); + + /* 40MHz MCS 32 */ + txpwr->mcs32 = min(txpwr->mcs32, local_constraint_qdbm); + +} + +/* Update the radio state (enable/disable) and tx power targets + * based on a new set of channel/regulatory information + */ +static void brcms_c_channels_commit(struct brcms_cm_info *wlc_cm) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + uint chan; + struct txpwr_limits txpwr; + + /* search for the existence of any valid channel */ + for (chan = 0; chan < MAXCHANNEL; chan++) { + if (brcms_c_valid_channel20_db(wlc->cmi, chan)) + break; + } + if (chan == MAXCHANNEL) + chan = INVCHANNEL; + + /* + * based on the channel search above, set or + * clear WL_RADIO_COUNTRY_DISABLE. + */ + if (chan == INVCHANNEL) { + /* + * country/locale with no valid channels, set + * the radio disable bit + */ + mboolset(wlc->pub->radio_disabled, WL_RADIO_COUNTRY_DISABLE); + wiphy_err(wlc->wiphy, "wl%d: %s: no valid channel for \"%s\" " + "nbands %d bandlocked %d\n", wlc->pub->unit, + __func__, wlc_cm->country_abbrev, wlc->pub->_nbands, + wlc->bandlocked); + } else if (mboolisset(wlc->pub->radio_disabled, + WL_RADIO_COUNTRY_DISABLE)) { + /* + * country/locale with valid channel, clear + * the radio disable bit + */ + mboolclr(wlc->pub->radio_disabled, WL_RADIO_COUNTRY_DISABLE); + } + + /* + * Now that the country abbreviation is set, if the radio supports 2G, + * then set channel 14 restrictions based on the new locale. + */ + if (wlc->pub->_nbands > 1 || wlc->band->bandtype == BRCM_BAND_2G) + wlc_phy_chanspec_ch14_widefilter_set(wlc->band->pi, + brcms_c_japan(wlc) ? true : + false); + + if (wlc->pub->up && chan != INVCHANNEL) { + brcms_c_channel_reg_limits(wlc_cm, wlc->chanspec, &txpwr); + brcms_c_channel_min_txpower_limits_with_local_constraint(wlc_cm, + &txpwr, BRCMS_TXPWR_MAX); + wlc_phy_txpower_limit_set(wlc->band->pi, &txpwr, wlc->chanspec); + } +} + +static int +brcms_c_channels_init(struct brcms_cm_info *wlc_cm, + const struct country_info *country) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + uint i, j; + struct brcms_band *band; + const struct locale_info *li; + struct brcms_chanvec sup_chan; + const struct locale_mimo_info *li_mimo; + + band = wlc->band; + for (i = 0; i < wlc->pub->_nbands; + i++, band = wlc->bandstate[OTHERBANDUNIT(wlc)]) { + + li = (band->bandtype == BRCM_BAND_5G) ? + brcms_c_get_locale_5g(country->locale_5G) : + brcms_c_get_locale_2g(country->locale_2G); + wlc_cm->bandstate[band->bandunit].locale_flags = li->flags; + li_mimo = (band->bandtype == BRCM_BAND_5G) ? + brcms_c_get_mimo_5g(country->locale_mimo_5G) : + brcms_c_get_mimo_2g(country->locale_mimo_2G); + + /* merge the mimo non-mimo locale flags */ + wlc_cm->bandstate[band->bandunit].locale_flags |= + li_mimo->flags; + + wlc_cm->bandstate[band->bandunit].restricted_channels = + g_table_restricted_chan[li->restricted_channels]; + wlc_cm->bandstate[band->bandunit].radar_channels = + g_table_radar_set[li->radar_channels]; + + /* + * set the channel availability, masking out the channels + * that may not be supported on this phy. + */ + wlc_phy_chanspec_band_validch(band->pi, band->bandtype, + &sup_chan); + brcms_c_locale_get_channels(li, + &wlc_cm->bandstate[band->bandunit]. + valid_channels); + for (j = 0; j < sizeof(struct brcms_chanvec); j++) + wlc_cm->bandstate[band->bandunit].valid_channels. + vec[j] &= sup_chan.vec[j]; + } + + brcms_c_quiet_channels_reset(wlc_cm); + brcms_c_channels_commit(wlc_cm); + + return 0; +} + +/* + * set the driver's current country and regulatory information + * using a country code as the source. Look up built in country + * information found with the country code. + */ +static void +brcms_c_set_country_common(struct brcms_cm_info *wlc_cm, + const char *country_abbrev, + const char *ccode, uint regrev, + const struct country_info *country) +{ + const struct locale_info *locale; + struct brcms_c_info *wlc = wlc_cm->wlc; + char prev_country_abbrev[BRCM_CNTRY_BUF_SZ]; + + /* save current country state */ + wlc_cm->country = country; + + memset(&prev_country_abbrev, 0, BRCM_CNTRY_BUF_SZ); + strncpy(prev_country_abbrev, wlc_cm->country_abbrev, + BRCM_CNTRY_BUF_SZ - 1); + + strncpy(wlc_cm->country_abbrev, country_abbrev, BRCM_CNTRY_BUF_SZ - 1); + strncpy(wlc_cm->ccode, ccode, BRCM_CNTRY_BUF_SZ - 1); + wlc_cm->regrev = regrev; + + if ((wlc->pub->_n_enab & SUPPORT_11N) != + wlc->protection->nmode_user) + brcms_c_set_nmode(wlc); + + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_2G_INDEX]); + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_5G_INDEX]); + /* set or restore gmode as required by regulatory */ + locale = brcms_c_get_locale_2g(country->locale_2G); + if (locale && (locale->flags & BRCMS_NO_OFDM)) + brcms_c_set_gmode(wlc, GMODE_LEGACY_B, false); + else + brcms_c_set_gmode(wlc, wlc->protection->gmode_user, false); + + brcms_c_channels_init(wlc_cm, country); + + return; +} + +static int +brcms_c_set_countrycode_rev(struct brcms_cm_info *wlc_cm, + const char *country_abbrev, + const char *ccode, int regrev) +{ + const struct country_info *country; + char mapped_ccode[BRCM_CNTRY_BUF_SZ]; + uint mapped_regrev; + + /* if regrev is -1, lookup the mapped country code, + * otherwise use the ccode and regrev directly + */ + if (regrev == -1) { + /* + * map the country code to a built-in country + * code, regrev, and country_info + */ + country = + brcms_c_countrycode_map(wlc_cm, ccode, mapped_ccode, + &mapped_regrev); + } else { + /* find the matching built-in country definition */ + country = brcms_c_country_lookup_direct(ccode, regrev); + strncpy(mapped_ccode, ccode, BRCM_CNTRY_BUF_SZ); + mapped_regrev = regrev; + } + + if (country == NULL) + return -EINVAL; + + /* set the driver state for the country */ + brcms_c_set_country_common(wlc_cm, country_abbrev, mapped_ccode, + mapped_regrev, country); + + return 0; +} + +/* + * set the driver's current country and regulatory information using + * a country code as the source. Lookup built in country information + * found with the country code. + */ +static int +brcms_c_set_countrycode(struct brcms_cm_info *wlc_cm, const char *ccode) +{ + char country_abbrev[BRCM_CNTRY_BUF_SZ]; + strncpy(country_abbrev, ccode, BRCM_CNTRY_BUF_SZ); + return brcms_c_set_countrycode_rev(wlc_cm, country_abbrev, ccode, -1); +} + +struct brcms_cm_info *brcms_c_channel_mgr_attach(struct brcms_c_info *wlc) +{ + struct brcms_cm_info *wlc_cm; + char country_abbrev[BRCM_CNTRY_BUF_SZ]; + const struct country_info *country; + struct brcms_pub *pub = wlc->pub; + char *ccode; + + BCMMSG(wlc->wiphy, "wl%d\n", wlc->pub->unit); + + wlc_cm = kzalloc(sizeof(struct brcms_cm_info), GFP_ATOMIC); + if (wlc_cm == NULL) + return NULL; + wlc_cm->pub = pub; + wlc_cm->wlc = wlc; + wlc->cmi = wlc_cm; + + /* store the country code for passing up as a regulatory hint */ + ccode = getvar(wlc->hw->sih, BRCMS_SROM_CCODE); + if (ccode) + strncpy(wlc->pub->srom_ccode, ccode, BRCM_CNTRY_BUF_SZ - 1); + + /* + * internal country information which must match + * regulatory constraints in firmware + */ + memset(country_abbrev, 0, BRCM_CNTRY_BUF_SZ); + strncpy(country_abbrev, "X2", sizeof(country_abbrev) - 1); + country = brcms_c_country_lookup(wlc, country_abbrev); + + /* save default country for exiting 11d regulatory mode */ + strncpy(wlc->country_default, country_abbrev, BRCM_CNTRY_BUF_SZ - 1); + + /* initialize autocountry_default to driver default */ + strncpy(wlc->autocountry_default, "X2", BRCM_CNTRY_BUF_SZ - 1); + + brcms_c_set_countrycode(wlc_cm, country_abbrev); + + return wlc_cm; +} + +void brcms_c_channel_mgr_detach(struct brcms_cm_info *wlc_cm) +{ + kfree(wlc_cm); +} + +u8 +brcms_c_channel_locale_flags_in_band(struct brcms_cm_info *wlc_cm, + uint bandunit) +{ + return wlc_cm->bandstate[bandunit].locale_flags; +} + +static bool +brcms_c_quiet_chanspec(struct brcms_cm_info *wlc_cm, u16 chspec) +{ + return (wlc_cm->wlc->pub->_n_enab & SUPPORT_11N) && + CHSPEC_IS40(chspec) ? + (isset(wlc_cm->quiet_channels.vec, + lower_20_sb(CHSPEC_CHANNEL(chspec))) || + isset(wlc_cm->quiet_channels.vec, + upper_20_sb(CHSPEC_CHANNEL(chspec)))) : + isset(wlc_cm->quiet_channels.vec, CHSPEC_CHANNEL(chspec)); +} + +void +brcms_c_channel_set_chanspec(struct brcms_cm_info *wlc_cm, u16 chanspec, + u8 local_constraint_qdbm) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + struct txpwr_limits txpwr; + + brcms_c_channel_reg_limits(wlc_cm, chanspec, &txpwr); + + brcms_c_channel_min_txpower_limits_with_local_constraint( + wlc_cm, &txpwr, local_constraint_qdbm + ); + + brcms_b_set_chanspec(wlc->hw, chanspec, + (brcms_c_quiet_chanspec(wlc_cm, chanspec) != 0), + &txpwr); +} + +#ifdef POWER_DBG +static void wlc_phy_txpower_limits_dump(struct txpwr_limits *txpwr) +{ + int i; + char buf[80]; + char fraction[4][4] = { " ", ".25", ".5 ", ".75" }; + + sprintf(buf, "CCK "); + for (i = 0; i < BRCMS_NUM_RATES_CCK; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->cck[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->cck[i] % BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz OFDM SISO "); + for (i = 0; i < BRCMS_NUM_RATES_OFDM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->ofdm[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->ofdm[i] % BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz OFDM CDD "); + for (i = 0; i < BRCMS_NUM_RATES_OFDM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->ofdm_cdd[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->ofdm_cdd[i] % BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz OFDM SISO "); + for (i = 0; i < BRCMS_NUM_RATES_OFDM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->ofdm_40_siso[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->ofdm_40_siso[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz OFDM CDD "); + for (i = 0; i < BRCMS_NUM_RATES_OFDM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->ofdm_40_cdd[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->ofdm_40_cdd[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz MCS0-7 SISO "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_20_siso[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_20_siso[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz MCS0-7 CDD "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_20_cdd[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_20_cdd[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz MCS0-7 STBC "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_20_stbc[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_20_stbc[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "20 MHz MCS8-15 SDM "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_2_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_20_mimo[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_20_mimo[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz MCS0-7 SISO "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_40_siso[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_40_siso[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz MCS0-7 CDD "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_40_cdd[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_40_cdd[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz MCS0-7 STBC "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_40_stbc[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_40_stbc[i] % + BRCMS_TXPWR_DB_FACTOR]); + printk(KERN_DEBUG "%s\n", buf); + + sprintf(buf, "40 MHz MCS8-15 SDM "); + for (i = 0; i < BRCMS_NUM_RATES_MCS_2_STREAM; i++) + sprintf(buf[strlen(buf)], " %2d%s", + txpwr->mcs_40_mimo[i] / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs_40_mimo[i] % + BRCMS_TXPWR_DB_FACTOR]); + } + printk(KERN_DEBUG "%s\n", buf); + + printk(KERN_DEBUG "MCS32 %2d%s\n", + txpwr->mcs32 / BRCMS_TXPWR_DB_FACTOR, + fraction[txpwr->mcs32 % BRCMS_TXPWR_DB_FACTOR]); +} +#endif /* POWER_DBG */ + +void +brcms_c_channel_reg_limits(struct brcms_cm_info *wlc_cm, u16 chanspec, + struct txpwr_limits *txpwr) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; |