| /* | 
 |  * SSI to SD card adapter. | 
 |  * | 
 |  * Copyright (c) 2007-2009 CodeSourcery. | 
 |  * 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. | 
 |  * | 
 |  * Contributions after 2012-01-13 are licensed under the terms of the | 
 |  * GNU GPL, version 2 or (at your option) any later version. | 
 |  */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "sysemu/blockdev.h" | 
 | #include "hw/ssi/ssi.h" | 
 | #include "migration/vmstate.h" | 
 | #include "hw/qdev-properties.h" | 
 | #include "hw/sd/sd.h" | 
 | #include "qapi/error.h" | 
 | #include "qemu/crc-ccitt.h" | 
 | #include "qemu/module.h" | 
 | #include "qom/object.h" | 
 |  | 
 | //#define DEBUG_SSI_SD 1 | 
 |  | 
 | #ifdef DEBUG_SSI_SD | 
 | #define DPRINTF(fmt, ...) \ | 
 | do { printf("ssi_sd: " fmt , ## __VA_ARGS__); } while (0) | 
 | #define BADF(fmt, ...) \ | 
 | do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) | 
 | #else | 
 | #define DPRINTF(fmt, ...) do {} while(0) | 
 | #define BADF(fmt, ...) \ | 
 | do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__);} while (0) | 
 | #endif | 
 |  | 
 | typedef enum { | 
 |     SSI_SD_CMD = 0, | 
 |     SSI_SD_CMDARG, | 
 |     SSI_SD_PREP_RESP, | 
 |     SSI_SD_RESPONSE, | 
 |     SSI_SD_PREP_DATA, | 
 |     SSI_SD_DATA_START, | 
 |     SSI_SD_DATA_READ, | 
 |     SSI_SD_DATA_CRC16, | 
 |     SSI_SD_DATA_WRITE, | 
 |     SSI_SD_SKIP_CRC16, | 
 | } ssi_sd_mode; | 
 |  | 
 | struct ssi_sd_state { | 
 |     SSIPeripheral ssidev; | 
 |     uint32_t mode; | 
 |     int cmd; | 
 |     uint8_t cmdarg[4]; | 
 |     uint8_t response[5]; | 
 |     uint16_t crc16; | 
 |     int32_t read_bytes; | 
 |     int32_t write_bytes; | 
 |     int32_t arglen; | 
 |     int32_t response_pos; | 
 |     int32_t stopping; | 
 |     SDBus sdbus; | 
 | }; | 
 |  | 
 | #define TYPE_SSI_SD "ssi-sd" | 
 | OBJECT_DECLARE_SIMPLE_TYPE(ssi_sd_state, SSI_SD) | 
 |  | 
 | /* State word bits.  */ | 
 | #define SSI_SDR_LOCKED          0x0001 | 
 | #define SSI_SDR_WP_ERASE        0x0002 | 
 | #define SSI_SDR_ERROR           0x0004 | 
 | #define SSI_SDR_CC_ERROR        0x0008 | 
 | #define SSI_SDR_ECC_FAILED      0x0010 | 
 | #define SSI_SDR_WP_VIOLATION    0x0020 | 
 | #define SSI_SDR_ERASE_PARAM     0x0040 | 
 | #define SSI_SDR_OUT_OF_RANGE    0x0080 | 
 | #define SSI_SDR_IDLE            0x0100 | 
 | #define SSI_SDR_ERASE_RESET     0x0200 | 
 | #define SSI_SDR_ILLEGAL_COMMAND 0x0400 | 
 | #define SSI_SDR_COM_CRC_ERROR   0x0800 | 
 | #define SSI_SDR_ERASE_SEQ_ERROR 0x1000 | 
 | #define SSI_SDR_ADDRESS_ERROR   0x2000 | 
 | #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 */ | 
 | #define SSI_TOKEN_SINGLE        0xfe | 
 |  | 
 | /* dummy value - don't care */ | 
 | #define SSI_DUMMY               0xff | 
 |  | 
 | /* data accepted */ | 
 | #define DATA_RESPONSE_ACCEPTED  0x05 | 
 |  | 
 | static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val) | 
 | { | 
 |     ssi_sd_state *s = SSI_SD(dev); | 
 |     SDRequest request; | 
 |     uint8_t longresp[16]; | 
 |  | 
 |     /* | 
 |      * Special case: allow CMD12 (STOP TRANSMISSION) while reading data. | 
 |      * | 
 |      * See "Physical Layer Specification Version 8.00" chapter 7.5.2.2, | 
 |      * 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) { | 
 |     case SSI_SD_CMD: | 
 |         switch (val) { | 
 |         case SSI_DUMMY: | 
 |             DPRINTF("NULL command\n"); | 
 |             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->mode = SSI_SD_CMDARG; | 
 |         s->arglen = 0; | 
 |         return SSI_DUMMY; | 
 |     case SSI_SD_CMDARG: | 
 |         if (s->arglen == 4) { | 
 |             /* FIXME: Check CRC.  */ | 
 |             request.cmd = s->cmd; | 
 |             request.arg = ldl_be_p(s->cmdarg); | 
 |             DPRINTF("CMD%d arg 0x%08x\n", s->cmd, request.arg); | 
 |             s->arglen = sdbus_do_command(&s->sdbus, &request, longresp); | 
 |             if (s->arglen <= 0) { | 
 |                 s->arglen = 1; | 
 |                 s->response[0] = 4; | 
 |                 DPRINTF("SD command failed\n"); | 
 |             } else if (s->cmd == 8 || s->cmd == 58) { | 
 |                 /* CMD8/CMD58 returns R3/R7 response */ | 
 |                 DPRINTF("Returned R3/R7\n"); | 
 |                 s->arglen = 5; | 
 |                 s->response[0] = 1; | 
 |                 memcpy(&s->response[1], longresp, 4); | 
 |             } else if (s->arglen != 4) { | 
 |                 BADF("Unexpected response to cmd %d\n", s->cmd); | 
 |                 /* Illegal command is about as near as we can get.  */ | 
 |                 s->arglen = 1; | 
 |                 s->response[0] = 4; | 
 |             } else { | 
 |                 /* All other commands return status.  */ | 
 |                 uint32_t cardstatus; | 
 |                 uint16_t status; | 
 |                 /* CMD13 returns a 2-byte statuse work. Other commands | 
 |                    only return the first byte.  */ | 
 |                 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); | 
 |                 status = 0; | 
 |                 if (((cardstatus >> 9) & 0xf) < 4) | 
 |                     status |= SSI_SDR_IDLE; | 
 |                 if (cardstatus & ERASE_RESET) | 
 |                     status |= SSI_SDR_ERASE_RESET; | 
 |                 if (cardstatus & ILLEGAL_COMMAND) | 
 |                     status |= SSI_SDR_ILLEGAL_COMMAND; | 
 |                 if (cardstatus & COM_CRC_ERROR) | 
 |                     status |= SSI_SDR_COM_CRC_ERROR; | 
 |                 if (cardstatus & ERASE_SEQ_ERROR) | 
 |                     status |= SSI_SDR_ERASE_SEQ_ERROR; | 
 |                 if (cardstatus & ADDRESS_ERROR) | 
 |                     status |= SSI_SDR_ADDRESS_ERROR; | 
 |                 if (cardstatus & CARD_IS_LOCKED) | 
 |                     status |= SSI_SDR_LOCKED; | 
 |                 if (cardstatus & (LOCK_UNLOCK_FAILED | WP_ERASE_SKIP)) | 
 |                     status |= SSI_SDR_WP_ERASE; | 
 |                 if (cardstatus & SD_ERROR) | 
 |                     status |= SSI_SDR_ERROR; | 
 |                 if (cardstatus & CC_ERROR) | 
 |                     status |= SSI_SDR_CC_ERROR; | 
 |                 if (cardstatus & CARD_ECC_FAILED) | 
 |                     status |= SSI_SDR_ECC_FAILED; | 
 |                 if (cardstatus & WP_VIOLATION) | 
 |                     status |= SSI_SDR_WP_VIOLATION; | 
 |                 if (cardstatus & ERASE_PARAM) | 
 |                     status |= SSI_SDR_ERASE_PARAM; | 
 |                 if (cardstatus & (OUT_OF_RANGE | CID_CSD_OVERWRITE)) | 
 |                     status |= SSI_SDR_OUT_OF_RANGE; | 
 |                 /* ??? Don't know what Parameter Error really means, so | 
 |                    assume it's set if the second byte is nonzero.  */ | 
 |                 if (status & 0xff) | 
 |                     status |= SSI_SDR_PARAMETER_ERROR; | 
 |                 s->response[0] = status >> 8; | 
 |                 s->response[1] = status; | 
 |                 DPRINTF("Card status 0x%02x\n", status); | 
 |             } | 
 |             s->mode = SSI_SD_PREP_RESP; | 
 |             s->response_pos = 0; | 
 |         } else { | 
 |             s->cmdarg[s->arglen++] = val; | 
 |         } | 
 |         return SSI_DUMMY; | 
 |     case SSI_SD_PREP_RESP: | 
 |         DPRINTF("Prepare card response (Ncr)\n"); | 
 |         s->mode = SSI_SD_RESPONSE; | 
 |         return SSI_DUMMY; | 
 |     case SSI_SD_RESPONSE: | 
 |         if (s->response_pos < s->arglen) { | 
 |             DPRINTF("Response 0x%02x\n", 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)) { | 
 |             DPRINTF("Data read\n"); | 
 |             s->mode = SSI_SD_DATA_START; | 
 |         } else { | 
 |             DPRINTF("End of command\n"); | 
 |             s->mode = SSI_SD_CMD; | 
 |         } | 
 |         return SSI_DUMMY; | 
 |     case SSI_SD_PREP_DATA: | 
 |         DPRINTF("Prepare data block (Nac)\n"); | 
 |         s->mode = SSI_SD_DATA_START; | 
 |         return SSI_DUMMY; | 
 |     case SSI_SD_DATA_START: | 
 |         DPRINTF("Start read block\n"); | 
 |         s->mode = SSI_SD_DATA_READ; | 
 |         s->response_pos = 0; | 
 |         return SSI_TOKEN_SINGLE; | 
 |     case SSI_SD_DATA_READ: | 
 |         val = sdbus_read_byte(&s->sdbus); | 
 |         s->read_bytes++; | 
 |         s->crc16 = crc_ccitt_false(s->crc16, (uint8_t *)&val, 1); | 
 |         if (!sdbus_data_ready(&s->sdbus) || s->read_bytes == 512) { | 
 |             DPRINTF("Data read end\n"); | 
 |             s->mode = SSI_SD_DATA_CRC16; | 
 |         } | 
 |         return val; | 
 |     case SSI_SD_DATA_CRC16: | 
 |         val = (s->crc16 & 0xff00) >> 8; | 
 |         s->crc16 <<= 8; | 
 |         s->response_pos++; | 
 |         if (s->response_pos == 2) { | 
 |             DPRINTF("CRC16 read end\n"); | 
 |             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; | 
 |         } | 
 |         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.  */ | 
 |     return SSI_DUMMY; | 
 | } | 
 |  | 
 | static int ssi_sd_post_load(void *opaque, int version_id) | 
 | { | 
 |     ssi_sd_state *s = (ssi_sd_state *)opaque; | 
 |  | 
 |     if (s->mode > SSI_SD_SKIP_CRC16) { | 
 |         return -EINVAL; | 
 |     } | 
 |     if (s->mode == SSI_SD_CMDARG && | 
 |         (s->arglen < 0 || s->arglen >= ARRAY_SIZE(s->cmdarg))) { | 
 |         return -EINVAL; | 
 |     } | 
 |     if (s->mode == SSI_SD_RESPONSE && | 
 |         (s->response_pos < 0 || s->response_pos >= ARRAY_SIZE(s->response) || | 
 |         (!s->stopping && s->arglen > ARRAY_SIZE(s->response)))) { | 
 |         return -EINVAL; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static const VMStateDescription vmstate_ssi_sd = { | 
 |     .name = "ssi_sd", | 
 |     .version_id = 7, | 
 |     .minimum_version_id = 7, | 
 |     .post_load = ssi_sd_post_load, | 
 |     .fields = (VMStateField []) { | 
 |         VMSTATE_UINT32(mode, ssi_sd_state), | 
 |         VMSTATE_INT32(cmd, ssi_sd_state), | 
 |         VMSTATE_UINT8_ARRAY(cmdarg, ssi_sd_state, 4), | 
 |         VMSTATE_UINT8_ARRAY(response, ssi_sd_state, 5), | 
 |         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(response_pos, ssi_sd_state), | 
 |         VMSTATE_INT32(stopping, ssi_sd_state), | 
 |         VMSTATE_SSI_PERIPHERAL(ssidev, ssi_sd_state), | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | static void ssi_sd_realize(SSIPeripheral *d, Error **errp) | 
 | { | 
 |     ssi_sd_state *s = SSI_SD(d); | 
 |  | 
 |     qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SD_BUS, DEVICE(d), "sd-bus"); | 
 | } | 
 |  | 
 | static void ssi_sd_reset(DeviceState *dev) | 
 | { | 
 |     ssi_sd_state *s = SSI_SD(dev); | 
 |  | 
 |     s->mode = SSI_SD_CMD; | 
 |     s->cmd = 0; | 
 |     memset(s->cmdarg, 0, sizeof(s->cmdarg)); | 
 |     memset(s->response, 0, sizeof(s->response)); | 
 |     s->crc16 = 0; | 
 |     s->read_bytes = 0; | 
 |     s->write_bytes = 0; | 
 |     s->arglen = 0; | 
 |     s->response_pos = 0; | 
 |     s->stopping = 0; | 
 | } | 
 |  | 
 | static void ssi_sd_class_init(ObjectClass *klass, void *data) | 
 | { | 
 |     DeviceClass *dc = DEVICE_CLASS(klass); | 
 |     SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); | 
 |  | 
 |     k->realize = ssi_sd_realize; | 
 |     k->transfer = ssi_sd_transfer; | 
 |     k->cs_polarity = SSI_CS_LOW; | 
 |     dc->vmsd = &vmstate_ssi_sd; | 
 |     dc->reset = ssi_sd_reset; | 
 |     /* Reason: GPIO chip-select line should be wired up */ | 
 |     dc->user_creatable = false; | 
 | } | 
 |  | 
 | static const TypeInfo ssi_sd_info = { | 
 |     .name          = TYPE_SSI_SD, | 
 |     .parent        = TYPE_SSI_PERIPHERAL, | 
 |     .instance_size = sizeof(ssi_sd_state), | 
 |     .class_init    = ssi_sd_class_init, | 
 | }; | 
 |  | 
 | static void ssi_sd_register_types(void) | 
 | { | 
 |     type_register_static(&ssi_sd_info); | 
 | } | 
 |  | 
 | type_init(ssi_sd_register_types) |