blob: 36d882d9e1a7a349c7ffa54e70cd4cae0f3acaf3 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <stdint.h>
#include <string.h>
#include <spi-generic.h>
#include <spi_sdcard.h>
#include <crc_byte.h>
#include <commonlib/helpers.h>
#include <console/console.h>
#define SPI_SDCARD_DEBUG 0
#define dprintk(fmt, args...) \
do { if (SPI_SDCARD_DEBUG) { printk(BIOS_DEBUG, fmt, ##args); }} while (0)
#define SDCARD_TYPE_SDSC 1
#define SDCARD_TYPE_SDHC 2
#define SDCARD_TYPE_SDXC 3
/* CMD */
#define GO_IDLE_STATE 0
#define SEND_OP_COND 1
#define SWITCH_FUNC 6
#define SEND_IF_COND 8
#define SEND_CSD 9
#define SEND_CID 10
#define STOP_TRANSMISSION 12
#define SEND_STATUS 13
#define SET_BLOCKLEN 16
#define READ_SINGLE_BLOCK 17
#define READ_MULTIPLEBLOCK 18
#define WRITE_BLOCK 24
#define WRITE_MULTIPLEBLOCK 25
#define PROGRAM_CSD 27
#define SET_WRITE_PROT 28
#define CLR_WRITE_PROT 29
#define SEND_WRITE_PROT 30
#define ERASE_WR_BLK_START_ADDR 32
#define ERASE_WR_BLK_END_ADDR 33
#define ERASE 38
#define LOCK_UNLOCK 42
#define APP_CMD 55
#define GEN_CMD 56
#define READ_OCR 58
#define CRC_ON_OFF 59
/* ACMD */
#define SD_STATUS 13
#define SEND_NUM_WR_BLOCKS 22
#define SET_WR_BLK_ERASE_COUNT 23
#define SD_SEND_OP_COND 41
#define SET_CLR_CARD_DETECT 42
#define SEND_SCR 51
/* control tokens */
#define CT_BLOCK_START 0xfe
#define CT_MULTIPLE_BLOCK_START 0xfc
#define CT_MULTIPLE_BLOCK_STOP 0xfd
#define CT_RESPONSE_MASK 0x1f
#define CT_RESPONSE_ACCEPTED 0x05
#define CT_RESPONSE_REJECTED_CRC 0x0b
#define CT_RESPONSE_REJECTED_WRITE_ERR 0x0d
/* response type */
#define RSP_R1 0
#define RSP_R1b 1
#define RSP_R2 2
#define RSP_R3 3
#define RSP_R4 4
#define RSP_R5 5
#define RSP_R7 7
#define RSP_ERR_CARD_IS_LOCKED (1 << 0)
#define RSP_ERR_WP_ERASE_SKIP (1 << 1)
#define RSP_ERR_GENERAL (1 << 2)
#define RSP_ERR_CC (1 << 3)
#define RSP_ERR_ECC (1 << 4)
#define RSP_ERR_WP_VIOLATION (1 << 5)
#define RSP_ERR_ERASE_PARAM (1 << 6)
#define RSP_ERR_OUT_OF_RANGE (1 << 7)
#define RSP_ERR_IN_IDLE (1 << 8)
#define RSP_ERR_ERASE_RESET (1 << 9)
#define RSP_ERR_ILLEGAL_COMMAND (1 << 10)
#define RSP_ERR_COM_CRC (1 << 11)
#define RSP_ERR_ERASE_SEQUENCE (1 << 12)
#define RSP_ERR_ADDRESS (1 << 13)
#define RSP_ERR_PARAMETER (1 << 14)
#define BLOCK_SIZE 512
static unsigned long long extract_bits(uint8_t *buff,
int width, int start, int end)
{
unsigned long long r = 0;
for (int i = end; i >= start; i--) {
int bitpos = width - i - 1;
int b = bitpos / 8;
int shift = 7 - bitpos % 8;
r = (r << 1) | ((buff[b] >> shift) & 1);
}
return r;
}
static void spi_sdcard_enable_cs(const struct spi_sdcard *card)
{
spi_claim_bus(&card->slave);
}
static void spi_sdcard_disable_cs(const struct spi_sdcard *card)
{
spi_release_bus(&card->slave);
}
static void spi_sdcard_sendbyte(const struct spi_sdcard *card, uint8_t b)
{
dprintk("sdcard -> %#x\n", b);
spi_xfer(&card->slave, &b, 1, NULL, 0);
}
static uint8_t spi_sdcard_recvbyte(const struct spi_sdcard *card)
{
uint8_t b, t = 0xff;
spi_xfer(&card->slave, &t, 1, &b, 1);
dprintk("sdcard <- %#x\n", b);
return b;
}
static uint8_t spi_sdcard_calculate_command_crc(uint8_t cmd, uint32_t argument)
{
uint8_t crc = 0;
crc = crc7_byte(crc, (cmd | 0x40) & 0x7f);
crc = crc7_byte(crc, (argument >> (3 * 8)) & 0xff);
crc = crc7_byte(crc, (argument >> (2 * 8)) & 0xff);
crc = crc7_byte(crc, (argument >> (1 * 8)) & 0xff);
crc = crc7_byte(crc, (argument >> (0 * 8)) & 0xff);
return crc | 1;
}
static int lookup_cmd_response_type(uint8_t cmd)
{
switch (cmd) {
case GO_IDLE_STATE:
case SEND_OP_COND:
case SWITCH_FUNC:
case SEND_CSD:
case SEND_CID:
case SET_BLOCKLEN:
case READ_SINGLE_BLOCK:
case READ_MULTIPLEBLOCK:
case WRITE_BLOCK:
case WRITE_MULTIPLEBLOCK:
case PROGRAM_CSD:
case SEND_WRITE_PROT:
case ERASE_WR_BLK_START_ADDR:
case ERASE_WR_BLK_END_ADDR:
case LOCK_UNLOCK:
case APP_CMD:
case GEN_CMD:
case CRC_ON_OFF:
return RSP_R1;
case STOP_TRANSMISSION:
case SET_WRITE_PROT:
case CLR_WRITE_PROT:
case ERASE:
return RSP_R1b;
case SEND_STATUS:
return RSP_R2;
case READ_OCR:
return RSP_R3;
case SEND_IF_COND:
return RSP_R7;
}
return -1;
}
static int lookup_acmd_response_type(uint8_t cmd)
{
switch (cmd) {
case SEND_NUM_WR_BLOCKS:
case SET_WR_BLK_ERASE_COUNT:
case SD_SEND_OP_COND:
case SET_CLR_CARD_DETECT:
case SEND_SCR:
return RSP_R1;
case SD_STATUS:
return RSP_R2;
}
return -1;
}
static int lookup_response_length(int response_type)
{
switch (response_type) {
case RSP_R1:
case RSP_R1b:
return 1;
case RSP_R2:
return 2;
case RSP_R3:
case RSP_R7:
return 5;
}
return -1;
}
static int response_resolve(int response_type, uint8_t *response,
uint32_t *out_register)
{
__maybe_unused static const char * const sd_err[] = {
"Card is locked",
"wp erase skip | lock/unlok cmd failed",
"error",
"CC error",
"card err failed",
"wp violation",
"erase param",
"out of range | csd overwrite",
"in idle state",
"erase reset",
"illegal command",
"com crc error",
"erase sequence error",
"address error",
"parameter error"
};
uint8_t r1 = 0, r2 = 0;
if ((response_type == RSP_R1)
|| (response_type == RSP_R1b)
|| (response_type == RSP_R2)
|| (response_type == RSP_R3)
|| (response_type == RSP_R7))
r1 = response[0];
if (response_type == RSP_R2)
r2 = response[1];
if (((response_type == RSP_R3) || (response_type == RSP_R7))
&& (out_register != NULL)) {
*out_register = 0;
*out_register = (*out_register << 8) | response[1];
*out_register = (*out_register << 8) | response[2];
*out_register = (*out_register << 8) | response[3];
*out_register = (*out_register << 8) | response[4];
}
if (r1 != 0 || r2 != 0) {
int i = 0;
uint16_t r = (r1 << 8) | r2;
while (r) {
if (r & 1)
dprintk("SDCARD ERROR: %s\n", sd_err[i]);
r = r >> 1;
i++;
}
return (r1 << 8) | r2;
}
return 0;
}
static int spi_sdcard_do_command_help(const struct spi_sdcard *card,
int is_acmd,
uint8_t cmd,
uint32_t argument,
uint32_t *out_register)
{
int ret, type, length, wait;
uint8_t crc, c, response[5];
/* calculate crc for command */
crc = spi_sdcard_calculate_command_crc(cmd, argument);
if (is_acmd)
dprintk("\nsdcard execute acmd%d, argument = %#x, crc = %#x\n",
cmd, argument, crc);
else
dprintk("\nsdcard execute cmd%d, argument = %#x, crc = %#x\n",
cmd, argument, crc);
/* lookup response type of command */
if (!is_acmd)
type = lookup_cmd_response_type(cmd);
else
type = lookup_acmd_response_type(cmd);
/* lookup response length of command */
length = lookup_response_length(type);
/* enable cs */
spi_sdcard_enable_cs(card);
/* just delay 8 clocks */
spi_sdcard_recvbyte(card);
/* send command */
spi_sdcard_sendbyte(card, (cmd | 0x40) & 0x7f);
/* send argument */
spi_sdcard_sendbyte(card, (argument >> (8 * 3)) & 0xff);
spi_sdcard_sendbyte(card, (argument >> (8 * 2)) & 0xff);
spi_sdcard_sendbyte(card, (argument >> (8 * 1)) & 0xff);
spi_sdcard_sendbyte(card, (argument >> (8 * 0)) & 0xff);
/* send crc */
spi_sdcard_sendbyte(card, crc);
/* waitting for response */
wait = 0xffff;
while (((c = spi_sdcard_recvbyte(card)) & 0x80) && --wait)
;
if (!wait) {
spi_sdcard_disable_cs(card);
return -1; /* timeout */
}
/* obtain response */
for (int i = 0; i < length; i++) {
response[i] = c;
c = spi_sdcard_recvbyte(card);
}
if (type == RSP_R1b) {
/* waitting done */
wait = 0xffffff;
while (c == 0 && --wait)
c = spi_sdcard_recvbyte(card);
if (!wait) {
spi_sdcard_disable_cs(card);
return -1; /* timeout */
}
}
spi_sdcard_disable_cs(card);
ret = response_resolve(type, response, out_register);
return ret;
}
static int spi_sdcard_do_command(const struct spi_sdcard *card,
uint8_t cmd,
uint32_t argument,
uint32_t *out_register)
{
return spi_sdcard_do_command_help(card, 0, cmd, argument, out_register);
}
static int spi_sdcard_do_app_command(const struct spi_sdcard *card,
uint8_t cmd,
uint32_t argument,
uint32_t *out_register)
{
/* CMD55 */
if (spi_sdcard_do_command(card, APP_CMD, 0, NULL))
return -1;
return spi_sdcard_do_command_help(card, 1, cmd, argument, out_register);
}
size_t spi_sdcard_size(const struct spi_sdcard *card)
{
int wait;
uint8_t csd[16];
uint16_t c = 0;
/* CMD9, send csd (128bits register) */
if (spi_sdcard_do_command(card, SEND_CSD, 0, NULL))
return -1;
/* enable CS */
spi_sdcard_enable_cs(card);
/* waitting start block token */
wait = 0xffff;
while ((spi_sdcard_recvbyte(card) != CT_BLOCK_START) && --wait)
;
if (!wait) {
spi_sdcard_disable_cs(card);
return -1;
}
/* receive data */
for (int i = 0; i < 16; i++) {
csd[i] = spi_sdcard_recvbyte(card);
c = crc16_byte(c, csd[i]);
}
/* receive crc and verify check sum */
if (((c >> 8) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
if (((c >> 0) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
/* disable cs */
spi_sdcard_disable_cs(card);
if (extract_bits(csd, 128, 126, 127) == 0) {
/* csd version 1.0 */
size_t c_size = extract_bits(csd, 128, 62, 73);
size_t mult = extract_bits(csd, 128, 47, 49);
size_t read_bl_len = extract_bits(csd, 128, 80, 83);
return (c_size + 1) * mult * (1 << read_bl_len);
}
if (extract_bits(csd, 128, 126, 127) == 1) {
/* csd version 2.0 */
size_t c_size = extract_bits(csd, 128, 48, 69);
return (c_size + 1) * 512 * 1024;
}
return -1;
}
int spi_sdcard_init(struct spi_sdcard *card,
const unsigned int bus, const unsigned int cs)
{
int resolve, wait;
uint32_t ocr;
/* initialize spi controller */
spi_setup_slave(bus, cs, &card->slave);
/* must wait at least 74 clock ticks after reset
* disable cs pin to enter spi mode */
spi_sdcard_disable_cs(card);
for (int i = 0; i < 10; i++)
spi_sdcard_sendbyte(card, 0xff);
/* CMD0, reset sdcard */
wait = 0xffff;
while ((spi_sdcard_do_command(card, GO_IDLE_STATE, 0, NULL)
!= RSP_ERR_IN_IDLE) && --wait)
;
if (!wait)
return -1; /* timeout */
/* CMD8 */
resolve = spi_sdcard_do_command(card, SEND_IF_COND, 0x1aa, NULL);
if (resolve & RSP_ERR_ILLEGAL_COMMAND) {
/* ACMD41, initialize card */
wait = 0xffff;
while ((resolve = spi_sdcard_do_app_command(card,
SD_SEND_OP_COND, 0, NULL)) && --wait)
;
if ((resolve & RSP_ERR_ILLEGAL_COMMAND) || !wait) {
wait = 0xffff;
/* CMD1, initialize card for 2.1mm SD Memory Card */
while (spi_sdcard_do_app_command(card, SEND_OP_COND,
0, NULL) && --wait)
;
if (!wait)
return -1; /* unknown card */
}
} else {
/* ACMD41, initialize card */
wait = 0xffff;
while (spi_sdcard_do_app_command(card, SD_SEND_OP_COND,
0x40000000, NULL) && --wait)
;
if (!wait)
return -1;
}
/* CMD58, read ocr register */
if (spi_sdcard_do_command(card, READ_OCR, 0, &ocr))
return -1;
/* CMD16, set block length to 512 bytes */
if (spi_sdcard_do_command(card, SET_BLOCKLEN, 512, NULL))
return -1;
/* CCS is bit30 of ocr register
* CCS = 0 -> SDSC
* CCS = 1 -> SDHC/SDXC
* */
if ((ocr & 0x40000000) == 0)
card->type = SDCARD_TYPE_SDSC;
else {
/* size > 32G -> SDXC */
if (spi_sdcard_size(card) > 32LL * 1024 * 1024 * 1024)
card->type = SDCARD_TYPE_SDXC;
else
card->type = SDCARD_TYPE_SDHC;
}
return 0;
}
int spi_sdcard_single_read(const struct spi_sdcard *card,
size_t block_address,
void *buff)
{
int wait;
uint16_t c = 0;
if (card->type == SDCARD_TYPE_SDSC)
block_address = block_address * 512;
/* CMD17, start single block read */
if (spi_sdcard_do_command(card, READ_SINGLE_BLOCK, block_address, NULL))
return -1;
/* enable cs */
spi_sdcard_enable_cs(card);
/* waitting start block token */
wait = 0xffff;
while ((spi_sdcard_recvbyte(card) != CT_BLOCK_START) && --wait)
;
if (!wait) { /* timeout */
spi_sdcard_disable_cs(card);
return -1;
}
/* receive data */
for (int i = 0; i < 512; i++) {
((uint8_t *)buff)[i] = spi_sdcard_recvbyte(card);
c = crc16_byte(c, ((uint8_t *)buff)[i]);
}
/* receive crc and verify check sum */
if (((c >> 8) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
if (((c >> 0) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
/* disable cs */
spi_sdcard_disable_cs(card);
return 0;
}
int spi_sdcard_multiple_read(const struct spi_sdcard *card,
size_t start_block_address,
size_t end_block_address,
void *buff)
{
int wait;
int block_num = end_block_address - start_block_address + 1;
if (card->type == SDCARD_TYPE_SDSC) {
start_block_address = start_block_address * 512;
end_block_address = end_block_address * 512;
}
/* CMD18, start multiple block read */
if (spi_sdcard_do_command(card,
READ_MULTIPLEBLOCK, start_block_address, NULL))
return -1;
/* enable cs */
spi_sdcard_enable_cs(card);
for (int i = 0; i < block_num; i++) {
uint16_t c = 0;
/* waitting start block token */
wait = 0xffff;
while ((spi_sdcard_recvbyte(card) != CT_BLOCK_START) && --wait)
;
if (!wait) { /* timeout */
spi_sdcard_disable_cs(card);
return -1;
}
/* receive data */
for (int k = 0; k < 512; k++) {
uint8_t tmp = spi_sdcard_recvbyte(card);
((uint8_t *)buff)[512 * i + k] = tmp;
c = crc16_byte(c, tmp);
}
/* receive crc and verify check sum */
if (((c >> 8) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
if (((c >> 0) & 0xff) != spi_sdcard_recvbyte(card)) {
spi_sdcard_disable_cs(card);
return -1;
}
}
/* disable cs */
spi_sdcard_disable_cs(card);
if (spi_sdcard_do_command(card, STOP_TRANSMISSION, 0, NULL))
if (spi_sdcard_do_command(card, SEND_STATUS, 0, NULL))
return -1;
return 0;
}
int spi_sdcard_read(const struct spi_sdcard *card,
void *dest,
size_t offset,
size_t count)
{
size_t start_block_address = offset / BLOCK_SIZE;
size_t end_block_address = (offset + count - 1) / BLOCK_SIZE;
size_t has_begin = !!(offset % BLOCK_SIZE);
size_t has_end = !!((offset + count) % BLOCK_SIZE);
if (start_block_address == end_block_address) {
uint8_t tmp[BLOCK_SIZE];
size_t o = offset % BLOCK_SIZE;
size_t l = count;
if (spi_sdcard_single_read(card, start_block_address, tmp))
return -1;
memcpy(dest, tmp + o, l);
return 0;
}
if (has_begin) {
uint8_t tmp[BLOCK_SIZE];
size_t o = offset % BLOCK_SIZE;
size_t l = BLOCK_SIZE - o;
if (spi_sdcard_single_read(card, start_block_address, tmp))
return -1;
memcpy(dest, tmp + o, l);
}
if (start_block_address + has_begin <= end_block_address - has_end) {
size_t start_lba = start_block_address + has_begin;
size_t end_lba = end_block_address - has_end;
size_t o = has_begin ? BLOCK_SIZE - offset % BLOCK_SIZE : 0;
if (start_lba < end_lba) {
if (spi_sdcard_multiple_read(card, start_lba, end_lba,
dest + o))
return -1;
} else {
if (spi_sdcard_single_read(card, start_lba, dest + o))
return -1;
}
}
if (has_end) {
uint8_t tmp[BLOCK_SIZE];
size_t o = 0;
size_t l = (offset + count) % BLOCK_SIZE;
if (spi_sdcard_single_read(card, end_block_address, tmp))
return -1;
memcpy(dest + count - l, tmp + o, l);
}
return 0;
}
int spi_sdcard_single_write(const struct spi_sdcard *card,
size_t block_address,
void *buff)
{
int wait;
uint16_t c = 0;
if (card->type == SDCARD_TYPE_SDSC)
block_address = block_address * 512;
if (spi_sdcard_do_command(card, WRITE_BLOCK, block_address, NULL))
return -1;
/* eanbele cs */
spi_sdcard_enable_cs(card);
/* send start block token */
spi_sdcard_sendbyte(card, CT_BLOCK_START);
/* send data */
for (int i = 0; i < 512; i++) {
spi_sdcard_sendbyte(card, ((uint8_t *)buff)[i]);
c = crc16_byte(c, ((uint8_t *)buff)[i]);
}
/* send crc check sum */
spi_sdcard_sendbyte(card, 0xff & (c >> 8));
spi_sdcard_sendbyte(card, 0xff & (c >> 0));
/* receive and verify data response token */
c = spi_sdcard_recvbyte(card);
if ((c & CT_RESPONSE_MASK) != CT_RESPONSE_ACCEPTED) {
spi_sdcard_disable_cs(card);
return -1;
}
wait = 0xffff;
while ((spi_sdcard_recvbyte(card) == 0) && --wait)
;/* wait for complete */
if (!wait) {
spi_sdcard_disable_cs(card);
return -1;
}
/* disable cs */
spi_sdcard_disable_cs(card);
return 0;
}
int spi_sdcard_multiple_write(const struct spi_sdcard *card,
size_t start_block_address,
size_t end_block_address,
void *buff)
{
int wait, ret = 0;
int block_num = end_block_address - start_block_address + 1;
if (card->type == SDCARD_TYPE_SDSC) {
start_block_address = start_block_address * 512;
end_block_address = end_block_address * 512;
}
if (spi_sdcard_do_command(card, WRITE_MULTIPLEBLOCK,
start_block_address, NULL))
return -1;
/* enable cs */
spi_sdcard_enable_cs(card);
for (int i = 0; i < block_num; i++) {
uint16_t c = 0;
ret = -1;
/* send start block token */
spi_sdcard_sendbyte(card, CT_MULTIPLE_BLOCK_START);
/* send data */
for (int k = 0; k < 512; k++) {
uint8_t tmp = ((uint8_t *)buff)[512 * i + k];
spi_sdcard_sendbyte(card, tmp);
c = crc16_byte(c, tmp);
}
/* send crc check sum */
spi_sdcard_sendbyte(card, 0xff & (c >> 8));
spi_sdcard_sendbyte(card, 0xff & (c >> 0));
/* receive and verify data response token */
c = spi_sdcard_recvbyte(card);
if ((c & CT_RESPONSE_MASK) != CT_RESPONSE_ACCEPTED)
break;
wait = 0xffff;
while ((spi_sdcard_recvbyte(card) == 0) && --wait)
;/* wait for complete */
if (!wait)
break;
ret = 0;
}
/* send stop transmission token */
spi_sdcard_sendbyte(card, CT_MULTIPLE_BLOCK_STOP);
/* disable cs */
spi_sdcard_disable_cs(card);
if (spi_sdcard_do_command(card, STOP_TRANSMISSION, 0, NULL))
if (spi_sdcard_do_command(card, SEND_STATUS, 0, NULL))
return -1;
return ret;
}
int spi_sdcard_erase(const struct spi_sdcard *card,
size_t start_block_address,
size_t end_block_address)
{
if (card->type == SDCARD_TYPE_SDSC) {
start_block_address = start_block_address * 512;
end_block_address = end_block_address * 512;
}
/* CMD32, set erase start address */
if (spi_sdcard_do_command(card, ERASE_WR_BLK_START_ADDR,
start_block_address, NULL))
return -1;
/* CMD33, set erase end address */
if (spi_sdcard_do_command(card, ERASE_WR_BLK_END_ADDR,
end_block_address, NULL))
return -1;
/* CMD38, erase */
if (spi_sdcard_do_command(card, ERASE, 0, NULL))
return -1;
return 0;
}
int spi_sdcard_erase_all(const struct spi_sdcard *card)
{
return spi_sdcard_erase(card, 0, spi_sdcard_size(card) / BLOCK_SIZE);
}