esp-open-rtos/extras/sdio/sdio.c
Ruslan V. Uss 2994a566a6 Driver for SD/MMC cards (#239)
* Driver for SD/MMC cards

* SDIO: read_register() bug fixed, schematics resized
2016-10-20 10:03:05 +03:00

449 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Hardware SPI driver for MMC/SD/SDHC cards
*
* Part of esp-open-rtos
* Copyright (C) 2016 Ruslan V. Uss <unclerus@gmail.com>
* BSD Licensed as described in the file LICENSE
*/
#include <esp/gpio.h>
#include <esp/spi.h>
#include <espressif/esp_common.h>
#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);
}