diff options
author | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2012-09-17 18:18:44 +0000 |
---|---|---|
committer | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2012-09-17 18:18:44 +0000 |
commit | 57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff (patch) | |
tree | 25d07d14e920d31c0b1947c9ca586f2a01fc32d8 /nuttx/drivers/mmcsd/mmcsd_spi.c | |
download | px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.tar.gz px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.tar.bz2 px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.zip |
Resync new repository with old repo r5166
git-svn-id: http://svn.code.sf.net/p/nuttx/code/trunk@5153 42af7a65-404d-4744-a932-0658087f49c3
Diffstat (limited to 'nuttx/drivers/mmcsd/mmcsd_spi.c')
-rw-r--r-- | nuttx/drivers/mmcsd/mmcsd_spi.c | 1878 |
1 files changed, 1878 insertions, 0 deletions
diff --git a/nuttx/drivers/mmcsd/mmcsd_spi.c b/nuttx/drivers/mmcsd/mmcsd_spi.c new file mode 100644 index 000000000..7dbadc55f --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_spi.c @@ -0,0 +1,1878 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_spi.c + * + * Copyright (C) 2008-2010, 2011-2012 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <nuttx/config.h> +#include <sys/types.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/clock.h> +#include <nuttx/spi.h> +#include <nuttx/fs/fs.h> +#include <nuttx/mmcsd.h> + +#include "mmcsd_spi.h" +#include "mmcsd_csd.h" +#include "mmcsd_internal.h" + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_MMCSD_NSLOTS +# ifdef CONFIG_CPP_HAVE_WARNING +# warning "CONFIG_MMCSD_NSLOTS not defined" +# endif +# define CONFIG_MMCSD_NSLOTS 1 +#endif + +#define MMCSD_IDMODE_CLOCK (400000) + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +# define MMCSD_MODE 0666 +#else +# define MMCSD_MODE 0444 +#endif + +#ifndef CONFIG_MMCSD_SPICLOCK +# define CONFIG_MMCSD_SPICLOCK 20000000 +#endif + +#ifndef CONFIG_MMCSD_SECTOR512 +# define CONFIG_MMCSD_SECTOR512 /* Force 512 byte sectors on all cards */ +#endif + +/* Slot struct info *********************************************************/ +/* Slot status definitions */ + +#define MMCSD_SLOTSTATUS_NOTREADY 0x01 /* Card not initialized */ +#define MMCSD_SLOTSTATUS_NODISK 0x02 /* No card in the slot */ +#define MMCSD_SLOTSTATUS_WRPROTECT 0x04 /* Card is write protected */ +#define MMCSD_SLOTSTATUS_MEDIACHGD 0x08 /* Media changed in slot */ + +/* Values in the MMC/SD command table ***************************************/ +/* These define the value returned by the MMC/SD command */ + +#define MMCSD_CMDRESP_R1 0 +#define MMCSD_CMDRESP_R1B 1 +#define MMCSD_CMDRESP_R2 2 +#define MMCSD_CMDRESP_R3 3 +#define MMCSD_CMDRESP_R7 4 + +#ifdef CONFIG_MMCSD_SECTOR512 +# define SECTORSIZE(s) (512) +#else +# define SECTORSIZE(s) ((s)->sectorsize) +#endif + +/* Time delays in units of the system clock. CLK_TCK is the number of clock + * ticks per second. + */ + +#define MMCSD_DELAY_10MS (CLK_TCK/100 + 1) +#define MMCSD_DELAY_50MS (CLK_TCK/20 + 1) +#define MMCSD_DELAY_100MS (CLK_TCK/10 + 1) +#define MMCSD_DELAY_250MS (CLK_TCK/4 + 1) +#define MMCSD_DELAY_500MS (CLK_TCK/2 + 1) +#define MMCSD_DELAY_1SEC (CLK_TCK + 1) +#define MMCSD_DELAY_10SEC (10 * CLK_TCK + 1) + +#define ELAPSED_TIME(t) (clock_systimer()-(t)) +#define START_TIME (clock_systimer()) + +/* SD read timeout: ~100msec, Write Time out ~250ms. Units of clock ticks */ + +#define SD_READACCESS MMCSD_DELAY_100MS +#define SD_WRITEACCESS MMCSD_DELAY_250MS + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure represents the state of one card slot */ + +struct mmcsd_slot_s +{ + FAR struct spi_dev_s *spi; /* SPI port bound to this slot */ + sem_t sem; /* Assures mutually exclusive accesss to card and SPI */ + uint8_t state; /* State of the slot (see MMCSD_SLOTSTATUS_* definitions) */ + uint8_t type; /* Disk type */ + uint8_t csd[16]; /* Copy of card CSD */ +#ifndef CONFIG_MMCSD_SECTOR512 + uint16_t sectorsize; /* Media block size (in bytes) */ +#endif + uint32_t nsectors; /* Number of blocks on the media */ + uint32_t taccess; /* Card access time */ + uint32_t twrite; /* Card write time */ + uint32_t ocr; /* Last 4 bytes of OCR (R3) */ + uint32_t r7; /* Last 4 bytes of R7 */ +}; + +struct mmcsd_cmdinfo_s +{ + uint8_t cmd; + uint8_t resp; + uint8_t chksum; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Misc *********************************************************************/ + +static void mmcsd_semtake(sem_t *sem); + +/* Card SPI interface *******************************************************/ + +static int mmcsd_waitready(FAR struct mmcsd_slot_s *slot); +static uint32_t mmcsd_sendcmd(FAR struct mmcsd_slot_s *slot, + const struct mmcsd_cmdinfo_s *cmd, uint32_t arg); +static void mmcsd_setblklen(FAR struct mmcsd_slot_s *slot, + uint32_t length); +static uint32_t mmcsd_nsac(FAR struct mmcsd_slot_s *slot, uint8_t *csd, + uint32_t frequency); +static uint32_t mmcsd_taac(FAR struct mmcsd_slot_s *slot, uint8_t *csd); +static void mmcsd_decodecsd(FAR struct mmcsd_slot_s *slot, uint8_t *csd); +static void mmcsd_checkwrprotect(FAR struct mmcsd_slot_s *slot, + uint8_t *csd); +static int mmcsd_getcardinfo(FAR struct mmcsd_slot_s *slot, + uint8_t *buffer, const struct mmcsd_cmdinfo_s *cmd); + +#define mmcsd_getcsd(slot, csd) mmcsd_getcardinfo(slot, csd, &g_cmd9); +#define mmcsd_getcid(slot, cid) mmcsd_getcardinfo(slot, cid, &g_cmd10); + +static int mmcsd_recvblock(FAR struct mmcsd_slot_s *slot, + uint8_t *buffer, int nbytes); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static int mmcsd_xmitblock(FAR struct mmcsd_slot_s *slot, + const uint8_t *buffer, int nbytes, uint8_t token); +#endif + +/* Block driver interfaces **************************************************/ + +static int mmcsd_open(FAR struct inode *inode); +static int mmcsd_close(FAR struct inode *inode); +static ssize_t mmcsd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static ssize_t mmcsd_write(FAR struct inode *inode, + const unsigned char *buffer, size_t start_sector, + unsigned int nsectors); +#endif +static int mmcsd_geometry(FAR struct inode *inode, + struct geometry *geometry); + +/* Initialization ***********************************************************/ + +static int mmcsd_mediainitialize(FAR struct mmcsd_slot_s *slot); +static void mmcsd_mediachanged(void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Driver state *************************************************************/ + +/* These are the lock driver methods supported by this file */ + +static const struct block_operations g_bops = +{ + mmcsd_open, /* open */ + mmcsd_close, /* close */ + mmcsd_read, /* read */ +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) + mmcsd_write, /* write */ +#else + NULL, /* write */ +#endif + mmcsd_geometry, /* geometry */ + NULL /* ioctl */ +}; + +/* A slot structure allocated for each configured slot */ + +static struct mmcsd_slot_s g_mmcsdslot[CONFIG_MMCSD_NSLOTS]; + +/* Timing *******************************************************************/ + +/* We will use the TRAN_SPEED from the CSD to determine the maximum SPI + * clocking (TRAN_SPEED defines the maximum transfer rate per bit per data + * line). + * + * The CSD TRAN_SPEED is provided as a 3 bit rate unit (RU) and a 4 bit time + * value (TU). We need the transfer frequency which is: RU*TU bits/sec + * + * g_transpeedru holds RU/10 and g_transpeedtu holds TU*10 so that the + * correct value is returned in the product + */ + +static const uint32_t g_transpeedru[8] = +{ + 10000, /* 0: 10 Kbit/sec / 10 */ + 100000, /* 1: 1 Mbit/sec / 10 */ + 1000000, /* 2: 10 Mbit/sec / 10 */ + 10000000, /* 3: 100 Mbit/sec / 10*/ + + 0, 0, 0, 0 /* 4-7: Reserved values */ +}; + +static const uint32_t g_transpeedtu[16] = +{ + 0, 10, 12, 13, /* 0-3: Reserved, 1.0, 1.1, 1.2, 1.3 */ + 15, 20, 25, 30, /* 4-7: 1.5, 2.0, 2.5, 3.0 */ + 35, 40, 45, 50, /* 8-11: 3.5, 4.0, 4.5, 5.0 */ + 55, 60, 70, 80, /* 12-15: 5.5, 6.0, 7.0, 8.0 */ +}; + +/* The TAAC defines the asynchronous part of the data access time. The + * read access time the sum of the TAAC and the NSAC. These define the + * time from the end bit of the read command to start bit of the data block. + * + * The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * g_taactu holds TU in units of nanoseconds and microseconds (you have to use + * the index to distiguish). g_taactv holds TV with 8-bits of fraction. + */ + +#define MAX_USTUNDX 2 +static const uint16_t g_taactu[8] = +{ + /* Units of nanoseconds */ + + 1, /* 0: 1 ns */ + 10, /* 1: 10 ns */ + 100, /* 2: 100 ns */ + + /* Units of microseconds */ + + 1, /* 3: 1 us 1,000 ns */ + 10, /* 4: 10 us 10,000 ns */ + 100, /* 5: 100 us 100,000 ns */ + 1000, /* 6: 1 ms 1,000,000 ns*/ + 10000, /* 7: 10 ms 10,000,000 ns */ +}; + +static const uint16_t g_taactv[] = +{ + 0x000, 0x100, 0x133, 0x14d, /* 0-3: Reserved, 1.0, 1.2, 1.3 */ + 0x180, 0x200, 0x280, 0x300, /* 4-7: 1.5, 2.0, 2.5, 3.0 */ + 0x380, 0x400, 0x480, 0x500, /* 8-11: 3.5, 4.0, 4.5, 5.0 */ + 0x580, 0x600, 0x700, 0x800 /* 12-15: 5.5, 6.0, 7.0, 8.0 */ +}; + +/* Commands *****************************************************************/ + +static const struct mmcsd_cmdinfo_s g_cmd0 = {CMD0, MMCSD_CMDRESP_R1, 0x95}; +static const struct mmcsd_cmdinfo_s g_cmd1 = {CMD1, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd8 = {CMD8, MMCSD_CMDRESP_R7, 0x87}; +static const struct mmcsd_cmdinfo_s g_cmd9 = {CMD9, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd10 = {CMD10, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd12 = {CMD12, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd16 = {CMD16, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd17 = {CMD17, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd18 = {CMD18, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd24 = {CMD24, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd25 = {CMD25, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd55 = {CMD55, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd58 = {CMD58, MMCSD_CMDRESP_R3, 0xff}; +static const struct mmcsd_cmdinfo_s g_acmd23 = {ACMD23, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_acmd41 = {ACMD41, MMCSD_CMDRESP_R1, 0xff}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_semtake + ****************************************************************************/ + +static void mmcsd_semtake(sem_t *sem) +{ + while (sem_wait(sem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +#define mmcsd_semgive(sem) sem_post(sem) + +/**************************************************************************** + * Name: mmcsd_waitready + * + * Description: + * Wait until the card is no longer busy + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static int mmcsd_waitready(FAR struct mmcsd_slot_s *slot) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t response; + uint32_t start; + uint32_t elapsed; + + /* Wait until the card is no longer busy (up to 500MS) */ + + start = START_TIME; + do + { + response = SPI_SEND(spi, 0xff); + if (response == 0xff) + { + return OK; + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_500MS); + + fdbg("Card still busy, last response: %02x\n", response); + return -EBUSY; +} + +/**************************************************************************** + * Name: mmcsd_sendcmd + * + * Description: + * Send a command to MMC + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static uint32_t mmcsd_sendcmd(FAR struct mmcsd_slot_s *slot, + const struct mmcsd_cmdinfo_s *cmd, uint32_t arg) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t result; + uint8_t response = 0xff; + int ret; + int i; + + /* Wait until the card is not busy */ + + ret = mmcsd_waitready(slot); + if (ret != OK) + { + return ret; + } + + /* Send command code */ + + SPI_SEND(spi, cmd->cmd); + + /* Send command's arguments (should be zero if there are no arguements) */ + + SPI_SEND(spi, (arg >> 24) & 0xff); + SPI_SEND(spi, (arg >> 16) & 0xff); + SPI_SEND(spi, (arg >> 8) & 0xff); + SPI_SEND(spi, arg & 0xff); + + /* Send CRC if needed. The SPI interface is initialized in non-protected + * mode. However, the reset command (CMD0) and CMD8 are received by the + * card while it is still in SD mode and, therefore, must have a valid + * CRC field. + */ + + SPI_SEND(spi, cmd->chksum); + + /* Skip stuff byte on CMD12 */ + + if (cmd->cmd == CMD12) + { + SPI_SEND(spi, 0xff); + } + + /* Get the response to the command. A valid response will have bit7=0. + * Usually, the non-response is 0xff, but I have seen 0xc0 too. + */ + + for (i = 0; i < 9 && (response & 0x80) != 0; i++) + { + response = SPI_SEND(spi, 0xff); + } + + if ((response & 0x80) != 0) + { + fdbg("Failed: i=%d response=%02x\n", i, response); + return (uint32_t)-1; + } + + /* Interpret the response according to the command */ + + result = response; + switch (cmd->resp) + { + /* The R1B response is two bytes long */ + + case MMCSD_CMDRESP_R1B: + { + uint32_t busy = 0; + uint32_t start; + uint32_t elapsed; + + start = START_TIME; + do + { + busy = SPI_SEND(spi, 0xff); + elapsed = ELAPSED_TIME(start); + } + while (elapsed < slot->twrite && busy != 0xff); + + if (busy != 0xff) + { + fdbg("Failed: card still busy (%02x)\n", busy); + return (uint32_t)-1; + } + + fvdbg("CMD%d[%08x] R1B=%02x\n", + cmd->cmd & 0x3f, arg, response); + } + break; + + /* The R1 response is a single byte */ + + case MMCSD_CMDRESP_R1: + { + fvdbg("CMD%d[%08x] R1=%02x\n", + cmd->cmd & 0x3f, arg, response); + } + break; + + /* The R2 response is two bytes long */ + + case MMCSD_CMDRESP_R2: + { + result = ((uint32_t)(response & 0xff) << 8); + result |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R2=%04x\n", + cmd->cmd & 0x3f, arg, result); + } + break; + + /* The R3 response is 5 bytes long */ + + case MMCSD_CMDRESP_R3: + { + slot->ocr = ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 24); + slot->ocr |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 16); + slot->ocr |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 8); + slot->ocr |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R1=%02x OCR=%08x\n", + cmd->cmd & 0x3f, arg, response, slot->ocr); + } + + /* The R7 response is 5 bytes long */ + case MMCSD_CMDRESP_R7: + default: + { + slot->r7 = ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 24); + slot->r7 |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 16); + slot->r7 |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 8); + slot->r7 |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R1=%02x R7=%08x\n", + cmd->cmd & 0x3f, arg, response, slot->r7); + } + break; + } + + return result; +} + +/**************************************************************************** + * Name: mmcsd_setblklen + * + * Description: + * Set block length + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static void mmcsd_setblklen(FAR struct mmcsd_slot_s *slot, uint32_t length) +{ + uint32_t response; + + fvdbg("Set block length to %d\n", length); + response = mmcsd_sendcmd(slot, &g_cmd16, length); + if (response != MMCSD_SPIR1_OK) + { + fdbg("Failed to set block length: %02x\n", response); + } +} + +/**************************************************************************** + * Name: mmcsd_nsac + * + * Description: Convert the value of the NSAC to microseconds + * + ****************************************************************************/ + +static uint32_t mmcsd_nsac(FAR struct mmcsd_slot_s *slot, uint8_t *csd, + uint32_t frequency) +{ + /* NSAC is 8-bits wide and is in units of 100 clock cycles. Therefore, the + * maximum value is 25.5K clock cycles. + */ + + uint32_t nsac = MMCSD_CSD_NSAC(csd) * ((uint32_t)100*1000); + uint32_t fhkz = (frequency + 500) / 1000; + return (nsac + (fhkz >> 1)) / fhkz; +} + +/**************************************************************************** + * Name: mmcsd_taac + * + * Description: Convert the value of the TAAC to microseconds + * + ****************************************************************************/ + +static uint32_t mmcsd_taac(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + int tundx; + + /*The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * g_taactu holds TU in units of nanoseconds and microseconds (you have to use + * the index to distiguish. g_taactv holds TV with 8-bits of fraction. + */ + + tundx = MMCSD_CSD_TAAC_TIMEUNIT(csd); + if (tundx <= MAX_USTUNDX) + { + /* The maximum value of the nanosecond TAAC is 800 ns. The rounded + * answer in microseconds will be at most 1. + */ + + return 1; + } + else + { + /* Return the answer in microseconds */ + + return (g_taactu[tundx]*g_taactv[MMCSD_CSD_TAAC_TIMEVALUE(csd)] + 0x80) >> 8; + } +} + +/**************************************************************************** + * Name: mmcsd_decodecsd + * + * Description: + * + ****************************************************************************/ + +static void mmcsd_decodecsd(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t maxfrequency; + uint32_t frequency; + uint32_t readbllen; + uint32_t csizemult; + uint32_t csize; + + /* Calculate SPI max clock */ + + maxfrequency = + g_transpeedtu[MMCSD_CSD_TRANSPEED_TIMEVALUE(csd)] * + g_transpeedru[MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd)]; + + /* Clip the max frequency to account for board limitations */ + + frequency = maxfrequency; + if (frequency > CONFIG_MMCSD_SPICLOCK) + { + frequency = CONFIG_MMCSD_SPICLOCK; + } + + /* Set the actual SPI frequency as close as possible to that value */ + + frequency = SPI_SETFREQUENCY(spi, frequency); + + /* Now determine the delay to access data */ + + if (slot->type == MMCSD_CARDTYPE_MMC) + { + /* The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * Example: TAAC = 1.5 ms, NSAC = 0, r2wfactor = 4, CLK_TCK=100 + * taccessus = 1,500uS + * taccess = (1,500 * 100) / 100,000) + 1 = 2 (ideal, 1.5) + * twrite = (1,500 * 4 * 100) / 100,000) + 1 = 7 (ideal 6.0) + * + * First get the access time in microseconds + */ + + uint32_t taccessus = mmcsd_taac(slot, csd) + mmcsd_nsac(slot, csd, frequency); + + /* Then convert to system clock ticks. The maximum read access is 10 times + * the tacc value: taccess = 10 * (taccessus / 1,000,000) * CLK_TCK, or + */ + + slot->taccess = (taccessus * CLK_TCK) / 100000 + 1; + + /* NOTE that we add one to taccess to assure that we wait at least this + * time. The write access time is larger by the R2WFACTOR: */ + + slot->taccess = (taccessus * MMCSD_CSD_R2WFACTOR(csd) * CLK_TCK) / 100000 + 1; + } + else + { + /* For SD, the average is still given by the TAAC+NSAC, but the + * maximum are the constants 100 and 250MS + */ + + slot->taccess = SD_READACCESS; + slot->twrite = SD_WRITEACCESS; + } + + fvdbg("SPI Frequency\n"); + fvdbg(" Maximum: %d Hz\n", maxfrequency); + fvdbg(" Actual: %d Hz\n", frequency); + fvdbg("Read access time: %d ticks\n", slot->taccess); + fvdbg("Write access time: %d ticks\n", slot->twrite); + + /* Get the physical geometry of the card: sector size and number of + * sectors. The card's total capacity is computed from + * + * capacity = BLOCKNR * BLOCK_LEN + * BLOCKNR = (C_SIZE+1)*MULT + * MULT = 2**(C_SIZE_MULT+2) (C_SIZE_MULT < 8) + * BLOCK_LEN = 2**READ_BL_LEN (READ_BL_LEN < 12) + * + * Or + * + * capacity = ((C_SIZE+1) << (READD_BL_LEN + C_SIZE_MULT + 2)) + * + * In units of the sector size (1 << READ_BL_LEN), then simplifies to + * + * nsectors = ((C_SIZE+1) << (C_SIZE_MULT + 2)) + */ + + if (MMCSD_CSD_CSDSTRUCT(csd) != 0) + { + /* SDC structure ver 2.xx */ + /* Note: On SD card WRITE_BL_LEN is always the same as READ_BL_LEN */ + + readbllen = SD20_CSD_READBLLEN(csd); + csizemult = SD20_CSD_CSIZEMULT(csd) + 2; + csize = SD20_CSD_CSIZE(csd) + 1; + } + else + { + /* MMC or SD structure ver 1.xx */ + /* Note: On SD card WRITE_BL_LEN is always the same as READ_BL_LEN */ + + readbllen = MMCSD_CSD_READBLLEN(csd); + csizemult = MMCSD_CSD_CSIZEMULT(csd) + 2; + csize = MMCSD_CSD_CSIZE(csd) + 1; + } + + /* SDHC ver2.x cards have fixed block transfer size of 512 bytes. SDC + * ver1.x cards with capacity less than 1Gb, will have sector size + * 512 byes. SDC ver1.x cards with capacity of 2Gb will report readbllen + * of 1024 but should use 512 bytes for block transfers. SDC ver1.x 4Gb + * cards will report readbllen of 2048 bytes -- are they also 512 bytes? + */ + +#ifdef CONFIG_MMCSD_SECTOR512 + if (readbllen > 9) + { + csizemult += (readbllen - 9); + } + else + { + DEBUGASSERT(readbllen == 9); + } +#else + if (IS_SDV2(slot->type)) + { + if (readbllen > 9) + { + fdbg("Forcing 512 byte sector size\n"); + csizemult += (readbllen - 9); + readbllen = 9; + } + } + + slot->sectorsize = 1 << readbllen; +#endif + slot->nsectors = csize << csizemult; + fvdbg("Sector size: %d\n", SECTORSIZE(slot)); + fvdbg("Number of sectors: %d\n", slot->nsectors); +} + +/**************************************************************************** + * Name: mmcsd_checkwrprotect + * + * Description: + * + ****************************************************************************/ + +static void mmcsd_checkwrprotect(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + FAR struct spi_dev_s *spi = slot->spi; + + /* Check if (1) the slot is reporting that reporting that write protection + * is set, (2) the card reports permanent write protect, or (2) the card + * reports temporary write protect. + */ + + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_WRPROTECTED) != 0 || + MMCSD_CSD_PERMWRITEPROTECT(csd) || + MMCSD_CSD_TMPWRITEPROTECT(csd)) + { + slot->state |= MMCSD_SLOTSTATUS_WRPROTECT; + } + else + { + slot->state &= ~MMCSD_SLOTSTATUS_WRPROTECT; + } +} + +/**************************************************************************** + * Name: mmcsd_getcardinfo + * + * Description: + * Read CSD or CID registers + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static int mmcsd_getcardinfo(FAR struct mmcsd_slot_s *slot, uint8_t *buffer, + const struct mmcsd_cmdinfo_s *cmd) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t result; + uint8_t response; + int i; + + SPI_SEND(spi, 0xff); + + /* Send the CMD9 or CMD10 */ + + result = mmcsd_sendcmd(slot, cmd, 0); + if (result != MMCSD_SPIR1_OK) + { + fdbg("CMD9/10 failed: R1=%02x\n", result); + return -EIO; + } + + /* Try up to 8 times to find the start of block (or until an error occurs) */ + + for (i = 0; i < 8; i++) + { + response = SPI_SEND(spi, 0xff); + fvdbg("%d. SPI send returned %02x\n", i, response); + + /* If a read operation fails and the card cannot provide the requested + * data, it will send a data error token instead. The 4 least + * significant bits are the same as those in the R2 response. + */ + + if (response != 0 && (response & MMCSD_SPIDET_UPPER) == 0) + { + fdbg("%d. Data transfer error: %02x\n", i, response); + return -EIO; + } + else if (response == MMCSD_SPIDT_STARTBLKSNGL) + { + for (i = 0; i < 16; ++i) + { + *buffer++ = SPI_SEND(spi, 0xff); + } + + /* CRC receive */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + return OK; + } + } + + fdbg("%d. Did not find start of block\n"); + return -EIO; +} + +/**************************************************************************** + * Name: mmcsd_recvblock + * + * Description: Receive a data block from the card + * + ****************************************************************************/ + +static int mmcsd_recvblock(FAR struct mmcsd_slot_s *slot, uint8_t *buffer, int nbytes) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t start; + uint32_t elapsed; + uint8_t token; + + /* Wait up to the maximum to receive a valid data token. taccess is the + * time from when the command is sent until the first byte of data is + * received */ + + start = START_TIME; + do + { + token = SPI_SEND(spi, 0xff); + elapsed = ELAPSED_TIME(start); + } + while (token == 0xff && elapsed < slot->taccess); + + if (token == MMCSD_SPIDT_STARTBLKSNGL) + { + /* Receive the block */ + + SPI_RECVBLOCK(spi, buffer, nbytes); + + /* Discard the CRC */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + return OK; + } + + fdbg("Did not receive data token (%02x)\n", token); + return ERROR; +} + +/**************************************************************************** + * Name: mmcsd_xmitblock + * + * Description: Transmit a data block to the card + * + ****************************************************************************/ + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static int mmcsd_xmitblock(FAR struct mmcsd_slot_s *slot, const uint8_t *buffer, + int nbytes, uint8_t token) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t response; + + /* Start the block transfer: + * 1. 0xff (sync) + * 2. 0xfe or 0xfc (start of block token) + * 3. Followed by the block of data and 2 byte CRC + */ + + SPI_SEND(spi, 0xff); /* sync */ + SPI_SEND(spi, token); /* data token */ + + /* Transmit the block to the MMC/SD card */ + + (void)SPI_SNDBLOCK(spi, buffer, nbytes); + + /* Add the bogus CRC. By default, the SPI interface is initialized in + * non-protected mode. However, we still have to send bogus CRC values + */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + + /* Now get the data response */ + + response = SPI_SEND(spi, 0xff); + if ((response & MMCSD_SPIDR_MASK) != MMCSD_SPIDR_ACCEPTED) + { + fdbg("Bad data response: %02x\n", response); + return -EIO; + } + return OK; +} +#endif /* CONFIG_FS_WRITABLE && !CONFIG_MMCSD_READONLY */ + +/**************************************************************************** + * Block Driver Operations + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int mmcsd_open(FAR struct inode *inode) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + int ret; + + fvdbg("Entry\n"); + +#ifdef CONFIG_DEBUG + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that an MMC/SD card has been inserted */ + + ret = -ENODEV; + mmcsd_semtake(&slot->sem); + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) != 0) + { + /* Yes.. a card is present. Has it been initialized? */ + + if (slot->type == MMCSD_CARDTYPE_UNKNOWN) + { + /* Ininitialize for the media in the slot */ + + ret = mmcsd_mediainitialize(slot); + if (ret < 0) + { + fvdbg("Failed to initialize card\n"); + goto errout_with_sem; + } + } + + /* Make sure that the card is ready */ + + SPI_SELECT(spi, SPIDEV_MMCSD, true); + ret = mmcsd_waitready(slot); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + } + +errout_with_sem: + mmcsd_semgive(&slot->sem); + return ret; +} + +/**************************************************************************** + * Name: mmcsd_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int mmcsd_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t mmcsd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + size_t nbytes; + off_t offset; + uint8_t response; + int i; + + fvdbg("start_sector=%d nsectors=%d\n", start_sector, nsectors); + +#ifdef CONFIG_DEBUG + if (!buffer) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that card is available */ + + if (slot->state & MMCSD_SLOTSTATUS_NOTREADY) + { + fdbg("Slot not ready\n"); + return -ENODEV; + } + + /* Do nothing on zero-length transfer */ + + if (nsectors < 1) + { + return 0; + } + + /* Convert sector and nsectors to nbytes and byte offset */ + + nbytes = nsectors * SECTORSIZE(slot); + if (IS_BLOCK(slot->type)) + { + offset = start_sector; + fvdbg("nbytes=%d sector offset=%d\n", nbytes, offset); + } + else + { + offset = start_sector * SECTORSIZE(slot); + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + } + + /* Select the slave */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + + /* Single or multiple block read? */ + + if (nsectors == 1) + { + /* Send CMD17: Reads a block of the size selected by the SET_BLOCKLEN + * command and verify that good R1 status is returned + */ + + response = mmcsd_sendcmd(slot, &g_cmd17, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD17 failed: R1=%02x\n", response); + goto errout_with_eio; + } + + /* Receive the block */ + + if (mmcsd_recvblock(slot, buffer, SECTORSIZE(slot)) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_eio; + } + } + else + { + /* Send CMD18: Reads a block of the size selected by the SET_BLOCKLEN + * command and verify that good R1 status is returned + */ + + response = mmcsd_sendcmd(slot, &g_cmd18, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD118 failed: R1=%02x\n", response); + goto errout_with_eio; + } + + /* Receive each block */ + + for (i = 0; i < nsectors; i++) + { + if (mmcsd_recvblock(slot, buffer, SECTORSIZE(slot)) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_eio; + } + buffer += SECTORSIZE(slot); + } + + /* Send CMD12: Stops transmission */ + + response = mmcsd_sendcmd(slot, &g_cmd12, 0); + } + + /* On success, return the number of sectors transfer */ + + SPI_SELECT(spi, SPIDEV_MMCSD, false); + SPI_SEND(spi, 0xff); + mmcsd_semgive(&slot->sem); + + fvdbg("Read %d bytes:\n", nbytes); + mmcsd_dumpbuffer("Read buffer", buffer, nbytes); + return nsectors; + +errout_with_eio: + SPI_SELECT(spi, SPIDEV_MMCSD, false); + mmcsd_semgive(&slot->sem); + return -EIO; +} + +/**************************************************************************** + * Name: mmcsd_write + * + * Description: + * Write the specified number of sectors + * + ****************************************************************************/ + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static ssize_t mmcsd_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + size_t nbytes; + off_t offset; + uint8_t response; + int ret; + int i; + + fvdbg("start_sector=%d nsectors=%d\n", start_sector, nsectors); + +#ifdef CONFIG_DEBUG + if (!buffer) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that card is available */ + + if (slot->state & MMCSD_SLOTSTATUS_NOTREADY) + { + fdbg("Slot not ready\n"); + return -ENODEV; + } + + /* Verify that the card is write enabled */ + + if (slot->state & MMCSD_SLOTSTATUS_WRPROTECT) + { + fdbg("Not write enabled\n"); + return -EACCES; + } + + /* Do nothing on zero-length transfer */ + + if (nsectors < 1) + { + return 0; + } + + /* Convert sector and nsectors to nbytes and byte offset */ + + nbytes = nsectors * SECTORSIZE(slot); + if (IS_BLOCK(slot->type)) + { + offset = start_sector; + fvdbg("nbytes=%d sector offset=%d\n", nbytes, offset); + } + else + { + offset = start_sector * SECTORSIZE(slot); + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + } + mmcsd_dumpbuffer("Write buffer", buffer, nbytes); + + /* Select the slave */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + + /* Single or multiple block transfer? */ + + if (nsectors == 1) + { + /* Send CMD24 (WRITE_BLOCK) and verify that good R1 status is returned */ + + response = mmcsd_sendcmd(slot, &g_cmd24, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD24 failed: R1=%02x\n", response); + goto errout_with_sem; + } + + /* Then transfer the sector */ + + if (mmcsd_xmitblock(slot, buffer, SECTORSIZE(slot), 0xfe) != 0) + { + fdbg("Block transfer failed\n"); + goto errout_with_sem; + } + } + else + { + /* Set the number of blocks to be pre-erased (SD only) */ + + if (IS_SD(slot->type)) + { + response = mmcsd_sendcmd(slot, &g_acmd23, nsectors); + if (response != MMCSD_SPIR1_OK) + { + fdbg("ACMD23 failed: R1=%02x\n", response); + goto errout_with_sem; + } + } + + /* Send CMD25: Continuously write blocks of data until the + * tranmission is stopped. + */ + + response = mmcsd_sendcmd(slot, &g_cmd25, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD25 failed: R1=%02x\n", response); + goto errout_with_sem; + } + + /* Transmit each block */ + + for (i = 0; i < nsectors; i++) + { + if (mmcsd_xmitblock(slot, buffer, SECTORSIZE(slot), 0xfc) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_sem; + } + buffer += SECTORSIZE(slot); + } + + /* Send the stop transmission token */ + + SPI_SEND(spi, MMCSD_SPIDT_STOPTRANS); + } + + /* Wait until the card is no longer busy */ + + ret = mmcsd_waitready(slot); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + SPI_SEND(spi, 0xff); + mmcsd_semgive(&slot->sem); + + /* The success return value is the number of sectors written */ + + return nsectors; + +errout_with_sem: + SPI_SELECT(spi, SPIDEV_MMCSD, false); + mmcsd_semgive(&slot->sem); + return -EIO; +} +#endif + +/**************************************************************************** + * Name: mmcsd_geometry + * + * Description: + * Return device geometry + * + ****************************************************************************/ + +static int mmcsd_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + uint8_t csd[16]; + int ret; + +#ifdef CONFIG_DEBUG + if (!geometry) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Re-sample the CSD */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + ret = mmcsd_getcsd(slot, csd); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + + if (ret < 0) + { + mmcsd_semgive(&slot->sem); + fdbg("mmcsd_getcsd returned %d\n", ret); + return ret; + } + + /* Check for changes related to write protection */ + + mmcsd_checkwrprotect(slot, csd); + + /* Then return the card geometry */ + + geometry->geo_available = + ((slot->state & (MMCSD_SLOTSTATUS_NOTREADY|MMCSD_SLOTSTATUS_NODISK)) == 0); + geometry->geo_mediachanged = + ((slot->state & MMCSD_SLOTSTATUS_MEDIACHGD) != 0); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) + geometry->geo_writeenabled = + ((slot->state & MMCSD_SLOTSTATUS_WRPROTECT) == 0); +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = slot->nsectors; + geometry->geo_sectorsize = SECTORSIZE(slot); + + /* After reporting mediachanged, clear the indication so that it is not + * reported again. + */ + + slot->state &= ~MMCSD_SLOTSTATUS_MEDIACHGD; + mmcsd_semgive(&slot->sem); + + fvdbg("geo_available: %d\n", geometry->geo_available); + fvdbg("geo_mediachanged: %d\n", geometry->geo_mediachanged); + fvdbg("geo_writeenabled: %d\n", geometry->geo_writeenabled); + fvdbg("geo_nsectors: %d\n", geometry->geo_nsectors); + fvdbg("geo_sectorsize: %d\n", geometry->geo_sectorsize); + + return OK; +} + +/**************************************************************************** + * Initialization + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_mediainitialize + * + * Description: + * Detect media and initialize + * + ****************************************************************************/ + +static int mmcsd_mediainitialize(FAR struct mmcsd_slot_s *slot) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t csd[16]; + uint32_t result = MMCSD_SPIR1_IDLESTATE; + uint32_t start; + uint32_t elapsed; + int i, j; + + /* Assume that the card is not ready (we'll clear this on successful card + * initialization. + */ + + slot->state |= MMCSD_SLOTSTATUS_NOTREADY; + + /* Check if there is a card present in the slot. This is normally a matter is + * of GPIO sensing and does not really involve SPI, but by putting this + * functionality in the SPI interface, we encapuslate the SPI MMC/SD + * interface + */ + + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) == 0) + { + fdbg("No card present\n"); + slot->state |= MMCSD_SLOTSTATUS_NODISK; + return -ENODEV; + } + + /* Clock Freq. Identification Mode < 400kHz */ + + SPI_SETFREQUENCY(spi, MMCSD_IDMODE_CLOCK); + + /* Set the maximum access time out */ + + slot->taccess = SD_READACCESS; + + /* The SD card wakes up in SD mode. It will enter SPI mode if the chip select signal is + * asserted (negative) during the reception of the reset command (CMD0) and the card is in + * IDLE state. + */ + + for (i = 0; i < 2; i++) + { + /* After power up at least 74 clock cycles are required prior to + * starting bus communication + */ + + for (j = 10; j; j--) + { + SPI_SEND(spi, 0xff); + } + + /* Send CMD0 (GO_TO_IDLE) with CS asserted to put MMC/SD in + * IDLE/SPI mode. Return from CMD0 is R1 which should now + * show IDLE STATE + */ + + fvdbg("Send CMD0\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + result = mmcsd_sendcmd(slot, &g_cmd0, 0); + if (result == MMCSD_SPIR1_IDLESTATE) + { + /* Break out of the loop with card selected */ + + fvdbg("Card is in IDLE state\n"); + break; + } + + /* De-select card and try again */ + + SPI_SELECT(spi, SPIDEV_MMCSD, false); + } + + /* Verify that we exit the above loop with the card reporting IDLE state */ + + if (result != MMCSD_SPIR1_IDLESTATE) + { + fdbg("Send CMD0 failed: R1=%02x\n", result); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + + slot->type = MMCSD_CARDTYPE_UNKNOWN; + + /* Check for SDHC Version 2.x. CMD 8 is reserved on SD version 1.0 and MMC. */ + + fvdbg("Send CMD8\n"); + result = mmcsd_sendcmd(slot, &g_cmd8, 0x1aa); + if (result == MMCSD_SPIR1_IDLESTATE) + { + /* Verify the operating voltage and that the 0xaa was correctly echoed */ + + if (((slot->r7 & MMCSD_SPIR7_VOLTAGE_MASK) == MMCSD_SPIR7_VOLTAGE_27) && + ((slot->r7 & MMCSD_SPIR7_ECHO_MASK) == 0xaa)) + { + /* Try CMD55/ACMD41 for up to 1 second or until the card exits + * the IDLE state + */ + + start = START_TIME; + elapsed = 0; + do + { + fvdbg("%d. Send CMD55/ACMD41\n", elapsed); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + result = mmcsd_sendcmd(slot, &g_acmd41, (uint32_t)1 << 30); + if (result == MMCSD_SPIR1_OK) + { + break; + } + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_1SEC); + + /* Check if ACMD41 was sent successfully */ + + if (elapsed < MMCSD_DELAY_1SEC) + { + fvdbg("Send CMD58\n"); + SPI_SEND(spi, 0xff); + result = mmcsd_sendcmd(slot, &g_cmd58, 0); + if (result == MMCSD_SPIR1_OK) + { + fvdbg("OCR: %08x\n", slot->ocr); + if ((slot->ocr & MMCSD_OCR_CCS) != 0) + { + fdbg("Identified SD ver2 card/with block access\n"); + slot->type = MMCSD_CARDTYPE_SDV2|MMCSD_CARDTYPE_BLOCK; + } + else + { + fdbg("Identified SD ver2 card\n"); + slot->type = MMCSD_CARDTYPE_SDV2; + } + } + } + } + } + + /* Check for SDC version 1.x or MMC */ + + else + { + /* Both the MMC card and the SD card support CMD55 */ + + fvdbg("Send CMD55/ACMD41\n"); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + /* But ACMD41 is supported only on SD */ + + result = mmcsd_sendcmd(slot, &g_acmd41, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + fdbg("Identified SD ver1 card\n"); + slot->type = MMCSD_CARDTYPE_SDV1; + } + } + + /* Make sure that we are out of the Idle state */ + + start = START_TIME; + elapsed = 0; + do + { + if (IS_SD(slot->type)) + { + fvdbg("%d. Send CMD55/ACMD41\n", elapsed); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + result = mmcsd_sendcmd(slot, &g_acmd41, 0); + if (result == MMCSD_SPIR1_OK) + { + break; + } + } + } + else + { + fvdbg("%d. Send CMD1\n", i); + result = mmcsd_sendcmd(slot, &g_cmd1, 0); + if (result == MMCSD_SPIR1_OK) + { + fdbg("%d. Identified MMC card\n", i); + slot->type = MMCSD_CARDTYPE_MMC; + break; + } + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_1SEC); + + if (elapsed >= MMCSD_DELAY_1SEC) + { + fdbg("Failed to exit IDLE state\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + } + + if (slot->type == MMCSD_CARDTYPE_UNKNOWN) + { + fdbg("Failed to identify card\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + + /* Read CSD. CSD must always be valid */ + + fvdbg("Get CSD\n"); + result = mmcsd_getcsd(slot, csd); + if (result != OK) + { + fdbg("mmcsd_getcsd(CMD9) failed: %d\n", result); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + mmcsd_dmpcsd(csd, slot->type); + + /* CSD data and set block size */ + + mmcsd_decodecsd(slot, csd); + mmcsd_checkwrprotect(slot, csd); + + /* SDHC ver2.x cards have fixed block transfer size of 512 bytes. SDC + * ver1.x cards with capacity less than 1Gb, will have sector size + * 512 byes. SDC ver1.x cards with capacity of 2Gb will report readbllen + * of 1024 but should use 512 bytes for block transfers. SDC ver1.x 4Gb + * cards will report readbllen of 2048 bytes -- are they also 512 bytes? + * I think that none of these high capacity cards support setting the + * block length?? + */ + +#ifdef CONFIG_MMCSD_SECTOR512 + /* Using 512 byte sectors, the maximum ver1.x capacity is 4096 x 512 blocks. + * The saved slot->nsectors is converted to 512 byte blocks, so if slot->nsectors + * exceeds 4096 x 512, then we must be dealing with a card with read_bl_len + * of 1024 or 2048. + */ + + if (!IS_SDV2(slot->type) && slot->nsectors <= ((uint32_t)4096*12)) + { + /* Don't set the block len on high capacity cards (ver1.x or ver2.x) */ + + mmcsd_setblklen(slot, SECTORSIZE(slot)); + } +#else + if (!IS_SDV2(slot->type)) + { + /* Don't set the block len on ver2.x cards */ + + mmcsd_setblklen(slot, SECTORSIZE(slot)); + } +#endif + + slot->state &= ~MMCSD_SLOTSTATUS_NOTREADY; + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_mediachanged + * + * Description: + * Handle initialization/media change events + * + ****************************************************************************/ + +static void mmcsd_mediachanged(void *arg) +{ + struct mmcsd_slot_s *slot = (struct mmcsd_slot_s*)arg; + FAR struct spi_dev_s *spi; + uint8_t oldstate; + int ret; + +#ifdef CONFIG_DEBUG + if (!slot || !slot->spi) + { + fdbg("Internal confusion\n"); + return; + } +#endif + spi = slot->spi; + + /* Save the current slot state and reassess the new state */ + + mmcsd_semtake(&slot->sem); + oldstate = slot->state; + + /* Check if media was removed or inserted */ + + slot->state &= ~(MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY|MMCSD_SLOTSTATUS_MEDIACHGD); + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) == 0) + { + /* Media is not present */ + + fdbg("No card present\n"); + slot->state |= (MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY); + + /* Was media removed? */ + + if ((oldstate & MMCSD_SLOTSTATUS_NODISK) == 0) + { + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + } + + /* Media is present, was it just inserted? Or, if it was previously not ready, + * then try re-initializing it + */ + + else if ((oldstate & (MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY)) != 0) + { + /* (Re-)ininitialize for the media in the slot */ + + ret = mmcsd_mediainitialize(slot); + if (ret == 0) + { + fvdbg("mmcsd_mediainitialize returned OK\n"); + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_spislotinitialize + * + * Description: + * Initialize one slot for operation using the SPI MMC/SD interface + * + * Input Parameters: + * minor - The MMC/SD minor device number. The MMC/SD device will be + * registered as /dev/mmcsdN where N is the minor number + * slotno - The slot number to use. This is only meaningful for architectures + * that support multiple MMC/SD slots. This value must be in the range + * {0, ..., CONFIG_MMCSD_NSLOTS}. + * spi - And instance of an SPI interface obtained by called + * up_spiinitialize() with the appropriate port number (see spi.h) + * + ****************************************************************************/ + +int mmcsd_spislotinitialize(int minor, int slotno, FAR struct spi_dev_s *spi) +{ + struct mmcsd_slot_s *slot; + char devname[16]; + int ret; + +#ifdef CONFIG_DEBUG + if ((unsigned)slotno >= CONFIG_MMCSD_NSLOTS || (unsigned)minor > 255 || !spi) + { + fdbg("Invalid arguments\n"); + return -EINVAL; + } +#endif + + /* Select the slot structure */ + + slot = &g_mmcsdslot[slotno]; + memset(slot, 0, sizeof(struct mmcsd_slot_s)); + sem_init(&slot->sem, 0, 1); + +#ifdef CONFIG_DEBUG + if (slot->spi) + { + fdbg("Already registered\n"); + return -EBUSY; + } +#endif + + /* Bind the SPI port to the slot */ + + slot->spi = spi; + + /* Ininitialize for the media in the slot (if any) */ + + ret = mmcsd_mediainitialize(slot); + if (ret == 0) + { + fvdbg("mmcsd_mediainitialize returned OK\n"); + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + + /* Create a MMC/SD device name */ + + snprintf(devname, 16, "/dev/mmcsd%d", minor); + + /* Register the driver, even on a failure condition. A + * card may be inserted later, for example. + */ + + ret = register_blockdriver(devname, &g_bops, MMCSD_MODE, slot); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + slot->spi = NULL; + return ret; + } + + /* Register a media change callback to handler insertion and + * removal of cards. + */ + + (void)SPI_REGISTERCALLBACK(spi, mmcsd_mediachanged, (void*)slot); + return OK; +} |