]>
Commit | Line | Data |
---|---|---|
775616c3 PB |
1 | /* |
2 | * SSI to SD card adapter. | |
3 | * | |
5493e33f | 4 | * Copyright (c) 2007-2009 CodeSourcery. |
775616c3 PB |
5 | * Written by Paul Brook |
6 | * | |
d56f3efa BM |
7 | * Copyright (c) 2021 Wind River Systems, Inc. |
8 | * Improved by Bin Meng <bin.meng@windriver.com> | |
9 | * | |
10 | * Validated with U-Boot v2021.01 and Linux v5.10 mmc_spi driver | |
11 | * | |
8e31bf38 | 12 | * This code is licensed under the GNU GPL v2. |
6b620ca3 PB |
13 | * |
14 | * Contributions after 2012-01-13 are licensed under the terms of the | |
15 | * GNU GPL, version 2 or (at your option) any later version. | |
775616c3 PB |
16 | */ |
17 | ||
0430891c | 18 | #include "qemu/osdep.h" |
9c17d615 | 19 | #include "sysemu/blockdev.h" |
8fd06719 | 20 | #include "hw/ssi/ssi.h" |
d6454270 | 21 | #include "migration/vmstate.h" |
a27bd6c7 | 22 | #include "hw/qdev-properties.h" |
e3382ef0 | 23 | #include "hw/sd/sd.h" |
7673bb4c | 24 | #include "qapi/error.h" |
2d174cc3 | 25 | #include "qemu/crc-ccitt.h" |
0b8fa32f | 26 | #include "qemu/module.h" |
db1015e9 | 27 | #include "qom/object.h" |
775616c3 PB |
28 | |
29 | //#define DEBUG_SSI_SD 1 | |
30 | ||
31 | #ifdef DEBUG_SSI_SD | |
001faf32 BS |
32 | #define DPRINTF(fmt, ...) \ |
33 | do { printf("ssi_sd: " fmt , ## __VA_ARGS__); } while (0) | |
34 | #define BADF(fmt, ...) \ | |
35 | do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) | |
775616c3 | 36 | #else |
001faf32 BS |
37 | #define DPRINTF(fmt, ...) do {} while(0) |
38 | #define BADF(fmt, ...) \ | |
39 | do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__);} while (0) | |
775616c3 PB |
40 | #endif |
41 | ||
42 | typedef enum { | |
2ccfd336 | 43 | SSI_SD_CMD = 0, |
775616c3 | 44 | SSI_SD_CMDARG, |
281c5c95 | 45 | SSI_SD_PREP_RESP, |
775616c3 | 46 | SSI_SD_RESPONSE, |
3a67cbe6 | 47 | SSI_SD_PREP_DATA, |
775616c3 PB |
48 | SSI_SD_DATA_START, |
49 | SSI_SD_DATA_READ, | |
2d174cc3 | 50 | SSI_SD_DATA_CRC16, |
5020e3cb BM |
51 | SSI_SD_DATA_WRITE, |
52 | SSI_SD_SKIP_CRC16, | |
775616c3 PB |
53 | } ssi_sd_mode; |
54 | ||
db1015e9 | 55 | struct ssi_sd_state { |
ec7e429b | 56 | SSIPeripheral ssidev; |
2ccfd336 | 57 | uint32_t mode; |
775616c3 PB |
58 | int cmd; |
59 | uint8_t cmdarg[4]; | |
60 | uint8_t response[5]; | |
2d174cc3 | 61 | uint16_t crc16; |
1365d863 | 62 | int32_t read_bytes; |
5020e3cb | 63 | int32_t write_bytes; |
2ccfd336 DDAG |
64 | int32_t arglen; |
65 | int32_t response_pos; | |
66 | int32_t stopping; | |
c3abd913 | 67 | SDBus sdbus; |
db1015e9 | 68 | }; |
775616c3 | 69 | |
8046d44f | 70 | #define TYPE_SSI_SD "ssi-sd" |
8063396b | 71 | OBJECT_DECLARE_SIMPLE_TYPE(ssi_sd_state, SSI_SD) |
8046d44f | 72 | |
775616c3 PB |
73 | /* State word bits. */ |
74 | #define SSI_SDR_LOCKED 0x0001 | |
75 | #define SSI_SDR_WP_ERASE 0x0002 | |
76 | #define SSI_SDR_ERROR 0x0004 | |
77 | #define SSI_SDR_CC_ERROR 0x0008 | |
78 | #define SSI_SDR_ECC_FAILED 0x0010 | |
79 | #define SSI_SDR_WP_VIOLATION 0x0020 | |
80 | #define SSI_SDR_ERASE_PARAM 0x0040 | |
81 | #define SSI_SDR_OUT_OF_RANGE 0x0080 | |
82 | #define SSI_SDR_IDLE 0x0100 | |
83 | #define SSI_SDR_ERASE_RESET 0x0200 | |
84 | #define SSI_SDR_ILLEGAL_COMMAND 0x0400 | |
85 | #define SSI_SDR_COM_CRC_ERROR 0x0800 | |
86 | #define SSI_SDR_ERASE_SEQ_ERROR 0x1000 | |
87 | #define SSI_SDR_ADDRESS_ERROR 0x2000 | |
88 | #define SSI_SDR_PARAMETER_ERROR 0x4000 | |
89 | ||
d56f3efa BM |
90 | /* multiple block write */ |
91 | #define SSI_TOKEN_MULTI_WRITE 0xfc | |
92 | /* terminate multiple block write */ | |
93 | #define SSI_TOKEN_STOP_TRAN 0xfd | |
bc1edaf2 BM |
94 | /* single block read/write, multiple block read */ |
95 | #define SSI_TOKEN_SINGLE 0xfe | |
96 | ||
97 | /* dummy value - don't care */ | |
98 | #define SSI_DUMMY 0xff | |
99 | ||
5020e3cb BM |
100 | /* data accepted */ |
101 | #define DATA_RESPONSE_ACCEPTED 0x05 | |
102 | ||
ec7e429b | 103 | static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val) |
775616c3 | 104 | { |
213f63df | 105 | ssi_sd_state *s = SSI_SD(dev); |
d56f3efa BM |
106 | SDRequest request; |
107 | uint8_t longresp[16]; | |
775616c3 | 108 | |
1365d863 BM |
109 | /* |
110 | * Special case: allow CMD12 (STOP TRANSMISSION) while reading data. | |
111 | * | |
112 | * See "Physical Layer Specification Version 8.00" chapter 7.5.2.2, | |
113 | * to avoid conflict between CMD12 response and next data block, | |
114 | * timing of CMD12 should be controlled as follows: | |
115 | * | |
116 | * - CMD12 issued at the timing that end bit of CMD12 and end bit of | |
117 | * data block is overlapped | |
118 | * - CMD12 issued after one clock cycle after host receives a token | |
119 | * (either Start Block token or Data Error token) | |
120 | * | |
121 | * We need to catch CMD12 in all of the data read states. | |
122 | */ | |
123 | if (s->mode >= SSI_SD_PREP_DATA && s->mode <= SSI_SD_DATA_CRC16) { | |
124 | if (val == 0x4c) { | |
125 | s->mode = SSI_SD_CMD; | |
126 | /* There must be at least one byte delay before the card responds */ | |
127 | s->stopping = 1; | |
128 | } | |
775616c3 PB |
129 | } |
130 | ||
131 | switch (s->mode) { | |
132 | case SSI_SD_CMD: | |
5020e3cb BM |
133 | switch (val) { |
134 | case SSI_DUMMY: | |
775616c3 | 135 | DPRINTF("NULL command\n"); |
bc1edaf2 | 136 | return SSI_DUMMY; |
5020e3cb BM |
137 | break; |
138 | case SSI_TOKEN_SINGLE: | |
d56f3efa | 139 | case SSI_TOKEN_MULTI_WRITE: |
5020e3cb BM |
140 | DPRINTF("Start write block\n"); |
141 | s->mode = SSI_SD_DATA_WRITE; | |
d56f3efa BM |
142 | return SSI_DUMMY; |
143 | case SSI_TOKEN_STOP_TRAN: | |
144 | DPRINTF("Stop multiple write\n"); | |
145 | ||
146 | /* manually issue cmd12 to stop the transfer */ | |
147 | request.cmd = 12; | |
148 | request.arg = 0; | |
149 | s->arglen = sdbus_do_command(&s->sdbus, &request, longresp); | |
150 | if (s->arglen <= 0) { | |
151 | s->arglen = 1; | |
152 | /* a zero value indicates the card is busy */ | |
153 | s->response[0] = 0; | |
154 | DPRINTF("SD card busy\n"); | |
155 | } else { | |
156 | s->arglen = 1; | |
157 | /* a non-zero value indicates the card is ready */ | |
158 | s->response[0] = SSI_DUMMY; | |
159 | } | |
160 | ||
5020e3cb | 161 | return SSI_DUMMY; |
775616c3 | 162 | } |
5020e3cb | 163 | |
775616c3 PB |
164 | s->cmd = val & 0x3f; |
165 | s->mode = SSI_SD_CMDARG; | |
166 | s->arglen = 0; | |
bc1edaf2 | 167 | return SSI_DUMMY; |
775616c3 PB |
168 | case SSI_SD_CMDARG: |
169 | if (s->arglen == 4) { | |
775616c3 PB |
170 | /* FIXME: Check CRC. */ |
171 | request.cmd = s->cmd; | |
b3141c06 | 172 | request.arg = ldl_be_p(s->cmdarg); |
775616c3 | 173 | DPRINTF("CMD%d arg 0x%08x\n", s->cmd, request.arg); |
c3abd913 | 174 | s->arglen = sdbus_do_command(&s->sdbus, &request, longresp); |
775616c3 PB |
175 | if (s->arglen <= 0) { |
176 | s->arglen = 1; | |
177 | s->response[0] = 4; | |
178 | DPRINTF("SD command failed\n"); | |
6ae29af3 BM |
179 | } else if (s->cmd == 8 || s->cmd == 58) { |
180 | /* CMD8/CMD58 returns R3/R7 response */ | |
181 | DPRINTF("Returned R3/R7\n"); | |
775616c3 PB |
182 | s->arglen = 5; |
183 | s->response[0] = 1; | |
184 | memcpy(&s->response[1], longresp, 4); | |
185 | } else if (s->arglen != 4) { | |
186 | BADF("Unexpected response to cmd %d\n", s->cmd); | |
187 | /* Illegal command is about as near as we can get. */ | |
188 | s->arglen = 1; | |
189 | s->response[0] = 4; | |
190 | } else { | |
191 | /* All other commands return status. */ | |
192 | uint32_t cardstatus; | |
193 | uint16_t status; | |
194 | /* CMD13 returns a 2-byte statuse work. Other commands | |
195 | only return the first byte. */ | |
196 | s->arglen = (s->cmd == 13) ? 2 : 1; | |
17674695 BM |
197 | |
198 | /* handle R1b */ | |
199 | if (s->cmd == 28 || s->cmd == 29 || s->cmd == 38) { | |
200 | s->stopping = 1; | |
201 | } | |
202 | ||
b3141c06 | 203 | cardstatus = ldl_be_p(longresp); |
775616c3 PB |
204 | status = 0; |
205 | if (((cardstatus >> 9) & 0xf) < 4) | |
206 | status |= SSI_SDR_IDLE; | |
207 | if (cardstatus & ERASE_RESET) | |
208 | status |= SSI_SDR_ERASE_RESET; | |
209 | if (cardstatus & ILLEGAL_COMMAND) | |
210 | status |= SSI_SDR_ILLEGAL_COMMAND; | |
211 | if (cardstatus & COM_CRC_ERROR) | |
212 | status |= SSI_SDR_COM_CRC_ERROR; | |
213 | if (cardstatus & ERASE_SEQ_ERROR) | |
214 | status |= SSI_SDR_ERASE_SEQ_ERROR; | |
215 | if (cardstatus & ADDRESS_ERROR) | |
216 | status |= SSI_SDR_ADDRESS_ERROR; | |
217 | if (cardstatus & CARD_IS_LOCKED) | |
218 | status |= SSI_SDR_LOCKED; | |
219 | if (cardstatus & (LOCK_UNLOCK_FAILED | WP_ERASE_SKIP)) | |
220 | status |= SSI_SDR_WP_ERASE; | |
221 | if (cardstatus & SD_ERROR) | |
222 | status |= SSI_SDR_ERROR; | |
223 | if (cardstatus & CC_ERROR) | |
224 | status |= SSI_SDR_CC_ERROR; | |
225 | if (cardstatus & CARD_ECC_FAILED) | |
226 | status |= SSI_SDR_ECC_FAILED; | |
227 | if (cardstatus & WP_VIOLATION) | |
228 | status |= SSI_SDR_WP_VIOLATION; | |
229 | if (cardstatus & ERASE_PARAM) | |
230 | status |= SSI_SDR_ERASE_PARAM; | |
231 | if (cardstatus & (OUT_OF_RANGE | CID_CSD_OVERWRITE)) | |
232 | status |= SSI_SDR_OUT_OF_RANGE; | |
233 | /* ??? Don't know what Parameter Error really means, so | |
234 | assume it's set if the second byte is nonzero. */ | |
235 | if (status & 0xff) | |
236 | status |= SSI_SDR_PARAMETER_ERROR; | |
237 | s->response[0] = status >> 8; | |
238 | s->response[1] = status; | |
239 | DPRINTF("Card status 0x%02x\n", status); | |
240 | } | |
281c5c95 | 241 | s->mode = SSI_SD_PREP_RESP; |
775616c3 PB |
242 | s->response_pos = 0; |
243 | } else { | |
244 | s->cmdarg[s->arglen++] = val; | |
245 | } | |
bc1edaf2 | 246 | return SSI_DUMMY; |
281c5c95 BM |
247 | case SSI_SD_PREP_RESP: |
248 | DPRINTF("Prepare card response (Ncr)\n"); | |
249 | s->mode = SSI_SD_RESPONSE; | |
bc1edaf2 | 250 | return SSI_DUMMY; |
775616c3 | 251 | case SSI_SD_RESPONSE: |
775616c3 PB |
252 | if (s->response_pos < s->arglen) { |
253 | DPRINTF("Response 0x%02x\n", s->response[s->response_pos]); | |
254 | return s->response[s->response_pos++]; | |
255 | } | |
5b45a366 BM |
256 | if (s->stopping) { |
257 | s->stopping = 0; | |
258 | s->mode = SSI_SD_CMD; | |
259 | return SSI_DUMMY; | |
260 | } | |
c3abd913 | 261 | if (sdbus_data_ready(&s->sdbus)) { |
775616c3 PB |
262 | DPRINTF("Data read\n"); |
263 | s->mode = SSI_SD_DATA_START; | |
264 | } else { | |
265 | DPRINTF("End of command\n"); | |
266 | s->mode = SSI_SD_CMD; | |
267 | } | |
bc1edaf2 | 268 | return SSI_DUMMY; |
3a67cbe6 BM |
269 | case SSI_SD_PREP_DATA: |
270 | DPRINTF("Prepare data block (Nac)\n"); | |
271 | s->mode = SSI_SD_DATA_START; | |
bc1edaf2 | 272 | return SSI_DUMMY; |
775616c3 PB |
273 | case SSI_SD_DATA_START: |
274 | DPRINTF("Start read block\n"); | |
275 | s->mode = SSI_SD_DATA_READ; | |
2d174cc3 | 276 | s->response_pos = 0; |
bc1edaf2 | 277 | return SSI_TOKEN_SINGLE; |
775616c3 | 278 | case SSI_SD_DATA_READ: |
8467f622 | 279 | val = sdbus_read_byte(&s->sdbus); |
1365d863 | 280 | s->read_bytes++; |
2d174cc3 | 281 | s->crc16 = crc_ccitt_false(s->crc16, (uint8_t *)&val, 1); |
1365d863 | 282 | if (!sdbus_data_ready(&s->sdbus) || s->read_bytes == 512) { |
775616c3 | 283 | DPRINTF("Data read end\n"); |
2d174cc3 BM |
284 | s->mode = SSI_SD_DATA_CRC16; |
285 | } | |
286 | return val; | |
287 | case SSI_SD_DATA_CRC16: | |
288 | val = (s->crc16 & 0xff00) >> 8; | |
289 | s->crc16 <<= 8; | |
290 | s->response_pos++; | |
291 | if (s->response_pos == 2) { | |
292 | DPRINTF("CRC16 read end\n"); | |
1365d863 BM |
293 | if (s->read_bytes == 512 && s->cmd != 17) { |
294 | s->mode = SSI_SD_PREP_DATA; | |
295 | } else { | |
296 | s->mode = SSI_SD_CMD; | |
297 | } | |
298 | s->read_bytes = 0; | |
2d174cc3 | 299 | s->response_pos = 0; |
775616c3 PB |
300 | } |
301 | return val; | |
5020e3cb BM |
302 | case SSI_SD_DATA_WRITE: |
303 | sdbus_write_byte(&s->sdbus, val); | |
304 | s->write_bytes++; | |
305 | if (!sdbus_receive_ready(&s->sdbus) || s->write_bytes == 512) { | |
306 | DPRINTF("Data write end\n"); | |
307 | s->mode = SSI_SD_SKIP_CRC16; | |
308 | s->response_pos = 0; | |
309 | } | |
310 | return val; | |
311 | case SSI_SD_SKIP_CRC16: | |
312 | /* we don't verify the crc16 */ | |
313 | s->response_pos++; | |
314 | if (s->response_pos == 2) { | |
315 | DPRINTF("CRC16 receive end\n"); | |
316 | s->mode = SSI_SD_RESPONSE; | |
317 | s->write_bytes = 0; | |
318 | s->arglen = 1; | |
319 | s->response[0] = DATA_RESPONSE_ACCEPTED; | |
320 | s->response_pos = 0; | |
321 | } | |
322 | return SSI_DUMMY; | |
775616c3 PB |
323 | } |
324 | /* Should never happen. */ | |
bc1edaf2 | 325 | return SSI_DUMMY; |
775616c3 PB |
326 | } |
327 | ||
2ccfd336 | 328 | static int ssi_sd_post_load(void *opaque, int version_id) |
23e39294 PB |
329 | { |
330 | ssi_sd_state *s = (ssi_sd_state *)opaque; | |
23e39294 | 331 | |
5020e3cb | 332 | if (s->mode > SSI_SD_SKIP_CRC16) { |
23e39294 | 333 | return -EINVAL; |
2ccfd336 | 334 | } |
a9c380db MT |
335 | if (s->mode == SSI_SD_CMDARG && |
336 | (s->arglen < 0 || s->arglen >= ARRAY_SIZE(s->cmdarg))) { | |
337 | return -EINVAL; | |
338 | } | |
a9c380db MT |
339 | if (s->mode == SSI_SD_RESPONSE && |
340 | (s->response_pos < 0 || s->response_pos >= ARRAY_SIZE(s->response) || | |
341 | (!s->stopping && s->arglen > ARRAY_SIZE(s->response)))) { | |
342 | return -EINVAL; | |
343 | } | |
23e39294 PB |
344 | |
345 | return 0; | |
346 | } | |
347 | ||
2ccfd336 DDAG |
348 | static const VMStateDescription vmstate_ssi_sd = { |
349 | .name = "ssi_sd", | |
5020e3cb BM |
350 | .version_id = 7, |
351 | .minimum_version_id = 7, | |
2ccfd336 | 352 | .post_load = ssi_sd_post_load, |
307119ba | 353 | .fields = (const VMStateField []) { |
2ccfd336 DDAG |
354 | VMSTATE_UINT32(mode, ssi_sd_state), |
355 | VMSTATE_INT32(cmd, ssi_sd_state), | |
356 | VMSTATE_UINT8_ARRAY(cmdarg, ssi_sd_state, 4), | |
357 | VMSTATE_UINT8_ARRAY(response, ssi_sd_state, 5), | |
2d174cc3 | 358 | VMSTATE_UINT16(crc16, ssi_sd_state), |
1365d863 | 359 | VMSTATE_INT32(read_bytes, ssi_sd_state), |
5020e3cb | 360 | VMSTATE_INT32(write_bytes, ssi_sd_state), |
2ccfd336 DDAG |
361 | VMSTATE_INT32(arglen, ssi_sd_state), |
362 | VMSTATE_INT32(response_pos, ssi_sd_state), | |
363 | VMSTATE_INT32(stopping, ssi_sd_state), | |
ec7e429b | 364 | VMSTATE_SSI_PERIPHERAL(ssidev, ssi_sd_state), |
2ccfd336 DDAG |
365 | VMSTATE_END_OF_LIST() |
366 | } | |
367 | }; | |
368 | ||
ec7e429b | 369 | static void ssi_sd_realize(SSIPeripheral *d, Error **errp) |
775616c3 | 370 | { |
213f63df | 371 | ssi_sd_state *s = SSI_SD(d); |
775616c3 | 372 | |
d637e1dc | 373 | qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SD_BUS, DEVICE(d), "sd-bus"); |
775616c3 | 374 | } |
5493e33f | 375 | |
8046d44f PM |
376 | static void ssi_sd_reset(DeviceState *dev) |
377 | { | |
378 | ssi_sd_state *s = SSI_SD(dev); | |
379 | ||
380 | s->mode = SSI_SD_CMD; | |
381 | s->cmd = 0; | |
382 | memset(s->cmdarg, 0, sizeof(s->cmdarg)); | |
383 | memset(s->response, 0, sizeof(s->response)); | |
2d174cc3 | 384 | s->crc16 = 0; |
1365d863 | 385 | s->read_bytes = 0; |
5020e3cb | 386 | s->write_bytes = 0; |
8046d44f PM |
387 | s->arglen = 0; |
388 | s->response_pos = 0; | |
389 | s->stopping = 0; | |
8046d44f PM |
390 | } |
391 | ||
cd6c4cf2 AL |
392 | static void ssi_sd_class_init(ObjectClass *klass, void *data) |
393 | { | |
2ccfd336 | 394 | DeviceClass *dc = DEVICE_CLASS(klass); |
ec7e429b | 395 | SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); |
cd6c4cf2 | 396 | |
7673bb4c | 397 | k->realize = ssi_sd_realize; |
cd6c4cf2 | 398 | k->transfer = ssi_sd_transfer; |
8120e714 | 399 | k->cs_polarity = SSI_CS_LOW; |
2ccfd336 | 400 | dc->vmsd = &vmstate_ssi_sd; |
8046d44f | 401 | dc->reset = ssi_sd_reset; |
36aa285f | 402 | /* Reason: GPIO chip-select line should be wired up */ |
61e9e3cb | 403 | dc->user_creatable = false; |
cd6c4cf2 AL |
404 | } |
405 | ||
88d2198c PMD |
406 | static const TypeInfo ssi_sd_types[] = { |
407 | { | |
408 | .name = TYPE_SSI_SD, | |
409 | .parent = TYPE_SSI_PERIPHERAL, | |
410 | .instance_size = sizeof(ssi_sd_state), | |
411 | .class_init = ssi_sd_class_init, | |
412 | }, | |
5493e33f PB |
413 | }; |
414 | ||
88d2198c | 415 | DEFINE_TYPES(ssi_sd_types) |