/**************************************************************************** * drivers/mmcsd/sdio.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define SDIO_CMD53_TIMEOUT_MS 1000 #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; begin_packed_struct 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; } end_packed_struct flags; uint32_t reserved_16 : 16; } end_packed_struct; union sdio_cmd5x { uint32_t value; struct sdio_cmd52 cmd52; struct sdio_cmd53 cmd53; }; /**************************************************************************** * Private Functions ****************************************************************************/ static int sdio_takelock(FAR struct sdio_dev_s *dev) { int ret; /* Take the lock, giving exclusive access to the driver (perhaps * waiting) */ if (!up_interrupt_context() && !sched_idletask()) { ret = nxmutex_lock(&dev->mutex); if (ret < 0) { return ret; } /* Lock the bus if mutually exclusive access to the * SDIO bus is required on this platform. */ #ifdef CONFIG_SDIO_MUXBUS SDIO_LOCK(dev, true); #endif } else { ret = OK; } return ret; } static void sdio_givelock(FAR struct sdio_dev_s *dev) { if (!up_interrupt_context() && !sched_idletask()) { /* Release the SDIO bus lock, then the MMC/SD driver mutex in the * opposite order that they were taken to assure that no deadlock * conditions will arise. */ #ifdef CONFIG_SDIO_MUXBUS SDIO_LOCK(dev, false); #endif nxmutex_unlock(&dev->mutex); } } static 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: %08" PRIx32 " failed: %d\n", cmd, ret); } } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ int sdio_io_rw_direct(FAR struct sdio_dev_s *dev, bool write, uint8_t function, uint32_t address, uint8_t inb, FAR uint8_t *outb) { union sdio_cmd5x arg; struct sdio_resp_r5 resp; uint32_t data; 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_takelock(dev); sdio_sendcmdpoll(dev, SD_ACMD52, arg.value); ret = SDIO_RECVR5(dev, SD_ACMD52, &data); sdio_givelock(dev); if (ret != OK) { wlerr("ERROR: SDIO_RECVR5 failed %d\n", ret); return ret; } memcpy(&resp, &data, sizeof(resp)); /* 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, FAR uint8_t *buf, unsigned int blocklen, unsigned int nblocks) { union sdio_cmd5x arg; struct sdio_resp_r5 resp; uint32_t data; 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; } sdio_takelock(dev); /* Send CMD53 command */ SDIO_BLOCKSETUP(dev, blocklen, nblocks); SDIO_WAITENABLE(dev, SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR, SDIO_CMD53_TIMEOUT_MS); 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_ACMD53WR, arg.value); wkupevent = SDIO_EVENTWAIT(dev); ret = SDIO_RECVR5(dev, SD_ACMD53WR, &data); } else { sdio_sendcmdpoll(dev, SD_ACMD53WR, arg.value); ret = SDIO_RECVR5(dev, SD_ACMD53WR, &data); SDIO_DMASENDSETUP(dev, buf, blocklen * nblocks); wkupevent = SDIO_EVENTWAIT(dev); } } else { wlinfo("prep read %d\n", blocklen * nblocks); SDIO_DMARECVSETUP(dev, buf, blocklen * nblocks); SDIO_SENDCMD(dev, SD_ACMD53RD, arg.value); wkupevent = SDIO_EVENTWAIT(dev); ret = SDIO_RECVR5(dev, SD_ACMD53RD, &data); } 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, &data); sdio_givelock(dev); if (ret != OK) { wlerr("ERROR: SDIO_RECVR5 failed %d\n", ret); return ret; } memcpy(&resp, &data, sizeof(resp)); /* 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(FAR 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; nxmutex_init(&dev->mutex); sdio_takelock(dev); /* Set device state from reset to idle */ ret = sdio_sendcmdpoll(dev, MMCSD_CMD0, 0); if (ret != OK) { goto err; } 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) { goto err; } /* Receive R4 response */ ret = SDIO_RECVR4(dev, SDIO_CMD5, &data); if (ret != OK) { goto err; } /* Device is in Card Identification Mode, request device RCA */ ret = sdio_sendcmdpoll(dev, SD_CMD3, 0); if (ret != OK) { goto err; } ret = SDIO_RECVR6(dev, SD_CMD3, &data); if (ret != OK) { wlerr("ERROR: RCA request failed: %d\n", ret); goto err; } wlinfo("rca is %" PRIx32 "\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); goto err; } ret = SDIO_RECVR1(dev, MMCSD_CMD7S, &data); if (ret != OK) { wlerr("ERROR: card selection failed: %d\n", ret); goto err; } /* Configure 4 bits bus width */ sdio_givelock(dev); return sdio_set_wide_bus(dev); err: sdio_givelock(dev); return ret; } 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 1s for function to be enabled */ int loops = 100; while (loops-- > 0) { nxsig_usleep(10 * 1000); ret = sdio_io_rw_direct(dev, false, 0, SDIO_CCCR_IORDY, 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); }