/* 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 #ifdef SPI_SDCARD_DEBUG #define dprintk(fmt, args...) \ printk(BIOS_DEBUG, fmt, ##args) #else #define dprintk(fmt, args...) \ do {} while (0) #endif #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) { __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); }