diff options
Diffstat (limited to 'drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c')
-rw-r--r-- | drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c | 1238 |
1 files changed, 1238 insertions, 0 deletions
diff --git a/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c new file mode 100644 index 0000000..f6c9c45 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@ -0,0 +1,1238 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <linux/types.h> +#include <bcmdefs.h> +#include <bcmdevs.h> +#include <bcmendian.h> +#include <bcmutils.h> +#include <osl.h> +#include <sdio.h> /* SDIO Device and Protocol Specs */ +#include <sdioh.h> /* SDIO Host Controller Specification */ +#include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */ +#include <sdiovar.h> /* ioctl/iovars */ + +#include <linux/mmc/core.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> + +#include <dngl_stats.h> +#include <dhd.h> + +#if defined(CONFIG_PM_SLEEP) +#include <linux/suspend.h> +extern volatile bool dhd_mmc_suspend; +#endif +#include "bcmsdh_sdmmc.h" + +extern int sdio_function_init(void); +extern void sdio_function_cleanup(void); + +#if !defined(OOB_INTR_ONLY) +static void IRQHandler(struct sdio_func *func); +static void IRQHandlerF2(struct sdio_func *func); +#endif /* !defined(OOB_INTR_ONLY) */ +static int sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, u32 regaddr); +extern int sdio_reset_comm(struct mmc_card *card); + +extern PBCMSDH_SDMMC_INSTANCE gInstance; + +uint sd_sdmode = SDIOH_MODE_SD4; /* Use SD4 mode by default */ +uint sd_f2_blocksize = 512; /* Default blocksize */ + +uint sd_divisor = 2; /* Default 48MHz/2 = 24MHz */ + +uint sd_power = 1; /* Default to SD Slot powered ON */ +uint sd_clock = 1; /* Default to SD Clock turned ON */ +uint sd_hiok = false; /* Don't use hi-speed mode by default */ +uint sd_msglevel = 0x01; +uint sd_use_dma = true; +DHD_PM_RESUME_WAIT_INIT(sdioh_request_byte_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_word_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_packet_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_buffer_wait); + +#define DMA_ALIGN_MASK 0x03 + +int sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, u32 regaddr, + int regsize, u32 *data); + +static int sdioh_sdmmc_card_enablefuncs(sdioh_info_t *sd) +{ + int err_ret; + u32 fbraddr; + u8 func; + + sd_trace(("%s\n", __func__)); + + /* Get the Card's common CIS address */ + sd->com_cis_ptr = sdioh_sdmmc_get_cisaddr(sd, SDIOD_CCCR_CISPTR_0); + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __func__, + sd->com_cis_ptr)); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIOD_FBR_STARTADDR, func = 1; + func <= sd->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + sd->func_cis_ptr[func] = + sdioh_sdmmc_get_cisaddr(sd, SDIOD_FBR_CISPTR_0 + fbraddr); + sd_info(("%s: Function %d CIS Ptr = 0x%x\n", __func__, func, + sd->func_cis_ptr[func])); + } + + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __func__, + sd->com_cis_ptr)); + + /* Enable Function 1 */ + sdio_claim_host(gInstance->func[1]); + err_ret = sdio_enable_func(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to enable F1 Err: 0x%08x", + err_ret)); + } + + return false; +} + +/* + * Public entry points & extern's + */ +extern sdioh_info_t *sdioh_attach(osl_t *osh, void *bar0, uint irq) +{ + sdioh_info_t *sd; + int err_ret; + + sd_trace(("%s\n", __func__)); + + if (gInstance == NULL) { + sd_err(("%s: SDIO Device not present\n", __func__)); + return NULL; + } + + sd = kzalloc(sizeof(sdioh_info_t), GFP_ATOMIC); + if (sd == NULL) { + sd_err(("sdioh_attach: out of memory\n")); + return NULL; + } + sd->osh = osh; + if (sdioh_sdmmc_osinit(sd) != 0) { + sd_err(("%s:sdioh_sdmmc_osinit() failed\n", __func__)); + kfree(sd); + return NULL; + } + + sd->num_funcs = 2; + sd->sd_blockmode = true; + sd->use_client_ints = true; + sd->client_block_size[0] = 64; + + gInstance->sd = sd; + + /* Claim host controller */ + sdio_claim_host(gInstance->func[1]); + + sd->client_block_size[1] = 64; + err_ret = sdio_set_block_size(gInstance->func[1], 64); + if (err_ret) + sd_err(("bcmsdh_sdmmc: Failed to set F1 blocksize\n")); + + /* Release host controller F1 */ + sdio_release_host(gInstance->func[1]); + + if (gInstance->func[2]) { + /* Claim host controller F2 */ + sdio_claim_host(gInstance->func[2]); + + sd->client_block_size[2] = sd_f2_blocksize; + err_ret = + sdio_set_block_size(gInstance->func[2], sd_f2_blocksize); + if (err_ret) + sd_err(("bcmsdh_sdmmc: Failed to set F2 blocksize " + "to %d\n", sd_f2_blocksize)); + + /* Release host controller F2 */ + sdio_release_host(gInstance->func[2]); + } + + sdioh_sdmmc_card_enablefuncs(sd); + + sd_trace(("%s: Done\n", __func__)); + return sd; +} + +extern SDIOH_API_RC sdioh_detach(osl_t *osh, sdioh_info_t *sd) +{ + sd_trace(("%s\n", __func__)); + + if (sd) { + + /* Disable Function 2 */ + sdio_claim_host(gInstance->func[2]); + sdio_disable_func(gInstance->func[2]); + sdio_release_host(gInstance->func[2]); + + /* Disable Function 1 */ + sdio_claim_host(gInstance->func[1]); + sdio_disable_func(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + + /* deregister irq */ + sdioh_sdmmc_osfree(sd); + + kfree(sd); + } + return SDIOH_API_RC_SUCCESS; +} + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) + +extern SDIOH_API_RC sdioh_enable_func_intr(void) +{ + u8 reg; + int err; + + if (gInstance->func[0]) { + sdio_claim_host(gInstance->func[0]); + + reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err); + if (err) { + sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n", + __func__, err)); + sdio_release_host(gInstance->func[0]); + return SDIOH_API_RC_FAIL; + } + + /* Enable F1 and F2 interrupts, set master enable */ + reg |= + (INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN | + INTR_CTL_MASTER_EN); + + sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err); + sdio_release_host(gInstance->func[0]); + + if (err) { + sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n", + __func__, err)); + return SDIOH_API_RC_FAIL; + } + } + + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC sdioh_disable_func_intr(void) +{ + u8 reg; + int err; + + if (gInstance->func[0]) { + sdio_claim_host(gInstance->func[0]); + reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err); + if (err) { + sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n", + __func__, err)); + sdio_release_host(gInstance->func[0]); + return SDIOH_API_RC_FAIL; + } + + reg &= ~(INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN); + /* Disable master interrupt with the last function interrupt */ + if (!(reg & 0xFE)) + reg = 0; + sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err); + + sdio_release_host(gInstance->func[0]); + if (err) { + sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n", + __func__, err)); + return SDIOH_API_RC_FAIL; + } + } + return SDIOH_API_RC_SUCCESS; +} +#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */ + +/* Configure callback to client when we recieve client interrupt */ +extern SDIOH_API_RC +sdioh_interrupt_register(sdioh_info_t *sd, sdioh_cb_fn_t fn, void *argh) +{ + sd_trace(("%s: Entering\n", __func__)); + if (fn == NULL) { + sd_err(("%s: interrupt handler is NULL, not registering\n", + __func__)); + return SDIOH_API_RC_FAIL; + } +#if !defined(OOB_INTR_ONLY) + sd->intr_handler = fn; + sd->intr_handler_arg = argh; + sd->intr_handler_valid = true; + + /* register and unmask irq */ + if (gInstance->func[2]) { + sdio_claim_host(gInstance->func[2]); + sdio_claim_irq(gInstance->func[2], IRQHandlerF2); + sdio_release_host(gInstance->func[2]); + } + + if (gInstance->func[1]) { + sdio_claim_host(gInstance->func[1]); + sdio_claim_irq(gInstance->func[1], IRQHandler); + sdio_release_host(gInstance->func[1]); + } +#elif defined(HW_OOB) + sdioh_enable_func_intr(); +#endif /* defined(OOB_INTR_ONLY) */ + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC sdioh_interrupt_deregister(sdioh_info_t *sd) +{ + sd_trace(("%s: Entering\n", __func__)); + +#if !defined(OOB_INTR_ONLY) + if (gInstance->func[1]) { + /* register and unmask irq */ + sdio_claim_host(gInstance->func[1]); + sdio_release_irq(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + } + + if (gInstance->func[2]) { + /* Claim host controller F2 */ + sdio_claim_host(gInstance->func[2]); + sdio_release_irq(gInstance->func[2]); + /* Release host controller F2 */ + sdio_release_host(gInstance->func[2]); + } + + sd->intr_handler_valid = false; + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; +#elif defined(HW_OOB) + sdioh_disable_func_intr(); +#endif /* !defined(OOB_INTR_ONLY) */ + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC sdioh_interrupt_query(sdioh_info_t *sd, bool *onoff) +{ + sd_trace(("%s: Entering\n", __func__)); + *onoff = sd->client_intr_enabled; + return SDIOH_API_RC_SUCCESS; +} + +#if defined(DHD_DEBUG) +extern bool sdioh_interrupt_pending(sdioh_info_t *sd) +{ + return 0; +} +#endif + +uint sdioh_query_iofnum(sdioh_info_t *sd) +{ + return sd->num_funcs; +} + +/* IOVar table */ +enum { + IOV_MSGLEVEL = 1, + IOV_BLOCKMODE, + IOV_BLOCKSIZE, + IOV_DMA, + IOV_USEINTS, + IOV_NUMINTS, + IOV_NUMLOCALINTS, + IOV_HOSTREG, + IOV_DEVREG, + IOV_DIVISOR, + IOV_SDMODE, + IOV_HISPEED, + IOV_HCIREGS, + IOV_POWER, + IOV_CLOCK, + IOV_RXCHAIN +}; + +const bcm_iovar_t sdioh_iovars[] = { + {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0}, + {"sd_blockmode", IOV_BLOCKMODE, 0, IOVT_BOOL, 0}, + {"sd_blocksize", IOV_BLOCKSIZE, 0, IOVT_UINT32, 0},/* ((fn << 16) | + size) */ + {"sd_dma", IOV_DMA, 0, IOVT_BOOL, 0}, + {"sd_ints", IOV_USEINTS, 0, IOVT_BOOL, 0}, + {"sd_numints", IOV_NUMINTS, 0, IOVT_UINT32, 0}, + {"sd_numlocalints", IOV_NUMLOCALINTS, 0, IOVT_UINT32, 0}, + {"sd_hostreg", IOV_HOSTREG, 0, IOVT_BUFFER, sizeof(sdreg_t)} + , + {"sd_devreg", IOV_DEVREG, 0, IOVT_BUFFER, sizeof(sdreg_t)} + , + {"sd_divisor", IOV_DIVISOR, 0, IOVT_UINT32, 0} + , + {"sd_power", IOV_POWER, 0, IOVT_UINT32, 0} + , + {"sd_clock", IOV_CLOCK, 0, IOVT_UINT32, 0} + , + {"sd_mode", IOV_SDMODE, 0, IOVT_UINT32, 100} + , + {"sd_highspeed", IOV_HISPEED, 0, IOVT_UINT32, 0} + , + {"sd_rxchain", IOV_RXCHAIN, 0, IOVT_BOOL, 0} + , + {NULL, 0, 0, 0, 0} +}; + +int +sdioh_iovar_op(sdioh_info_t *si, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + const bcm_iovar_t *vi = NULL; + int bcmerror = 0; + int val_size; + s32 int_val = 0; + bool bool_val; + u32 actionid; + + ASSERT(name); + ASSERT(len >= 0); + + /* Get must have return space; Set does not take qualifiers */ + ASSERT(set || (arg && len)); + ASSERT(!set || (!params && !plen)); + + sd_trace(("%s: Enter (%s %s)\n", __func__, (set ? "set" : "get"), + name)); + + vi = bcm_iovar_lookup(sdioh_iovars, name); + if (vi == NULL) { + bcmerror = BCME_UNSUPPORTED; + goto exit; + } + + bcmerror = bcm_iovar_lencheck(vi, arg, len, set); + if (bcmerror != 0) + goto exit; + + /* Set up params so get and set can share the convenience variables */ + if (params == NULL) { + params = arg; + plen = len; + } + + if (vi->type == IOVT_VOID) + val_size = 0; + else if (vi->type == IOVT_BUFFER) + val_size = len; + else + val_size = sizeof(int); + + if (plen >= (int)sizeof(int_val)) + bcopy(params, &int_val, sizeof(int_val)); + + bool_val = (int_val != 0) ? true : false; + + actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid); + switch (actionid) { + case IOV_GVAL(IOV_MSGLEVEL): + int_val = (s32) sd_msglevel; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_MSGLEVEL): + sd_msglevel = int_val; + break; + + case IOV_GVAL(IOV_BLOCKMODE): + int_val = (s32) si->sd_blockmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKMODE): + si->sd_blockmode = (bool) int_val; + /* Haven't figured out how to make non-block mode with DMA */ + break; + + case IOV_GVAL(IOV_BLOCKSIZE): + if ((u32) int_val > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + int_val = (s32) si->client_block_size[int_val]; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKSIZE): + { + uint func = ((u32) int_val >> 16); + uint blksize = (u16) int_val; + uint maxsize; + + if (func > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + + switch (func) { + case 0: + maxsize = 32; + break; + case 1: + maxsize = BLOCK_SIZE_4318; + break; + case 2: + maxsize = BLOCK_SIZE_4328; + break; + default: + maxsize = 0; + } + if (blksize > maxsize) { + bcmerror = BCME_BADARG; + break; + } + if (!blksize) + blksize = maxsize; + + /* Now set it */ + si->client_block_size[func] = blksize; + + break; + } + + case IOV_GVAL(IOV_RXCHAIN): + int_val = false; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_DMA): + int_val = (s32) si->sd_use_dma; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DMA): + si->sd_use_dma = (bool) int_val; + break; + + case IOV_GVAL(IOV_USEINTS): + int_val = (s32) si->use_client_ints; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_USEINTS): + si->use_client_ints = (bool) int_val; + if (si->use_client_ints) + si->intmask |= CLIENT_INTR; + else + si->intmask &= ~CLIENT_INTR; + + break; + + case IOV_GVAL(IOV_DIVISOR): + int_val = (u32) sd_divisor; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DIVISOR): + sd_divisor = int_val; + break; + + case IOV_GVAL(IOV_POWER): + int_val = (u32) sd_power; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_POWER): + sd_power = int_val; + break; + + case IOV_GVAL(IOV_CLOCK): + int_val = (u32) sd_clock; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_CLOCK): + sd_clock = int_val; + break; + + case IOV_GVAL(IOV_SDMODE): + int_val = (u32) sd_sdmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_SDMODE): + sd_sdmode = int_val; + break; + + case IOV_GVAL(IOV_HISPEED): + int_val = (u32) sd_hiok; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_HISPEED): + sd_hiok = int_val; + break; + + case IOV_GVAL(IOV_NUMINTS): + int_val = (s32) si->intrcount; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_NUMLOCALINTS): + int_val = (s32) 0; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *) params; + + if (sd_ptr->offset < SD_SysAddr + || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __func__, + sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: rreg%d at offset %d\n", __func__, + (sd_ptr->offset & 1) ? 8 + : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + if (sd_ptr->offset & 1) + int_val = 8; /* sdioh_sdmmc_rreg8(si, + sd_ptr->offset); */ + else if (sd_ptr->offset & 2) + int_val = 16; /* sdioh_sdmmc_rreg16(si, + sd_ptr->offset); */ + else + int_val = 32; /* sdioh_sdmmc_rreg(si, + sd_ptr->offset); */ + + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *) params; + + if (sd_ptr->offset < SD_SysAddr + || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __func__, + sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: wreg%d value 0x%08x at offset %d\n", + __func__, sd_ptr->value, + (sd_ptr->offset & 1) ? 8 + : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + break; + } + + case IOV_GVAL(IOV_DEVREG): + { + sdreg_t *sd_ptr = (sdreg_t *) params; + u8 data = 0; + + if (sdioh_cfg_read + (si, sd_ptr->func, sd_ptr->offset, &data)) { + bcmerror = BCME_SDIO_ERROR; + break; + } + + int_val = (int)data; + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_DEVREG): + { + sdreg_t *sd_ptr = (sdreg_t *) params; + u8 data = (u8) sd_ptr->value; + + if (sdioh_cfg_write + (si, sd_ptr->func, sd_ptr->offset, &data)) { + bcmerror = BCME_SDIO_ERROR; + break; + } + break; + } + + default: + bcmerror = BCME_UNSUPPORTED; + break; + } +exit: + + return bcmerror; +} + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) + +SDIOH_API_RC sdioh_enable_hw_oob_intr(sdioh_info_t *sd, bool enable) +{ + SDIOH_API_RC status; + u8 data; + + if (enable) + data = 3; /* enable hw oob interrupt */ + else + data = 4; /* disable hw oob interrupt */ + data |= 4; /* Active HIGH */ + + status = sdioh_request_byte(sd, SDIOH_WRITE, 0, 0xf2, &data); + return status; +} +#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */ + +extern SDIOH_API_RC +sdioh_cfg_read(sdioh_info_t *sd, uint fnc_num, u32 addr, u8 *data) +{ + SDIOH_API_RC status; + /* No lock needed since sdioh_request_byte does locking */ + status = sdioh_request_byte(sd, SDIOH_READ, fnc_num, addr, data); + return status; +} + +extern SDIOH_API_RC +sdioh_cfg_write(sdioh_info_t *sd, uint fnc_num, u32 addr, u8 *data) +{ + /* No lock needed since sdioh_request_byte does locking */ + SDIOH_API_RC status; + status = sdioh_request_byte(sd, SDIOH_WRITE, fnc_num, addr, data); + return status; +} + +static int sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, u32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i; + u32 scratch, regdata; + u8 *ptr = (u8 *)&scratch; + for (i = 0; i < 3; i++) { + if ((sdioh_sdmmc_card_regread(sd, 0, regaddr, 1, ®data)) != + SUCCESS) + sd_err(("%s: Can't read!\n", __func__)); + + *ptr++ = (u8) regdata; + regaddr++; + } + + /* Only the lower 17-bits are valid */ + scratch = ltoh32(scratch); + scratch &= 0x0001FFFF; + return scratch; +} + +extern SDIOH_API_RC +sdioh_cis_read(sdioh_info_t *sd, uint func, u8 *cisd, u32 length) +{ + u32 count; + int offset; + u32 foo; + u8 *cis = cisd; + + sd_trace(("%s: Func = %d\n", __func__, func)); + + if (!sd->func_cis_ptr[func]) { + bzero(cis, length); + sd_err(("%s: no func_cis_ptr[%d]\n", __func__, func)); + return SDIOH_API_RC_FAIL; + } + + sd_err(("%s: func_cis_ptr[%d]=0x%04x\n", __func__, func, + sd->func_cis_ptr[func])); + + for (count = 0; count < length; count++) { + offset = sd->func_cis_ptr[func] + count; + if (sdioh_sdmmc_card_regread(sd, 0, offset, 1, &foo) < 0) { + sd_err(("%s: regread failed: Can't read CIS\n", + __func__)); + return SDIOH_API_RC_FAIL; + } + + *cis = (u8) (foo & 0xff); + cis++; + } + + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_request_byte(sdioh_info_t *sd, uint rw, uint func, uint regaddr, + u8 *byte) +{ + int err_ret; + + sd_info(("%s: rw=%d, func=%d, addr=0x%05x\n", __func__, rw, func, + regaddr)); + + DHD_PM_RESUME_WAIT(sdioh_request_byte_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + if (rw) { /* CMD52 Write */ + if (func == 0) { + /* Can only directly write to some F0 registers. + * Handle F2 enable + * as a special case. + */ + if (regaddr == SDIOD_CCCR_IOEN) { + if (gInstance->func[2]) { + sdio_claim_host(gInstance->func[2]); + if (*byte & SDIO_FUNC_ENABLE_2) { + /* Enable Function 2 */ + err_ret = + sdio_enable_func + (gInstance->func[2]); + if (err_ret) + sd_err(("bcmsdh_sdmmc: enable F2 failed:%d", + err_ret)); + } else { + /* Disable Function 2 */ + err_ret = + sdio_disable_func + (gInstance->func[2]); + if (err_ret) + sd_err(("bcmsdh_sdmmc: Disab F2 failed:%d", + err_ret)); + } + sdio_release_host(gInstance->func[2]); + } + } +#if defined(MMC_SDIO_ABORT) + /* to allow abort command through F1 */ + else if (regaddr == SDIOD_CCCR_IOABORT) { + sdio_claim_host(gInstance->func[func]); + /* + * this sdio_f0_writeb() can be replaced + * with another api + * depending upon MMC driver change. + * As of this time, this is temporaray one + */ + sdio_writeb(gInstance->func[func], *byte, + regaddr, &err_ret); + sdio_release_host(gInstance->func[func]); + } +#endif /* MMC_SDIO_ABORT */ + else if (regaddr < 0xF0) { + sd_err(("bcmsdh_sdmmc: F0 Wr:0x%02x: write " + "disallowed\n", regaddr)); + } else { + /* Claim host controller, perform F0 write, + and release */ + sdio_claim_host(gInstance->func[func]); + sdio_f0_writeb(gInstance->func[func], *byte, + regaddr, &err_ret); + sdio_release_host(gInstance->func[func]); + } + } else { + /* Claim host controller, perform Fn write, + and release */ + sdio_claim_host(gInstance->func[func]); + sdio_writeb(gInstance->func[func], *byte, regaddr, + &err_ret); + sdio_release_host(gInstance->func[func]); + } + } else { /* CMD52 Read */ + /* Claim host controller, perform Fn read, and release */ + sdio_claim_host(gInstance->func[func]); + + if (func == 0) { + *byte = + sdio_f0_readb(gInstance->func[func], regaddr, + &err_ret); + } else { + *byte = + sdio_readb(gInstance->func[func], regaddr, + &err_ret); + } + + sdio_release_host(gInstance->func[func]); + } + + if (err_ret) + sd_err(("bcmsdh_sdmmc: Failed to %s byte F%d:@0x%05x=%02x, " + "Err: %d\n", rw ? "Write" : "Read", func, regaddr, + *byte, err_ret)); + + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +extern SDIOH_API_RC +sdioh_request_word(sdioh_info_t *sd, uint cmd_type, uint rw, uint func, + uint addr, u32 *word, uint nbytes) +{ + int err_ret = SDIOH_API_RC_FAIL; + + if (func == 0) { + sd_err(("%s: Only CMD52 allowed to F0.\n", __func__)); + return SDIOH_API_RC_FAIL; + } + + sd_info(("%s: cmd_type=%d, rw=%d, func=%d, addr=0x%05x, nbytes=%d\n", + __func__, cmd_type, rw, func, addr, nbytes)); + + DHD_PM_RESUME_WAIT(sdioh_request_word_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + /* Claim host controller */ + sdio_claim_host(gInstance->func[func]); + + if (rw) { /* CMD52 Write */ + if (nbytes == 4) { + sdio_writel(gInstance->func[func], *word, addr, + &err_ret); + } else if (nbytes == 2) { + sdio_writew(gInstance->func[func], (*word & 0xFFFF), + addr, &err_ret); + } else { + sd_err(("%s: Invalid nbytes: %d\n", __func__, nbytes)); + } + } else { /* CMD52 Read */ + if (nbytes == 4) { + *word = + sdio_readl(gInstance->func[func], addr, &err_ret); + } else if (nbytes == 2) { + *word = + sdio_readw(gInstance->func[func], addr, + &err_ret) & 0xFFFF; + } else { + sd_err(("%s: Invalid nbytes: %d\n", __func__, nbytes)); + } + } + + /* Release host controller */ + sdio_release_host(gInstance->func[func]); + + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to %s word, Err: 0x%08x", + rw ? "Write" : "Read", err_ret)); + } + + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +static SDIOH_API_RC +sdioh_request_packet(sdioh_info_t *sd, uint fix_inc, uint write, uint func, + uint addr, void *pkt) +{ + bool fifo = (fix_inc == SDIOH_DATA_FIX); + u32 SGCount = 0; + int err_ret = 0; + + void *pnext; + + sd_trace(("%s: Enter\n", __func__)); + + ASSERT(pkt); + DHD_PM_RESUME_WAIT(sdioh_request_packet_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + + /* Claim host controller */ + sdio_claim_host(gInstance->func[func]); + for (pnext = pkt; pnext; pnext = PKTNEXT(pnext)) { + uint pkt_len = PKTLEN(pnext); + pkt_len += 3; + pkt_len &= 0xFFFFFFFC; + +#ifdef CONFIG_MMC_MSM7X00A + if ((pkt_len % 64) == 32) { + sd_trace(("%s: Rounding up TX packet +=32\n", + __func__)); + pkt_len += 32; + } +#endif /* CONFIG_MMC_MSM7X00A */ + /* Make sure the packet is aligned properly. + * If it isn't, then this + * is the fault of sdioh_request_buffer() which + * is supposed to give + * us something we can work with. + */ + ASSERT(((u32) (PKTDATA(pkt)) & DMA_ALIGN_MASK) == 0); + + if ((write) && (!fifo)) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((u8 *) PKTDATA(pnext)), + pkt_len); + } else if (write) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((u8 *) PKTDATA(pnext)), + pkt_len); + } else if (fifo) { + err_ret = sdio_readsb(gInstance->func[func], + ((u8 *) PKTDATA(pnext)), + addr, pkt_len); + } else { + err_ret = sdio_memcpy_fromio(gInstance->func[func], + ((u8 *) PKTDATA(pnext)), + addr, pkt_len); + } + + if (err_ret) { + sd_err(("%s: %s FAILED %p[%d], addr=0x%05x, pkt_len=%d," + "ERR=0x%08x\n", __func__, + (write) ? "TX" : "RX", + pnext, SGCount, addr, pkt_len, err_ret)); + } else { + sd_trace(("%s: %s xfr'd %p[%d], addr=0x%05x, len=%d\n", + __func__, + (write) ? "TX" : "RX", + pnext, SGCount, addr, pkt_len)); + } + + if (!fifo) + addr += pkt_len; + SGCount++; + + } + + /* Release host controller */ + sdio_release_host(gInstance->func[func]); + + sd_trace(("%s: Exit\n", __func__)); + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +/* + * 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. + * + */ +extern SDIOH_API_RC +sdioh_request_buffer(sdioh_info_t *sd, uint pio_dma, uint fix_inc, uint write, + uint func, uint addr, uint reg_width, uint buflen_u, + u8 *buffer, void *pkt) +{ + SDIOH_API_RC Status; + void *mypkt = NULL; + + sd_trace(("%s: Enter\n", __func__)); + + DHD_PM_RESUME_WAIT(sdioh_request_buffer_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + /* Case 1: we don't have a packet. */ + if (pkt == NULL) { + sd_data(("%s: Creating new %s Packet, len=%d\n", + __func__, write ? "TX" : "RX", buflen_u)); + mypkt = PKTGET(sd->osh, buflen_u, write ? true : false); + if (!mypkt) { + sd_err(("%s: PKTGET failed: len %d\n", + __func__, buflen_u)); + return SDIOH_API_RC_FAIL; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) + bcopy(buffer, PKTDATA(mypkt), buflen_u); + + Status = + sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) + bcopy(PKTDATA(mypkt), buffer, buflen_u); + + PKTFREE(sd->osh, mypkt, write ? true : false); + } else if (((u32) (PKTDATA(pkt)) & DMA_ALIGN_MASK) != 0) { + /* Case 2: We have a packet, but it is unaligned. */ + + /* In this case, we cannot have a chain. */ + ASSERT(PKTNEXT(pkt) == NULL); + + sd_data(("%s: Creating aligned %s Packet, len=%d\n", + __func__, write ? "TX" : "RX", PKTLEN(pkt))); + mypkt = PKTGET(sd->osh, PKTLEN(pkt), write ? true : false); + if (!mypkt) { + sd_err(("%s: PKTGET failed: len %d\n", + __func__, PKTLEN(pkt))); + return SDIOH_API_RC_FAIL; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) + bcopy(PKTDATA(pkt), PKTDATA(mypkt), PKTLEN(pkt)); + + Status = + sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) + bcopy(PKTDATA(mypkt), PKTDATA(pkt), PKTLEN(mypkt)); + + PKTFREE(sd->osh, mypkt, write ? true : false); + } else { /* case 3: We have a packet and + it is aligned. */ + sd_data(("%s: Aligned %s Packet, direct DMA\n", + __func__, write ? "Tx" : "Rx")); + Status = + sdioh_request_packet(sd, fix_inc, write, func, addr, pkt); + } + + return Status; +} + +/* this function performs "abort" for both of host & device */ +extern int sdioh_abort(sdioh_info_t *sd, uint func) +{ +#if defined(MMC_SDIO_ABORT) + char t_func = (char)func; +#endif /* defined(MMC_SDIO_ABORT) */ + sd_trace(("%s: Enter\n", __func__)); + +#if defined(MMC_SDIO_ABORT) + /* issue abort cmd52 command through F1 */ + sdioh_request_byte(sd, SD_IO_OP_WRITE, SDIO_FUNC_0, SDIOD_CCCR_IOABORT, + &t_func); +#endif /* defined(MMC_SDIO_ABORT) */ + + sd_trace(("%s: Exit\n", __func__)); + return SDIOH_API_RC_SUCCESS; +} + +/* Reset and re-initialize the device */ +int sdioh_sdio_reset(sdioh_info_t *si) +{ + sd_trace(("%s: Enter\n", __func__)); + sd_trace(("%s: Exit\n", __func__)); + return SDIOH_API_RC_SUCCESS; +} + +/* Disable device interrupt */ +void sdioh_sdmmc_devintr_off(sdioh_info_t *sd) +{ + sd_trace(("%s: %d\n", __func__, sd->use_client_ints)); + sd->intmask &= ~CLIENT_INTR; +} + +/* Enable device interrupt */ +void sdioh_sdmmc_devintr_on(sdioh_info_t *sd) +{ + sd_trace(("%s: %d\n", __func__, sd->use_client_ints)); + sd->intmask |= CLIENT_INTR; +} + +/* Read client card reg */ +int +sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, u32 regaddr, + int regsize, u32 *data) +{ + + if ((func == 0) || (regsize == 1)) { + u8 temp = 0; + + sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp); + *data = temp; + *data &= 0xff; + sd_data(("%s: byte read data=0x%02x\n", __func__, *data)); + } else { + sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, data, + regsize); + if (regsize == 2) + *data &= 0xffff; + + sd_data(("%s: word read data=0x%08x\n", __func__, *data)); + } + + return SUCCESS; +} + +#if !defined(OOB_INTR_ONLY) +/* bcmsdh_sdmmc interrupt handler */ +static void IRQHandler(struct sdio_func *func) +{ + sdioh_info_t *sd; + + sd_trace(("bcmsdh_sdmmc: ***IRQHandler\n")); + sd = gInstance->sd; + + ASSERT(sd != NULL); + sdio_release_host(gInstance->func[0]); + + if (sd->use_client_ints) { + sd->intrcount++; + ASSERT(sd->intr_handler); + ASSERT(sd->intr_handler_arg); + (sd->intr_handler) (sd->intr_handler_arg); + } else { + sd_err(("bcmsdh_sdmmc: ***IRQHandler\n")); + + sd_err(("%s: Not ready for intr: enabled %d, handler %p\n", + __func__, sd->client_intr_enabled, sd->intr_handler)); + } + + sdio_claim_host(gInstance->func[0]); +} + +/* bcmsdh_sdmmc interrupt handler for F2 (dummy handler) */ +static void IRQHandlerF2(struct sdio_func *func) +{ + sdioh_info_t *sd; + + sd_trace(("bcmsdh_sdmmc: ***IRQHandlerF2\n")); + + sd = gInstance->sd; + + ASSERT(sd != NULL); +} +#endif /* !defined(OOB_INTR_ONLY) */ + +#ifdef NOTUSED +/* Write client card reg */ +static int +sdioh_sdmmc_card_regwrite(sdioh_info_t *sd, int func, u32 regaddr, + int regsize, u32 data) +{ + + if ((func == 0) || (regsize == 1)) { + u8 temp; + + temp = data & 0xff; + sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp); + sd_data(("%s: byte write data=0x%02x\n", __func__, data)); + } else { + if (regsize == 2) + data &= 0xffff; + + sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, &data, + regsize); + + sd_data(("%s: word write data=0x%08x\n", __func__, data)); + } + + return SUCCESS; +} +#endif /* NOTUSED */ + +int sdioh_start(sdioh_info_t *si, int stage) +{ + return 0; +} + +int sdioh_stop(sdioh_info_t *si) +{ + return 0; +} |