SD/MMC patches
- Various improvements for SD cards in SPI mode (Bin Meng) - Add Bin Meng as SD/MMC cards co-maintainer -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAmAwU0gACgkQ4+MsLN6t wN6gUg//TIPzwJSZsJ36S8E0TIflSRRbCBmrvhQF0KeXkaImFx2wOzaeb7eGFhWS l12z5tCo6kYgeDzfmcKQdMkKjweP7C3ct/tSytTRdPiApaDvae/nHcxsBKrtHqDU otVC6kQ5cB/u/6uf9vDIRrBCqB4AiXrBnL5l/NzDgrUqHkmYbXOjk+K2Xy+2MkYw Vfwzdh50gdFgYDQW2nM9GfD1VRq5XAtzCNXjxQhwBZkQLQ8G7KLMVE8MjzFjw3Vt PDXzfD8zWZoHM3awC34imWnJC+br0h0NNQpTkRj5DBcYpYYwo4FkBmr9pE2LuwYG dma0TalP+/gnlbmJr+Wq9wChGkmPmuyHfBanbgEtmA5cuNB/YRfWPaHqCvvCXSvk 4+UF0xgGQG3kuSUbeujVuRuak+/2a7f30fxK3EZ579L2TVbnUdloQoyVgoIr7xTd DWMsA0AjPNcvq4vA9myoFkj8/GWcT2jiYGGLQGIfnqcbMc2ii9E9wBibsAkuAnHb OvO6xdEWEui8LbTGJZ/PpMd68MWIcGQk65Su+53Ls5oPkOCoNNRBQ2eHMuObDDSm kNyQ5QgQed345U6W7gFp+krdOtBWbXM2X24eIo9lQ2U59kitdE1DPYgmTzO4SNSz 3uM4GiNd3i13fZshH+vUZJrJ87PliCVsQfazIOeeVBFoms/Cz7o= =LijF -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/philmd-gitlab/tags/sdmmc-20210220' into staging SD/MMC patches - Various improvements for SD cards in SPI mode (Bin Meng) - Add Bin Meng as SD/MMC cards co-maintainer # gpg: Signature made Sat 20 Feb 2021 00:09:44 GMT # gpg: using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE # gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full] # Primary key fingerprint: FAAB E75E 1291 7221 DCFD 6BB2 E3E3 2C2C DEAD C0DE * remotes/philmd-gitlab/tags/sdmmc-20210220: MAINTAINERS: Add Bin Meng as co-maintainer for SD/MMC cards hw/sd: sdhci: Simplify updating s->prnsts in sdhci_sdma_transfer_multi_blocks() hw/sd: sd: Bypass the RCA check for CMD13 in SPI mode hw/sd: sd: Skip write protect groups check in CMD24/25 for high capacity cards hw/sd: sd: Skip write protect groups check in sd_erase() for high capacity cards hw/sd: sd: Move the sd_block_{read, write} and macros ahead hw/sd: sd: Fix CMD30 response type hw/sd: sd: Only SDSC cards support CMD28/29/30 hw/sd: sd: Fix address check in sd_erase() hw/sd: ssi-sd: Handle the rest commands with R1b response type hw/sd: ssi-sd: Fix STOP_TRANSMISSION (CMD12) response hw/sd: ssi-sd: Fix SEND_IF_COND (CMD8) response hw/sd: ssi-sd: Support multiple block write hw/sd: ssi-sd: Support single block write hw/sd: Introduce receive_ready() callback hw/sd: sd: Allow single/multiple block write for SPI mode hw/sd: sd: Remove duplicated codes in single/multiple block read/write hw/sd: ssi-sd: Support multiple block read Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
a528b8c4c6
|
@ -1754,6 +1754,7 @@ F: hw/ssi/xilinx_*
|
||||||
|
|
||||||
SD (Secure Card)
|
SD (Secure Card)
|
||||||
M: Philippe Mathieu-Daudé <f4bug@amsat.org>
|
M: Philippe Mathieu-Daudé <f4bug@amsat.org>
|
||||||
|
M: Bin Meng <bin.meng@windriver.com>
|
||||||
L: qemu-block@nongnu.org
|
L: qemu-block@nongnu.org
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: include/hw/sd/sd*
|
F: include/hw/sd/sd*
|
||||||
|
|
13
hw/sd/core.c
13
hw/sd/core.c
|
@ -160,6 +160,19 @@ void sdbus_read_data(SDBus *sdbus, void *buf, size_t length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sdbus_receive_ready(SDBus *sdbus)
|
||||||
|
{
|
||||||
|
SDState *card = get_card(sdbus);
|
||||||
|
|
||||||
|
if (card) {
|
||||||
|
SDCardClass *sc = SD_CARD_GET_CLASS(card);
|
||||||
|
|
||||||
|
return sc->receive_ready(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool sdbus_data_ready(SDBus *sdbus)
|
bool sdbus_data_ready(SDBus *sdbus)
|
||||||
{
|
{
|
||||||
SDState *card = get_card(sdbus);
|
SDState *card = get_card(sdbus);
|
||||||
|
|
149
hw/sd/sd.c
149
hw/sd/sd.c
|
@ -739,11 +739,33 @@ void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert)
|
||||||
qemu_set_irq(insert, sd->blk ? blk_is_inserted(sd->blk) : 0);
|
qemu_set_irq(insert, sd->blk ? blk_is_inserted(sd->blk) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sd_blk_read(SDState *sd, uint64_t addr, uint32_t len)
|
||||||
|
{
|
||||||
|
trace_sdcard_read_block(addr, len);
|
||||||
|
if (!sd->blk || blk_pread(sd->blk, addr, sd->data, len) < 0) {
|
||||||
|
fprintf(stderr, "sd_blk_read: read error on host side\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len)
|
||||||
|
{
|
||||||
|
trace_sdcard_write_block(addr, len);
|
||||||
|
if (!sd->blk || blk_pwrite(sd->blk, addr, sd->data, len, 0) < 0) {
|
||||||
|
fprintf(stderr, "sd_blk_write: write error on host side\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BLK_READ_BLOCK(a, len) sd_blk_read(sd, a, len)
|
||||||
|
#define BLK_WRITE_BLOCK(a, len) sd_blk_write(sd, a, len)
|
||||||
|
#define APP_READ_BLOCK(a, len) memset(sd->data, 0xec, len)
|
||||||
|
#define APP_WRITE_BLOCK(a, len)
|
||||||
|
|
||||||
static void sd_erase(SDState *sd)
|
static void sd_erase(SDState *sd)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
uint64_t erase_start = sd->erase_start;
|
uint64_t erase_start = sd->erase_start;
|
||||||
uint64_t erase_end = sd->erase_end;
|
uint64_t erase_end = sd->erase_end;
|
||||||
|
bool sdsc = true;
|
||||||
|
|
||||||
trace_sdcard_erase(sd->erase_start, sd->erase_end);
|
trace_sdcard_erase(sd->erase_start, sd->erase_end);
|
||||||
if (sd->erase_start == INVALID_ADDRESS
|
if (sd->erase_start == INVALID_ADDRESS
|
||||||
|
@ -758,25 +780,30 @@ static void sd_erase(SDState *sd)
|
||||||
/* High capacity memory card: erase units are 512 byte blocks */
|
/* High capacity memory card: erase units are 512 byte blocks */
|
||||||
erase_start *= 512;
|
erase_start *= 512;
|
||||||
erase_end *= 512;
|
erase_end *= 512;
|
||||||
|
sdsc = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sd->erase_start > sd->size || sd->erase_end > sd->size) {
|
if (erase_start > sd->size || erase_end > sd->size) {
|
||||||
sd->card_status |= OUT_OF_RANGE;
|
sd->card_status |= OUT_OF_RANGE;
|
||||||
sd->erase_start = INVALID_ADDRESS;
|
sd->erase_start = INVALID_ADDRESS;
|
||||||
sd->erase_end = INVALID_ADDRESS;
|
sd->erase_end = INVALID_ADDRESS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
erase_start = sd_addr_to_wpnum(erase_start);
|
|
||||||
erase_end = sd_addr_to_wpnum(erase_end);
|
|
||||||
sd->erase_start = INVALID_ADDRESS;
|
sd->erase_start = INVALID_ADDRESS;
|
||||||
sd->erase_end = INVALID_ADDRESS;
|
sd->erase_end = INVALID_ADDRESS;
|
||||||
sd->csd[14] |= 0x40;
|
sd->csd[14] |= 0x40;
|
||||||
|
|
||||||
for (i = erase_start; i <= erase_end; i++) {
|
/* Only SDSC cards support write protect groups */
|
||||||
assert(i < sd->wpgrps_size);
|
if (sdsc) {
|
||||||
if (test_bit(i, sd->wp_groups)) {
|
erase_start = sd_addr_to_wpnum(erase_start);
|
||||||
sd->card_status |= WP_ERASE_SKIP;
|
erase_end = sd_addr_to_wpnum(erase_end);
|
||||||
|
|
||||||
|
for (i = erase_start; i <= erase_end; i++) {
|
||||||
|
assert(i < sd->wpgrps_size);
|
||||||
|
if (test_bit(i, sd->wp_groups)) {
|
||||||
|
sd->card_status |= WP_ERASE_SKIP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1136,8 +1163,9 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
case 13: /* CMD13: SEND_STATUS */
|
case 13: /* CMD13: SEND_STATUS */
|
||||||
switch (sd->mode) {
|
switch (sd->mode) {
|
||||||
case sd_data_transfer_mode:
|
case sd_data_transfer_mode:
|
||||||
if (sd->rca != rca)
|
if (!sd->spi && sd->rca != rca) {
|
||||||
return sd_r0;
|
return sd_r0;
|
||||||
|
}
|
||||||
|
|
||||||
return sd_r1;
|
return sd_r1;
|
||||||
|
|
||||||
|
@ -1181,24 +1209,6 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 17: /* CMD17: READ_SINGLE_BLOCK */
|
case 17: /* CMD17: READ_SINGLE_BLOCK */
|
||||||
switch (sd->state) {
|
|
||||||
case sd_transfer_state:
|
|
||||||
|
|
||||||
if (addr + sd->blk_len > sd->size) {
|
|
||||||
sd->card_status |= ADDRESS_ERROR;
|
|
||||||
return sd_r1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sd->state = sd_sendingdata_state;
|
|
||||||
sd->data_start = addr;
|
|
||||||
sd->data_offset = 0;
|
|
||||||
return sd_r1;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 18: /* CMD18: READ_MULTIPLE_BLOCK */
|
case 18: /* CMD18: READ_MULTIPLE_BLOCK */
|
||||||
switch (sd->state) {
|
switch (sd->state) {
|
||||||
case sd_transfer_state:
|
case sd_transfer_state:
|
||||||
|
@ -1245,41 +1255,9 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
|
|
||||||
/* Block write commands (Class 4) */
|
/* Block write commands (Class 4) */
|
||||||
case 24: /* CMD24: WRITE_SINGLE_BLOCK */
|
case 24: /* CMD24: WRITE_SINGLE_BLOCK */
|
||||||
switch (sd->state) {
|
|
||||||
case sd_transfer_state:
|
|
||||||
/* Writing in SPI mode not implemented. */
|
|
||||||
if (sd->spi)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (addr + sd->blk_len > sd->size) {
|
|
||||||
sd->card_status |= ADDRESS_ERROR;
|
|
||||||
return sd_r1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sd->state = sd_receivingdata_state;
|
|
||||||
sd->data_start = addr;
|
|
||||||
sd->data_offset = 0;
|
|
||||||
sd->blk_written = 0;
|
|
||||||
|
|
||||||
if (sd_wp_addr(sd, sd->data_start)) {
|
|
||||||
sd->card_status |= WP_VIOLATION;
|
|
||||||
}
|
|
||||||
if (sd->csd[14] & 0x30) {
|
|
||||||
sd->card_status |= WP_VIOLATION;
|
|
||||||
}
|
|
||||||
return sd_r1;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */
|
case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */
|
||||||
switch (sd->state) {
|
switch (sd->state) {
|
||||||
case sd_transfer_state:
|
case sd_transfer_state:
|
||||||
/* Writing in SPI mode not implemented. */
|
|
||||||
if (sd->spi)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (addr + sd->blk_len > sd->size) {
|
if (addr + sd->blk_len > sd->size) {
|
||||||
sd->card_status |= ADDRESS_ERROR;
|
sd->card_status |= ADDRESS_ERROR;
|
||||||
|
@ -1291,8 +1269,10 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
sd->data_offset = 0;
|
sd->data_offset = 0;
|
||||||
sd->blk_written = 0;
|
sd->blk_written = 0;
|
||||||
|
|
||||||
if (sd_wp_addr(sd, sd->data_start)) {
|
if (sd->size <= SDSC_MAX_CAPACITY) {
|
||||||
sd->card_status |= WP_VIOLATION;
|
if (sd_wp_addr(sd, sd->data_start)) {
|
||||||
|
sd->card_status |= WP_VIOLATION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (sd->csd[14] & 0x30) {
|
if (sd->csd[14] & 0x30) {
|
||||||
sd->card_status |= WP_VIOLATION;
|
sd->card_status |= WP_VIOLATION;
|
||||||
|
@ -1334,6 +1314,10 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
|
|
||||||
/* Write protection (Class 6) */
|
/* Write protection (Class 6) */
|
||||||
case 28: /* CMD28: SET_WRITE_PROT */
|
case 28: /* CMD28: SET_WRITE_PROT */
|
||||||
|
if (sd->size > SDSC_MAX_CAPACITY) {
|
||||||
|
return sd_illegal;
|
||||||
|
}
|
||||||
|
|
||||||
switch (sd->state) {
|
switch (sd->state) {
|
||||||
case sd_transfer_state:
|
case sd_transfer_state:
|
||||||
if (addr >= sd->size) {
|
if (addr >= sd->size) {
|
||||||
|
@ -1353,6 +1337,10 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 29: /* CMD29: CLR_WRITE_PROT */
|
case 29: /* CMD29: CLR_WRITE_PROT */
|
||||||
|
if (sd->size > SDSC_MAX_CAPACITY) {
|
||||||
|
return sd_illegal;
|
||||||
|
}
|
||||||
|
|
||||||
switch (sd->state) {
|
switch (sd->state) {
|
||||||
case sd_transfer_state:
|
case sd_transfer_state:
|
||||||
if (addr >= sd->size) {
|
if (addr >= sd->size) {
|
||||||
|
@ -1372,13 +1360,17 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 30: /* CMD30: SEND_WRITE_PROT */
|
case 30: /* CMD30: SEND_WRITE_PROT */
|
||||||
|
if (sd->size > SDSC_MAX_CAPACITY) {
|
||||||
|
return sd_illegal;
|
||||||
|
}
|
||||||
|
|
||||||
switch (sd->state) {
|
switch (sd->state) {
|
||||||
case sd_transfer_state:
|
case sd_transfer_state:
|
||||||
sd->state = sd_sendingdata_state;
|
sd->state = sd_sendingdata_state;
|
||||||
*(uint32_t *) sd->data = sd_wpbits(sd, req.arg);
|
*(uint32_t *) sd->data = sd_wpbits(sd, req.arg);
|
||||||
sd->data_start = addr;
|
sd->data_start = addr;
|
||||||
sd->data_offset = 0;
|
sd->data_offset = 0;
|
||||||
return sd_r1b;
|
return sd_r1;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -1792,27 +1784,6 @@ send_response:
|
||||||
return rsplen;
|
return rsplen;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sd_blk_read(SDState *sd, uint64_t addr, uint32_t len)
|
|
||||||
{
|
|
||||||
trace_sdcard_read_block(addr, len);
|
|
||||||
if (!sd->blk || blk_pread(sd->blk, addr, sd->data, len) < 0) {
|
|
||||||
fprintf(stderr, "sd_blk_read: read error on host side\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len)
|
|
||||||
{
|
|
||||||
trace_sdcard_write_block(addr, len);
|
|
||||||
if (!sd->blk || blk_pwrite(sd->blk, addr, sd->data, len, 0) < 0) {
|
|
||||||
fprintf(stderr, "sd_blk_write: write error on host side\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BLK_READ_BLOCK(a, len) sd_blk_read(sd, a, len)
|
|
||||||
#define BLK_WRITE_BLOCK(a, len) sd_blk_write(sd, a, len)
|
|
||||||
#define APP_READ_BLOCK(a, len) memset(sd->data, 0xec, len)
|
|
||||||
#define APP_WRITE_BLOCK(a, len)
|
|
||||||
|
|
||||||
void sd_write_byte(SDState *sd, uint8_t value)
|
void sd_write_byte(SDState *sd, uint8_t value)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -1853,9 +1824,11 @@ void sd_write_byte(SDState *sd, uint8_t value)
|
||||||
sd->card_status |= ADDRESS_ERROR;
|
sd->card_status |= ADDRESS_ERROR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (sd_wp_addr(sd, sd->data_start)) {
|
if (sd->size <= SDSC_MAX_CAPACITY) {
|
||||||
sd->card_status |= WP_VIOLATION;
|
if (sd_wp_addr(sd, sd->data_start)) {
|
||||||
break;
|
sd->card_status |= WP_VIOLATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sd->data[sd->data_offset++] = value;
|
sd->data[sd->data_offset++] = value;
|
||||||
|
@ -2087,6 +2060,11 @@ uint8_t sd_read_byte(SDState *sd)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool sd_receive_ready(SDState *sd)
|
||||||
|
{
|
||||||
|
return sd->state == sd_receivingdata_state;
|
||||||
|
}
|
||||||
|
|
||||||
static bool sd_data_ready(SDState *sd)
|
static bool sd_data_ready(SDState *sd)
|
||||||
{
|
{
|
||||||
return sd->state == sd_sendingdata_state;
|
return sd->state == sd_sendingdata_state;
|
||||||
|
@ -2197,6 +2175,7 @@ static void sd_class_init(ObjectClass *klass, void *data)
|
||||||
sc->do_command = sd_do_command;
|
sc->do_command = sd_do_command;
|
||||||
sc->write_byte = sd_write_byte;
|
sc->write_byte = sd_write_byte;
|
||||||
sc->read_byte = sd_read_byte;
|
sc->read_byte = sd_read_byte;
|
||||||
|
sc->receive_ready = sd_receive_ready;
|
||||||
sc->data_ready = sd_data_ready;
|
sc->data_ready = sd_data_ready;
|
||||||
sc->enable = sd_enable;
|
sc->enable = sd_enable;
|
||||||
sc->get_inserted = sd_get_inserted;
|
sc->get_inserted = sd_get_inserted;
|
||||||
|
|
|
@ -596,9 +596,9 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
|
||||||
page_aligned = true;
|
page_aligned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
|
||||||
if (s->trnmod & SDHC_TRNS_READ) {
|
if (s->trnmod & SDHC_TRNS_READ) {
|
||||||
s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
|
s->prnsts |= SDHC_DOING_READ;
|
||||||
SDHC_DAT_LINE_ACTIVE;
|
|
||||||
while (s->blkcnt) {
|
while (s->blkcnt) {
|
||||||
if (s->data_count == 0) {
|
if (s->data_count == 0) {
|
||||||
sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
|
sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
|
||||||
|
@ -625,8 +625,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
|
s->prnsts |= SDHC_DOING_WRITE;
|
||||||
SDHC_DAT_LINE_ACTIVE;
|
|
||||||
while (s->blkcnt) {
|
while (s->blkcnt) {
|
||||||
begin = s->data_count;
|
begin = s->data_count;
|
||||||
if (((boundary_count + begin) < block_size) && page_aligned) {
|
if (((boundary_count + begin) < block_size) && page_aligned) {
|
||||||
|
|
136
hw/sd/ssi-sd.c
136
hw/sd/ssi-sd.c
|
@ -4,6 +4,11 @@
|
||||||
* Copyright (c) 2007-2009 CodeSourcery.
|
* Copyright (c) 2007-2009 CodeSourcery.
|
||||||
* Written by Paul Brook
|
* Written by Paul Brook
|
||||||
*
|
*
|
||||||
|
* Copyright (c) 2021 Wind River Systems, Inc.
|
||||||
|
* Improved by Bin Meng <bin.meng@windriver.com>
|
||||||
|
*
|
||||||
|
* Validated with U-Boot v2021.01 and Linux v5.10 mmc_spi driver
|
||||||
|
*
|
||||||
* This code is licensed under the GNU GPL v2.
|
* This code is licensed under the GNU GPL v2.
|
||||||
*
|
*
|
||||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||||
|
@ -43,6 +48,8 @@ typedef enum {
|
||||||
SSI_SD_DATA_START,
|
SSI_SD_DATA_START,
|
||||||
SSI_SD_DATA_READ,
|
SSI_SD_DATA_READ,
|
||||||
SSI_SD_DATA_CRC16,
|
SSI_SD_DATA_CRC16,
|
||||||
|
SSI_SD_DATA_WRITE,
|
||||||
|
SSI_SD_SKIP_CRC16,
|
||||||
} ssi_sd_mode;
|
} ssi_sd_mode;
|
||||||
|
|
||||||
struct ssi_sd_state {
|
struct ssi_sd_state {
|
||||||
|
@ -52,6 +59,8 @@ struct ssi_sd_state {
|
||||||
uint8_t cmdarg[4];
|
uint8_t cmdarg[4];
|
||||||
uint8_t response[5];
|
uint8_t response[5];
|
||||||
uint16_t crc16;
|
uint16_t crc16;
|
||||||
|
int32_t read_bytes;
|
||||||
|
int32_t write_bytes;
|
||||||
int32_t arglen;
|
int32_t arglen;
|
||||||
int32_t response_pos;
|
int32_t response_pos;
|
||||||
int32_t stopping;
|
int32_t stopping;
|
||||||
|
@ -78,37 +87,86 @@ OBJECT_DECLARE_SIMPLE_TYPE(ssi_sd_state, SSI_SD)
|
||||||
#define SSI_SDR_ADDRESS_ERROR 0x2000
|
#define SSI_SDR_ADDRESS_ERROR 0x2000
|
||||||
#define SSI_SDR_PARAMETER_ERROR 0x4000
|
#define SSI_SDR_PARAMETER_ERROR 0x4000
|
||||||
|
|
||||||
|
/* multiple block write */
|
||||||
|
#define SSI_TOKEN_MULTI_WRITE 0xfc
|
||||||
|
/* terminate multiple block write */
|
||||||
|
#define SSI_TOKEN_STOP_TRAN 0xfd
|
||||||
/* single block read/write, multiple block read */
|
/* single block read/write, multiple block read */
|
||||||
#define SSI_TOKEN_SINGLE 0xfe
|
#define SSI_TOKEN_SINGLE 0xfe
|
||||||
|
|
||||||
/* dummy value - don't care */
|
/* dummy value - don't care */
|
||||||
#define SSI_DUMMY 0xff
|
#define SSI_DUMMY 0xff
|
||||||
|
|
||||||
|
/* data accepted */
|
||||||
|
#define DATA_RESPONSE_ACCEPTED 0x05
|
||||||
|
|
||||||
static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
{
|
{
|
||||||
ssi_sd_state *s = SSI_SD(dev);
|
ssi_sd_state *s = SSI_SD(dev);
|
||||||
|
SDRequest request;
|
||||||
|
uint8_t longresp[16];
|
||||||
|
|
||||||
/* Special case: allow CMD12 (STOP TRANSMISSION) while reading data. */
|
/*
|
||||||
if (s->mode == SSI_SD_DATA_READ && val == 0x4c) {
|
* Special case: allow CMD12 (STOP TRANSMISSION) while reading data.
|
||||||
s->mode = SSI_SD_CMD;
|
*
|
||||||
/* There must be at least one byte delay before the card responds. */
|
* See "Physical Layer Specification Version 8.00" chapter 7.5.2.2,
|
||||||
s->stopping = 1;
|
* to avoid conflict between CMD12 response and next data block,
|
||||||
|
* timing of CMD12 should be controlled as follows:
|
||||||
|
*
|
||||||
|
* - CMD12 issued at the timing that end bit of CMD12 and end bit of
|
||||||
|
* data block is overlapped
|
||||||
|
* - CMD12 issued after one clock cycle after host receives a token
|
||||||
|
* (either Start Block token or Data Error token)
|
||||||
|
*
|
||||||
|
* We need to catch CMD12 in all of the data read states.
|
||||||
|
*/
|
||||||
|
if (s->mode >= SSI_SD_PREP_DATA && s->mode <= SSI_SD_DATA_CRC16) {
|
||||||
|
if (val == 0x4c) {
|
||||||
|
s->mode = SSI_SD_CMD;
|
||||||
|
/* There must be at least one byte delay before the card responds */
|
||||||
|
s->stopping = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (s->mode) {
|
switch (s->mode) {
|
||||||
case SSI_SD_CMD:
|
case SSI_SD_CMD:
|
||||||
if (val == SSI_DUMMY) {
|
switch (val) {
|
||||||
|
case SSI_DUMMY:
|
||||||
DPRINTF("NULL command\n");
|
DPRINTF("NULL command\n");
|
||||||
return SSI_DUMMY;
|
return SSI_DUMMY;
|
||||||
|
break;
|
||||||
|
case SSI_TOKEN_SINGLE:
|
||||||
|
case SSI_TOKEN_MULTI_WRITE:
|
||||||
|
DPRINTF("Start write block\n");
|
||||||
|
s->mode = SSI_SD_DATA_WRITE;
|
||||||
|
return SSI_DUMMY;
|
||||||
|
case SSI_TOKEN_STOP_TRAN:
|
||||||
|
DPRINTF("Stop multiple write\n");
|
||||||
|
|
||||||
|
/* manually issue cmd12 to stop the transfer */
|
||||||
|
request.cmd = 12;
|
||||||
|
request.arg = 0;
|
||||||
|
s->arglen = sdbus_do_command(&s->sdbus, &request, longresp);
|
||||||
|
if (s->arglen <= 0) {
|
||||||
|
s->arglen = 1;
|
||||||
|
/* a zero value indicates the card is busy */
|
||||||
|
s->response[0] = 0;
|
||||||
|
DPRINTF("SD card busy\n");
|
||||||
|
} else {
|
||||||
|
s->arglen = 1;
|
||||||
|
/* a non-zero value indicates the card is ready */
|
||||||
|
s->response[0] = SSI_DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SSI_DUMMY;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->cmd = val & 0x3f;
|
s->cmd = val & 0x3f;
|
||||||
s->mode = SSI_SD_CMDARG;
|
s->mode = SSI_SD_CMDARG;
|
||||||
s->arglen = 0;
|
s->arglen = 0;
|
||||||
return SSI_DUMMY;
|
return SSI_DUMMY;
|
||||||
case SSI_SD_CMDARG:
|
case SSI_SD_CMDARG:
|
||||||
if (s->arglen == 4) {
|
if (s->arglen == 4) {
|
||||||
SDRequest request;
|
|
||||||
uint8_t longresp[16];
|
|
||||||
/* FIXME: Check CRC. */
|
/* FIXME: Check CRC. */
|
||||||
request.cmd = s->cmd;
|
request.cmd = s->cmd;
|
||||||
request.arg = ldl_be_p(s->cmdarg);
|
request.arg = ldl_be_p(s->cmdarg);
|
||||||
|
@ -118,9 +176,9 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
s->arglen = 1;
|
s->arglen = 1;
|
||||||
s->response[0] = 4;
|
s->response[0] = 4;
|
||||||
DPRINTF("SD command failed\n");
|
DPRINTF("SD command failed\n");
|
||||||
} else if (s->cmd == 58) {
|
} else if (s->cmd == 8 || s->cmd == 58) {
|
||||||
/* CMD58 returns R3 response (OCR) */
|
/* CMD8/CMD58 returns R3/R7 response */
|
||||||
DPRINTF("Returned OCR\n");
|
DPRINTF("Returned R3/R7\n");
|
||||||
s->arglen = 5;
|
s->arglen = 5;
|
||||||
s->response[0] = 1;
|
s->response[0] = 1;
|
||||||
memcpy(&s->response[1], longresp, 4);
|
memcpy(&s->response[1], longresp, 4);
|
||||||
|
@ -136,6 +194,12 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
/* CMD13 returns a 2-byte statuse work. Other commands
|
/* CMD13 returns a 2-byte statuse work. Other commands
|
||||||
only return the first byte. */
|
only return the first byte. */
|
||||||
s->arglen = (s->cmd == 13) ? 2 : 1;
|
s->arglen = (s->cmd == 13) ? 2 : 1;
|
||||||
|
|
||||||
|
/* handle R1b */
|
||||||
|
if (s->cmd == 28 || s->cmd == 29 || s->cmd == 38) {
|
||||||
|
s->stopping = 1;
|
||||||
|
}
|
||||||
|
|
||||||
cardstatus = ldl_be_p(longresp);
|
cardstatus = ldl_be_p(longresp);
|
||||||
status = 0;
|
status = 0;
|
||||||
if (((cardstatus >> 9) & 0xf) < 4)
|
if (((cardstatus >> 9) & 0xf) < 4)
|
||||||
|
@ -185,14 +249,15 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
s->mode = SSI_SD_RESPONSE;
|
s->mode = SSI_SD_RESPONSE;
|
||||||
return SSI_DUMMY;
|
return SSI_DUMMY;
|
||||||
case SSI_SD_RESPONSE:
|
case SSI_SD_RESPONSE:
|
||||||
if (s->stopping) {
|
|
||||||
s->stopping = 0;
|
|
||||||
return SSI_DUMMY;
|
|
||||||
}
|
|
||||||
if (s->response_pos < s->arglen) {
|
if (s->response_pos < s->arglen) {
|
||||||
DPRINTF("Response 0x%02x\n", s->response[s->response_pos]);
|
DPRINTF("Response 0x%02x\n", s->response[s->response_pos]);
|
||||||
return s->response[s->response_pos++];
|
return s->response[s->response_pos++];
|
||||||
}
|
}
|
||||||
|
if (s->stopping) {
|
||||||
|
s->stopping = 0;
|
||||||
|
s->mode = SSI_SD_CMD;
|
||||||
|
return SSI_DUMMY;
|
||||||
|
}
|
||||||
if (sdbus_data_ready(&s->sdbus)) {
|
if (sdbus_data_ready(&s->sdbus)) {
|
||||||
DPRINTF("Data read\n");
|
DPRINTF("Data read\n");
|
||||||
s->mode = SSI_SD_DATA_START;
|
s->mode = SSI_SD_DATA_START;
|
||||||
|
@ -212,8 +277,9 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
return SSI_TOKEN_SINGLE;
|
return SSI_TOKEN_SINGLE;
|
||||||
case SSI_SD_DATA_READ:
|
case SSI_SD_DATA_READ:
|
||||||
val = sdbus_read_byte(&s->sdbus);
|
val = sdbus_read_byte(&s->sdbus);
|
||||||
|
s->read_bytes++;
|
||||||
s->crc16 = crc_ccitt_false(s->crc16, (uint8_t *)&val, 1);
|
s->crc16 = crc_ccitt_false(s->crc16, (uint8_t *)&val, 1);
|
||||||
if (!sdbus_data_ready(&s->sdbus)) {
|
if (!sdbus_data_ready(&s->sdbus) || s->read_bytes == 512) {
|
||||||
DPRINTF("Data read end\n");
|
DPRINTF("Data read end\n");
|
||||||
s->mode = SSI_SD_DATA_CRC16;
|
s->mode = SSI_SD_DATA_CRC16;
|
||||||
}
|
}
|
||||||
|
@ -224,10 +290,36 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val)
|
||||||
s->response_pos++;
|
s->response_pos++;
|
||||||
if (s->response_pos == 2) {
|
if (s->response_pos == 2) {
|
||||||
DPRINTF("CRC16 read end\n");
|
DPRINTF("CRC16 read end\n");
|
||||||
s->mode = SSI_SD_CMD;
|
if (s->read_bytes == 512 && s->cmd != 17) {
|
||||||
|
s->mode = SSI_SD_PREP_DATA;
|
||||||
|
} else {
|
||||||
|
s->mode = SSI_SD_CMD;
|
||||||
|
}
|
||||||
|
s->read_bytes = 0;
|
||||||
s->response_pos = 0;
|
s->response_pos = 0;
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
|
case SSI_SD_DATA_WRITE:
|
||||||
|
sdbus_write_byte(&s->sdbus, val);
|
||||||
|
s->write_bytes++;
|
||||||
|
if (!sdbus_receive_ready(&s->sdbus) || s->write_bytes == 512) {
|
||||||
|
DPRINTF("Data write end\n");
|
||||||
|
s->mode = SSI_SD_SKIP_CRC16;
|
||||||
|
s->response_pos = 0;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
case SSI_SD_SKIP_CRC16:
|
||||||
|
/* we don't verify the crc16 */
|
||||||
|
s->response_pos++;
|
||||||
|
if (s->response_pos == 2) {
|
||||||
|
DPRINTF("CRC16 receive end\n");
|
||||||
|
s->mode = SSI_SD_RESPONSE;
|
||||||
|
s->write_bytes = 0;
|
||||||
|
s->arglen = 1;
|
||||||
|
s->response[0] = DATA_RESPONSE_ACCEPTED;
|
||||||
|
s->response_pos = 0;
|
||||||
|
}
|
||||||
|
return SSI_DUMMY;
|
||||||
}
|
}
|
||||||
/* Should never happen. */
|
/* Should never happen. */
|
||||||
return SSI_DUMMY;
|
return SSI_DUMMY;
|
||||||
|
@ -237,7 +329,7 @@ static int ssi_sd_post_load(void *opaque, int version_id)
|
||||||
{
|
{
|
||||||
ssi_sd_state *s = (ssi_sd_state *)opaque;
|
ssi_sd_state *s = (ssi_sd_state *)opaque;
|
||||||
|
|
||||||
if (s->mode > SSI_SD_DATA_CRC16) {
|
if (s->mode > SSI_SD_SKIP_CRC16) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
if (s->mode == SSI_SD_CMDARG &&
|
if (s->mode == SSI_SD_CMDARG &&
|
||||||
|
@ -255,8 +347,8 @@ static int ssi_sd_post_load(void *opaque, int version_id)
|
||||||
|
|
||||||
static const VMStateDescription vmstate_ssi_sd = {
|
static const VMStateDescription vmstate_ssi_sd = {
|
||||||
.name = "ssi_sd",
|
.name = "ssi_sd",
|
||||||
.version_id = 5,
|
.version_id = 7,
|
||||||
.minimum_version_id = 5,
|
.minimum_version_id = 7,
|
||||||
.post_load = ssi_sd_post_load,
|
.post_load = ssi_sd_post_load,
|
||||||
.fields = (VMStateField []) {
|
.fields = (VMStateField []) {
|
||||||
VMSTATE_UINT32(mode, ssi_sd_state),
|
VMSTATE_UINT32(mode, ssi_sd_state),
|
||||||
|
@ -264,6 +356,8 @@ static const VMStateDescription vmstate_ssi_sd = {
|
||||||
VMSTATE_UINT8_ARRAY(cmdarg, ssi_sd_state, 4),
|
VMSTATE_UINT8_ARRAY(cmdarg, ssi_sd_state, 4),
|
||||||
VMSTATE_UINT8_ARRAY(response, ssi_sd_state, 5),
|
VMSTATE_UINT8_ARRAY(response, ssi_sd_state, 5),
|
||||||
VMSTATE_UINT16(crc16, ssi_sd_state),
|
VMSTATE_UINT16(crc16, ssi_sd_state),
|
||||||
|
VMSTATE_INT32(read_bytes, ssi_sd_state),
|
||||||
|
VMSTATE_INT32(write_bytes, ssi_sd_state),
|
||||||
VMSTATE_INT32(arglen, ssi_sd_state),
|
VMSTATE_INT32(arglen, ssi_sd_state),
|
||||||
VMSTATE_INT32(response_pos, ssi_sd_state),
|
VMSTATE_INT32(response_pos, ssi_sd_state),
|
||||||
VMSTATE_INT32(stopping, ssi_sd_state),
|
VMSTATE_INT32(stopping, ssi_sd_state),
|
||||||
|
@ -316,6 +410,8 @@ static void ssi_sd_reset(DeviceState *dev)
|
||||||
memset(s->cmdarg, 0, sizeof(s->cmdarg));
|
memset(s->cmdarg, 0, sizeof(s->cmdarg));
|
||||||
memset(s->response, 0, sizeof(s->response));
|
memset(s->response, 0, sizeof(s->response));
|
||||||
s->crc16 = 0;
|
s->crc16 = 0;
|
||||||
|
s->read_bytes = 0;
|
||||||
|
s->write_bytes = 0;
|
||||||
s->arglen = 0;
|
s->arglen = 0;
|
||||||
s->response_pos = 0;
|
s->response_pos = 0;
|
||||||
s->stopping = 0;
|
s->stopping = 0;
|
||||||
|
|
|
@ -116,6 +116,7 @@ struct SDCardClass {
|
||||||
* Return: byte value read
|
* Return: byte value read
|
||||||
*/
|
*/
|
||||||
uint8_t (*read_byte)(SDState *sd);
|
uint8_t (*read_byte)(SDState *sd);
|
||||||
|
bool (*receive_ready)(SDState *sd);
|
||||||
bool (*data_ready)(SDState *sd);
|
bool (*data_ready)(SDState *sd);
|
||||||
void (*set_voltage)(SDState *sd, uint16_t millivolts);
|
void (*set_voltage)(SDState *sd, uint16_t millivolts);
|
||||||
uint8_t (*get_dat_lines)(SDState *sd);
|
uint8_t (*get_dat_lines)(SDState *sd);
|
||||||
|
@ -187,6 +188,7 @@ void sdbus_write_data(SDBus *sdbus, const void *buf, size_t length);
|
||||||
* Read multiple bytes of data on the data lines of a SD bus.
|
* Read multiple bytes of data on the data lines of a SD bus.
|
||||||
*/
|
*/
|
||||||
void sdbus_read_data(SDBus *sdbus, void *buf, size_t length);
|
void sdbus_read_data(SDBus *sdbus, void *buf, size_t length);
|
||||||
|
bool sdbus_receive_ready(SDBus *sd);
|
||||||
bool sdbus_data_ready(SDBus *sd);
|
bool sdbus_data_ready(SDBus *sd);
|
||||||
bool sdbus_get_inserted(SDBus *sd);
|
bool sdbus_get_inserted(SDBus *sd);
|
||||||
bool sdbus_get_readonly(SDBus *sd);
|
bool sdbus_get_readonly(SDBus *sd);
|
||||||
|
|
Loading…
Reference in New Issue