Improve SD/MMC Card support, add comments
authorNathael Pajani <nathael.pajani@ed3l.fr>
Mon, 7 Nov 2022 16:50:04 +0000 (17:50 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
extdrv/sdmmc.c
include/extdrv/sdmmc.h

index 829f068..89389ab 100644 (file)
@@ -48,7 +48,7 @@ static inline void sdmmc_cs_release(const struct sdmmc_card* mmc)
        gpio_set(mmc->chip_select);
 }
 
-/* Wait for MMC Card to be ready.
+/* Wait for SD/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()
@@ -101,7 +101,7 @@ uint8_t sdmmc_crc7(uint8_t* data, int len)
  * 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)
+static uint8_t 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;
@@ -125,7 +125,7 @@ static int sdmmc_send_command(const struct sdmmc_card* mmc, uint8_t index, uint3
        /* Get R1 */
        for (i = 0; i < 8; i++) {
                r1 = (uint8_t)spi_transfer_single_frame(mmc->ssp_bus_num, 0xFF);
-               if (r1 != 0xFF) {
+               if (!(r1 & 0x80)) {
                        break;
                }
        }
@@ -146,6 +146,7 @@ static int sdmmc_send_command(const struct sdmmc_card* mmc, uint8_t index, uint3
        }
        if (len > 1) {
                /* Receive response data  */
+               memset(buf, 0xFF, len);
                spi_transfer_multiple_frames(mmc->ssp_bus_num, NULL, buf, len, 8);
        }
 
@@ -154,7 +155,7 @@ static int sdmmc_send_command(const struct sdmmc_card* mmc, uint8_t index, uint3
 
 static int sdmmc_send_app_command(const struct sdmmc_card* mmc, uint8_t index, uint32_t arg)
 {
-       int r1 = 0;
+       uint8_t r1 = 0;
 
        /* Send APP_CMD (CMD55) first */
        r1 = sdmmc_send_command(mmc, MMC_APP_CMD, 0, NULL, 0);
@@ -164,11 +165,40 @@ static int sdmmc_send_app_command(const struct sdmmc_card* mmc, uint8_t index, u
        return sdmmc_send_command(mmc, index, arg, NULL, 0);
 }
 
-int sdmmc_reset(struct sdmmc_card* mmc)
+void sdmmc_set_card_to_spi_mode(const struct sdmmc_card* mmc)
 {
-       int r1 = 0, ret = 0;
+       int i = 0;
+       /* Get SPI Bus */
+       spi_get_mutex(mmc->ssp_bus_num);
 
+       /* Set SPI pins to GPIO mode */
+       set_pins(mmc->pin_cfg_mode_gpio);
+       /* Configure GPIOs */
        config_gpio(&(mmc->chip_select), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 1);
+       config_gpio(&(mmc->sclk), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 1);
+       config_gpio(&(mmc->mosi), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 1);
+
+       /* Toggle SCLK to get 74 clock cycles while holding MOSI and CS high */
+       gpio_set(mmc->chip_select);
+       gpio_set(mmc->mosi);
+       for (i = 0; i < (80 * 2); i++) {
+               gpio_toggle(mmc->sclk);
+       }
+
+       /* Set SPI pins to GPIO mode */
+       set_pins(mmc->pin_cfg_mode_spi);
+
+       /* Release SPI Bus */
+       spi_release_mutex(mmc->ssp_bus_num);
+}
+
+int sdmmc_reset(struct sdmmc_card* mmc)
+{
+       int ret = 0;
+       uint8_t r1 = 0;
+
+       /* Set SD/MMC Card to SPI mode */
+       sdmmc_set_card_to_spi_mode(mmc);
 
        /* Get SPI Bus */
        spi_get_mutex(mmc->ssp_bus_num);
@@ -180,6 +210,7 @@ int sdmmc_reset(struct sdmmc_card* mmc)
                ret = -EIO; /* This one should have succeded even without a card inserted ... */
        }
 
+       /* Release SPI Bus */
        sdmmc_cs_release(mmc);
        spi_release_mutex(mmc->ssp_bus_num);
 
@@ -187,26 +218,18 @@ int sdmmc_reset(struct sdmmc_card* mmc)
 }
 
 
-int sdmmc_init(struct sdmmc_card* mmc)
+int sdmmc_init_start(struct sdmmc_card* mmc)
 {
-       int r1 = 0, ret = 0;
        uint8_t buf[MMC_CMD_SIZE];
+       int ret = 0;
+       uint8_t r1 = 0;
 
-       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"
@@ -254,7 +277,7 @@ init_release_bus:
 
 int sdmmc_init_wait_card_ready(struct sdmmc_card* mmc)
 {
-       int r1 = 0;
+       uint8_t r1 = 0;
 
        /* Get SPI Bus */
        spi_get_mutex(mmc->ssp_bus_num);
@@ -271,8 +294,9 @@ int sdmmc_init_wait_card_ready(struct sdmmc_card* mmc)
 
 int sdmmc_init_end(struct sdmmc_card* mmc)
 {
-       int r1 = 0, ret = 0;
        uint8_t buf[MMC_CMD_SIZE];
+       int ret = 0;
+       uint8_t r1 = 0;
 
        /* Get SPI Bus */
        spi_get_mutex(mmc->ssp_bus_num);
@@ -321,7 +345,61 @@ end_init_release_bus:
 }
 
 
+/* sdmmc_init_single does a single loop of initialisation steps, but allows for a
+ *  user defined number of "wait" step retries (with at least one wait step).
+ * the loop is :
+ *  start
+ *  (wait * retries)
+ *  end
+ * There is a 10ms delay before each wait step (sdmmc_init_wait_card_ready() call),
+ *  including the first one.
+ * It is not possible to skip the first delay and wait step, even with a nretry of 0.
+ * The loop may stop at any of the steps, with "rstep" set to the step number (if not NULL).
+ * if step = 0 : returned value is the return value of sdmmc_init_start().
+ * if step = 1 : returned value is the return value of last sdmmc_init_wait_card_ready().
+ * if step = 2 : returned value is the return value of sdmmc_init_end().
+ * If the returned value is 0, then step should be 2.
+ * If nretries is not NULL, it is used as the number of "wait" steps, and updated
+ *  with the number of retries executed.
+ */
+int sdmmc_init_single(struct sdmmc_card* mmc, uint8_t* rstep, uint8_t* nretries)
+{
+       int ret = 0, step = 0, i = 0;
+       uint8_t retries = 0;
+
+       ret = sdmmc_init_start(mmc);
+       if (ret == 0) {
+               step = 1;
+               if (nretries != NULL) {
+                       retries = *nretries;
+               }
+               do {
+                       msleep(10);
+                       ret = sdmmc_init_wait_card_ready(mmc);
+                       i++;
+               } while ((ret != 0 ) && (i < retries));
+               if (ret <= 1) {
+                       step = 2;
+                       ret = sdmmc_init_end(mmc);
+               }
+       }
+
+       if (rstep != NULL) {
+               *rstep = step;
+       }
+       if (nretries != NULL) {
+               *nretries = i;
+       }
+       return ret;
+}
+
 /* Read one block of data.
+ * One block should be of "mmc->block_size" length, so provided buffer must be at least
+ * big enough to receive "mmc->block_size" bytes.
+ * If the attached card is of type MMC_CARDTYPE_SDV2_HC it is mandatory that we read
+ * full sector, which is MMC_MAX_SECTOR_SIZE bytes long. In this case, if the requested
+ * block size (mmc->block_size) is less than MMC_MAX_SECTOR_SIZE, this function will
+ * read the remaining data from the card and silently drop it.
  * Returns -EINVAL on arguments error, -ENODEV on command error,
  *         -EBUSY on timeout, -EIO on CRC error,
  *         or 0 on success
@@ -366,7 +444,7 @@ int sdmmc_read_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8_
        ret = spi_transfer_multiple_frames(mmc->ssp_bus_num, NULL, buffer, mmc->block_size, 8);
        /* Compute CRC of this part */
        crc = crc_ccitt(0x0000, buffer, mmc->block_size);
-       /* Read data, remaining part */
+       /* Read data, remaining part (need to read full sector) */
        if ((mmc->card_type == MMC_CARDTYPE_SDV2_HC) && (mmc->block_size != MMC_MAX_SECTOR_SIZE)) {
                char tmpbuf[TMPBUF_SIZE];
                int size = (MMC_MAX_SECTOR_SIZE - mmc->block_size);
@@ -498,4 +576,34 @@ write_release:
 }
 
 
+/* Wait for write to be finished on SD/MMC card
+ * In some cases the write may take longer on some cards and the sdmmc_write_block()
+ * call will return -EBUSY.
+ * It is then necessary to check that the card is ready before reading or writting
+ * again.
+ * Return 0 when card is ready, or -EBUSY when card is still busy.
+ */
+int sdmmc_wait_write_end(const struct sdmmc_card* mmc)
+{
+       int ret = 0;
+
+       /* Get SPI Bus */
+       spi_get_mutex(mmc->ssp_bus_num);
+       sdmmc_cs_activate(mmc);
+
+       /* Wait for card ready */
+       ret = sdmmc_wait_for_ready(mmc, 0xFF);
+       if (ret != 0xFF) {
+               ret = -EBUSY;
+               goto wait_ready_release;
+       }
+       ret = 0;
+
+wait_ready_release:
+       /* Release SPI Bus */
+       sdmmc_cs_release(mmc);
+       spi_release_mutex(mmc->ssp_bus_num);
+       return ret;
+}
+
 
index 1e113b7..6faab8d 100644 (file)
@@ -36,20 +36,82 @@ struct sdmmc_card {
        uint16_t block_size;
        uint8_t block_shift;
        struct pio chip_select;
+       struct pio sclk;
+       struct pio mosi;
+       struct pio miso;
+       struct pio_config* pin_cfg_mode_spi;
+       struct pio_config* pin_cfg_mode_gpio;
 };
 
-int sdmmc_init(struct sdmmc_card* mmc);
+
+/* Reset the card
+ * It is mandatory to call this function before any other sd/mmc function to set
+ * the SD/MMC card to SPI mode, which is the only supported mode for the LPC112x
+ * micro-controller.
+ */
+int sdmmc_reset(struct sdmmc_card* mmc);
+
+/* The card initialisation has been split in three parts, as for some cards it may
+ * be required to iterrate many times over the middle part (waiting for the card
+ * to get ready).
+ * This splitting allows the user to perform other stuff while waiting, like
+ * feeding a watchdog.
+ */
+int sdmmc_init_start(struct sdmmc_card* mmc);
 int sdmmc_init_wait_card_ready(struct sdmmc_card* mmc);
 int sdmmc_init_end(struct sdmmc_card* mmc);
 
-int sdmmc_reset(struct sdmmc_card* mmc);
+/* sdmmc_init_single does a single loop of initialisation steps, but allows for a
+ *  user defined number of "wait" step retries (with at least one wait step).
+ * the loop is :
+ *  start
+ *  (wait * retries)
+ *  end
+ * There is a 10ms delay before each wait step (sdmmc_init_wait_card_ready() call),
+ *  including the first one.
+ * It is not possible to skip the first delay and wait step, even with a nretry of 0.
+ * The loop may stop at any of the steps, with "rstep" set to the step number (if not NULL).
+ * if step = 0 : returned value is the return value of sdmmc_init_start().
+ * if step = 1 : returned value is the return value of last sdmmc_init_wait_card_ready().
+ * if step = 2 : returned value is the return value of sdmmc_init_end().
+ * If the returned value is 0, then step should be 2.
+ * If nretries is not NULL, it is used as the number of "wait" steps, and updated
+ *  with the number of retries executed.
+ */
+int sdmmc_init_single(struct sdmmc_card* mmc, uint8_t* rstep, uint8_t* nretries);
+
+
 
 /* Read one block of data.
- * Return -ENODEV on error, -EBUSY on timeout, -EIO on CRC error, or 0 on success
+ * One block should be of "mmc->block_size" length, so provided buffer must be at least
+ * big enough to receive "mmc->block_size" bytes.
+ * If the attached card is of type MMC_CARDTYPE_SDV2_HC it is mandatory that we read
+ * full sector, which is MMC_MAX_SECTOR_SIZE bytes long. In this case, if the requested
+ * block size (mmc->block_size) is less than MMC_MAX_SECTOR_SIZE, this function will
+ * read the remaining data from the card and silently drop it.
+ * Returns -EINVAL on arguments error, -ENODEV on command error,
+ *         -EBUSY on timeout, -EIO on CRC error,
+ *         or 0 on success
  */
 int sdmmc_read_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8_t *buffer);
+
+/* Write one block of data.
+ * This routine does not pre-erase the block, so if the user did not pre-erase the
+ *   corresponding block then the write takes longer.
+ * Returns -EINVAL on arguments error, -ENODEV on command error,
+ *         -ECOMM on response error, -EIO on CRC error, -EPERM on write error
+ *         or 0 on success
+ */
 int sdmmc_write_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8_t *buffer);
 
+/* Wait for write to be finished on SD/MMC card
+ * In some cases the write may take longer on some cards and the sdmmc_write_block()
+ * call will return -EBUSY.
+ * It is then necessary to check that the card is ready before reading or writting
+ * again.
+ * Return 0 when card is ready, or -EBUSY when card is still busy.
+ */
+int sdmmc_wait_write_end(const struct sdmmc_card* mmc);
 
 /* Card states and Operation modes */
 /* Inactive operation mode */
@@ -74,7 +136,7 @@ int sdmmc_write_block(const struct sdmmc_card* mmc, uint32_t block_number, uint8
  * Response type is R1 unless specified
  */
 #define MMC_GO_IDLE_STATE           0  /* CMD0 */
-#define MMC_SEND_OP_COND            1
+#define MMC_SEND_OP_COND            1  /* CMD1 or ACMD41 */
 #define MMC_ALL_SEND_CID            2  /* R2 */
 #define MMC_SEND_REL_ADDR           3  /* R6 */
 #define MMC_SET_DSR                 4