From 52d2711bfdd8c76759894f9a7297fb93527768fe Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Fri, 28 Oct 2016 03:19:19 +0200 Subject: [PATCH] Adding SD card support (partial) over SPI bus Raw read only supported yet. --- extdrv/sdmmc.c | 335 +++++++++++++++++++++++++++++++++++++++++ include/extdrv/sdmmc.h | 153 +++++++++++++++++++ 2 files changed, 488 insertions(+) create mode 100644 extdrv/sdmmc.c create mode 100644 include/extdrv/sdmmc.h diff --git a/extdrv/sdmmc.c b/extdrv/sdmmc.c new file mode 100644 index 0000000..382f39a --- /dev/null +++ b/extdrv/sdmmc.c @@ -0,0 +1,335 @@ +/**************************************************************************** + * extdrv/sdmmc.c + * + * + * Copyright 2016 Nathael Pajani + * Copyright 2012 Gabriel Huau + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + +#include "core/system.h" +#include "lib/string.h" +#include "lib/errno.h" +#include "drivers/gpio.h" +#include "drivers/ssp.h" + +#include "extdrv/sdmmc.h" + + + + +/***************************************************************************** */ +/* Support for SD/MMC cards access over SSP/SPI bus */ +/***************************************************************************** */ + + +static inline void sdmmc_cs_activate(const struct sdmmc_card* mmc) +{ + gpio_clear(mmc->chip_select); +} +static inline void sdmmc_cs_release(const struct sdmmc_card* mmc) +{ + gpio_set(mmc->chip_select); +} + +/* Wait for MMC Card to be ready. + * return -EBUSY on timeout. + * Note : The SPI Bus mutex must be held by the calling function. + * FIXME : handle timeout with sleep() + */ +static int sdmmc_wait_for_ready(const struct sdmmc_card* mmc, uint8_t token) +{ + int i = 0; + uint8_t val = 0; + + while (i++ < MMC_MAX_TIMEOUT) { + val = (uint8_t)spi_transfer_single_frame(mmc->ssp_bus_num, 0xFF); + if (token != 0) { + if (val == token) + return val; + } else { + if (val != 0) + return val; + } + } + + return -EBUSY; +} + +uint8_t sdmmc_crc7(uint8_t* data, int len) +{ + int i, idx; + uint8_t crc = 0, val = 0; + + for (idx = 0; idx < len; idx++) { + val = data[idx]; + for (i = 0; i < 8; i++) { + crc <<= 1; + if ((val & 0x80) ^ (crc & 0x80)) { + crc ^= 0x09; + } + val <<= 1; + } + } + crc = (crc << 1) | 1; + return crc; +} + + +/* Send a command to the card. + * The len parameter indicates the response type (and is the length of the buffer) : + * R1 : len = 0 + * R1b : len = -1 + * R2 : len = 1 + * R3 or R7 : len = 4 + * Note : The SPI Bus mutex must be held by the calling function. + * Return R1 or (R1 | 0x0100) on timeout. + */ +static int sdmmc_send_command(const struct sdmmc_card* mmc, uint8_t index, uint32_t arg, uint8_t* buf, int len) +{ + uint8_t mmc_command[MMC_CMD_SIZE]; + int i = 0; + uint8_t r1 = 0; + + if ((len > 0) && (buf == NULL)) { + return -EINVAL; + } + + /* Command buffer */ + mmc_command[0] = 0x40 | index; + mmc_command[1] = (arg >> 24) & 0xFF; + mmc_command[2] = (arg >> 16) & 0xFF; + mmc_command[3] = (arg >> 8) & 0xFF; + mmc_command[4] = arg & 0xFF; + mmc_command[5] = sdmmc_crc7(mmc_command, 5); + + /* Send command */ + spi_transfer_multiple_frames(mmc->ssp_bus_num, mmc_command, NULL, MMC_CMD_SIZE, 8); + + /* Get R1 */ + for (i = 0; i < 8; i++) { + r1 = (uint8_t)spi_transfer_single_frame(mmc->ssp_bus_num, 0xFF); + if (r1 != 0xFF) { + break; + } + } + /* Maybe get R2 ? */ + if (len == 1) { + *buf = (uint8_t)spi_transfer_single_frame(mmc->ssp_bus_num, 0xFF); + } + /* Error ? Do not wait for the data in case of error */ + if (r1 > MMC_R1_IN_IDLE_STATE) { + return r1; + } + /* Wait for end of busy state ? (The R1 + busy case) */ + if (len == -1) { + int ret = sdmmc_wait_for_ready(mmc, 0); + if (ret == -EBUSY) { + return (r1 | 0x0100); + } + } + if (len > 1) { + /* Receive response data */ + spi_transfer_multiple_frames(mmc->ssp_bus_num, NULL, buf, len, 8); + } + + return r1; +} + +static int sdmmc_send_app_command(const struct sdmmc_card* mmc, uint8_t index, uint32_t arg) +{ + int r1 = 0; + + /* Send APP_CMD (CMD55) first */ + r1 = sdmmc_send_command(mmc, MMC_APP_CMD, 0, NULL, 0); + if (r1 > MMC_R1_IN_IDLE_STATE) { + return r1; + } + return sdmmc_send_command(mmc, index, arg, NULL, 0); +} + + +int sdmmc_init(struct sdmmc_card* mmc) +{ + int r1 = 0, ret = 0; + uint8_t buf[MMC_CMD_SIZE]; + + config_gpio(&(mmc->chip_select), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 1); + mmc->card_type = MMC_CARDTYPE_UNKNOWN; + + /* Get SPI Bus */ + spi_get_mutex(mmc->ssp_bus_num); + sdmmc_cs_activate(mmc); + + /* Send CMD0 (RESET or GO_IDLE_STATE) */ + r1 = sdmmc_send_command(mmc, MMC_GO_IDLE_STATE, 0, NULL, 0); + if (r1 > MMC_R1_IN_IDLE_STATE) { + ret = -EIO; /* This one should have succeded even without a card inserted ... */ + goto init_release_bus; + } + msleep(1); + + /* Send CMD8, check the card type + * CMD8 is the Send interface Condition Command. + * 0x01 is "2.7V to 3.6V" + * 0xAA is a "check pattern", which the card must send back. + */ + r1 = sdmmc_send_command(mmc, MMC_SEND_IF_COND, 0x1AA, buf, 4); + if (r1 > MMC_R1_IN_IDLE_STATE) { + ret = -ENODEV; /* No card inserted */ + goto init_release_bus; + } + if (r1 == MMC_R1_IN_IDLE_STATE) { + /* Card is SD V2 */ + if (buf[2] != 0x01 || buf[3] != 0xAA) { + ret = -EREMOTEIO; + mmc->card_type = MMC_CARDTYPE_UNKNOWN; + goto init_release_bus; + } + mmc->card_type = MMC_CARDTYPE_SDV2_SC; + } else { + mmc->card_type = MMC_CARDTYPE_SDV1; /* Card is 2.1mm thick SD Card */ + } + + /* Read OCR (CMD58) */ + if (mmc->card_type == MMC_CARDTYPE_SDV2_SC) { + r1 = sdmmc_send_command(mmc, MMC_READ_OCR, 0, buf, 4); + if (r1 > MMC_R1_IN_IDLE_STATE) { + ret = -EREMOTEIO; + goto init_release_bus; + } + } + + /* Send first ACMD41 command */ + r1 = sdmmc_send_app_command(mmc, MMC_SD_SEND_OP_COND, SDHC_SUPPORT_OK); + if (r1 > MMC_R1_IN_IDLE_STATE) { + ret = -EREMOTEIO; + } + +init_release_bus: + /* Release SPI Bus */ + sdmmc_cs_release(mmc); + spi_release_mutex(mmc->ssp_bus_num); + + return ret; +} + +int sdmmc_init_wait_card_ready(struct sdmmc_card* mmc) +{ + int r1 = 0; + + /* Get SPI Bus */ + spi_get_mutex(mmc->ssp_bus_num); + sdmmc_cs_activate(mmc); + + r1 = sdmmc_send_app_command(mmc, MMC_SD_SEND_OP_COND, SDHC_SUPPORT_OK); + + /* Release SPI Bus */ + sdmmc_cs_release(mmc); + spi_release_mutex(mmc->ssp_bus_num); + + return r1; +} + +int sdmmc_init_end(struct sdmmc_card* mmc) +{ + int r1 = 0, ret = 0; + uint8_t buf[MMC_CMD_SIZE]; + + /* Get SPI Bus */ + spi_get_mutex(mmc->ssp_bus_num); + sdmmc_cs_activate(mmc); + + /* Read OCR (CMD58) */ + if (mmc->card_type == MMC_CARDTYPE_SDV2_SC) { + r1 = sdmmc_send_command(mmc, MMC_READ_OCR, 0, buf, 4); + if (r1 > MMC_R1_IN_IDLE_STATE) { + ret = -EREMOTEIO; + goto end_init_release_bus; + } + mmc->card_type = (buf[0] & 0x40) ? MMC_CARDTYPE_SDV2_HC : MMC_CARDTYPE_SDV2_SC; + } + + /* Set block length */ + if (mmc->card_type == MMC_CARDTYPE_SDV2_HC) { + mmc->block_size = MMC_MAX_SECTOR_SIZE; + } else { + if (mmc->block_size > MMC_MAX_SECTOR_SIZE) { + mmc->block_size = MMC_MAX_SECTOR_SIZE; + } + r1 = sdmmc_send_command(mmc, MMC_SET_BLOCKLEN, mmc->block_size, NULL, 0); + if (r1 > MMC_R1_IN_IDLE_STATE) { + mmc->card_type = MMC_CARDTYPE_UNKNOWN; + } + } + +end_init_release_bus: + /* Release SPI Bus */ + sdmmc_cs_release(mmc); + spi_release_mutex(mmc->ssp_bus_num); + + return ret; +} + + +/* Read one block of data. + * Return -ENODEV on error, -EBUSY on timeout, or CRC on success + */ +int sdmmc_read_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8_t* buffer) +{ + uint16_t crc = 0xFFFF; + int ret = 0; + + if ((buffer == NULL) || (mmc->card_type == MMC_CARDTYPE_UNKNOWN)) { + return -EINVAL; + } + + /* Garbage for the SPI out data */ + memset(buffer, 0xFF, mmc->block_size); + + /* Get SPI Bus */ + spi_get_mutex(mmc->ssp_bus_num); + sdmmc_cs_activate(mmc); + + ret = sdmmc_send_command(mmc, MMC_READ_SINGLE_BLOCK, block_number, NULL, 0); + if (ret != MMC_R1_NO_ERROR) { + ret = -ENODEV; + goto read_release; + } + + /* Wait for start of Data */ + ret = sdmmc_wait_for_ready(mmc, MMC_START_DATA_BLOCK_TOCKEN); + if (ret != MMC_START_DATA_BLOCK_TOCKEN) { + ret = -EBUSY; + goto read_release; + } + + /* Read data */ + spi_transfer_multiple_frames(mmc->ssp_bus_num, NULL, buffer, mmc->block_size, 8); + /* Read CRC */ + spi_transfer_multiple_frames(mmc->ssp_bus_num, NULL, (uint8_t*)(&crc), 2, 8); + + ret = crc; + +read_release: + /* Release SPI Bus */ + sdmmc_cs_release(mmc); + spi_release_mutex(mmc->ssp_bus_num); + return ret; +} + + diff --git a/include/extdrv/sdmmc.h b/include/extdrv/sdmmc.h new file mode 100644 index 0000000..72e33b9 --- /dev/null +++ b/include/extdrv/sdmmc.h @@ -0,0 +1,153 @@ +/**************************************************************************** + * extdrv/sdmmc.h + * + * Copyright 2016 Nathael Pajani + * Copyright 2012 Gabriel Huau + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ +#ifndef EXTDRV_SDMMC_H +#define EXTDRV_SDMMC_H + +#include "lib/stdint.h" +#include "core/pio.h" + + +/***************************************************************************** */ +/* Support for SD/MMC cards access over SSP/SPI bus */ +/***************************************************************************** */ + + +struct sdmmc_card { + uint8_t ssp_bus_num; + uint8_t card_type; + uint16_t block_size; + struct pio chip_select; +}; + +int sdmmc_init(struct sdmmc_card* mmc); +int sdmmc_init_wait_card_ready(struct sdmmc_card* mmc); +int sdmmc_init_end(struct sdmmc_card* mmc); + +int sdmmc_read_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8_t *buffer); + + +/* Card states and Operation modes */ +/* Inactive operation mode */ +#define MMC_OP_MODE_INACTIVE 0 +#define MMC_CARD_STATE_INACTIVE 0 +/* Identification operation mode */ +#define MMC_OP_MODE_IDENTIFICATION 1 +#define MMC_CARD_STATE_IDLE 1 +#define MMC_CARD_STATE_READY 2 +#define MMC_CARD_STATE_IDENTIFICATION 3 +/* Data transfer mode */ +#define MMC_OP_MODE_DATA_TRANSFER 4 +#define MMC_CARD_STATE_STANDBY 4 +#define MMC_CARD_STATE_TRANSFER 5 +#define MMC_CARD_STATE_SENDING_DATA 6 +#define MMC_CARD_STATE_RECEIVE_DATA 7 +#define MMC_CARD_STATE_PROGRAMMING 8 +#define MMC_CARD_STATE_DISCONNECT 9 + + +/* Command definitions in SPI bus mode + * Response type is R1 unless specified + */ +#define MMC_GO_IDLE_STATE 0 +#define MMC_SEND_OP_COND 1 +#define MMC_SWITCH_FUNC 6 +#define MMC_SEND_IF_COND 8 /* R7 (R1 + 4 bytes) */ +#define MMC_SEND_CSD 9 +#define MMC_SEND_CID 10 +#define MMC_STOP_TRANSMISSION 12 /* R1b (busy) */ +#define MMC_SEND_STATUS 13 /* R2 (2 bytes) */ +#define MMC_SET_BLOCKLEN 16 +#define MMC_READ_SINGLE_BLOCK 17 +#define MMC_READ_MULTIPLE_BLOCK 18 +#define MMC_WRITE_SINGLE_BLOCK 24 +#define MMC_WRITE_MULTIPLE_BLOCK 25 +#define MMC_PROGRAMM_CSD 27 +/* Write protect commands are unsupported by SDHC and SDXC cards */ +#define MMC_SET_WRITE_PROTECT 28 /* R1b (busy) */ +#define MMC_CLR_WRITE_PROTECT 29 /* R1b (busy) */ +#define MMC_SEND_WRITE_PROTECT 30 +#define ERASE_WR_BLK_START_ADDR 32 +#define ERASE_WR_BLK_END_ADDR 33 +#define MMC_ERASE 38 /* R1b (busy) */ +#define MMC_LOCK_UNLOCK 42 +#define MMC_APP_CMD 55 +#define MMC_GEN_CMD 56 +#define MMC_READ_OCR 58 /* R3 (R1 + 4 bytes of OCR )*/ +#define MMC_CRC_ON_OFF 59 + +/* Application specific commands supported by SD. + * All these commands shall be preceded with APP_CMD (CMD55). + */ +#define MMC_SD_SEND_STATUS 13 /* R2 (2 bytes) */ +#define MMC_SD_SEND_NUM_WR_BLOCKS 22 +#define MMC_SD_SET_WR_BLK_ERASE_COUNT 23 +#define MMC_SD_SEND_OP_COND 41 +#define MMC_SD_SET_CLR_CARD_DETECT 42 +#define MMC_SD_SEND_SCR 51 + +/* HCS bit */ +#define SDHC_SUPPORT_OK (0x01 << 30) + + +/* R1 response bit flag definition */ +#define MMC_R1_NO_ERROR 0x00 +#define MMC_R1_IN_IDLE_STATE (0x01 << 0) +#define MMC_R1_ERASE_RESET (0x01 << 1) +#define MMC_R1_ILLEGAL_CMD (0x01 << 2) +#define MMC_R1_COM_CRC_ERROR (0x01 << 3) +#define MMC_R1_ERASE_SEQ_ERROR (0x01 << 4) +#define MMC_R1_ADDRESS_ERROR (0x01 << 5) +#define MMC_R1_PARAMETER_ERROR (0x01 << 6) +#define MMC_R1_MASK 0x7F + +/* Memory card type definitions */ +#define MMC_CARDTYPE_UNKNOWN 0 +#define MMC_CARDTYPE_MMC 1 /* MMC */ +#define MMC_CARDTYPE_SDV1 2 /* V1.x Standard Capacity SD card */ +#define MMC_CARDTYPE_SDV2_SC 3 /* V2.0 or later Standard Capacity SD card */ +#define MMC_CARDTYPE_SDV2_HC 4 /* V2.0 or later High/eXtended Capacity SD card */ + +/* The sector size is fixed to 512bytes in most applications. */ +#define MMC_MAX_SECTOR_SIZE 512 +#define MMC_SECTOR_BITS 9 + +/* Buffer CMD/DATA size */ +#define MMC_CMD_SIZE 6 +#define MMC_DATA_SIZE 512 + +#define MMC_MAX_TIMEOUT 10000 + + +#define MMC_CMD8_CHECK_PATERN 0xAA +#define MMC_VOLTAGE_SELECT_3V3 0x01 + + +#define MMC_START_DATA_BLOCK_TOCKEN 0xFE + +#define MMC_IS_WRITE_RESPONSE_TOKEN(x) (((x) & 0x11) == 0x01) +#define MMC_WRITE_RESPONSE_TOKEN(x) (((x) & 0x0E) >> 1) +#define MMC_WRITE_RESPONSE_OK 2 +#define MMC_WRITE_RESPONSE_CRC_ERR 5 +#define MMC_WRITE_RESPONSE_WR_ERROR 6 + +#endif /* EXTDRV_SDMMC_H */ + + -- 2.43.0