/** 
 *  @brief This function downloads firmware image to the card.
 *  
 *  @param priv    	A pointer to bt_private structure
 *  @return 	   	BT_STATUS_SUCCESS or BT_STATUS_FAILURE
 */
int
sd_download_firmware_w_helper(bt_private * priv)
{
    struct sdio_mmc_card *card = (struct sdio_mmc_card *) priv->bt_dev.card;
    const struct firmware *fw_firmware = NULL;
    u8 *firmware = NULL;
    int firmwarelen;
    u8 base0;
    u8 base1;
    int ret = BT_STATUS_SUCCESS;
    int offset;
    void *tmpfwbuf = NULL;
    int tmpfwbufsz;
    u8 *fwbuf;
    u16 len;
    int txlen = 0;
    int tx_blocks = 0;
    int i = 0;
    int tries = 0;
#ifdef FW_DOWNLOAD_SPEED
    u32 tv1, tv2;
#endif
    char *cur_fw_name = NULL;

    ENTER();

    if (fw_name == NULL)
        /* Check revision ID */
        switch (priv->adapter->chip_rev) {
        case SD8787_W0:
        case SD8787_W1:
            cur_fw_name = SD8787_W1_FW_NAME;
            break;
        case SD8787_A0_A1:
            cur_fw_name = SD8787_AX_FW_NAME;
            break;
        default:
            cur_fw_name = DEFAULT_FW_NAME;
            break;
    } else
        cur_fw_name = fw_name;
    if ((ret =
         request_firmware(&fw_firmware, cur_fw_name,
                          priv->hotplug_device)) < 0) {
        PRINTM(FATAL, "request_firmware() failed, error code = %#x\n", ret);
        goto done;
    }

    if (fw_firmware) {
        firmware = (u8 *) fw_firmware->data;
        firmwarelen = fw_firmware->size;
    } else {
        PRINTM(MSG, "No firmware image found! Terminating download\n");
        ret = BT_STATUS_FAILURE;
        goto done;
    }

    PRINTM(INFO, "Downloading FW image (%d bytes)\n", firmwarelen);

#ifdef FW_DOWNLOAD_SPEED
    tv1 = get_utimeofday();
#endif

#ifdef PXA3XX_DMA_ALIGN
    tmpfwbufsz = ALIGN_SZ(BT_UPLD_SIZE, PXA3XX_DMA_ALIGNMENT);
#else /* PXA3XX_DMA_ALIGN */
    tmpfwbufsz = BT_UPLD_SIZE;
#endif
    tmpfwbuf = kmalloc(tmpfwbufsz, GFP_KERNEL);
    if (!tmpfwbuf) {
        PRINTM(ERROR,
               "Unable to allocate buffer for firmware. Terminating download\n");
        ret = BT_STATUS_FAILURE;
        goto done;
    }
    memset(tmpfwbuf, 0, tmpfwbufsz);
#ifdef PXA3XX_DMA_ALIGN
    /* Ensure 8-byte aligned firmware buffer */
    fwbuf = (u8 *) ALIGN_ADDR(tmpfwbuf, PXA3XX_DMA_ALIGNMENT);
#else /* PXA3XX_DMA_ALIGN */
    fwbuf = (u8 *) tmpfwbuf;
#endif

    /* Perform firmware data transfer */
    offset = 0;
    do {
        /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY bits */
        ret = sd_poll_card_status(priv, CARD_IO_READY | DN_LD_CARD_RDY);
        if (ret < 0) {
            PRINTM(FATAL, "FW download with helper poll status timeout @ %d\n",
                   offset);
            goto done;
        }

        /* More data? */
        if (offset >= firmwarelen)
            break;

        for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
            base0 = sdio_readb(card->func, SQ_READ_BASE_ADDRESS_A0_REG, &ret);
            if (ret) {
                PRINTM(WARN, "Dev BASE0 register read failed:"
                       " base0=0x%04X(%d). Terminating download\n", base0,
                       base0);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            base1 = sdio_readb(card->func, SQ_READ_BASE_ADDRESS_A1_REG, &ret);
            if (ret) {
                PRINTM(WARN, "Dev BASE1 register read failed:"
                       " base1=0x%04X(%d). Terminating download\n", base1,
                       base1);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            len = (((u16) base1) << 8) | base0;

            if (len != 0)
                break;
            udelay(10);
        }

        if (len == 0)
            break;
        else if (len > BT_UPLD_SIZE) {
            PRINTM(FATAL, "FW download failure @ %d, invalid length %d\n",
                   offset, len);
            ret = BT_STATUS_FAILURE;
            goto done;
        }

        txlen = len;

        if (len & BIT(0)) {
            i++;
            if (i > MAX_WRITE_IOMEM_RETRY) {
                PRINTM(FATAL,
                       "FW download failure @ %d, over max retry count\n",
                       offset);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            PRINTM(ERROR, "FW CRC error indicated by the helper:"
                   " len = 0x%04X, txlen = %d\n", len, txlen);
            len &= ~BIT(0);
            /* Setting this to 0 to resend from same offset */
            txlen = 0;
        } else {
            i = 0;

            /* Set blocksize to transfer - checking for last block */
            if (firmwarelen - offset < txlen)
                txlen = firmwarelen - offset;

            PRINTM(INFO, ".");

            tx_blocks = (txlen + SD_BLOCK_SIZE_FW_DL - 1) / SD_BLOCK_SIZE_FW_DL;

            /* Copy payload to buffer */
            memcpy(fwbuf, &firmware[offset], txlen);
        }

        /* Send data */
        ret =
            sdio_writesb(card->func, priv->bt_dev.ioport, fwbuf,
                         tx_blocks * SD_BLOCK_SIZE_FW_DL);

        if (ret < 0) {
            PRINTM(ERROR, "FW download, write iomem (%d) failed @ %d\n", i,
                   offset);
            sdio_writeb(card->func, 0x04, CONFIGURATION_REG, &ret);
            if (ret)
                PRINTM(ERROR, "write ioreg failed (CFG)\n");
        }

        offset += txlen;
    } while (TRUE);

    PRINTM(INFO, "\nFW download over, size %d bytes\n", offset);

    ret = BT_STATUS_SUCCESS;
  done:
#ifdef FW_DOWNLOAD_SPEED
    tv2 = get_utimeofday();
    PRINTM(INFO, "FW: %ld.%03ld.%03ld ", tv1 / 1000000,
           (tv1 % 1000000) / 1000, tv1 % 1000);
    PRINTM(INFO, " -> %ld.%03ld.%03ld ", tv2 / 1000000,
           (tv2 % 1000000) / 1000, tv2 % 1000);
    tv2 -= tv1;
    PRINTM(INFO, " == %ld.%03ld.%03ld\n", tv2 / 1000000,
           (tv2 % 1000000) / 1000, tv2 % 1000);
#endif
    if (tmpfwbuf)
        kfree(tmpfwbuf);
    if (fw_firmware)
        release_firmware(fw_firmware);

    LEAVE();
    return ret;
}
예제 #2
0
static void smssdio_work_thread(struct work_struct *arg)
{
	int ret, isr;


        struct smscore_buffer_t *cb;
	struct SmsMsgHdr_S *hdr;
	size_t size;
        struct smssdio_device *smsdev = container_of(arg, struct smssdio_device, work_thread);
	 struct sdio_func *sdfunc = smsdev->func;

	/*
	 * The interrupt register has no defined meaning. It is just
	 * a way of turning of the level triggered interrupt.
	 */
	sdio_claim_host(smsdev->func);

	isr = sdio_readb(smsdev->func, SMSSDIO_INT, &ret);
	if (ret) {
		sms_err("Got error reading interrupt status=%d, isr=%d\n", ret, isr);
		isr = sdio_readb(smsdev->func, SMSSDIO_INT, &ret);
		if (ret)
		{
			sms_err("Second read also failed, try to recover\n");
			sdio_release_host(smsdev->func);
			sdfunc = kmemdup(smsdev->func, sizeof(struct sdio_func), GFP_KERNEL);
			if (!sdfunc)
			{
				sms_err("Out of memory!!!");
				return;
			}
	  	        sdfunc->num = 0;
			sdio_claim_host(sdfunc);
			sdio_writeb(sdfunc, 2,  SMSSDIO_CCCR, &ret);
			sms_err("Read ISR status (write returned) %d\n", ret);
			isr = sdio_readb(smsdev->func, SMSSDIO_INT, &ret);
			sms_err("Read returned ret=%d, isr=%d\n", ret, isr);
			sdio_writeb(sdfunc, 0,  SMSSDIO_CCCR, &ret);
	        	sdio_release_host(sdfunc);
	        	kfree(sdfunc);
			sms_err("Recovered, but this transaction is lost.");
			return;
		}
		sms_err("Second read succeed status=%d, isr=%d (continue)\n", ret, isr);
	}

	if (smsdev->split_cb == NULL) {
		cb = smscore_getbuffer(smsdev->coredev);
		if (!cb) {
			sms_err("Unable to allocate data buffer!\n");
			sdio_release_host(smsdev->func);
			return;
		}

		ret = sdio_memcpy_fromio(smsdev->func,
					 cb->p,
					 SMSSDIO_DATA,
					 SMSSDIO_BLOCK_SIZE);
		if (ret) {
			sms_warn("Error %d reading initial block, "
				"continue with sequence.\n", ret);

		}

		hdr = cb->p;
		if (hdr->msgFlags & MSG_HDR_FLAG_SPLIT_MSG) {
			smsdev->split_cb = cb;
			sdio_release_host(smsdev->func);
			return;
		}

		if (hdr->msgLength > smsdev->func->cur_blksize)
			size = hdr->msgLength - smsdev->func->cur_blksize;
		else
			size = 0;
	} else {
		cb = smsdev->split_cb;
		hdr = cb->p;

		size = hdr->msgLength - sizeof(struct SmsMsgHdr_S);

		smsdev->split_cb = NULL;
	}
	if (size) {
		void *buffer;

		buffer = cb->p + (hdr->msgLength - size);
		size = ALIGN(size, SMSSDIO_BLOCK_SIZE);

		BUG_ON(smsdev->func->cur_blksize != SMSSDIO_BLOCK_SIZE);

		/*
		 * First attempt to transfer all of it in one go...
		 */
		ret = sdio_memcpy_fromio(smsdev->func,
					 buffer,
					 SMSSDIO_DATA,
					 size);
		if (ret && ret != -EINVAL) {
			smscore_putbuffer(smsdev->coredev, cb);
			sms_err("Error %d reading data from card!\n", ret);
			sdio_release_host(smsdev->func);
			return;
		}

		/*
		 * ..then fall back to one block at a time if that is
		 * not possible...
		 *
		 * (we have to do this manually because of the
		 * problem with the "increase address" bit)
		 */
		if (ret == -EINVAL) {
			while (size) {
				ret = sdio_memcpy_fromio(smsdev->func,
						  buffer, SMSSDIO_DATA,
						  smsdev->func->cur_blksize);
				if (ret) {
					smscore_putbuffer(smsdev->coredev, cb);
					sms_err("Error %d reading "
						"data from card!\n", ret);
					sdio_release_host(smsdev->func);
					return;
				}

				buffer += smsdev->func->cur_blksize;
				if (size > smsdev->func->cur_blksize)
					size -= smsdev->func->cur_blksize;
				else
					size = 0;
			}
		}
	}

	sdio_release_host(smsdev->func);
	cb->size = hdr->msgLength;
	cb->offset = 0;
	smscore_onresponse(smsdev->coredev, cb);
}
예제 #3
0
/** 
 *  @brief Transfer firmware to card
 *  
 *  @param priv      A Pointer to bt_private structure
 *  @return 	     BT_STATUS_SUCCESS/BT_STATUS_FAILURE or other error no.	
 */
static int
sd_init_fw_dpc(bt_private * priv)
{
    struct sdio_mmc_card *card = (struct sdio_mmc_card *) priv->bt_dev.card;
    u8 *firmware = NULL;
    int firmwarelen;
    u8 base0;
    u8 base1;
    int ret = BT_STATUS_SUCCESS;
    int offset;
    void *tmpfwbuf = NULL;
    int tmpfwbufsz;
    u8 *fwbuf;
    u16 len;
    int txlen = 0;
    int tx_blocks = 0;
    int i = 0;
    int tries = 0;
#ifdef FW_DOWNLOAD_SPEED
    u32 tv1, tv2;
#endif
    u8 crc_buffer = 0;

    ENTER();
    firmware = (u8 *) priv->firmware->data;
    firmwarelen = priv->firmware->size;

    PRINTM(INFO, "BT: Downloading FW image (%d bytes)\n", firmwarelen);

#ifdef FW_DOWNLOAD_SPEED
    tv1 = get_utimeofday();
#endif

    tmpfwbufsz = ALIGN_SZ(BT_UPLD_SIZE, DMA_ALIGNMENT);
    tmpfwbuf = kmalloc(tmpfwbufsz, GFP_KERNEL);
    if (!tmpfwbuf) {
        PRINTM(ERROR,
               "BT: Unable to allocate buffer for firmware. Terminating download\n");
        ret = BT_STATUS_FAILURE;
        goto done;
    }
    memset(tmpfwbuf, 0, tmpfwbufsz);
    /* Ensure 8-byte aligned firmware buffer */
    fwbuf = (u8 *) ALIGN_ADDR(tmpfwbuf, DMA_ALIGNMENT);

    if (!(priv->fw_crc_check)) {
        /* CRC check not required, use custom header first */
        firmware = fw_crc_header;
        firmwarelen = FW_CRC_HEADER;
        crc_buffer = 1;
    }

    /* Perform firmware data transfer */
    offset = 0;
    do {
        /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY bits */
        ret = sd_poll_card_status(priv, CARD_IO_READY | DN_LD_CARD_RDY);
        if (ret < 0) {
            PRINTM(FATAL,
                   "BT: FW download with helper poll status timeout @ %d\n",
                   offset);
            goto done;
        }

        if (!crc_buffer)
            /* More data? */
            if (offset >= firmwarelen)
                break;

        for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
            base0 = sdio_readb(card->func, SQ_READ_BASE_ADDRESS_A0_REG, &ret);
            if (ret) {
                PRINTM(WARN, "Dev BASE0 register read failed:"
                       " base0=0x%04X(%d). Terminating download\n", base0,
                       base0);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            base1 = sdio_readb(card->func, SQ_READ_BASE_ADDRESS_A1_REG, &ret);
            if (ret) {
                PRINTM(WARN, "Dev BASE1 register read failed:"
                       " base1=0x%04X(%d). Terminating download\n", base1,
                       base1);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            len = (((u16) base1) << 8) | base0;

            if (len != 0)
                break;
            udelay(10);
        }

        if (len == 0)
            break;
        else if (len > BT_UPLD_SIZE) {
            PRINTM(FATAL, "BT: FW download failure @ %d, invalid length %d\n",
                   offset, len);
            ret = BT_STATUS_FAILURE;
            goto done;
        }

        txlen = len;

        if (len & BIT(0)) {
            i++;
            if (i > MAX_WRITE_IOMEM_RETRY) {
                PRINTM(FATAL,
                       "BT: FW download failure @ %d, over max retry count\n",
                       offset);
                ret = BT_STATUS_FAILURE;
                goto done;
            }
            PRINTM(ERROR, "BT: FW CRC error indicated by the helper:"
                   " len = 0x%04X, txlen = %d\n", len, txlen);
            len &= ~BIT(0);

            PRINTM(ERROR, "BT: retry: %d, offset %d\n", i, offset);
            DBG_HEXDUMP(ERROR, "BT: FW block:", fwbuf, len);

            /* Setting this to 0 to resend from same offset */
            txlen = 0;
        } else {
            i = 0;

            /* Set blocksize to transfer - checking for last block */
            if (firmwarelen - offset < txlen)
                txlen = firmwarelen - offset;

            PRINTM(INFO, ".");

            tx_blocks = (txlen + SD_BLOCK_SIZE_FW_DL - 1) / SD_BLOCK_SIZE_FW_DL;

            /* Copy payload to buffer */
            memcpy(fwbuf, &firmware[offset], txlen);
        }

        /* Send data */
        ret =
            sdio_writesb(card->func, priv->bt_dev.ioport, fwbuf,
                         tx_blocks * SD_BLOCK_SIZE_FW_DL);

        if (ret < 0) {
            PRINTM(ERROR, "BT: FW download, write iomem (%d) failed @ %d\n", i,
                   offset);
            sdio_writeb(card->func, 0x04, CONFIGURATION_REG, &ret);
            if (ret)
                PRINTM(ERROR, "write ioreg failed (CFG)\n");
        }

        offset += txlen;
        if (crc_buffer) {
            if (offset >= FW_CRC_HEADER) {
                /* Custom header download complete, restore original FW */
                offset = 0;
                firmware = (u8 *) priv->firmware->data;
                firmwarelen = priv->firmware->size;
                crc_buffer = 0;
            }
        }
    } while (TRUE);

    PRINTM(INFO, "\nBT: FW download over, size %d bytes\n", offset);

    ret = BT_STATUS_SUCCESS;
  done:
#ifdef FW_DOWNLOAD_SPEED
    tv2 = get_utimeofday();
    PRINTM(INFO, "FW: %ld.%03ld.%03ld ", tv1 / 1000000,
           (tv1 % 1000000) / 1000, tv1 % 1000);
    PRINTM(INFO, " -> %ld.%03ld.%03ld ", tv2 / 1000000,
           (tv2 % 1000000) / 1000, tv2 % 1000);
    tv2 -= tv1;
    PRINTM(INFO, " == %ld.%03ld.%03ld\n", tv2 / 1000000,
           (tv2 % 1000000) / 1000, tv2 % 1000);
#endif
    if (tmpfwbuf)
        kfree(tmpfwbuf);
    LEAVE();
    return ret;
}
예제 #4
0
static void iwmct_irq(struct sdio_func *func)
{
	struct iwmct_priv *priv;
	int val, ret;
	int iosize;
	int addr = IWMC_SDIO_INTR_GET_SIZE_ADDR;
	struct iwmct_work_struct *read_req;

	priv = sdio_get_drvdata(func);

	LOG_TRACE(priv, IRQ, "enter iwmct_irq\n");

	/* read the function's status register */
	val = sdio_readb(func, IWMC_SDIO_INTR_STATUS_ADDR, &ret);

	LOG_TRACE(priv, IRQ, "iir value = %d, ret=%d\n", val, ret);

	if (!val) {
		LOG_ERROR(priv, IRQ, "iir = 0, exiting ISR\n");
		goto exit_clear_intr;
	}


	/*
	 * read 2 bytes of the transaction size
	 * IMPORTANT: sdio transaction size has to be read before clearing
	 * sdio interrupt!!!
	 */
	val = sdio_readb(priv->func, addr++, &ret);
	iosize = val;
	val = sdio_readb(priv->func, addr++, &ret);
	iosize += val << 8;

	LOG_INFO(priv, IRQ, "READ size %d\n", iosize);

	if (iosize == 0) {
		LOG_ERROR(priv, IRQ, "READ size %d, exiting ISR\n", iosize);
		goto exit_clear_intr;
	}

	/* allocate a work structure to pass iosize to the worker */
	read_req = kzalloc(sizeof(struct iwmct_work_struct), GFP_KERNEL);
	if (!read_req) {
		LOG_ERROR(priv, IRQ, "failed to allocate read_req, exit ISR\n");
		goto exit_clear_intr;
	}

	INIT_LIST_HEAD(&read_req->list);
	read_req->iosize = iosize;

	list_add_tail(&priv->read_req_list, &read_req->list);

	/* clear the function's interrupt request bit (write 1 to clear) */
	sdio_writeb(func, 1, IWMC_SDIO_INTR_CLEAR_ADDR, &ret);

	queue_work(priv->wq, &priv->isr_worker);

	LOG_TRACE(priv, IRQ, "exit iwmct_irq\n");

	return;

exit_clear_intr:
	/* clear the function's interrupt request bit (write 1 to clear) */
	sdio_writeb(func, 1, IWMC_SDIO_INTR_CLEAR_ADDR, &ret);
}
예제 #5
0
파일: sdio.c 프로젝트: CSCLOG/beaglebone
static void iwm_sdio_isr(struct sdio_func *func)
{
	struct iwm_priv *iwm;
	struct iwm_sdio_priv *hw;
	struct iwm_rx_info *rx_info;
	struct sk_buff *skb;
	unsigned long buf_size, read_size;
	int ret;
	u8 val;

	hw = sdio_get_drvdata(func);
	iwm = hw_to_iwm(hw);

	buf_size = hw->blk_size;

	/* We're checking the status */
	val = sdio_readb(func, IWM_SDIO_INTR_STATUS_ADDR, &ret);
	if (val == 0 || ret < 0) {
		IWM_ERR(iwm, "Wrong INTR_STATUS\n");
		return;
	}

	/* See if we have free buffers */
	if (skb_queue_len(&iwm->rx_list) > IWM_RX_LIST_SIZE) {
		IWM_ERR(iwm, "No buffer for more Rx frames\n");
		return;
	}

	/* We first read the transaction size */
	read_size = sdio_readb(func, IWM_SDIO_INTR_GET_SIZE_ADDR + 1, &ret);
	read_size = read_size << 8;

	if (ret < 0) {
		IWM_ERR(iwm, "Couldn't read the xfer size\n");
		return;
	}

	/* We need to clear the INT register */
	sdio_writeb(func, 1, IWM_SDIO_INTR_CLEAR_ADDR, &ret);
	if (ret < 0) {
		IWM_ERR(iwm, "Couldn't clear the INT register\n");
		return;
	}

	while (buf_size < read_size)
		buf_size <<= 1;

	skb = dev_alloc_skb(buf_size);
	if (!skb) {
		IWM_ERR(iwm, "Couldn't alloc RX skb\n");
		return;
	}
	rx_info = skb_to_rx_info(skb);
	rx_info->rx_size = read_size;
	rx_info->rx_buf_size = buf_size;

	/* Now we can read the actual buffer */
	ret = sdio_memcpy_fromio(func, skb_put(skb, read_size),
				 IWM_SDIO_DATA_ADDR, read_size);

	/* The skb is put on a driver's specific Rx SKB list */
	skb_queue_tail(&iwm->rx_list, skb);

	/* We can now schedule the actual worker */
	queue_work(hw->isr_wq, &hw->isr_worker);
}
예제 #6
0
/** 
 *  @brief This function registers the device.
 *  
 *  @param priv    A pointer to bt_private structure
 *  @return 	   BT_STATUS_SUCCESS or BT_STATUS_FAILURE
 */
int
sbi_register_dev(bt_private * priv)
{
    int ret = BT_STATUS_SUCCESS;
    u8 reg;
    u8 chiprev;
    struct sdio_mmc_card *card = priv->bt_dev.card;
    struct sdio_func *func;

    ENTER();

    if (!card || !card->func) {
        PRINTM(ERROR, "BT: Error: card or function is NULL!\n");
        goto failed;
    }
    func = card->func;
    priv->hotplug_device = &func->dev;
    if (fw_name == NULL)
        fw_name = DEFAULT_FW_NAME;

    /* Initialize the private structure */
    strncpy(priv->bt_dev.name, "bt_sdio0", sizeof(priv->bt_dev.name));
    priv->bt_dev.ioport = 0;
    priv->bt_dev.fn = func->num;

    sdio_claim_host(func);
    ret = sdio_claim_irq(func, sd_interrupt);
    if (ret) {
        PRINTM(FATAL, "BT: sdio_claim_irq failed: ret=%d\n", ret);
        goto release_host;
    }
    ret = sdio_set_block_size(card->func, SD_BLOCK_SIZE);
    if (ret) {
        PRINTM(FATAL, "BT: %s: cannot set SDIO block size\n", __FUNCTION__);
        goto release_irq;
    }

    /* read Revision Register to get the chip revision number */
    chiprev = sdio_readb(func, CARD_REVISION_REG, &ret);
    if (ret) {
        PRINTM(FATAL, "BT: cannot read CARD_REVISION_REG\n");
        goto release_irq;
    }
    priv->adapter->chip_rev = chiprev;
    PRINTM(INFO, "revision=%#x\n", chiprev);

    /* 
     * Read the HOST_INTSTATUS_REG for ACK the first interrupt got
     * from the bootloader. If we don't do this we get a interrupt
     * as soon as we register the irq. 
     */
    reg = sdio_readb(func, HOST_INTSTATUS_REG, &ret);
    if (ret < 0)
        goto release_irq;

    /* Read the IO port */
    reg = sdio_readb(func, IO_PORT_0_REG, &ret);
    if (ret < 0)
        goto release_irq;
    else
        priv->bt_dev.ioport |= reg;

    reg = sdio_readb(func, IO_PORT_1_REG, &ret);
    if (ret < 0)
        goto release_irq;
    else
        priv->bt_dev.ioport |= (reg << 8);

    reg = sdio_readb(func, IO_PORT_2_REG, &ret);
    if (ret < 0)
        goto release_irq;
    else
        priv->bt_dev.ioport |= (reg << 16);

    PRINTM(INFO, "BT: SDIO FUNC%d IO port: 0x%x\n", priv->bt_dev.fn,
           priv->bt_dev.ioport);
#define SDIO_INT_MASK       0x3F
    /* Set Host interrupt reset to read to clear */
    reg = sdio_readb(func, HOST_INT_RSR_REG, &ret);
    if (ret < 0)
        goto release_irq;
    sdio_writeb(func, reg | SDIO_INT_MASK, HOST_INT_RSR_REG, &ret);
    if (ret < 0)
        goto release_irq;
    /* Set auto re-enable */
    reg = sdio_readb(func, CARD_MISC_CFG_REG, &ret);
    if (ret < 0)
        goto release_irq;
    sdio_writeb(func, reg | AUTO_RE_ENABLE_INT, CARD_MISC_CFG_REG, &ret);
    if (ret < 0)
        goto release_irq;

    sdio_set_drvdata(func, card);
    sdio_release_host(func);

    LEAVE();
    return BT_STATUS_SUCCESS;
  release_irq:
    sdio_release_irq(func);
  release_host:
    sdio_release_host(func);
  failed:

    LEAVE();
    return BT_STATUS_FAILURE;
}
예제 #7
0
int sqn_wakeup_fw(struct sdio_func *func)
{
	int rv = 0;
	int ver = 0;
	int counter = 0;

	int retry_cnt = 3;
	u32 wakeup_delay = 0;
	unsigned long timeout = msecs_to_jiffies(800);

	unsigned long irq_flags = 0;
	struct sqn_private *priv = ((struct sqn_sdio_card *)sdio_get_drvdata(func))->priv;
	struct sqn_sdio_card *card = priv->card;
	u8 need_to_unlock_wakelock = 0;

	sqn_pr_enter();
	sqn_pr_info("waking up the card...\n");

	if (!wake_lock_active(&card->wakelock_tx)) {
		if (mmc_wimax_get_sdio_wakelock_log()) {
			printk(KERN_INFO "[WIMAX] lock wl_tx2,");
			PRINTRTC;
		}
		wake_lock(&card->wakelock_tx);
		need_to_unlock_wakelock = 1;
	}

retry:
	if (priv->removed)
		goto out;

	sdio_claim_host(func);

#define  SDIO_CCCR_CCCR_SDIO_VERSION_VALUE	0x11

    wakeup_delay = 2;
	counter = 5;
	do {
		sqn_pr_dbg("CMD52 #%d, delay %d msec\n", counter, wakeup_delay);
		ver = sdio_readb(func, SDIO_CCCR_CCCR_SDIO_VERSION, &rv);
		// To avoid FW sutck in PLLOFF, SDIO isn't able to wake up it.
		mdelay(wakeup_delay);
		--counter;
	} while((rv || ver != SDIO_CCCR_CCCR_SDIO_VERSION_VALUE) && counter > 0);

	if (rv) {
		sqn_pr_err("error when reading SDIO_VERSION\n");

		if (mmc_wimax_get_wimax_FW_freeze_WK_TX()) {
			sqn_pr_info("[ste]set is_card_sleeps 0 to avoid TX polling\n");
			card->is_card_sleeps = 0;
		}

		sdio_release_host(func);
		goto out;
	} else
		sqn_pr_dbg("SDIO_VERSION has been read successfully\n");

	sqn_pr_dbg("send wake-up signal to card\n");
	sdio_writeb(func, 1, SQN_SOC_SIGS_LSBS, &rv);

	if (rv)
		sqn_pr_err("error when writing to SQN_SOC_SIGS_LSBS: %d\n", rv);

	sdio_release_host(func);

	sqn_pr_info("wait for completion (timeout %d msec)...\n"
		, jiffies_to_msecs(timeout));

	rv = wait_event_interruptible_timeout(g_card_sleep_waitq
		, 0 == card->is_card_sleeps || priv->removed, timeout);

	if (priv->removed)
		goto out;

	if (-ERESTARTSYS == rv) {
		sqn_pr_warn("got a signal from kernel %d\n", rv);
	} else if (0 == rv) {
		rv = -1;
		sqn_pr_err("can't wake up the card - timeout elapsed\n");

		if (retry_cnt-- > 0 && card->is_card_sleeps) {
			sqn_pr_info("retry wake up\n");
			goto retry;
    	}
		sqn_pr_info("giving up to wake up the card\n");

		spin_lock_irqsave(&priv->drv_lock, irq_flags);
		card->is_card_sleeps = 0;
		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
	} else {
		rv = 0;
		sqn_pr_info("card is waked up successfully\n");
	}

out:
	if (need_to_unlock_wakelock && wake_lock_active(&card->wakelock_tx)) {
		if (mmc_wimax_get_sdio_wakelock_log()) {
			printk(KERN_INFO "[WIMAX] release wake_lock_tx in %s,", __func__);
			PRINTRTC;
		}
		wake_unlock(&card->wakelock_tx); /* TX */
	}
	sqn_pr_leave();
	return rv;
}
예제 #8
0
static void iwm_sdio_isr(struct sdio_func *func)
{
	struct iwm_priv *iwm;
	struct iwm_sdio_priv *hw;
	struct iwm_rx_info *rx_info;
	struct sk_buff *skb;
	unsigned long buf_size, read_size;
	int ret;
	u8 val;

	hw = sdio_get_drvdata(func);
	iwm = hw_to_iwm(hw);

	buf_size = hw->blk_size;

	
	val = sdio_readb(func, IWM_SDIO_INTR_STATUS_ADDR, &ret);
	if (val == 0 || ret < 0) {
		IWM_ERR(iwm, "Wrong INTR_STATUS\n");
		return;
	}

	
	if (skb_queue_len(&iwm->rx_list) > IWM_RX_LIST_SIZE) {
		IWM_ERR(iwm, "No buffer for more Rx frames\n");
		return;
	}

	
	read_size = sdio_readb(func, IWM_SDIO_INTR_GET_SIZE_ADDR + 1, &ret);
	read_size = read_size << 8;

	if (ret < 0) {
		IWM_ERR(iwm, "Couldn't read the xfer size\n");
		return;
	}

	
	sdio_writeb(func, 1, IWM_SDIO_INTR_CLEAR_ADDR, &ret);
	if (ret < 0) {
		IWM_ERR(iwm, "Couldn't clear the INT register\n");
		return;
	}

	while (buf_size < read_size)
		buf_size <<= 1;

	skb = dev_alloc_skb(buf_size);
	if (!skb) {
		IWM_ERR(iwm, "Couldn't alloc RX skb\n");
		return;
	}
	rx_info = skb_to_rx_info(skb);
	rx_info->rx_size = read_size;
	rx_info->rx_buf_size = buf_size;

	
	ret = sdio_memcpy_fromio(func, skb_put(skb, read_size),
				 IWM_SDIO_DATA_ADDR, read_size);

	
	skb_queue_tail(&iwm->rx_list, skb);

	
	queue_work(hw->isr_wq, &hw->isr_worker);
}
예제 #9
0
unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, int *err_ret) {
	return sdio_readb(func, addr, err_ret);
}
예제 #10
0
static void smssdio_interrupt(struct sdio_func *func)
{
	int ret, isr;

	struct smssdio_device *smsdev;
	struct smscore_buffer_t *cb;
	struct SmsMsgHdr_ST *hdr;
	size_t size;

	smsdev = sdio_get_drvdata(func);

	/*
	 * The interrupt register has no defined meaning. It is just
	 * a way of turning of the level triggered interrupt.
	 */
	isr = sdio_readb(func, SMSSDIO_INT, &ret);
	if (ret) {
		sms_err("Unable to read interrupt register!\n");
		return;
	}

	if (smsdev->split_cb == NULL) {
		cb = smscore_getbuffer(smsdev->coredev);
		if (!cb) {
			sms_err("Unable to allocate data buffer!\n");
			return;
		}

		ret = sdio_memcpy_fromio(smsdev->func,
					 cb->p,
					 SMSSDIO_DATA,
					 SMSSDIO_BLOCK_SIZE);
		if (ret) {
			sms_err("Error %d reading initial block!\n", ret);
			return;
		}

		hdr = cb->p;

		if (hdr->msgFlags & MSG_HDR_FLAG_SPLIT_MSG) {
			smsdev->split_cb = cb;
			return;
		}

		if (hdr->msgLength > smsdev->func->cur_blksize)
			size = hdr->msgLength - smsdev->func->cur_blksize;
		else
			size = 0;
	} else {
		cb = smsdev->split_cb;
		hdr = cb->p;

		size = hdr->msgLength - sizeof(struct SmsMsgHdr_ST);

		smsdev->split_cb = NULL;
	}

	if (size) {
		void *buffer;

		buffer = cb->p + (hdr->msgLength - size);
		size = ALIGN(size, SMSSDIO_BLOCK_SIZE);

		BUG_ON(smsdev->func->cur_blksize != SMSSDIO_BLOCK_SIZE);

		/*
		 * First attempt to transfer all of it in one go...
		 */
		ret = sdio_memcpy_fromio(smsdev->func,
					 buffer,
					 SMSSDIO_DATA,
					 size);
		if (ret && ret != -EINVAL) {
			smscore_putbuffer(smsdev->coredev, cb);
			sms_err("Error %d reading data from card!\n", ret);
			return;
		}

		/*
		 * ..then fall back to one block at a time if that is
		 * not possible...
		 *
		 * (we have to do this manually because of the
		 * problem with the "increase address" bit)
		 */
		if (ret == -EINVAL) {
			while (size) {
				ret = sdio_memcpy_fromio(smsdev->func,
						  buffer, SMSSDIO_DATA,
						  smsdev->func->cur_blksize);
				if (ret) {
					smscore_putbuffer(smsdev->coredev, cb);
					sms_err("Error %d reading "
						"data from card!\n", ret);
					return;
				}

				buffer += smsdev->func->cur_blksize;
				if (size > smsdev->func->cur_blksize)
					size -= smsdev->func->cur_blksize;
				else
					size = 0;
			}
		}
	}

	cb->size = hdr->msgLength;
	cb->offset = 0;

	smscore_onresponse(smsdev->coredev, cb);
}
예제 #11
0
/**
 *  @brief This function registers the device.
 *
 *  @param priv    A pointer to bt_private structure
 *  @return        BT_STATUS_SUCCESS or BT_STATUS_FAILURE
 */
int
sbi_register_dev(bt_private *priv)
{
	int ret = BT_STATUS_SUCCESS;
	u8 reg;
	u8 chiprev;
	struct sdio_mmc_card *card = priv->bt_dev.card;
	struct sdio_func *func;
	u8 host_intstatus_reg = HOST_INTSTATUS_REG;
	u8 card_revision_reg = CARD_REVISION_REG;
	u8 io_port_0_reg = IO_PORT_0_REG;
	u8 io_port_1_reg = IO_PORT_1_REG;
	u8 io_port_2_reg = IO_PORT_2_REG;

	ENTER();

	if (!card || !card->func) {
		PRINTM(ERROR, "BT: Error: card or function is NULL!\n");
		goto failed;
	}
	func = card->func;
	priv->hotplug_device = &func->dev;

	/* Initialize the private structure */
	strncpy(priv->bt_dev.name, "bt_sdio0", sizeof(priv->bt_dev.name));
	priv->bt_dev.ioport = 0;
	priv->bt_dev.fn = func->num;

	sdio_claim_host(func);
	ret = sdio_claim_irq(func, sd_interrupt);
	if (ret) {
		PRINTM(FATAL, ": sdio_claim_irq failed: ret=%d\n", ret);
		goto release_host;
	}
	ret = sdio_set_block_size(card->func, SD_BLOCK_SIZE);
	if (ret) {
		PRINTM(FATAL, ": %s: cannot set SDIO block size\n", __func__);
		goto release_irq;
	}

	/* read Revision Register to get the chip revision number */
	chiprev = sdio_readb(func, card_revision_reg, &ret);
	if (ret) {
		PRINTM(FATAL, ": cannot read CARD_REVISION_REG\n");
		goto release_irq;
	}
	priv->adapter->chip_rev = chiprev;
	PRINTM(INFO, "revision=%#x\n", chiprev);

	/*
	 * Read the HOST_INTSTATUS_REG for ACK the first interrupt got
	 * from the bootloader. If we don't do this we get a interrupt
	 * as soon as we register the irq.
	 */
	reg = sdio_readb(func, host_intstatus_reg, &ret);
	if (ret < 0)
		goto release_irq;

	/* Read the IO port */
	reg = sdio_readb(func, io_port_0_reg, &ret);
	if (ret < 0)
		goto release_irq;
	else
		priv->bt_dev.ioport |= reg;

	reg = sdio_readb(func, io_port_1_reg, &ret);
	if (ret < 0)
		goto release_irq;
	else
		priv->bt_dev.ioport |= (reg << 8);

	reg = sdio_readb(func, io_port_2_reg, &ret);
	if (ret < 0)
		goto release_irq;
	else
		priv->bt_dev.ioport |= (reg << 16);

	PRINTM(INFO, ": SDIO FUNC%d IO port: 0x%x\n", priv->bt_dev.fn,
	       priv->bt_dev.ioport);

	sdio_set_drvdata(func, card);
	sdio_release_host(func);

	LEAVE();
	return BT_STATUS_SUCCESS;
release_irq:
	sdio_release_irq(func);
release_host:
	sdio_release_host(func);
failed:

	LEAVE();
	return BT_STATUS_FAILURE;
}
예제 #12
0
static void rtw_dev_remove(struct sdio_func *func)
{
	PADAPTER padapter;
	struct net_device *pnetdev;
#ifdef CONFIG_IOCTL_CFG80211
	struct wireless_dev *wdev;
#endif

_func_enter_;

	RT_TRACE(_module_hci_intfs_c_, _drv_notice_, ("+rtw_dev_remove\n"));

	padapter = ((struct dvobj_priv*)sdio_get_drvdata(func))->padapter;
#ifdef CONFIG_IOCTL_CFG80211
	wdev = padapter->rtw_wdev;
#endif

#if defined(CONFIG_HAS_EARLYSUSPEND ) || defined(CONFIG_ANDROID_POWER)
	rtw_unregister_early_suspend(&padapter->pwrctrlpriv);
#endif

	if (padapter->bSurpriseRemoved == _FALSE)
	{
		// test surprise remove
		int err;

		sdio_claim_host(func);
		sdio_readb(func, 0, &err);
		sdio_release_host(func);
		if (err == -ENOMEDIUM) {
			padapter->bSurpriseRemoved = _TRUE;
			DBG_871X(KERN_NOTICE "%s: device had been removed!\n", __func__);
		}
	}

#ifdef CONFIG_HOSTAPD_MLME
	hostapd_mode_unload(padapter);
#endif
	LeaveAllPowerSaveMode(padapter);

	pnetdev = (struct net_device*)padapter->pnetdev;
	if (pnetdev) {
		unregister_netdev(pnetdev); //will call netdev_close()
		RT_TRACE(_module_hci_intfs_c_, _drv_notice_, ("rtw_dev_remove: unregister netdev\n"));
#ifdef CONFIG_PROC_DEBUG
		rtw_proc_remove_one(pnetdev);
#endif
	} else {
		RT_TRACE(_module_hci_intfs_c_, _drv_err_, ("rtw_dev_remove: NO padapter->pnetdev!\n"));
	}

	rtw_cancel_all_timer(padapter);

	rtw_dev_unload(padapter);

	// interface deinit
	sdio_deinit(padapter);
	RT_TRACE(_module_hci_intfs_c_, _drv_notice_, ("rtw_dev_remove: deinit intf complete!\n"));

	rtw_free_drv_sw(padapter);

#ifdef CONFIG_IOCTL_CFG80211
	rtw_wdev_free(wdev);
#endif

	RT_TRACE(_module_hci_intfs_c_, _drv_notice_, ("-rtw_dev_remove\n"));

_func_exit_;
}
예제 #13
0
static struct buffer_descriptor *rx_packet(struct net_adapter *adapter)
{
	int ret = 0;
	int read_idx;
	struct buffer_descriptor *bufdsc;
	s32							t_len;
	s32							t_index;
	s32							t_size;
	u8							*t_buff;

	read_idx = sdio_readb(adapter->func, SDIO_C2H_RP_REG, &ret);

	bufdsc = kmalloc(sizeof(*bufdsc), GFP_KERNEL);
	if (unlikely(!bufdsc)) {
		pr_err("%s bufdsc alloc fail", __func__);
		return NULL;
	}
	if (unlikely(ret)) {
		pr_err("%s sdio_readb error", __func__);
		schedule_work(&adapter->wimax_reset);
		goto err;
	}

#if 0
	/*check modem buffer overflow*/
	if (read_idx == sdio_readb(adapter->func, SDIO_C2H_WP_REG, &ret)) {
		read_idx = -1;
		goto err;
	}
#endif
#ifdef CMC7xx_MULTIPACKET_SUPPORT
	if (adapter->download_complete)
		t_len = sdio_readl(adapter->func, (SDIO_RX_BANK_ADDR +
					(read_idx * SDIO_RXBANK_SIZE)), &ret);
	else
#endif
		t_len = sdio_readl(adapter->func, (SDIO_RX_BANK_ADDR +
					(read_idx * SDIO_BANK_SIZE)), &ret);

	if (unlikely(ret)) {
		pr_err("%s sdio_readl error", __func__);
		schedule_work(&adapter->wimax_reset);
		goto err;
	}

#ifdef CMC7xx_MULTIPACKET_SUPPORT
	if (adapter->download_complete) {
		if (unlikely(t_len > (SDIO_RXBANK_SIZE -
						CMC732_PACKET_LENGTH_SIZE))) {
			pr_err("%s length out of bound", __func__);
			t_len = SDIO_RXBANK_SIZE - CMC732_PACKET_LENGTH_SIZE;
		}
		sdio_writeb(adapter->func, (read_idx + 1) % SDIO_RXBANK_COUNT,
			SDIO_C2H_RP_REG, NULL);
	}	else
#endif
	{
		if (unlikely(t_len > (SDIO_BANK_SIZE -
						CMC732_PACKET_LENGTH_SIZE))) {
			pr_err("%s length out of bound", __func__);
			t_len = SDIO_BANK_SIZE - CMC732_PACKET_LENGTH_SIZE;
		}
		sdio_writeb(adapter->func, (read_idx + 1) % 16,
			SDIO_C2H_RP_REG, NULL);
	}

	bufdsc->buffer = kmalloc(t_len, GFP_KERNEL);
	if (unlikely(!bufdsc->buffer)) {
		pr_err("%s bufdsc->buffer alloc fail", __func__);
		goto err;
	}

	bufdsc->length = (s32)t_len;
	t_buff = (u8 *)bufdsc->buffer;
#ifdef RX_SINGLE_BLOCK_MODE
#ifdef CMC7xx_MULTIPACKET_SUPPORT
	if (adapter->download_complete)
		t_index = (SDIO_RX_BANK_ADDR +
				(SDIO_RXBANK_SIZE * read_idx) + 4);
	else
#endif
		t_index = (SDIO_RX_BANK_ADDR + (SDIO_BANK_SIZE * read_idx) + 4);

	while (likely(t_len)) {
		t_size = (t_len > CMC_BLOCK_SIZE) ?
			(CMC_BLOCK_SIZE) : t_len;
		ret = sdio_memcpy_fromio(adapter->func, (void *)t_buff,
				t_index, t_size);

		if (unlikely(ret)) {
			pr_err("%s sdio_memcpy_fromio fail\n", __func__);
			schedule_work(&adapter->wimax_reset);
			goto err_2;
		}
		t_len -= t_size;
		t_buff += t_size;
		t_index += t_size;
	}
#else
		ret = sdio_memcpy_fromio(adapter->func, (void *)t_buff,
				t_index, t_len);

		if (unlikely(ret)) {
			pr_err("%s sdio_memcpy_fromio fail", __func__);
			schedule_work(&adapter->wimax_reset);
			goto err_2;
		}
#endif

	return bufdsc;

err_2:
	kfree(bufdsc->buffer);
err:
	kfree(bufdsc);
	adapter->netstats.rx_dropped++;
	return NULL;
}
예제 #14
0
파일: wlan.c 프로젝트: Anon0/openiBoot
int wlan_prog_helper(const uint8_t * firmware, int size)
{
	int ret;
	uint8_t status;
	uint8_t *chunk_buffer;
	uint32_t chunk_size;
	uint64_t startTime;

	bufferPrintf("wlan: programming firmware helper...\r\n");

	chunk_buffer = (uint8_t*) memalign(64, 4);
	if (!chunk_buffer) {
		ret = -1;
		goto release_fw;
	}

	ret = sdio_set_block_size(1, 32);
	if (ret)
		goto release;

	while (size) {
		startTime = timer_get_system_microtime();
		while (TRUE) {
			status = sdio_readb(1, IF_SDIO_STATUS, &ret);
			if (ret)
				goto release;

			if ((status & IF_SDIO_IO_RDY) &&
					(status & IF_SDIO_DL_RDY))
				break;

			if(has_elapsed(startTime, 1000 * 1000)) {
				ret = -1;
				goto release;
			}

			udelay(1000);
		}

		if(size > 60)
			chunk_size = 60;
		else
			chunk_size = size;


		*((uint32_t*)chunk_buffer) = chunk_size;
		memcpy(chunk_buffer + 4, firmware, chunk_size);

		//bufferPrintf("wlan: sending %d bytes chunk\r\n", chunk_size);
		
		ret = sdio_writesb(1, ioport,
				chunk_buffer, 64);
		if (ret)
			goto release;

		firmware += chunk_size;
		size -= chunk_size;
	}

	/* an empty block marks the end of the transfer */
	memset(chunk_buffer, 0, 4);
	ret = sdio_writesb(1, ioport, chunk_buffer, 64);
	if (ret)
		goto release;

	bufferPrintf("wlan: waiting for helper to boot\r\n");

	/* wait for the helper to boot by looking at the size register */
	startTime = timer_get_system_microtime();
	while (TRUE) {
		uint16_t req_size;

		req_size = sdio_readb(1, IF_SDIO_RD_BASE, &ret);
		if (ret)
			goto release;

		req_size |= sdio_readb(1, IF_SDIO_RD_BASE + 1, &ret) << 8;
		if (ret)
			goto release;

		if (req_size != 0)
			break;

		if(has_elapsed(startTime, 1000 * 1000)) {
			ret = -1;
			goto release;
		}

		udelay(10000);
	}

	ret = 0;
	bufferPrintf("wlan: helper has booted!\r\n");

release:
	free(chunk_buffer);

release_fw:

	if (ret)
		bufferPrintf("wlan: failed to load helper firmware\r\n");

	return ret;
}
예제 #15
0
파일: wlan.c 프로젝트: Anon0/openiBoot
int wlan_prog_real(const uint8_t* firmware, size_t size)
{
	int ret;
	uint8_t status;
	uint8_t *chunk_buffer;
	uint32_t chunk_size;
	size_t req_size;
	uint64_t startTime;

	bufferPrintf("wlan: programming firmware...\r\n");

	chunk_buffer = (uint8_t*) memalign(512, 4);
	if (!chunk_buffer) {
		ret = -1;
		goto release_fw;
	}
	
	ret = sdio_set_block_size(1, 32);
	if (ret)
		goto release;

	while (size) {
		startTime = timer_get_system_microtime();
		while (1) {
			status = sdio_readb(1, IF_SDIO_STATUS, &ret);
			if (ret)
				goto release;
			if ((status & IF_SDIO_IO_RDY) &&
					(status & IF_SDIO_DL_RDY))
				break;
			if(has_elapsed(startTime, 1000 * 1000)) {
				ret = -1;
				goto release;
			}
			udelay(1000);
		}

		req_size = sdio_readb(1, IF_SDIO_RD_BASE, &ret);
		if (ret)
			goto release;

		req_size |= sdio_readb(1, IF_SDIO_RD_BASE + 1, &ret) << 8;
		if (ret)
			goto release;

		//bufferPrintf("wlan: firmware helper wants %d bytes\r\n", (int)req_size);

		if (req_size == 0) {
			bufferPrintf("wlan: firmware helper gave up early\r\n");
			ret = -1;
			goto release;
		}

		if (req_size & 0x01) {
			bufferPrintf("wlan: firmware helper signalled error\r\n");
			ret = -1;
			goto release;
		}

		if (req_size > size)
			req_size = size;

		while (req_size) {
			if(req_size > 512)
				chunk_size = 512;
			else
				chunk_size = req_size;

			memcpy(chunk_buffer, firmware, chunk_size);

			//bufferPrintf("wlan: sending %d bytes (%d bytes) chunk\r\n",
			//   chunk_size, (chunk_size + 31) / 32 * 32);

			int to_send;
			to_send = chunk_size / 32;
			to_send *= 32;

			if(to_send < chunk_size)
				to_send += 32;

			ret = sdio_writesb(1, ioport,
					chunk_buffer, to_send);
			if (ret)
				goto release;

			firmware += chunk_size;
			size -= chunk_size;
			req_size -= chunk_size;
		}
	}

	ret = 0;

	bufferPrintf("wlan: waiting for firmware to boot\r\n");

	/* wait for the firmware to boot */
	startTime = timer_get_system_microtime();
	while (TRUE) {
		uint16_t scratch;

		scratch = wlan_read_scratch(&ret);
		if (ret)
			goto release;

		if (scratch == IF_SDIO_FIRMWARE_OK)
			break;

		if(has_elapsed(startTime, 1000 * 1000)) {
			ret = -1;
			goto release;
		}

		udelay(10000);
	}

	ret = 0;

	bufferPrintf("wlan: firmware booted!\r\n");

release:
	free(chunk_buffer);

release_fw:

	if (ret)
		bufferPrintf("wlan: failed to load firmware\r\n");

	return ret;
}