From faf22609f4a434ff9981bc9d422a38261aa5b00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Th=C3=A9baudeau?= Date: Thu, 17 Nov 2016 17:41:44 +0100 Subject: [PATCH] disk: Add SD/MMC driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only SPI is supported. Signed-off-by: Benoît Thébaudeau --- dev/disk/mmc/mmc-arch.h | 98 +++++++ dev/disk/mmc/mmc.c | 608 ++++++++++++++++++++++++++++++++++++++++ dev/disk/mmc/mmc.h | 61 ++++ 3 files changed, 767 insertions(+) create mode 100644 dev/disk/mmc/mmc-arch.h create mode 100644 dev/disk/mmc/mmc.c create mode 100644 dev/disk/mmc/mmc.h diff --git a/dev/disk/mmc/mmc-arch.h b/dev/disk/mmc/mmc-arch.h new file mode 100644 index 000000000..69009d99e --- /dev/null +++ b/dev/disk/mmc/mmc-arch.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016, Benoît Thébaudeau + * All rights reserved. + * + * 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 of the copyright holder 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 HOLDER 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. + */ +/** + * \addtogroup mmc + * @{ + * + * \defgroup mmc-arch SD/MMC architecture-specific definitions + * + * SD/MMC device driver architecture-specific definitions. + * @{ + * + * \file + * Header file for the SD/MMC device driver architecture-specific definitions. + */ +#ifndef MMC_ARCH_H_ +#define MMC_ARCH_H_ + +#include +#include +#include + +/** \brief Callback of the SD/MMC driver to call when the card-detection signal + * changes. + * \param dev Device + * \param cd Whether a card is detected + * \note Using this function is not mandatory. This only allows to detect a card + * replacement between two successive calls to the SD/MMC driver API. + */ +void mmc_arch_cd_changed_callback(uint8_t dev, bool cd); + +/** \brief Gets the state of the card-detection signal. + * \param dev Device + * \return Whether a card is detected + */ +bool mmc_arch_get_cd(uint8_t dev); + +/** \brief Gets the state of the write-protection signal. + * \param dev Device + * \return Whether the card is write-protected + */ +bool mmc_arch_get_wp(uint8_t dev); + +/** \brief Sets the SPI /CS signal as indicated. + * \param dev Device + * \param sel Whether to assert /CS + */ +void mmc_arch_spi_select(uint8_t dev, bool sel); + +/** \brief Sets the SPI clock frequency. + * \param dev Device + * \param freq Frequency (Hz) + */ +void mmc_arch_spi_set_clock_freq(uint8_t dev, uint32_t freq); + +/** \brief Performs an SPI transfer. + * \param dev Device + * \param tx_buf Pointer to the transmission buffer, or \c NULL + * \param tx_cnt Number of bytes to transmit, or \c 0 + * \param rx_buf Pointer to the reception buffer, or \c NULL + * \param rx_cnt Number of bytes to receive, or \c 0 + */ +void mmc_arch_spi_xfer(uint8_t dev, const void *tx_buf, size_t tx_cnt, + void *rx_buf, size_t rx_cnt); + +#endif /* MMC_ARCH_H_ */ + +/** + * @} + * @} + */ diff --git a/dev/disk/mmc/mmc.c b/dev/disk/mmc/mmc.c new file mode 100644 index 000000000..11582de17 --- /dev/null +++ b/dev/disk/mmc/mmc.c @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2016, Benoît Thébaudeau + * All rights reserved. + * + * Based on the FatFs Module STM32 Sample Project, + * Copyright (c) 2014, ChaN + * All rights reserved. + * + * 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 of the copyright holder 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 HOLDER 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. + */ +/** + * \addtogroup mmc + * @{ + * + * \file + * Implementation of the SD/MMC device driver. + */ +#include +#include +#include "contiki.h" +#include "sys/clock.h" +#include "sys/rtimer.h" +#include "dev/watchdog.h" +#include "mmc-arch.h" +#include "mmc.h" + +/* Data read/write block length */ +#define BLOCK_LEN 512 +/* + * Logical sector size exposed to the disk API, not to be confused with the SDSC + * sector size, which is the size of an erasable sector + */ +#define SECTOR_SIZE BLOCK_LEN + +/* Clock frequency in card identification mode: fOD <= 400 kHz */ +#define CLOCK_FREQ_CARD_ID_MODE 400000 +/* + * Clock frequency in data transfer mode: fPP <= 20 MHz, limited by the + * backward-compatible MMC interface timings + */ +#define CLOCK_FREQ_DATA_XFER_MODE 20000000 + +/* SPI-mode command list */ +#define CMD0 0 /* GO_IDLE_STATE */ +#define CMD1 1 /* SEND_OP_COND */ +#define CMD8 8 /* SEND_IF_COND */ +#define CMD8_VHS_2_7_3_6 0x1 +#define CMD8_ARG(vhs, check_pattern) ((vhs) << 8 | (check_pattern)) +#define CMD8_ECHO_MASK 0x00000fff +#define CMD9 9 /* SEND_CSD */ +#define CMD10 10 /* SEND_CID */ +#define CMD12 12 /* STOP_TRANSMISSION */ +#define CMD13 13 /* SEND_STATUS */ +#define CMD16 16 /* SET_BLOCKLEN */ +#define CMD17 17 /* READ_SINGLE_BLOCK */ +#define CMD18 18 /* READ_MULTIPLE_BLOCK */ +#define CMD23 23 /* SET_BLOCK_COUNT */ +#define CMD24 24 /* WRITE_BLOCK */ +#define CMD25 25 /* WRITE_MULTIPLE_BLOCK */ +#define CMD32 32 /* ERASE_WR_BLK_START */ +#define CMD33 33 /* ERASE_WR_BLK_END */ +#define CMD38 38 /* ERASE */ +#define CMD55 55 /* APP_CMD */ +#define CMD58 58 /* READ_OCR */ +#define ACMD 0x80 /* Application-specific command */ +#define ACMD13 (ACMD | 13) /* SD_STATUS */ +#define ACMD23 (ACMD | 23) /* SET_WR_BLK_ERASE_COUNT */ +#define ACMD41 (ACMD | 41) /* SD_APP_OP_COND */ +#define ACMD41_HCS (1 << 30) + +#define CMD_TX 0x40 /* Command transmission bit */ + +#define R1_MSB 0x00 +#define R1_SUCCESS 0x00 +#define R1_IDLE_STATE (1 << 0) +#define R1_ERASE_RESET (1 << 1) +#define R1_ILLEGAL_COMMAND (1 << 2) +#define R1_COM_CRC_ERROR (1 << 3) +#define R1_ERASE_SEQUENCE_ERROR (1 << 4) +#define R1_ADDRESS_ERROR (1 << 5) +#define R1_PARAMETER_ERROR (1 << 6) + +#define TOK_DATA_RESP_MASK 0x1f +#define TOK_DATA_RESP_ACCEPTED 0x05 +#define TOK_DATA_RESP_CRC_ERROR 0x0b +#define TOK_DATA_RESP_WR_ERROR 0x0d +#define TOK_RD_SINGLE_WR_START_BLOCK 0xfe +#define TOK_MULTI_WR_START_BLOCK 0xfc +#define TOK_MULTI_WR_STOP_TRAN 0xfd + +/* The SD Status is one data block of 512 bits. */ +#define SD_STATUS_SIZE (512 / 8) +#define SD_STATUS_AU_SIZE(sd_status) ((sd_status)[10] >> 4) + +#define OCR_CCS (1 << 30) + +#define CSD_SIZE 16 +#define CSD_STRUCTURE(csd) ((csd)[0] >> 6) +#define CSD_STRUCTURE_SD_V1_0 0 +#define CSD_STRUCTURE_SD_V2_0 1 +#define CSD_SD_V1_0_READ_BL_LEN(csd) ((csd)[5] & 0x0f) +#define CSD_SD_V1_0_BLOCK_LEN(csd) (1ull << CSD_SD_V1_0_READ_BL_LEN(csd)) +#define CSD_SD_V1_0_C_SIZE(csd) \ + (((csd)[6] & 0x03) << 10 | (csd)[7] << 2 | (csd)[8] >> 6) +#define CSD_SD_V1_0_C_SIZE_MULT(csd) \ + (((csd)[9] & 0x03) << 1 | (csd)[10] >> 7) +#define CSD_SD_V1_0_MULT(csd) \ + (1 << (CSD_SD_V1_0_C_SIZE_MULT(csd) + 2)) +#define CSD_SD_V1_0_BLOCKNR(csd) \ + (((uint32_t)CSD_SD_V1_0_C_SIZE(csd) + 1) * CSD_SD_V1_0_MULT(csd)) +#define CSD_SD_V1_0_CAPACITY(csd) \ + (CSD_SD_V1_0_BLOCKNR(csd) * CSD_SD_V1_0_BLOCK_LEN(csd)) +#define CSD_SD_V1_0_SECTOR_SIZE(csd) \ + (((csd)[10] & 0x3f) << 1 | (csd)[11] >> 7) +#define CSD_SD_V1_0_WRITE_BL_LEN(csd) \ + (((csd)[12] & 0x03) << 2 | (csd)[13] >> 6) +#define CSD_SD_V2_0_C_SIZE(csd) \ + (((csd)[7] & 0x3f) << 16 | (csd)[8] << 8 | (csd)[9]) +#define CSD_SD_V2_0_CAPACITY(csd) \ + (((uint64_t)CSD_SD_V2_0_C_SIZE(csd) + 1) << 19) +#define CSD_MMC_ERASE_GRP_SIZE(csd) (((csd)[10] & 0x7c) >> 2) +#define CSD_MMC_ERASE_GRP_MULT(csd) \ + (((csd)[10] & 0x03) << 3 | (csd)[11] >> 5) +#define CSD_MMC_WRITE_BL_LEN(csd) \ + (((csd)[12] & 0x03) << 2 | (csd)[13] >> 6) + +typedef enum { + CARD_TYPE_MMC = 0x01, /* MMC v3 */ + CARD_TYPE_SD1 = 0x02, /* SD v1 */ + CARD_TYPE_SD2 = 0x04, /* SD v2 */ + CARD_TYPE_SD = CARD_TYPE_SD1 | CARD_TYPE_SD2, /* SD */ + CARD_TYPE_BLOCK = 0x08 /* Block addressing */ +} card_type_t; + +static struct mmc_priv { + uint8_t status; + uint8_t card_type; +} mmc_priv[MMC_CONF_DEV_COUNT]; + +/*----------------------------------------------------------------------------*/ +static uint8_t +mmc_spi_xchg(uint8_t dev, uint8_t tx_byte) +{ + uint8_t rx_byte; + + mmc_arch_spi_xfer(dev, &tx_byte, 1, &rx_byte, 1); + return rx_byte; +} +/*----------------------------------------------------------------------------*/ +static void +mmc_spi_tx(uint8_t dev, const void *buf, size_t cnt) +{ + mmc_arch_spi_xfer(dev, buf, cnt, NULL, 0); +} +/*----------------------------------------------------------------------------*/ +static void +mmc_spi_rx(uint8_t dev, void *buf, size_t cnt) +{ + mmc_arch_spi_xfer(dev, NULL, 0, buf, cnt); +} +/*----------------------------------------------------------------------------*/ +static bool +mmc_wait_ready(uint8_t dev, uint16_t timeout_ms) +{ + rtimer_clock_t timeout_end = + RTIMER_NOW() + ((uint32_t)timeout_ms * RTIMER_SECOND + 999) / 1000; + uint8_t rx_byte; + + do { + rx_byte = mmc_spi_xchg(dev, 0xff); + watchdog_periodic(); + } while(rx_byte != 0xff && RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end)); + return rx_byte == 0xff; +} +/*----------------------------------------------------------------------------*/ +static bool +mmc_select(uint8_t dev, bool sel) +{ + mmc_arch_spi_select(dev, sel); + mmc_spi_xchg(dev, 0xff); /* Dummy clock (force D0) */ + if(sel && !mmc_wait_ready(dev, 500)) { + mmc_select(dev, false); + return false; + } + return true; +} +/*----------------------------------------------------------------------------*/ +static uint8_t +mmc_send_cmd(uint8_t dev, uint8_t cmd, uint32_t arg) +{ + uint8_t resp, n; + + /* Send a CMD55 prior to a ACMD. */ + if(cmd & ACMD) { + cmd &= ~ACMD; + resp = mmc_send_cmd(dev, CMD55, 0); + if(resp != R1_SUCCESS && resp != R1_IDLE_STATE) { + return resp; + } + } + + /* + * Select the card and wait for ready, except to stop a multiple-block read. + */ + if(cmd != CMD12) { + mmc_select(dev, false); + if(!mmc_select(dev, true)) { + return 0xff; + } + } + + /* Send the command packet. */ + mmc_spi_xchg(dev, CMD_TX | cmd); /* Start & tx bits, cmd index */ + mmc_spi_xchg(dev, arg >> 24); /* Argument[31..24] */ + mmc_spi_xchg(dev, arg >> 16); /* Argument[23..16] */ + mmc_spi_xchg(dev, arg >> 8); /* Argument[15..8] */ + mmc_spi_xchg(dev, arg); /* Argument[7..0] */ + switch(cmd) { + case CMD0: + n = 0x95; /* CMD0(0) CRC7, end bit */ + break; + case CMD8: + n = 0x87; /* CMD8(0x1aa) CRC7, end bit */ + break; + default: + n = 0x01; /* Dummy CRC7, end bit */ + break; + } + mmc_spi_xchg(dev, n); + + /* Receive the command response. */ + if(cmd == CMD12) { + mmc_spi_xchg(dev, 0xff); /* Discard following byte if CMD12. */ + } + /* Wait for the response (max. 10 bytes). */ + n = 10; + do { + resp = mmc_spi_xchg(dev, 0xff); + } while((resp & 0x80) != R1_MSB && --n); + return resp; +} +/*----------------------------------------------------------------------------*/ +static bool +mmc_tx_block(uint8_t dev, const void *buf, uint8_t token) +{ + uint8_t resp; + + if(!mmc_wait_ready(dev, 500)) { + return false; + } + + mmc_spi_xchg(dev, token); + if(token != TOK_MULTI_WR_STOP_TRAN) { + mmc_spi_tx(dev, buf, BLOCK_LEN); + mmc_spi_xchg(dev, 0xff); /* Dummy CRC */ + mmc_spi_xchg(dev, 0xff); + + resp = mmc_spi_xchg(dev, 0xff); + if((resp & TOK_DATA_RESP_MASK) != TOK_DATA_RESP_ACCEPTED) { + return false; + } + } + return true; +} +/*----------------------------------------------------------------------------*/ +static bool +mmc_rx(uint8_t dev, void *buf, size_t cnt) +{ + rtimer_clock_t timeout_end = + RTIMER_NOW() + (200ul * RTIMER_SECOND + 999) / 1000; + uint8_t token; + + do { + token = mmc_spi_xchg(dev, 0xff); + watchdog_periodic(); + } while(token == 0xff && RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end)); + if(token != TOK_RD_SINGLE_WR_START_BLOCK) { + return false; + } + + mmc_spi_rx(dev, buf, cnt); + mmc_spi_xchg(dev, 0xff); /* Discard CRC. */ + mmc_spi_xchg(dev, 0xff); + return true; +} +/*----------------------------------------------------------------------------*/ +void +mmc_arch_cd_changed_callback(uint8_t dev, bool cd) +{ + uint8_t status; + + if(dev >= MMC_CONF_DEV_COUNT) { + return; + } + + if(cd) { + status = DISK_STATUS_DISK; + if(!mmc_arch_get_wp(dev)) { + status |= DISK_STATUS_WRITABLE; + } + } else { + status = 0; + } + mmc_priv[dev].status = status; +} +/*----------------------------------------------------------------------------*/ +static disk_status_t +mmc_status(uint8_t dev) +{ + bool cd; + struct mmc_priv *priv; + + if(dev >= MMC_CONF_DEV_COUNT) { + return DISK_RESULT_INVALID_ARG; + } + + cd = mmc_arch_get_cd(dev); + priv = &mmc_priv[dev]; + if(cd == !(priv->status & DISK_STATUS_DISK)) { + mmc_arch_cd_changed_callback(dev, cd); + } + return priv->status; +} +/*----------------------------------------------------------------------------*/ +static disk_status_t +mmc_initialize(uint8_t dev) +{ + disk_status_t status; + uint8_t n, cmd; + card_type_t card_type; + rtimer_clock_t timeout_end; + uint32_t arg, resp, ocr; + struct mmc_priv *priv; + + if(dev >= MMC_CONF_DEV_COUNT) { + return DISK_RESULT_INVALID_ARG; + } + status = mmc_status(dev); + if(!(status & DISK_STATUS_DISK)) { + return status; + } + + mmc_arch_spi_select(dev, false); + clock_delay_usec(10000); + + mmc_arch_spi_set_clock_freq(dev, CLOCK_FREQ_CARD_ID_MODE); + for(n = 10; n; n--) { + mmc_spi_xchg(dev, 0xff); /* Generate 80 dummy clock cycles. */ + } + + card_type = 0; + if(mmc_send_cmd(dev, CMD0, 0) == R1_IDLE_STATE) { + timeout_end = RTIMER_NOW() + RTIMER_SECOND; + arg = CMD8_ARG(CMD8_VHS_2_7_3_6, 0xaa); /* Arbitrary check pattern */ + if(mmc_send_cmd(dev, CMD8, arg) == R1_IDLE_STATE) { /* SD v2? */ + resp = 0; + for(n = 4; n; n--) { + resp = resp << 8 | mmc_spi_xchg(dev, 0xff); + } + /* Does the card support 2.7 V - 3.6 V? */ + if((arg & CMD8_ECHO_MASK) == (resp & CMD8_ECHO_MASK)) { + /* Wait for end of initialization. */ + while(RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end) && + mmc_send_cmd(dev, ACMD41, ACMD41_HCS) != R1_SUCCESS) { + watchdog_periodic(); + } + if(RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end) && + mmc_send_cmd(dev, CMD58, 0) == R1_SUCCESS) { /* Read OCR. */ + ocr = 0; + for(n = 4; n; n--) { + ocr = ocr << 8 | mmc_spi_xchg(dev, 0xff); + } + card_type = CARD_TYPE_SD2; + if(ocr & OCR_CCS) { + card_type |= CARD_TYPE_BLOCK; + } + } + } + } else { /* Not SD v2 */ + resp = mmc_send_cmd(dev, ACMD41, 0); + if(resp == R1_SUCCESS || resp == R1_IDLE_STATE) { /* SD v1 or MMC? */ + card_type = CARD_TYPE_SD1; + cmd = ACMD41; + } else { + card_type = CARD_TYPE_MMC; + cmd = CMD1; + } + /* Wait for end of initialization. */ + while(RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end) && + mmc_send_cmd(dev, cmd, 0) != R1_SUCCESS) { + watchdog_periodic(); + } + /* Set block length. */ + if(!RTIMER_CLOCK_LT(RTIMER_NOW(), timeout_end) || + mmc_send_cmd(dev, CMD16, BLOCK_LEN) != R1_SUCCESS) { + card_type = 0; + } + } + } + priv = &mmc_priv[dev]; + priv->card_type = card_type; + mmc_select(dev, false); + + status = priv->status; + if(status & DISK_STATUS_DISK && card_type) { /* OK */ + mmc_arch_spi_set_clock_freq(dev, CLOCK_FREQ_DATA_XFER_MODE); + status |= DISK_STATUS_INIT; + } else { /* Failed */ + status &= ~DISK_STATUS_INIT; + } + priv->status = status; + return status; +} +/*----------------------------------------------------------------------------*/ +static disk_result_t +mmc_read(uint8_t dev, void *buff, uint32_t sector, uint32_t count) +{ + if(dev >= MMC_CONF_DEV_COUNT || !count) { + return DISK_RESULT_INVALID_ARG; + } + if(!(mmc_status(dev) & DISK_STATUS_INIT)) { + return DISK_RESULT_NO_INIT; + } + + if(!(mmc_priv[dev].card_type & CARD_TYPE_BLOCK)) { + sector *= SECTOR_SIZE; + } + + if(count == 1) { + if(mmc_send_cmd(dev, CMD17, sector) == R1_SUCCESS && + mmc_rx(dev, buff, SECTOR_SIZE)) { + count = 0; + } + } else if(mmc_send_cmd(dev, CMD18, sector) == R1_SUCCESS) { + do { + if(!mmc_rx(dev, buff, SECTOR_SIZE)) { + break; + } + buff = (uint8_t *)buff + SECTOR_SIZE; + watchdog_periodic(); + } while(--count); + mmc_send_cmd(dev, CMD12, 0); /* Stop transmission. */ + } + mmc_select(dev, false); + return count ? DISK_RESULT_IO_ERROR : DISK_RESULT_OK; +} +/*----------------------------------------------------------------------------*/ +static disk_result_t +mmc_write(uint8_t dev, const void *buff, uint32_t sector, uint32_t count) +{ + disk_status_t status; + card_type_t card_type; + + if(dev >= MMC_CONF_DEV_COUNT || !count) { + return DISK_RESULT_INVALID_ARG; + } + status = mmc_status(dev); + if(!(status & DISK_STATUS_INIT)) { + return DISK_RESULT_NO_INIT; + } + if(!(status & DISK_STATUS_WRITABLE)) { + return DISK_RESULT_WR_PROTECTED; + } + + card_type = mmc_priv[dev].card_type; + if(!(card_type & CARD_TYPE_BLOCK)) { + sector *= SECTOR_SIZE; + } + + if(count == 1) { + if(mmc_send_cmd(dev, CMD24, sector) == R1_SUCCESS && + mmc_tx_block(dev, buff, TOK_RD_SINGLE_WR_START_BLOCK)) { + count = 0; + } + } else { + if(card_type & CARD_TYPE_SD) { + mmc_send_cmd(dev, ACMD23, count); + } + if(mmc_send_cmd(dev, CMD25, sector) == R1_SUCCESS) { + do { + if(!mmc_tx_block(dev, buff, TOK_MULTI_WR_START_BLOCK)) { + break; + } + buff = (uint8_t *)buff + BLOCK_LEN; + watchdog_periodic(); + } while(--count); + if(!mmc_tx_block(dev, NULL, TOK_MULTI_WR_STOP_TRAN)) { + count = 1; + } + } + } + mmc_select(dev, false); + return count ? DISK_RESULT_IO_ERROR : DISK_RESULT_OK; +} +/*----------------------------------------------------------------------------*/ +static disk_result_t +mmc_ioctl(uint8_t dev, uint8_t cmd, void *buff) +{ + card_type_t card_type; + disk_result_t res; + uint8_t csd[CSD_SIZE], sd_status[SD_STATUS_SIZE], au_size; + uint64_t capacity; + uint32_t block_size; + + static const uint8_t AU_TO_BLOCK_SIZE[] = {12, 16, 24, 32, 64}; + + if(dev >= MMC_CONF_DEV_COUNT) { + return DISK_RESULT_INVALID_ARG; + } + if(!(mmc_status(dev) & DISK_STATUS_INIT)) { + return DISK_RESULT_NO_INIT; + } + + card_type = mmc_priv[dev].card_type; + res = DISK_RESULT_IO_ERROR; + + switch(cmd) { + case DISK_IOCTL_CTRL_SYNC: + if(mmc_select(dev, true)) { + res = DISK_RESULT_OK; + } + break; + + case DISK_IOCTL_GET_SECTOR_COUNT: + if(mmc_send_cmd(dev, CMD9, 0) == R1_SUCCESS && mmc_rx(dev, csd, CSD_SIZE)) { + capacity = CSD_STRUCTURE(csd) == CSD_STRUCTURE_SD_V2_0 ? + CSD_SD_V2_0_CAPACITY(csd) : CSD_SD_V1_0_CAPACITY(csd); + *(uint32_t *)buff = capacity / SECTOR_SIZE; + res = DISK_RESULT_OK; + } + break; + + case DISK_IOCTL_GET_SECTOR_SIZE: + *(uint16_t *)buff = SECTOR_SIZE; + res = DISK_RESULT_OK; + break; + + case DISK_IOCTL_GET_BLOCK_SIZE: + if(card_type & CARD_TYPE_SD2) { + if(mmc_send_cmd(dev, ACMD13, 0) == R1_SUCCESS) { /* Read SD status. */ + mmc_spi_xchg(dev, 0xff); + if(mmc_rx(dev, sd_status, SD_STATUS_SIZE)) { + au_size = SD_STATUS_AU_SIZE(sd_status); + if(au_size) { + block_size = au_size <= 0xa ? 8192ull << au_size : + (uint32_t)AU_TO_BLOCK_SIZE[au_size - 0xb] << 20; + *(uint32_t *)buff = block_size / SECTOR_SIZE; + res = DISK_RESULT_OK; + } + } + } + } else if(mmc_send_cmd(dev, CMD9, 0) == R1_SUCCESS && + mmc_rx(dev, csd, CSD_SIZE)) { + if(card_type & CARD_TYPE_SD1) { + block_size = (uint32_t)(CSD_SD_V1_0_SECTOR_SIZE(csd) + 1) << + CSD_SD_V1_0_WRITE_BL_LEN(csd); + } else { /* MMC */ + block_size = (uint32_t)(CSD_MMC_ERASE_GRP_SIZE(csd) + 1) * + (CSD_MMC_ERASE_GRP_MULT(csd) + 1) << + CSD_MMC_WRITE_BL_LEN(csd); + } + *(uint32_t *)buff = block_size / SECTOR_SIZE; + res = DISK_RESULT_OK; + } + break; + + default: + res = DISK_RESULT_INVALID_ARG; + break; + } + mmc_select(dev, false); + return res; +} +/*----------------------------------------------------------------------------*/ +const struct disk_driver mmc_driver = { + .status = mmc_status, + .initialize = mmc_initialize, + .read = mmc_read, + .write = mmc_write, + .ioctl = mmc_ioctl +}; +/*----------------------------------------------------------------------------*/ + +/** @} */ diff --git a/dev/disk/mmc/mmc.h b/dev/disk/mmc/mmc.h new file mode 100644 index 000000000..e639b2f96 --- /dev/null +++ b/dev/disk/mmc/mmc.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016, Benoît Thébaudeau + * All rights reserved. + * + * 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 of the copyright holder 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 HOLDER 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. + */ +/** + * \addtogroup disk + * @{ + * + * \defgroup mmc SD/MMC + * + * SD/MMC device driver. + * @{ + * + * \file + * Header file for the SD/MMC device driver. + */ +#ifndef MMC_H_ +#define MMC_H_ + +#include "contiki-conf.h" +#include "../disk.h" + +#ifndef MMC_CONF_DEV_COUNT +/** Number of SD/MMC devices. */ +#define MMC_CONF_DEV_COUNT 1 +#endif + +extern const struct disk_driver mmc_driver; + +#endif /* MMC_H_ */ + +/** + * @} + * @} + */