From 2994a566a699a434e3503f8450990f6d9451cb49 Mon Sep 17 00:00:00 2001 From: "Ruslan V. Uss" Date: Thu, 20 Oct 2016 13:03:05 +0600 Subject: [PATCH] Driver for SD/MMC cards (#239) * Driver for SD/MMC cards * SDIO: read_register() bug fixed, schematics resized --- examples/sdio_raw/Makefile | 4 + examples/sdio_raw/main.c | 117 ++++++++ examples/sdio_raw/schematics.png | Bin 0 -> 44483 bytes extras/sdio/component.mk | 7 + extras/sdio/sdio.c | 449 +++++++++++++++++++++++++++++++ extras/sdio/sdio.h | 98 +++++++ extras/sdio/sdio_impl.h | 154 +++++++++++ 7 files changed, 829 insertions(+) create mode 100644 examples/sdio_raw/Makefile create mode 100644 examples/sdio_raw/main.c create mode 100644 examples/sdio_raw/schematics.png create mode 100644 extras/sdio/component.mk create mode 100644 extras/sdio/sdio.c create mode 100644 extras/sdio/sdio.h create mode 100644 extras/sdio/sdio_impl.h diff --git a/examples/sdio_raw/Makefile b/examples/sdio_raw/Makefile new file mode 100644 index 0000000..1629f05 --- /dev/null +++ b/examples/sdio_raw/Makefile @@ -0,0 +1,4 @@ +PROGRAM = sdio_raw +EXTRA_COMPONENTS = extras/sdio +#ESPBAUD = 460800 +include ../../common.mk diff --git a/examples/sdio_raw/main.c b/examples/sdio_raw/main.c new file mode 100644 index 0000000..0d620fc --- /dev/null +++ b/examples/sdio_raw/main.c @@ -0,0 +1,117 @@ +/* + * Example of using SDIO driver + * + * Part of esp-open-rtos + * Copyright (C) 2016 Ruslan V. Uss + * BSD Licensed as described in the file LICENSE + */ +#include +#include +#include +#include +#include +#include +#include + +// Blink while IO :) +#define CS_GPIO_PIN 2 +// Set SPI frequency to 20MHz +#define SDIO_SPI_FREQ_DIV SPI_FREQ_DIV_20M + +static const char *errors[] = { + [SDIO_ERR_NONE] = NULL, + [SDIO_ERR_TIMEOUT] = "Timeout", + [SDIO_ERR_UNSUPPORTED] = "Unsupported", + [SDIO_ERR_IO] = "General I/O error", + [SDIO_ERR_CRC] = "CRC check failed" +}; + +static const char *types[] = { + [SDIO_TYPE_UNKNOWN] = "Unknown", + [SDIO_TYPE_MMC] = "MMC", + [SDIO_TYPE_SD1] = "SD v1.x", + [SDIO_TYPE_SD2] = "SD v2.x", + [SDIO_TYPE_SDHC] = "SDHC", +}; + +static void dump_line(const uint8_t *data) +{ + for (uint8_t i = 0; i < 16; i ++) + printf(" %02x", data[i]); + printf("\n"); +} + +static uint8_t buffer[SDIO_BLOCK_SIZE]; + +#define TEST_COUNT 1000 + +inline static void test_read(sdio_card_t *card) +{ + printf("Simple random access test speed: "); + uint32_t start = sdk_system_get_time(); + //sdk_system_update_cpu_freq(160); + for (uint32_t i = 0; i < TEST_COUNT; i ++) + { + printf("%08u / %08u\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08", i + 1, TEST_COUNT); + if (sdio_read_sectors(card, hwrand() % card->sectors, buffer, 1) != SDIO_ERR_NONE) + { + printf("Error: %d (%s)\n", card->error, errors[card->error]); + return; + } + } + uint32_t time = sdk_system_get_time() - start; + float speed = (float)TEST_COUNT / time * 1000000; + float stime = (time / 1000000.0) / TEST_COUNT; + uint32_t bs = (uint32_t)speed * 512; + printf("\nDone. Time: %u ms, speed: %.4f sectors/s, sector in %.4f s, %u bytes/s \n", time, speed, stime, bs); +} + +inline static void dump_card(sdio_card_t *card) +{ + char product_name[6]; + memcpy(product_name, card->cid.bits.product_name, 5); + product_name[5] = 0; + + printf("-------------------------------------------\n"); + printf(" SD/MMC card info\n"); + printf("-------------------------------------------\n"); + printf("%20s : %s\n", "Card type", types[card->type]); + printf("%20s : %s\n", "CRC enabled", card->crc_enabled ? "yes" : "no"); + printf("%20s : %d\n", "512 byte sectors", card->sectors); + printf("%20s : 0x%02x\n", "Manufacturer ID", card->cid.bits.manufacturer_id); + printf("%20s : %c%c\n", "OEM ID", card->cid.bits.oem_id[0], card->cid.bits.oem_id[1]); + printf("%20s : %s\n", "Product name", product_name); + printf("%20s : %d.%d\n", "Product revision", card->cid.bits.product_rev_major, card->cid.bits.product_rev_minor); + printf("%20s : 0x%08x\n", "Serial #", card->cid.bits.product_serial); + printf("%20s : %02d / %04d\n", "Manufacturing date", card->cid.bits.date_month, card->cid.bits.date_year_h * 10 + card->cid.bits.date_year_l + 2000); + printf("%20s : 0x%08x\n", "OCR", card->ocr.data); + printf("%20s :", "CID"); + dump_line(card->cid.data); + printf("%20s :", "CSD"); + dump_line(card->csd.data); + test_read(card); +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n\n", sdk_system_get_sdk_version()); + + while (true) + { + printf("\nInitializing card...\n"); + do + { + sdio_card_t card; + if (sdio_init(&card, CS_GPIO_PIN, SDIO_SPI_FREQ_DIV)) + { + printf("Error: %d (%s)\n", card.error, errors[card.error]); + break; + } + dump_card(&card); + } while (0); + + for (size_t i = 0; i < 1000; i ++) + sdk_os_delay_us(1000); + } +} diff --git a/examples/sdio_raw/schematics.png b/examples/sdio_raw/schematics.png new file mode 100644 index 0000000000000000000000000000000000000000..93fb22719a32a59a77fccda1c51e5274bc6905a9 GIT binary patch literal 44483 zcmb@tbyU>R*EdQ?cOwibodP0V0#X73(hVYlghS_$iiAUlqyhud-7$nPG)hau5Yj^r zFu)!CJ5%z<7Yd zz`$a_$3{~kgbHlYjoX(mUa4YWVvtji($G=UV%*1JU@)^VJz`YPQxbv0CEi5q9jX*}m zuWXH;3pi-r>#b0Uf1Z+R9(-n00(b&Qw)-Kk&J@zI*NRSu5~)iZhp^d-!KJ zwn-y3V~|jY5B@vPqy!JiE?XlnZ;yB0pDUf8Cin>0eBh@_dmj)+d1Q+*ZTreNN-x7t zs>J<`XJBS5=qWjb6$?fA4b@fEwN;;cz_oQ9N&OBr4Sij8^{owc4bAnf?Ja#R zExR@4=_484?QOxcf$bfz4ru#OM`wF??__ℜ2f)`)5}Vv~wWgM}KeMkHw+BKC@Gc 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 + * BSD Licensed as described in the file LICENSE + */ +#include +#include +#include +#include "sdio.h" + +#define BUS 1 + +#define BV(x) (1 << (x)) + +#define MS 1000 + +#define INIT_TIMEOUT_US (2000 * MS) +#define IO_TIMEOUT_US (500 * MS) + +#define MAX_ERR_COUNT 0xff + +#define R1_IDLE_STATE 0 +#define R1_ERASE_RESET 1 +#define R1_ILLEGAL_CMD 2 +#define R1_CRC_ERR 3 +#define R1_ERASE_SEQ_ERR 4 +#define R1_ADDR_ERR 5 +#define R1_PARAM_ERR 6 +#define R1_BUSY 7 +#define R2_LOCKED 8 +#define R2_WPE_SKIP_LF 9 +#define R2_ERROR 10 +#define R2_CC_ERROR 11 +#define R2_ECC_FAILED 12 +#define R2_WP_VIOLATION 13 +#define R2_ERASE_PARAM 14 +#define R2_OUT_OF_RANGE 15 + +#define OCR_CCS 30 +#define OCR_BUSY 31 +#define OCR_SDHC (BV(OCR_CCS) | BV(OCR_BUSY)) + +#define TOKEN_SINGLE_TRAN 0xfe +#define TOKEN_MULTI_TRAN 0xfc +#define TOKEN_STOP_TRAN 0xfd +#define WRITE_RES_MASK 0x1f +#define WRITE_RES_OK 0x05 + + +#define CMD0 0x00 // GO_IDLE_STATE - Resets the SD Memory Card +#define CMD1 0x01 // SEND_OP_COND - Sends host capacity support information + // and activates the card's initialization process. +#define CMD6 0x06 // SWITCH_FUNC - Checks switchable function (mode 0) and + // switches card function (mode 1). +#define CMD8 0x08 // SEND_IF_COND - Sends SD Memory Card interface condition + // that includes host supply voltage information and asks + // the accessed card whether card can operate in supplied + // voltage range. +#define CMD9 0x09 // SEND_CSD - Asks the selected card to send its + // card-specific data (CSD register) +#define CMD10 0x0a // SEND_CID - Asks the selected card to send its card + // identification (CID register) +#define CMD12 0x0c // STOP_TRANSMISSION - Forces the card to stop transmission + // in Multiple Block Read Operation +#define CMD13 0x0d // SEND_STATUS - Asks the selected card to send its + // status register. +#define CMD16 0x10 // SET_BLOCKLEN - Sets a block length (in bytes) for all + // following block commands (read and write) of a Standard + // Capacity Card. Block length of the read and write + // commands are fixed to 512 bytes in a High Capacity Card. + // The length of LOCK_UNLOCK command is set by this command + // in both capacity cards. +#define CMD17 0x11 // READ_SINGLE_BLOCK - Reads a block of the size selected + // by the SET_BLOCKLEN command. +#define CMD18 0x12 // READ_MULTIPLE_BLOCK - Continuously transfers data blocks + // from card to host until interrupted by a + // STOP_TRANSMISSION command. +#define CMD24 0x18 // WRITE_BLOCK - Writes a block of the size selected by the + // SET_BLOCKLEN command. +#define CMD25 0x19 // WRITE_MULTIPLE_BLOCK - Continuously writes blocks of + // data until ’Stop Tran’ token is sent (instead ’Start + // Block’). +#define CMD27 0x1b // PROGRAM_CSD - Programming of the programmable bits of + // the CSD. +#define CMD28 0x1c // SET_WRITE_PROT +#define CMD29 0x1d // CLR_WRITE_PROT +#define CMD32 0x20 // ERASE_WR_BLK_START - Sets the address of the first block + // to be erased. +#define CMD33 0x21 // ERASE_WR_BLK_END - Sets the address of the last block of + // the continuous range to be erased. +#define CMD38 0x26 // ERASE - Erases all previously selected blocks. +#define CMD55 0x37 // APP_CMD - Defines to the card that the next command is + // an application specific command rather than a standard + // command. +#define CMD58 0x3a // READ_OCR - Reads the OCR register of a card. +#define CMD59 0x3b // CRC_ON_OFF - Turns the CRC option on or off. + +#define ACMD23 0x17 // SET_WR_BLK_ERASE_COUNT - Sets the number of write blocks + // to be pre-erased before writing +#define ACMD41 0x29 // SD_SEND_OP_COMD - Sends host capacity support information + // and activates the card's initialization process + +static uint8_t crc7(const uint8_t* data, uint8_t n) +{ + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) + { + uint8_t d = data[i]; + for (uint8_t j = 0; j < 8; j++) + { + crc <<= 1; + if ((d & 0x80) ^ (crc & 0x80)) + crc ^= 0x09; + d <<= 1; + } + } + return (crc << 1) | 1; +} + +static uint16_t crc_ccitt(const uint8_t *data, size_t n) +{ + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) + { + crc = (uint8_t)(crc >> 8) | (crc << 8); + crc ^= data[i]; + crc ^= (uint8_t)(crc & 0xff) >> 4; + crc ^= crc << 12; + crc ^= (crc & 0xff) << 5; + } + return crc; +} + +#define spi_cs_low(card) do { gpio_write(card->cs_pin, false); } while(0) +#define spi_cs_high(card) do { gpio_write(card->cs_pin, true); } while(0) +#define spi_read_byte() (spi_transfer_8(BUS, 0xff)) +#define spi_read_word() (((uint16_t)spi_read_byte() << 8) | spi_read_byte()) +#define spi_read_dword() (((uint32_t)spi_read_byte() << 24) | ((uint32_t)spi_read_byte() << 16) | ((uint32_t)spi_read_byte() << 8) | spi_read_byte()) +#define spi_skip_word() do { spi_read_byte(); spi_read_byte(); } while(0) +#define spi_skip_dword() do { spi_read_byte(); spi_read_byte(); spi_read_byte(); spi_read_byte(); } while(0) + +inline static uint16_t spi_write_word(uint16_t word) +{ + return (spi_transfer_8(BUS, word >> 8) << 8) | spi_transfer_8(BUS, word); +} + +inline static void spi_read_bytes(uint8_t *dst, size_t size) +{ + for (uint8_t *offs = dst; offs < dst + size; offs ++) + *offs = spi_read_byte(); +} + +static bool wait() +{ + uint32_t stop = sdk_system_get_time() + IO_TIMEOUT_US; + while (spi_read_byte() != 0xff) + if (sdk_system_get_time() >= stop) + return false; + return true; +} + +static uint8_t command(sdio_card_t *card, uint8_t cmd, uint32_t arg) +{ + uint8_t buf[6] = { + cmd | 0x40, + arg >> 24, + arg >> 16, + arg >> 8, + arg + }; + if (card->crc_enabled) + buf[5] = crc7(buf, 5); + else + buf[5] = cmd == CMD0 ? 0x95 : 0x87; + + spi_cs_low(card); + wait(); + spi_transfer(BUS, buf, NULL, 6, SPI_8BIT); + + // R1b response + if (cmd == CMD12 || cmd == CMD28 || cmd == CMD29) + spi_read_byte(); + + uint8_t res; + for (uint8_t i = 0; i < MAX_ERR_COUNT; i ++) + { + res = spi_read_byte(); + if (!(res & BV(R1_BUSY))) + break; + } + return res; +} + +inline static uint8_t app_command(sdio_card_t *card, uint8_t cmd, uint32_t arg) +{ + command(card, CMD55, 0); + return command(card, cmd, arg); +} + +inline static sdio_error_t set_error(sdio_card_t *card, sdio_error_t err) +{ + card->error = err; + spi_cs_high(card); + return err; +} + +static sdio_error_t read_data(sdio_card_t *card, uint8_t *dst, size_t size) +{ + uint32_t timeout = sdk_system_get_time() + IO_TIMEOUT_US; + + while (true) + { + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + + uint8_t b = spi_read_byte(); + if (b == TOKEN_SINGLE_TRAN) + break; + + if (b != 0xff) + return set_error(card, SDIO_ERR_IO); + } + + spi_read_bytes(dst, size); + + uint16_t crc = spi_read_word(); + if (card->crc_enabled && crc_ccitt(dst, size) != crc) + return set_error(card, SDIO_ERR_CRC); + + return SDIO_ERR_NONE; +} + +static sdio_error_t read_register(sdio_card_t *card, uint8_t cmd, void *dst) +{ + if (command(card, cmd, 0)) + return set_error(card, SDIO_ERR_IO); + return read_data(card, dst, 16); +} + +static sdio_error_t write_data_block(sdio_card_t *card, uint8_t token, uint8_t *src) +{ + if (!wait()) + return set_error(card, SDIO_ERR_TIMEOUT); + spi_transfer_8(BUS, token); + spi_transfer(BUS, src, NULL, SDIO_BLOCK_SIZE, SPI_8BIT); + spi_write_word(card->crc_enabled ? crc_ccitt(src, SDIO_BLOCK_SIZE) : 0xffff); + + if ((spi_read_byte() & WRITE_RES_MASK) != WRITE_RES_OK) + return set_error(card, SDIO_ERR_IO); + + return SDIO_ERR_NONE; +} + +sdio_error_t sdio_init(sdio_card_t *card, uint8_t cs_pin, uint32_t high_freq_divider) +{ + card->cs_pin = cs_pin; + card->type = SDIO_TYPE_UNKNOWN; + + // setup SPI at 125kHz + spi_settings_t s = { + .mode = SPI_MODE0, + .freq_divider = SPI_FREQ_DIV_125K, + .msb = true, + .endianness = SPI_LITTLE_ENDIAN, + .minimal_pins = true + }; + spi_set_settings(BUS, &s); + gpio_enable(card->cs_pin, GPIO_OUTPUT); + + uint32_t timeout = sdk_system_get_time() + INIT_TIMEOUT_US; + + spi_cs_low(card); + spi_cs_high(card); + for (uint8_t i = 0; i < 10; i++) + spi_read_byte(); + + // Set card to the SPI idle mode + while (command(card, CMD0, 0) != BV(R1_IDLE_STATE)) + { + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + } + + // Enable CRC + card->crc_enabled = command(card, CMD59, 1) == BV(R1_IDLE_STATE); + + // Get card type + while (true) + { + if (command(card, CMD8, 0x1aa) & BV(R1_ILLEGAL_CMD)) + { + card->type = SDIO_TYPE_SD1; + break; + } + if ((spi_read_dword() & 0xff) == 0xaa) + { + card->type = SDIO_TYPE_SD2; + break; + } + + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + } + + if (card->type == SDIO_TYPE_SD1) + { + // SD1 or MMC3 + if (app_command(card, ACMD41, 0) > 1) + { + card->type = SDIO_TYPE_MMC; + while (command(card, CMD1, 0)) + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + } + else + { + while (app_command(card, ACMD41, 0)) + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + } + + if (command(card, CMD16, SDIO_BLOCK_SIZE)) + return set_error(card, SDIO_ERR_UNSUPPORTED); + } + else + { + // SD2 or SDHC + while (app_command(card, ACMD41, BV(30)) != 0) + if (sdk_system_get_time() >= timeout) + return set_error(card, SDIO_ERR_TIMEOUT); + } + // read OCR + if (command(card, CMD58, 0)) + return set_error(card, SDIO_ERR_IO); + card->ocr.data = spi_read_dword(); + + if (card->type == SDIO_TYPE_SD2 && (card->ocr.data & OCR_SDHC) == OCR_SDHC) + card->type = SDIO_TYPE_SDHC; + + spi_set_frequency_div(BUS, high_freq_divider); + + if (read_register(card, CMD10, &card->cid.data) != SDIO_ERR_NONE) + return card->error; + if (read_register(card, CMD9, &card->csd.data) != SDIO_ERR_NONE) + return card->error; + + // Card size + if (card->csd.v1.csd_ver == 0) + card->sectors = (uint32_t)(((card->csd.v1.c_size_high << 10) | (card->csd.v1.c_size_mid << 2) | card->csd.v1.c_size_low) + 1) + << (((card->csd.v1.c_size_mult_high << 1) | card->csd.v1.c_size_mult_low) + card->csd.v1.read_bl_len - 7); + else if (card->csd.v2.csd_ver == 1) + card->sectors = (((uint32_t)card->csd.v2.c_size_high << 16) + ((uint32_t)card->csd.v2.c_size_mid << 8) + card->csd.v2.c_size_low + 1) << 10; + else + return set_error(card, SDIO_ERR_UNSUPPORTED); + + return set_error(card, SDIO_ERR_NONE); +} + +sdio_error_t sdio_read_sectors(sdio_card_t *card, uint32_t sector, uint8_t *dst, uint32_t count) +{ + if (!count) + return set_error(card, SDIO_ERR_IO); + + if (card->type != SDIO_TYPE_SDHC) + sector <<= 9; + + bool multi = count > 1; + if (command(card, multi ? CMD18 : CMD17, sector)) + return set_error(card, SDIO_ERR_IO); + + while (count--) + { + if (read_data(card, dst, SDIO_BLOCK_SIZE) != SDIO_ERR_NONE) + return card->error; + dst += SDIO_BLOCK_SIZE; + } + + if (multi && command(card, CMD12, 0)) + return set_error(card, SDIO_ERR_IO); + + return set_error(card, SDIO_ERR_NONE); +} + +sdio_error_t sdio_write_sectors(sdio_card_t *card, uint32_t sector, uint8_t *src, uint32_t count) +{ + if (!count) + return set_error(card, SDIO_ERR_IO); + + if (card->type != SDIO_TYPE_SDHC) + sector <<= 9; + + if (count == 1) + { + // single block + if (command(card, CMD24, sector)) + return set_error(card, SDIO_ERR_IO); + return set_error(card, write_data_block(card, TOKEN_SINGLE_TRAN, src)); + } + + // send pre-erase count + if ((card->type == SDIO_TYPE_SD1 + || card->type == SDIO_TYPE_SD2 + || card->type == SDIO_TYPE_SDHC) + && app_command(card, ACMD23, count)) + { + return set_error(card, SDIO_ERR_IO); + } + + if (command(card, CMD25, sector)) + return set_error(card, SDIO_ERR_IO); + + while (count--) + { + if (write_data_block(card, TOKEN_MULTI_TRAN, src) != SDIO_ERR_NONE) + return card->error; + src += SDIO_BLOCK_SIZE; + } + spi_transfer_8(BUS, TOKEN_STOP_TRAN); + + return set_error(card, SDIO_ERR_NONE); +} + +sdio_error_t sdio_erase_sectors(sdio_card_t *card, uint32_t first, uint32_t last) +{ + if (!card->csd.v1.erase_blk_en) + { + uint8_t mask = (card->csd.v1.sector_size_high << 1) | card->csd.v1.sector_size_low; + if ((first & mask) || ((last + 1) & mask)) + return set_error(card, SDIO_ERR_UNSUPPORTED); + } + + if (card->type != SDIO_TYPE_SDHC) + { + first <<= 9; + last <<= 9; + } + + if (command(card, CMD32, first) + || command(card, CMD33, last) + || command(card, CMD38, 0)) + { + return set_error(card, SDIO_ERR_IO); + } + + return set_error(card, wait() ? SDIO_ERR_NONE : SDIO_ERR_TIMEOUT); +} + diff --git a/extras/sdio/sdio.h b/extras/sdio/sdio.h new file mode 100644 index 0000000..c51200d --- /dev/null +++ b/extras/sdio/sdio.h @@ -0,0 +1,98 @@ +/* + * Hardware SPI driver for MMC/SD/SDHC cards + * + * Part of esp-open-rtos + * Copyright (C) 2016 Ruslan V. Uss + * BSD Licensed as described in the file LICENSE + */ +#ifndef _EXTRAS_SDIO_H_ +#define _EXTRAS_SDIO_H_ + +#include +#include +#include +#include "sdio_impl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDIO_BLOCK_SIZE 512 + +typedef enum { + SDIO_ERR_NONE = 0, + SDIO_ERR_TIMEOUT, + SDIO_ERR_UNSUPPORTED, + SDIO_ERR_IO, + SDIO_ERR_CRC +} sdio_error_t; + +typedef enum { + SDIO_TYPE_UNKNOWN = 0, + SDIO_TYPE_MMC, + SDIO_TYPE_SD1, + SDIO_TYPE_SD2, + SDIO_TYPE_SDHC +} sdio_card_type_t; + +/** + * SD card descriptor + */ +typedef struct +{ + sdio_error_t error; //!< Last operation result + uint8_t cs_pin; //!< Chip Select GPIO pin + sdio_card_type_t type; //!< Card type + bool crc_enabled; //!< True if CRC enabled for IO + sdio_ocr_t ocr; //!< OCR register + sdio_csd_t csd; //!< CSD register + sdio_cid_t cid; //!< CID register + uint32_t sectors; //!< Card size in 512 byte sectors +} sdio_card_t; + +/** + * \brief Init SD card + * Device descriptor (registers, sectors count and so on) will be filled during initialization + * SPI_FREQ_DIV_40M is good for modern SD cards, older SD can use SPI_FREQ_DIV_20M and lower + * \param card Pointer to the device descriptor + * \param cs_pin GPIO pin used for CS + * \param high_freq_divider SPI bus frequency divider + * \return Operation result + */ +sdio_error_t sdio_init(sdio_card_t *card, uint8_t cs_pin, uint32_t high_freq_divider); + +/** + * \brief Read 512 byte sectors from SD card + * \param card Pointer to the device descriptor + * \param sector Start sector + * \param dst Receive buffer + * \param count Number of sectors to read + * \return Operation result + */ +sdio_error_t sdio_read_sectors(sdio_card_t *card, uint32_t sector, uint8_t *dst, uint32_t count); + +/** + * \brief Write 512 byte sectors to SD card + * \param card Pointer to the device descriptor + * \param sector Start sector + * \param src Data to write + * \param count Number of sectors to read + * \return Operation result + */ +sdio_error_t sdio_write_sectors(sdio_card_t *card, uint32_t sector, uint8_t *src, uint32_t count); + +/** + * \brief Erase 512 byte sectors from SD card + * \param card Pointer to the device descriptor + * \param first First sector to erase + * \param last Last sector to erase + * \return Operation result + */ +sdio_error_t sdio_erase_sectors(sdio_card_t *card, uint32_t first, uint32_t last); + + +#ifdef __cplusplus +} +#endif + +#endif /* _EXTRAS_SDIO_H_ */ diff --git a/extras/sdio/sdio_impl.h b/extras/sdio/sdio_impl.h new file mode 100644 index 0000000..fc6a251 --- /dev/null +++ b/extras/sdio/sdio_impl.h @@ -0,0 +1,154 @@ +/* + * Hardware SPI driver for MMC/SD/SDHC cards + * + * Part of esp-open-rtos + * Copyright (C) 2016 Ruslan V. Uss + * BSD Licensed as described in the file LICENSE + */ +#ifndef _EXTRAS_SDIO_IMPL_H_ +#define _EXTRAS_SDIO_IMPL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union +{ + uint32_t data; + struct + { + uint16_t reserverd1 :15; + uint8_t vcc_2v7_2v8 :1; + uint8_t vcc_2v8_2v9 :1; + uint8_t vcc_2v9_3v0 :1; + uint8_t vcc_3v0_3v1 :1; + uint8_t vcc_3v1_3v2 :1; + uint8_t vcc_3v2_3v3 :1; + uint8_t vcc_3v3_3v4 :1; + uint8_t vcc_3v4_3v5 :1; + uint8_t vcc_3v5_3v6 :1; + uint8_t reserverd2 :6; + uint8_t ccs :1; + uint8_t busy :1; + } __attribute__((packed)) bits; +} sdio_ocr_t; + +typedef union +{ + uint8_t data[16]; + struct + { + uint8_t manufacturer_id; + char oem_id[2]; + char product_name[5]; + uint8_t product_rev_minor :4; + uint8_t product_rev_major :4; + uint32_t product_serial; + uint8_t reserved1 :4; + uint8_t date_year_h :4; + uint8_t date_year_l :4; + uint8_t date_month :4; + uint8_t always1 :1; + uint8_t crc :7; + } __attribute__((packed)) bits; +} sdio_cid_t; + +typedef union +{ + uint8_t data[16]; + struct + { + uint8_t reserved1 :6; + uint8_t csd_ver :2; + uint8_t taac; + uint8_t nsac; + uint8_t tran_speed; + uint8_t ccc_high; + uint8_t read_bl_len :4; + uint8_t ccc_low :4; + uint16_t c_size_high :2; + uint8_t reserved2 :2; + uint8_t dsr_imp :1; + uint8_t read_blk_misalign :1; + uint8_t write_blk_misalign :1; + uint8_t read_bl_partial :1; + uint8_t c_size_mid; + uint8_t vdd_r_curr_max :3; + uint8_t vdd_r_curr_min :3; + uint8_t c_size_low :2; + uint8_t c_size_mult_high :2; + uint8_t vdd_w_cur_max :3; + uint8_t vdd_w_curr_min :3; + uint8_t sector_size_high :6; + uint8_t erase_blk_en :1; + uint8_t c_size_mult_low :1; + uint8_t wp_grp_size :7; + uint8_t sector_size_low :1; + uint8_t write_bl_len_high :2; + uint8_t r2w_factor :3; + uint8_t reserved3 :2; + uint8_t wp_grp_enable :1; + uint8_t reserved4 :5; + uint8_t write_partial :1; + uint8_t write_bl_len_low :2; + uint8_t reserved5 :2; + uint8_t file_format :2; + uint8_t tmp_write_protect :1; + uint8_t perm_write_protect :1; + uint8_t copy :1; + uint8_t file_format_grp :1; + uint8_t always1 :1; + uint8_t crc :7; + } __attribute__((packed)) v1; + + struct + { + uint8_t reserved1 :6; + uint8_t csd_ver :2; + uint8_t taac; + uint8_t nsac; + uint8_t tran_speed; + uint8_t ccc_high; + uint8_t read_bl_len :4; + uint8_t ccc_low :4; + uint8_t reserved2 :4; + uint8_t dsr_imp :1; + uint8_t read_blk_misalign :1; + uint8_t write_blk_misalign :1; + uint8_t read_bl_partial :1; + uint16_t c_size_high :6; + uint8_t reserved3 :2; + uint8_t c_size_mid; + uint8_t c_size_low; + uint8_t sector_size_high :6; + uint8_t erase_blk_en :1; + uint8_t reserved4 :1; + uint8_t wp_grp_size :7; + uint8_t sector_size_low :1; + uint8_t write_bl_len_high :2; + uint8_t r2w_factor :3; + uint8_t reserved5 :2; + uint8_t wp_grp_enable :1; + uint8_t reserved6 :5; + uint8_t write_partial :1; + uint8_t write_bl_len_low :2; + uint8_t reserved7 :2; + uint8_t file_format :2; + uint8_t tmp_write_protect :1; + uint8_t perm_write_protect :1; + uint8_t copy :1; + uint8_t file_format_grp :1; + uint8_t always1 :1; + uint8_t crc :7; + } __attribute__((packed)) v2; +} sdio_csd_t; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _EXTRAS_SDIO_IMPL_H_ */