From 331c02b6d3733299284482bd42b38019e9643c55 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Mon, 7 Nov 2022 17:50:04 +0100 Subject: [PATCH] Improve SD/MMC Card support, add comments --- extdrv/sdmmc.c | 148 +++++++++++++++++++++++++++++++++++------ include/extdrv/sdmmc.h | 70 +++++++++++++++++-- 2 files changed, 194 insertions(+), 24 deletions(-) diff --git a/extdrv/sdmmc.c b/extdrv/sdmmc.c index 829f068..89389ab 100644 --- a/extdrv/sdmmc.c +++ b/extdrv/sdmmc.c @@ -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; +} + diff --git a/include/extdrv/sdmmc.h b/include/extdrv/sdmmc.h index 1e113b7..6faab8d 100644 --- a/include/extdrv/sdmmc.h +++ b/include/extdrv/sdmmc.h @@ -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 -- 2.43.0