/**************************************************************************** * drivers/wireless/bcm43xxx/ieee80211/mmc_sdio.h * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Author: Simon Piriou * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define SDIO_CMD53_TIMEOUT_MS 100 #define SDIO_IDLE_DELAY_MS 50 /**************************************************************************** * Private Types ****************************************************************************/ begin_packed_struct struct sdio_cmd52 { uint32_t write_data : 8; uint32_t reserved_8 : 1; uint32_t register_address : 17; uint32_t reserved_26 : 1; uint32_t raw_flag : 1; uint32_t function_number : 3; uint32_t rw_flag : 1; } end_packed_struct; begin_packed_struct struct sdio_cmd53 { uint32_t byte_block_count : 9; uint32_t register_address : 17; uint32_t op_code : 1; uint32_t block_mode : 1; uint32_t function_number : 3; uint32_t rw_flag : 1; } end_packed_struct; begin_packed_struct struct sdio_resp_R5 { uint32_t data : 8; struct { uint32_t out_of_range : 1; uint32_t function_number : 1; uint32_t rfu : 1; uint32_t error : 1; uint32_t io_current_state : 2; uint32_t illegal_command : 1; uint32_t com_crc_error : 1; } flags; uint32_t reserved_16 : 16; } end_packed_struct; union sdio_cmd5x { uint32_t value; struct sdio_cmd52 cmd52; struct sdio_cmd53 cmd53; }; /**************************************************************************** * Public Functions ****************************************************************************/ int sdio_sendcmdpoll(FAR struct sdio_dev_s *dev, uint32_t cmd, uint32_t arg) { int ret; /* Send the command */ ret = SDIO_SENDCMD(dev, cmd, arg); if (ret == OK) { /* Then poll-wait until the response is available */ ret = SDIO_WAITRESPONSE(dev, cmd); if (ret != OK) { wlerr("ERROR: Wait for response to cmd: %08x failed: %d\n", cmd, ret); } } return ret; } int sdio_io_rw_direct(FAR struct sdio_dev_s *dev, bool write, uint8_t function, uint32_t address, uint8_t inb, uint8_t *outb) { union sdio_cmd5x arg; struct sdio_resp_R5 resp; int ret; /* Setup CMD52 argument */ arg.value = 0; if (write) { arg.cmd52.write_data = inb; } else { arg.cmd52.write_data = 0; } arg.cmd52.register_address = address & 0x1ffff; arg.cmd52.raw_flag = (write && outb); arg.cmd52.function_number = function & 7; arg.cmd52.rw_flag = write; /* Send CMD52 command */ sdio_sendcmdpoll(dev, SD_ACMD52, arg.value); ret = SDIO_RECVR5(dev, SD_ACMD52, (uint32_t *)&resp); if (ret != OK) { wlerr("ERROR: SDIO_RECVR5 failed %d\n", ret); return ret; } /* Check for errors */ if (resp.flags.error) { return -EIO; } if (resp.flags.function_number || resp.flags.out_of_range) { return -EINVAL; } /* Write output byte */ if (outb) { *outb = resp.data & 0xff; } return OK; } int sdio_io_rw_extended(FAR struct sdio_dev_s *dev, bool write, uint8_t function, uint32_t address, bool inc_addr, uint8_t *buf, unsigned int blocklen, unsigned int nblocks) { union sdio_cmd5x arg; struct sdio_resp_R5 resp; int ret; sdio_eventset_t wkupevent; /* Setup CMD53 argument */ arg.value = 0; arg.cmd53.register_address = address & 0x1ffff; arg.cmd53.op_code = inc_addr; arg.cmd53.function_number = function & 7; arg.cmd53.rw_flag = write; if (nblocks == 0 && blocklen < 512) { /* Use byte mode */ arg.cmd53.block_mode = 0; arg.cmd53.byte_block_count = blocklen; nblocks = 1; } else { /* Use block mode */ arg.cmd53.block_mode = 1; arg.cmd53.byte_block_count = nblocks; } /* Send CMD53 command */ SDIO_BLOCKSETUP(dev, blocklen, nblocks); SDIO_WAITENABLE(dev, SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR); if (write) { wlinfo("prep write %d %d\n", blocklen, nblocks); /* Get the capabilities of the SDIO hardware */ if ((SDIO_CAPABILITIES(dev) & SDIO_CAPS_DMABEFOREWRITE) != 0) { SDIO_DMASENDSETUP(dev, buf, blocklen * nblocks); SDIO_SENDCMD(dev, SD_ACMD53, (uint32_t)arg.value); wkupevent = SDIO_EVENTWAIT(dev, SDIO_CMD53_TIMEOUT_MS); ret = SDIO_RECVR5(dev, SD_ACMD53, (uint32_t *)&resp); } else { sdio_sendcmdpoll(dev, SD_ACMD53, (uint32_t)arg.value); ret = SDIO_RECVR5(dev, SD_ACMD53, (uint32_t *)&resp); SDIO_DMASENDSETUP(dev, buf, blocklen * nblocks); wkupevent = SDIO_EVENTWAIT(dev, SDIO_CMD53_TIMEOUT_MS); } } else { wlinfo("prep read %d\n", blocklen * nblocks); SDIO_DMARECVSETUP(dev, buf, blocklen * nblocks); SDIO_SENDCMD(dev, SD_ACMD53, (uint32_t)arg.value); wkupevent = SDIO_EVENTWAIT(dev, SDIO_CMD53_TIMEOUT_MS); ret = SDIO_RECVR5(dev, SD_ACMD53, (uint32_t *)&resp); } wlinfo("Transaction ends\n"); sdio_sendcmdpoll(dev, SD_ACMD52ABRT, 0); /* There may not be a response to this, so don't look for one */ SDIO_RECVR1(dev, SD_ACMD52ABRT, (uint32_t *)&resp); if (ret != OK) { wlerr("ERROR: SDIO_RECVR5 failed %d\n", ret); return ret; } /* Check for errors */ if (wkupevent & SDIOWAIT_TIMEOUT) { wlerr("timeout\n"); return -ETIMEDOUT; } if (resp.flags.error || (wkupevent & SDIOWAIT_ERROR)) { wlerr("error 1\n"); return -EIO; } if (resp.flags.function_number || resp.flags.out_of_range) { wlerr("error 2\n"); return -EINVAL; } return OK; } int sdio_set_wide_bus(struct sdio_dev_s *dev) { int ret; uint8_t value; /* Read Bus Interface Control register */ ret = sdio_io_rw_direct(dev, false, 0, SDIO_CCCR_BUS_IF, 0, &value); if (ret != OK) { return ret; } /* Set 4 bits bus width setting */ value &= ~SDIO_CCCR_BUS_IF_WIDTH_MASK; value |= SDIO_CCCR_BUS_IF_4_BITS; ret = sdio_io_rw_direct(dev, true, 0, SDIO_CCCR_BUS_IF, value, NULL); if (ret != OK) { return ret; } SDIO_WIDEBUS(dev, true); return OK; } int sdio_probe(FAR struct sdio_dev_s *dev) { int ret; uint32_t data = 0; /* Set device state from reset to idle */ ret = sdio_sendcmdpoll(dev, MMCSD_CMD0, 0); if (ret != OK) { return ret; } up_mdelay(SDIO_IDLE_DELAY_MS); /* Device is SDIO card compatible so we can send CMD5 instead of ACMD41 */ ret = sdio_sendcmdpoll(dev, SDIO_CMD5, 0); if (ret != OK) { return ret; } /* Receive R4 response */ ret = SDIO_RECVR4(dev, SDIO_CMD5, &data); if (ret != OK) { return ret; } /* Device is in Card Identification Mode, request device RCA */ ret = sdio_sendcmdpoll(dev, SD_CMD3, 0); if (ret != OK) { return ret; } ret = SDIO_RECVR6(dev, SD_CMD3, &data); if (ret != OK) { wlerr("ERROR: RCA request failed: %d\n", ret); return ret; } wlinfo("rca is %x\n", data >> 16); /* Send CMD7 with the argument == RCA in order to select the card * and put it in Transfer State. */ ret = sdio_sendcmdpoll(dev, MMCSD_CMD7S, data & 0xffff0000); if (ret != OK) { wlerr("ERROR: CMD7 request failed: %d\n", ret); return ret; } ret = SDIO_RECVR1(dev, MMCSD_CMD7S, &data); if (ret != OK) { wlerr("ERROR: card selection failed: %d\n", ret); return ret; } /* Configure 4 bits bus width */ ret = sdio_set_wide_bus(dev); if (ret != OK) { return ret; } return OK; } int sdio_set_blocksize(FAR struct sdio_dev_s *dev, uint8_t function, uint16_t blocksize) { int ret; ret = sdio_io_rw_direct(dev, true, 0, (function << SDIO_FBR_SHIFT) + SDIO_CCCR_FN0_BLKSIZE_0, blocksize & 0xff, NULL); if (ret != OK) { return ret; } ret = sdio_io_rw_direct(dev, true, 0, (function << SDIO_FBR_SHIFT) + SDIO_CCCR_FN0_BLKSIZE_1, (blocksize >> 8), NULL); if (ret != OK) { return ret; } return OK; } int sdio_enable_function(FAR struct sdio_dev_s *dev, uint8_t function) { int ret; uint8_t value; /* Read current I/O Enable register */ ret = sdio_io_rw_direct(dev, false, 0, SDIO_CCCR_IOEN, 0, &value); if (ret != OK) { return ret; } ret = sdio_io_rw_direct(dev, true, 0, SDIO_CCCR_IOEN, value | (1 << function), NULL); if (ret != OK) { return ret; } /* Wait 10ms for function to be enabled */ int loops = 10; while (loops-- > 0) { up_mdelay(1); ret = sdio_io_rw_direct(dev, false, 0, SDIO_CCCR_IOEN, 0, &value); if (ret != OK) { return ret; } if (value & (1 << function)) { /* Function enabled */ wlinfo("Function %d enabled\n", function); return OK; } } return -ETIMEDOUT; } int sdio_enable_interrupt(FAR struct sdio_dev_s *dev, uint8_t function) { int ret; uint8_t value; /* Read current Int Enable register */ ret = sdio_io_rw_direct(dev, false, 0, SDIO_CCCR_INTEN, 0, &value); if (ret != OK) { return ret; } return sdio_io_rw_direct(dev, true, 0, SDIO_CCCR_INTEN, value | (1 << function), NULL); }