From: Stephen Rothwell Date: Fri, 14 Oct 2011 02:26:28 +0000 (+1100) Subject: Merge remote-tracking branch 'wireless/master' X-Git-Tag: next-20111014~60 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=fbab38f41e8f0cf595446873fd2a8bbc79feb7a3;p=karo-tx-linux.git Merge remote-tracking branch 'wireless/master' --- fbab38f41e8f0cf595446873fd2a8bbc79feb7a3 diff --cc drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c index 000000000000,e919de210f70..bbaeb2d5c93a mode 000000,100644..100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@@ -1,0 -1,625 +1,626 @@@ + /* + * 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 + #include + #include + #include + #include + #include + #include + #include + #include + #include /* request_irq() */ ++#include + #include + + #include + #include + #include + #include + #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 --cc drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c index 000000000000,03607cae3b88..1adc3be2c9b4 mode 000000,100644..100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c @@@ -1,0 -1,1354 +1,1355 @@@ + /* + * 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 + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include ++#include + #include + #include + #include + #include + #include + + #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 (drvr_priv->iflist[ifidx] == NULL) { + brcmf_dbg(ERROR, "null i/f %d\n", ifidx); + return ""; + } + + if (drvr_priv->iflist[ifidx]->ndev) + return drvr_priv->iflist[ifidx]->ndev->name; + + return ""; + } + + 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_rx_mode = 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 --cc drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c index 000000000000,6885755f4ec6..bd9db484fbc0 mode 000000,100644..100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@@ -1,0 -1,4581 +1,4582 @@@ + /* + * 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 + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include + #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 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 + + #include "dhd.h" + #include "dhd_bus.h" + #include "dhd_proto.h" + #include "dhd_dbg.h" + #include + + #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 "=\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 --cc drivers/net/wireless/brcm80211/brcmutil/utils.c index 000000000000,62bcc71eadf6..e96bdbf1f6d6 mode 000000,100644..100644 --- a/drivers/net/wireless/brcm80211/brcmutil/utils.c +++ b/drivers/net/wireless/brcm80211/brcmutil/utils.c @@@ -1,0 -1,600 +1,601 @@@ + /* + * 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 ++#include + #include + + MODULE_AUTHOR("Broadcom Corporation"); + MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN driver utilities."); + MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN cards"); + MODULE_LICENSE("Dual BSD/GPL"); + + struct sk_buff *brcmu_pkt_buf_get_skb(uint len) + { + struct sk_buff *skb; + + skb = dev_alloc_skb(len); + if (skb) { + skb_put(skb, len); + skb->priority = 0; + } + + return skb; + } + EXPORT_SYMBOL(brcmu_pkt_buf_get_skb); + + /* Free the driver packet. Free the tag if present */ + void brcmu_pkt_buf_free_skb(struct sk_buff *skb) + { + struct sk_buff *nskb; + int nest = 0; + + /* perversion: we use skb->next to chain multi-skb packets */ + while (skb) { + nskb = skb->next; + skb->next = NULL; + + if (skb->destructor) + /* cannot kfree_skb() on hard IRQ (net/core/skbuff.c) if + * destructor exists + */ + dev_kfree_skb_any(skb); + else + /* can free immediately (even in_irq()) if destructor + * does not exist + */ + dev_kfree_skb(skb); + + nest++; + skb = nskb; + } + } + EXPORT_SYMBOL(brcmu_pkt_buf_free_skb); + + + /* copy a buffer into a pkt buffer chain */ + uint brcmu_pktfrombuf(struct sk_buff *p, uint offset, int len, + unsigned char *buf) + { + uint n, ret = 0; + + /* skip 'offset' bytes */ + for (; p && offset; p = p->next) { + if (offset < (uint) (p->len)) + break; + offset -= p->len; + } + + if (!p) + return 0; + + /* copy the data */ + for (; p && len; p = p->next) { + n = min((uint) (p->len) - offset, (uint) len); + memcpy(p->data + offset, buf, n); + buf += n; + len -= n; + ret += n; + offset = 0; + } + + return ret; + } + EXPORT_SYMBOL(brcmu_pktfrombuf); + + /* return total length of buffer chain */ + uint brcmu_pkttotlen(struct sk_buff *p) + { + uint total; + + total = 0; + for (; p; p = p->next) + total += p->len; + return total; + } + EXPORT_SYMBOL(brcmu_pkttotlen); + + /* + * osl multiple-precedence packet queue + * hi_prec is always >= the number of the highest non-empty precedence + */ + struct sk_buff *brcmu_pktq_penq(struct pktq *pq, int prec, + struct sk_buff *p) + { + struct pktq_prec *q; + + if (pktq_full(pq) || pktq_pfull(pq, prec)) + return NULL; + + q = &pq->q[prec]; + + if (q->head) + q->tail->prev = p; + else + q->head = p; + + q->tail = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (u8) prec; + + return p; + } + EXPORT_SYMBOL(brcmu_pktq_penq); + + struct sk_buff *brcmu_pktq_penq_head(struct pktq *pq, int prec, + struct sk_buff *p) + { + struct pktq_prec *q; + + if (pktq_full(pq) || pktq_pfull(pq, prec)) + return NULL; + + q = &pq->q[prec]; + + if (q->head == NULL) + q->tail = p; + + p->prev = q->head; + q->head = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (u8) prec; + + return p; + } + EXPORT_SYMBOL(brcmu_pktq_penq_head); + + struct sk_buff *brcmu_pktq_pdeq(struct pktq *pq, int prec) + { + struct pktq_prec *q; + struct sk_buff *p; + + q = &pq->q[prec]; + + p = q->head; + if (p == NULL) + return NULL; + + q->head = p->prev; + if (q->head == NULL) + q->tail = NULL; + + q->len--; + + pq->len--; + + p->prev = NULL; + + return p; + } + EXPORT_SYMBOL(brcmu_pktq_pdeq); + + struct sk_buff *brcmu_pktq_pdeq_tail(struct pktq *pq, int prec) + { + struct pktq_prec *q; + struct sk_buff *p, *prev; + + q = &pq->q[prec]; + + p = q->head; + if (p == NULL) + return NULL; + + for (prev = NULL; p != q->tail; p = p->prev) + prev = p; + + if (prev) + prev->prev = NULL; + else + q->head = NULL; + + q->tail = prev; + q->len--; + + pq->len--; + + return p; + } + EXPORT_SYMBOL(brcmu_pktq_pdeq_tail); + + void + brcmu_pktq_pflush(struct pktq *pq, int prec, bool dir, + bool (*fn)(struct sk_buff *, void *), void *arg) + { + struct pktq_prec *q; + struct sk_buff *p, *prev = NULL; + + q = &pq->q[prec]; + p = q->head; + while (p) { + if (fn == NULL || (*fn) (p, arg)) { + bool head = (p == q->head); + if (head) + q->head = p->prev; + else + prev->prev = p->prev; + p->prev = NULL; + brcmu_pkt_buf_free_skb(p); + q->len--; + pq->len--; + p = (head ? q->head : prev->prev); + } else { + prev = p; + p = p->prev; + } + } + + if (q->head == NULL) + q->tail = NULL; + } + EXPORT_SYMBOL(brcmu_pktq_pflush); + + void brcmu_pktq_flush(struct pktq *pq, bool dir, + bool (*fn)(struct sk_buff *, void *), void *arg) + { + int prec; + for (prec = 0; prec < pq->num_prec; prec++) + brcmu_pktq_pflush(pq, prec, dir, fn, arg); + } + EXPORT_SYMBOL(brcmu_pktq_flush); + + void brcmu_pktq_init(struct pktq *pq, int num_prec, int max_len) + { + int prec; + + /* pq is variable size; only zero out what's requested */ + memset(pq, 0, + offsetof(struct pktq, q) + (sizeof(struct pktq_prec) * num_prec)); + + pq->num_prec = (u16) num_prec; + + pq->max = (u16) max_len; + + for (prec = 0; prec < num_prec; prec++) + pq->q[prec].max = pq->max; + } + EXPORT_SYMBOL(brcmu_pktq_init); + + struct sk_buff *brcmu_pktq_peek_tail(struct pktq *pq, int *prec_out) + { + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (pq->q[prec].head) + break; + + if (prec_out) + *prec_out = prec; + + return pq->q[prec].tail; + } + EXPORT_SYMBOL(brcmu_pktq_peek_tail); + + /* Return sum of lengths of a specific set of precedences */ + int brcmu_pktq_mlen(struct pktq *pq, uint prec_bmp) + { + int prec, len; + + len = 0; + + for (prec = 0; prec <= pq->hi_prec; prec++) + if (prec_bmp & (1 << prec)) + len += pq->q[prec].len; + + return len; + } + EXPORT_SYMBOL(brcmu_pktq_mlen); + + /* Priority dequeue from a specific set of precedences */ + struct sk_buff *brcmu_pktq_mdeq(struct pktq *pq, uint prec_bmp, + int *prec_out) + { + struct pktq_prec *q; + struct sk_buff *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + while ((prec_bmp & (1 << prec)) == 0 || pq->q[prec].head == NULL) + if (prec-- == 0) + return NULL; + + q = &pq->q[prec]; + + p = q->head; + if (p == NULL) + return NULL; + + q->head = p->prev; + if (q->head == NULL) + q->tail = NULL; + + q->len--; + + if (prec_out) + *prec_out = prec; + + pq->len--; + + p->prev = NULL; + + return p; + } + EXPORT_SYMBOL(brcmu_pktq_mdeq); + + #if defined(BCMDBG) + /* pretty hex print a pkt buffer chain */ + void brcmu_prpkt(const char *msg, struct sk_buff *p0) + { + struct sk_buff *p; + + if (msg && (msg[0] != '\0')) + printk(KERN_DEBUG "%s:\n", msg); + + for (p = p0; p; p = p->next) + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, p->data, p->len); + } + EXPORT_SYMBOL(brcmu_prpkt); + #endif /* defined(BCMDBG) */ + + /* + * Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag + */ + struct brcmu_tlv *brcmu_parse_tlvs(void *buf, int buflen, uint key) + { + struct brcmu_tlv *elt; + int totlen; + + elt = (struct brcmu_tlv *) buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= 2) { + int len = elt->len; + + /* validate remaining totlen */ + if ((elt->id == key) && (totlen >= (len + 2))) + return elt; + + elt = (struct brcmu_tlv *) ((u8 *) elt + (len + 2)); + totlen -= (len + 2); + } + + return NULL; + } + EXPORT_SYMBOL(brcmu_parse_tlvs); + + + #if defined(BCMDBG) + int + brcmu_format_flags(const struct brcmu_bit_desc *bd, u32 flags, char *buf, + int len) + { + int i; + char *p = buf; + char hexstr[16]; + int slen = 0, nlen = 0; + u32 bit; + const char *name; + + if (len < 2 || !buf) + return 0; + + buf[0] = '\0'; + + for (i = 0; flags != 0; i++) { + bit = bd[i].bit; + name = bd[i].name; + if (bit == 0 && flags != 0) { + /* print any unnamed bits */ + snprintf(hexstr, 16, "0x%X", flags); + name = hexstr; + flags = 0; /* exit loop */ + } else if ((flags & bit) == 0) + continue; + flags &= ~bit; + nlen = strlen(name); + slen += nlen; + /* count btwn flag space */ + if (flags != 0) + slen += 1; + /* need NULL char as well */ + if (len <= slen) + break; + /* copy NULL char but don't count it */ + strncpy(p, name, nlen + 1); + p += nlen; + /* copy btwn flag space and NULL char */ + if (flags != 0) + p += snprintf(p, 2, " "); + len -= slen; + } + + /* indicate the str was too short */ + if (flags != 0) { + if (len < 2) + p -= 2 - len; /* overwrite last char */ + p += snprintf(p, 2, ">"); + } + + return (int)(p - buf); + } + EXPORT_SYMBOL(brcmu_format_flags); + + /* + * print bytes formatted as hex to a string. return the resulting + * string length + */ + int brcmu_format_hex(char *str, const void *bytes, int len) + { + int i; + char *p = str; + const u8 *src = (const u8 *)bytes; + + for (i = 0; i < len; i++) { + p += snprintf(p, 3, "%02X", *src); + src++; + } + return (int)(p - str); + } + EXPORT_SYMBOL(brcmu_format_hex); + #endif /* defined(BCMDBG) */ + + char *brcmu_chipname(uint chipid, char *buf, uint len) + { + const char *fmt; + + fmt = ((chipid > 0xa000) || (chipid < 0x4000)) ? "%d" : "%x"; + snprintf(buf, len, fmt, chipid); + return buf; + } + EXPORT_SYMBOL(brcmu_chipname); + + uint brcmu_mkiovar(char *name, char *data, uint datalen, char *buf, uint buflen) + { + uint len; + + len = strlen(name) + 1; + + if ((len + datalen) > buflen) + return 0; + + strncpy(buf, name, buflen); + + /* append data onto the end of the name string */ + memcpy(&buf[len], data, datalen); + len += datalen; + + return len; + } + EXPORT_SYMBOL(brcmu_mkiovar); + + /* Quarter dBm units to mW + * Table starts at QDBM_OFFSET, so the first entry is mW for qdBm=153 + * Table is offset so the last entry is largest mW value that fits in + * a u16. + */ + + #define QDBM_OFFSET 153 /* Offset for first entry */ + #define QDBM_TABLE_LEN 40 /* Table size */ + + /* Smallest mW value that will round up to the first table entry, QDBM_OFFSET. + * Value is ( mW(QDBM_OFFSET - 1) + mW(QDBM_OFFSET) ) / 2 + */ + #define QDBM_TABLE_LOW_BOUND 6493 /* Low bound */ + + /* Largest mW value that will round down to the last table entry, + * QDBM_OFFSET + QDBM_TABLE_LEN-1. + * Value is ( mW(QDBM_OFFSET + QDBM_TABLE_LEN - 1) + + * mW(QDBM_OFFSET + QDBM_TABLE_LEN) ) / 2. + */ + #define QDBM_TABLE_HIGH_BOUND 64938 /* High bound */ + + static const u16 nqdBm_to_mW_map[QDBM_TABLE_LEN] = { + /* qdBm: +0 +1 +2 +3 +4 +5 +6 +7 */ + /* 153: */ 6683, 7079, 7499, 7943, 8414, 8913, 9441, 10000, + /* 161: */ 10593, 11220, 11885, 12589, 13335, 14125, 14962, 15849, + /* 169: */ 16788, 17783, 18836, 19953, 21135, 22387, 23714, 25119, + /* 177: */ 26607, 28184, 29854, 31623, 33497, 35481, 37584, 39811, + /* 185: */ 42170, 44668, 47315, 50119, 53088, 56234, 59566, 63096 + }; + + u16 brcmu_qdbm_to_mw(u8 qdbm) + { + uint factor = 1; + int idx = qdbm - QDBM_OFFSET; + + if (idx >= QDBM_TABLE_LEN) + /* clamp to max u16 mW value */ + return 0xFFFF; + + /* scale the qdBm index up to the range of the table 0-40 + * where an offset of 40 qdBm equals a factor of 10 mW. + */ + while (idx < 0) { + idx += 40; + factor *= 10; + } + + /* return the mW value scaled down to the correct factor of 10, + * adding in factor/2 to get proper rounding. + */ + return (nqdBm_to_mW_map[idx] + factor / 2) / factor; + } + EXPORT_SYMBOL(brcmu_qdbm_to_mw); + + u8 brcmu_mw_to_qdbm(u16 mw) + { + u8 qdbm; + int offset; + uint mw_uint = mw; + uint boundary; + + /* handle boundary case */ + if (mw_uint <= 1) + return 0; + + offset = QDBM_OFFSET; + + /* move mw into the range of the table */ + while (mw_uint < QDBM_TABLE_LOW_BOUND) { + mw_uint *= 10; + offset -= 40; + } + + for (qdbm = 0; qdbm < QDBM_TABLE_LEN - 1; qdbm++) { + boundary = nqdBm_to_mW_map[qdbm] + (nqdBm_to_mW_map[qdbm + 1] - + nqdBm_to_mW_map[qdbm]) / 2; + if (mw_uint < boundary) + break; + } + + qdbm += (u8) offset; + + return qdbm; + } + EXPORT_SYMBOL(brcmu_mw_to_qdbm); + + uint brcmu_bitcount(u8 *bitmap, uint length) + { + uint bitcount = 0, i; + u8 tmp; + for (i = 0; i < length; i++) { + tmp = bitmap[i]; + while (tmp) { + bitcount++; + tmp &= (tmp - 1); + } + } + return bitcount; + } + EXPORT_SYMBOL(brcmu_bitcount);