Adding SD card support (partial) over SPI bus Raw read only supported yet.
authorNathael Pajani <nathael.pajani@ed3l.fr>
Fri, 28 Oct 2016 01:19:19 +0000 (03:19 +0200)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
extdrv/sdmmc.c [new file with mode: 0644]
include/extdrv/sdmmc.h [new file with mode: 0644]

diff --git a/extdrv/sdmmc.c b/extdrv/sdmmc.c
new file mode 100644 (file)
index 0000000..382f39a
--- /dev/null
@@ -0,0 +1,335 @@
+/****************************************************************************
+ *   extdrv/sdmmc.c
+ *
+ *
+ * Copyright 2016 Nathael Pajani <nathael.pajani@ed3l.fr>
+ * Copyright 2012 Gabriel Huau <contact@huau-gabriel.fr>
+ *
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#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 (file)
index 0000000..72e33b9
--- /dev/null
@@ -0,0 +1,153 @@
+/****************************************************************************
+ *  extdrv/sdmmc.h
+ *
+ * Copyright 2016 Nathael Pajani <nathael.pajani@ed3l.fr>
+ * Copyright 2012 Gabriel Huau <contact@huau-gabriel.fr>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+#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 */
+
+