Use DMA for SCSI and SD card transfers for a massive performance boost.
[SCSI2SD-V6.git] / software / SCSI2SD / src / disk.c
1 //      Copyright (C) 2013 Michael McMaster <michael@codesrc.com>\r
2 //      Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>\r
3 //\r
4 //      This file is part of SCSI2SD.\r
5 //\r
6 //      SCSI2SD is free software: you can redistribute it and/or modify\r
7 //      it under the terms of the GNU General Public License as published by\r
8 //      the Free Software Foundation, either version 3 of the License, or\r
9 //      (at your option) any later version.\r
10 //\r
11 //      SCSI2SD is distributed in the hope that it will be useful,\r
12 //      but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 //      GNU General Public License for more details.\r
15 //\r
16 //      You should have received a copy of the GNU General Public License\r
17 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.\r
18 \r
19 #include "device.h"\r
20 #include "scsi.h"\r
21 #include "scsiPhy.h"\r
22 #include "config.h"\r
23 #include "disk.h"\r
24 #include "sd.h"\r
25 \r
26 #include <string.h>\r
27 \r
28 // Global\r
29 BlockDevice blockDev;\r
30 Transfer transfer;\r
31 \r
32 static int doSdInit()\r
33 {\r
34         int result = sdInit();\r
35         if (result)\r
36         {\r
37                 blockDev.state = blockDev.state | DISK_INITIALISED;\r
38         }\r
39         return result;\r
40 }\r
41 \r
42 // Callback once all data has been read in the data out phase.\r
43 static void doFormatUnitComplete(void)\r
44 {\r
45         // TODO start writing the initialisation pattern to the SD\r
46         // card\r
47         scsiDev.phase = STATUS;\r
48 }\r
49 \r
50 static void doFormatUnitSkipData(int bytes)\r
51 {\r
52         // We may not have enough memory to store the initialisation pattern and\r
53         // defect list data.  Since we're not making use of it yet anyway, just\r
54         // discard the bytes.\r
55         scsiEnterPhase(DATA_OUT);\r
56         int i;\r
57         for (i = 0; i < bytes; ++i)\r
58         {\r
59                 scsiReadByte(); \r
60         }\r
61 }\r
62 \r
63 // Callback from the data out phase.\r
64 static void doFormatUnitPatternHeader(void)\r
65 {\r
66         int defectLength =\r
67                 ((((uint16_t)scsiDev.data[2])) << 8) +\r
68                         scsiDev.data[3];\r
69 \r
70         int patternLength =\r
71                 ((((uint16_t)scsiDev.data[4 + 2])) << 8) +\r
72                 scsiDev.data[4 + 3];\r
73 \r
74                 doFormatUnitSkipData(defectLength + patternLength);\r
75                 doFormatUnitComplete();\r
76 }\r
77 \r
78 // Callback from the data out phase.\r
79 static void doFormatUnitHeader(void)\r
80 {\r
81         int IP = (scsiDev.data[1] & 0x08) ? 1 : 0;\r
82         int DSP = (scsiDev.data[1] & 0x04) ? 1 : 0;\r
83         \r
84         if (! DSP) // disable save parameters\r
85         {\r
86                 configSave(); // Save the "MODE SELECT savable parameters"\r
87         }\r
88         \r
89         if (IP)\r
90         {\r
91                 // We need to read the initialisation pattern header first.\r
92                 scsiDev.dataLen += 4;\r
93                 scsiDev.phase = DATA_OUT;\r
94                 scsiDev.postDataOutHook = doFormatUnitPatternHeader;\r
95         }\r
96         else\r
97         {\r
98                 // Read the defect list data\r
99                 int defectLength =\r
100                         ((((uint16_t)scsiDev.data[2])) << 8) +\r
101                         scsiDev.data[3];\r
102                 doFormatUnitSkipData(defectLength);\r
103                 doFormatUnitComplete();\r
104         }\r
105 }\r
106 \r
107 static void doReadCapacity()\r
108 {\r
109         uint32_t lba = (((uint32) scsiDev.cdb[2]) << 24) +\r
110                 (((uint32) scsiDev.cdb[3]) << 16) +\r
111                 (((uint32) scsiDev.cdb[4]) << 8) +\r
112                 scsiDev.cdb[5];\r
113         int pmi = scsiDev.cdb[8] & 1;\r
114 \r
115         uint32_t capacity = getScsiCapacity();\r
116 \r
117         if (!pmi && lba)\r
118         {\r
119                 // error.\r
120                 // We don't do anything with the "partial medium indicator", and\r
121                 // assume that delays are constant across each block. But the spec\r
122                 // says we must return this error if pmi is specified incorrectly.\r
123                 scsiDev.status = CHECK_CONDITION;\r
124                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
125                 scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
126                 scsiDev.phase = STATUS;\r
127         }\r
128         else if (capacity > 0)\r
129         {\r
130                 uint32_t highestBlock = capacity - 1;\r
131 \r
132                 scsiDev.data[0] = highestBlock >> 24;\r
133                 scsiDev.data[1] = highestBlock >> 16;\r
134                 scsiDev.data[2] = highestBlock >> 8;\r
135                 scsiDev.data[3] = highestBlock;\r
136 \r
137                 scsiDev.data[4] = config->bytesPerSector >> 24;\r
138                 scsiDev.data[5] = config->bytesPerSector >> 16;\r
139                 scsiDev.data[6] = config->bytesPerSector >> 8;\r
140                 scsiDev.data[7] = config->bytesPerSector;\r
141                 scsiDev.dataLen = 8;\r
142                 scsiDev.phase = DATA_IN;\r
143         }\r
144         else\r
145         {\r
146                 scsiDev.status = CHECK_CONDITION;\r
147                 scsiDev.sense.code = NOT_READY;\r
148                 scsiDev.sense.asc = MEDIUM_NOT_PRESENT;\r
149                 scsiDev.phase = STATUS;\r
150         }\r
151 }\r
152 \r
153 static void doWrite(uint32 lba, uint32 blocks)\r
154 {\r
155         if (blockDev.state & DISK_WP)\r
156         {\r
157                 scsiDev.status = CHECK_CONDITION;\r
158                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
159                 scsiDev.sense.asc = WRITE_PROTECTED;\r
160                 scsiDev.phase = STATUS;\r
161         }\r
162         else if (((uint64) lba) + blocks > getScsiCapacity())\r
163         {\r
164                 scsiDev.status = CHECK_CONDITION;\r
165                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
166                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
167                 scsiDev.phase = STATUS;\r
168         }\r
169         else\r
170         {\r
171                 transfer.dir = TRANSFER_WRITE;\r
172                 transfer.lba = lba;\r
173                 transfer.blocks = blocks;\r
174                 transfer.currentBlock = 0;\r
175                 scsiDev.phase = DATA_OUT;\r
176                 scsiDev.dataLen = config->bytesPerSector;\r
177                 scsiDev.dataPtr = config->bytesPerSector; // TODO FIX scsiDiskPoll()\r
178 \r
179                 // No need for single-block writes atm.  Overhead of the\r
180                 // multi-block write is minimal.\r
181                 transfer.multiBlock = 1;\r
182                 \r
183                 if (blocks > 1) scsiDev.needReconnect = 1;\r
184                 sdWriteMultiSectorPrep();\r
185         }\r
186 }\r
187 \r
188 \r
189 static void doRead(uint32 lba, uint32 blocks)\r
190 {\r
191         uint32_t capacity = getScsiCapacity();\r
192         if (((uint64) lba) + blocks > capacity)\r
193         {\r
194                 scsiDev.status = CHECK_CONDITION;\r
195                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
196                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
197                 scsiDev.phase = STATUS;\r
198         }\r
199         else\r
200         {\r
201                 transfer.dir = TRANSFER_READ;\r
202                 transfer.lba = lba;\r
203                 transfer.blocks = blocks;\r
204                 transfer.currentBlock = 0;\r
205                 scsiDev.phase = DATA_IN;\r
206                 scsiDev.dataLen = 0; // No data yet\r
207 \r
208                 if ((blocks == 1) ||\r
209                         (((uint64) lba) + blocks == capacity)\r
210                         )\r
211                 {\r
212                         // We get errors on reading the last sector using a multi-sector\r
213                         // read :-(\r
214                         transfer.multiBlock = 0;\r
215                 }\r
216                 else\r
217                 {\r
218                         transfer.multiBlock = 1;\r
219                         scsiDev.needReconnect = 1;\r
220                         sdReadMultiSectorPrep();\r
221                 }\r
222         }\r
223 }\r
224 \r
225 static void doSeek(uint32 lba)\r
226 {\r
227         if (lba >= getScsiCapacity())\r
228         {\r
229                 scsiDev.status = CHECK_CONDITION;\r
230                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
231                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
232                 scsiDev.phase = STATUS;\r
233         }\r
234 }\r
235 \r
236 static int doTestUnitReady()\r
237 {\r
238         int ready = 1;\r
239         if (!(blockDev.state & DISK_STARTED))\r
240         {\r
241                 ready = 0;\r
242                 scsiDev.status = CHECK_CONDITION;\r
243                 scsiDev.sense.code = NOT_READY;\r
244                 scsiDev.sense.asc = LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED;\r
245                 scsiDev.phase = STATUS;\r
246         }\r
247         else if (!(blockDev.state & DISK_PRESENT))\r
248         {\r
249                 ready = 0;\r
250                 scsiDev.status = CHECK_CONDITION;\r
251                 scsiDev.sense.code = NOT_READY;\r
252                 scsiDev.sense.asc = MEDIUM_NOT_PRESENT;\r
253                 scsiDev.phase = STATUS;\r
254         }\r
255         else if (!(blockDev.state & DISK_INITIALISED))\r
256         {\r
257                 ready = 0;\r
258                 scsiDev.status = CHECK_CONDITION;\r
259                 scsiDev.sense.code = NOT_READY;\r
260                 scsiDev.sense.asc = LOGICAL_UNIT_NOT_READY_CAUSE_NOT_REPORTABLE;\r
261                 scsiDev.phase = STATUS;\r
262         }\r
263         return ready;\r
264 }\r
265 \r
266 // Handle direct-access scsi device commands\r
267 int scsiDiskCommand()\r
268 {\r
269         int commandHandled = 1;\r
270 \r
271         uint8 command = scsiDev.cdb[0];\r
272         if (command == 0x1B)\r
273         {\r
274                 // START STOP UNIT\r
275                 // Enable or disable media access operations.\r
276                 // Ignore load/eject requests. We can't do that.\r
277                 //int immed = scsiDev.cdb[1] & 1;\r
278                 int start = scsiDev.cdb[4] & 1;\r
279 \r
280                 if (start)\r
281                 {\r
282                         blockDev.state = blockDev.state | DISK_STARTED;\r
283                         if (!(blockDev.state & DISK_INITIALISED))\r
284                         {\r
285                                 doSdInit();\r
286                         }\r
287                 }\r
288                 else\r
289                 {\r
290                         blockDev.state &= ~DISK_STARTED;\r
291                 }\r
292         }\r
293         else if (command == 0x00)\r
294         {\r
295                 // TEST UNIT READY\r
296                 doTestUnitReady();\r
297         }\r
298         else if (!doTestUnitReady())\r
299         {\r
300                 // Status and sense codes already set by doTestUnitReady\r
301         }\r
302         else if (command == 0x04)\r
303         {\r
304                 // FORMAT UNIT\r
305                 // We don't really do any formatting, but we need to read the correct\r
306                 // number of bytes in the DATA_OUT phase to make the SCSI host happy.\r
307                 \r
308                 int fmtData = (scsiDev.cdb[1] & 0x10) ? 1 : 0;\r
309                 if (fmtData)\r
310                 {\r
311                         // We need to read the parameter list, but we don't know how\r
312                         // big it is yet. Start with the header.\r
313                         scsiDev.dataLen = 4;\r
314                         scsiDev.phase = DATA_OUT;\r
315                         scsiDev.postDataOutHook = doFormatUnitHeader;\r
316                 }\r
317                 else\r
318                 {\r
319                         // No data to read, we're already finished!\r
320                 }\r
321         }\r
322         else if (command == 0x08)\r
323         {\r
324                 // READ(6)\r
325                 uint32 lba =\r
326                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
327                         (((uint32) scsiDev.cdb[2]) << 8) +\r
328                         scsiDev.cdb[3];\r
329                 uint32 blocks = scsiDev.cdb[4];\r
330                 if (blocks == 0) blocks = 256;\r
331                 doRead(lba, blocks);\r
332         }\r
333 \r
334         else if (command == 0x28)\r
335         {\r
336                 // READ(10)\r
337                 // Ignore all cache control bits - we don't support a memory cache.\r
338 \r
339                 uint32 lba =\r
340                         (((uint32) scsiDev.cdb[2]) << 24) +\r
341                         (((uint32) scsiDev.cdb[3]) << 16) +\r
342                         (((uint32) scsiDev.cdb[4]) << 8) +\r
343                         scsiDev.cdb[5];\r
344                 uint32 blocks =\r
345                         (((uint32) scsiDev.cdb[7]) << 8) +\r
346                         scsiDev.cdb[8];\r
347 \r
348                 doRead(lba, blocks);\r
349         }\r
350 \r
351         else if (command == 0x25)\r
352         {\r
353                 // READ CAPACITY\r
354                 doReadCapacity();\r
355         }\r
356 \r
357         else if (command == 0x0B)\r
358         {\r
359                 // SEEK(6)\r
360                 uint32 lba =\r
361                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
362                         (((uint32) scsiDev.cdb[2]) << 8) +\r
363                         scsiDev.cdb[3];\r
364 \r
365                 doSeek(lba);\r
366         }\r
367 \r
368         else if (command == 0x2B)\r
369         {\r
370                 // SEEK(10)\r
371                 uint32 lba =\r
372                         (((uint32) scsiDev.cdb[2]) << 24) +\r
373                         (((uint32) scsiDev.cdb[3]) << 16) +\r
374                         (((uint32) scsiDev.cdb[4]) << 8) +\r
375                         scsiDev.cdb[5];\r
376 \r
377                 doSeek(lba);\r
378         }\r
379         else if (command == 0x0A)\r
380         {\r
381                 // WRITE(6)\r
382                 uint32 lba =\r
383                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
384                         (((uint32) scsiDev.cdb[2]) << 8) +\r
385                         scsiDev.cdb[3];\r
386                 uint32 blocks = scsiDev.cdb[4];\r
387                 if (blocks == 0) blocks = 256;\r
388                 doWrite(lba, blocks);\r
389         }\r
390 \r
391         else if (command == 0x2A)\r
392         {\r
393                 // WRITE(10)\r
394                 // Ignore all cache control bits - we don't support a memory cache.\r
395 \r
396                 uint32 lba =\r
397                         (((uint32) scsiDev.cdb[2]) << 24) +\r
398                         (((uint32) scsiDev.cdb[3]) << 16) +\r
399                         (((uint32) scsiDev.cdb[4]) << 8) +\r
400                         scsiDev.cdb[5];\r
401                 uint32 blocks =\r
402                         (((uint32) scsiDev.cdb[7]) << 8) +\r
403                         scsiDev.cdb[8];\r
404 \r
405                 doWrite(lba, blocks);\r
406         }\r
407         else if (command == 0x36)\r
408         {\r
409                 // LOCK UNLOCK CACHE\r
410                 // We don't have a cache to lock data into. do nothing.\r
411         }\r
412         else if (command == 0x34)\r
413         {\r
414                 // PRE-FETCH.\r
415                 // We don't have a cache to pre-fetch into. do nothing.\r
416         }\r
417         else if (command == 0x1E)\r
418         {\r
419                 // PREVENT ALLOW MEDIUM REMOVAL\r
420                 // Not much we can do to prevent the user removing the SD card.\r
421                 // do nothing.\r
422         }\r
423         else if (command == 0x01)\r
424         {\r
425                 // REZERO UNIT\r
426                 // Set the lun to a vendor-specific state. Ignore.\r
427         }\r
428         else if (command == 0x35)\r
429         {\r
430                 // SYNCHRONIZE CACHE\r
431                 // We don't have a cache. do nothing.\r
432         }\r
433         else if (command == 0x2F)\r
434         {\r
435                 // VERIFY\r
436                 // TODO: When they supply data to verify, we should read the data and\r
437                 // verify it. If they don't supply any data, just say success.\r
438                 if ((scsiDev.cdb[1] & 0x02) == 0)\r
439                 {\r
440                         // They are asking us to do a medium verification with no data\r
441                         // comparison. Assume success, do nothing.\r
442                 }\r
443                 else\r
444                 {\r
445                         // TODO. This means they are supplying data to verify against.\r
446                         // Technically we should probably grab the data and compare it.\r
447                         scsiDev.status = CHECK_CONDITION;\r
448                         scsiDev.sense.code = ILLEGAL_REQUEST;\r
449                         scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
450                         scsiDev.phase = STATUS;\r
451                 }\r
452         }\r
453         else\r
454         {\r
455                 commandHandled = 0;\r
456         }\r
457 \r
458         return commandHandled;\r
459 }\r
460 \r
461 void scsiDiskPoll()\r
462 {\r
463         if (scsiDev.phase == DATA_IN &&\r
464                 transfer.currentBlock != transfer.blocks)\r
465         {\r
466                 int totalSDSectors = transfer.blocks * SDSectorsPerSCSISector();\r
467                 uint32_t sdLBA = SCSISector2SD(transfer.lba);\r
468                 int buffers = sizeof(scsiDev.data) / SD_SECTOR_SIZE;\r
469                 int prep = 0;\r
470                 int i = 0;\r
471                 int scsiActive = 0;\r
472                 int sdActive = 0;\r
473                 while ((i < totalSDSectors) &&\r
474                         (scsiDev.phase == DATA_IN) &&\r
475                         !scsiDev.resetFlag)\r
476                 {\r
477                         if ((sdActive == 1) && sdReadSectorDMAPoll())\r
478                         {\r
479                                 sdActive = 0;\r
480                                 prep++;\r
481                         }\r
482                         else if ((sdActive == 0) && (prep - i < buffers) && (prep < totalSDSectors))\r
483                         {\r
484                                 // Start an SD transfer if we have space.\r
485                                 if (transfer.multiBlock)\r
486                                 {\r
487                                         sdReadMultiSectorDMA(&scsiDev.data[SD_SECTOR_SIZE * (prep % buffers)]);\r
488                                 }\r
489                                 else\r
490                                 {\r
491                                         sdReadSingleSectorDMA(sdLBA + prep, &scsiDev.data[SD_SECTOR_SIZE * (prep % buffers)]);\r
492                                 }\r
493                                 sdActive = 1;\r
494                         }\r
495 \r
496                         if ((scsiActive == 1) && scsiWriteDMAPoll())\r
497                         {\r
498                                 scsiActive = 0;\r
499                                 ++i;\r
500                         }\r
501                         else if ((scsiActive == 0) && ((prep - i) > 0))\r
502                         {\r
503                                 int dmaBytes = SD_SECTOR_SIZE;\r
504                                 if (i % SDSectorsPerSCSISector() == SDSectorsPerSCSISector() - 1)\r
505                                 {\r
506                                         dmaBytes = config->bytesPerSector % SD_SECTOR_SIZE;\r
507                                         if (dmaBytes == 0) dmaBytes = SD_SECTOR_SIZE;\r
508                                 }\r
509                                 scsiWriteDMA(&scsiDev.data[SD_SECTOR_SIZE * (i % buffers)], dmaBytes);\r
510                                 scsiActive = 1;\r
511                         }\r
512                 }\r
513                 if (scsiDev.phase == DATA_IN)\r
514                 {\r
515                         scsiDev.phase = STATUS;\r
516                 }\r
517                 scsiDiskReset();\r
518         }\r
519         else if (scsiDev.phase == DATA_OUT &&\r
520                 transfer.currentBlock != transfer.blocks)\r
521         {\r
522                 int totalSDSectors = transfer.blocks * SDSectorsPerSCSISector();\r
523                 int buffers = sizeof(scsiDev.data) / SD_SECTOR_SIZE;\r
524                 int prep = 0;\r
525                 int i = 0;\r
526                 int scsiActive = 0;\r
527                 int sdActive = 0;\r
528                 while ((i < totalSDSectors) &&\r
529                         (scsiDev.phase == DATA_OUT) &&\r
530                         !scsiDev.resetFlag)\r
531                 {\r
532                         if ((sdActive == 1) && sdWriteSectorDMAPoll())\r
533                         {\r
534                                 sdActive = 0;\r
535                                 i++;\r
536                         }\r
537                         else if ((sdActive == 0) && ((prep - i) > 0))\r
538                         {\r
539                                 // Start an SD transfer if we have space.\r
540                                 sdWriteMultiSectorDMA(&scsiDev.data[SD_SECTOR_SIZE * (i % buffers)]);\r
541                                 sdActive = 1;\r
542                         }\r
543 \r
544                         if ((scsiActive == 1) && scsiReadDMAPoll())\r
545                         {\r
546                                 scsiActive = 0;\r
547                                 ++prep;\r
548                         }\r
549                         else if ((scsiActive == 0) && ((prep - i) < buffers) && (prep < totalSDSectors))\r
550                         {\r
551                                 int dmaBytes = SD_SECTOR_SIZE;\r
552                                 if (prep % SDSectorsPerSCSISector() == SDSectorsPerSCSISector() - 1)\r
553                                 {\r
554                                         dmaBytes = config->bytesPerSector % SD_SECTOR_SIZE;\r
555                                         if (dmaBytes == 0) dmaBytes = SD_SECTOR_SIZE;\r
556                                 }\r
557                                 scsiReadDMA(&scsiDev.data[SD_SECTOR_SIZE * (prep % buffers)], dmaBytes);\r
558                                 scsiActive = 1;\r
559                         }\r
560                 }\r
561                 if (scsiDev.phase == DATA_OUT)\r
562                 {\r
563                         scsiDev.phase = STATUS;\r
564                 }\r
565                 scsiDiskReset();\r
566         }\r
567 }\r
568 \r
569 void scsiDiskReset()\r
570 {\r
571         scsiDev.dataPtr = 0;\r
572         scsiDev.savedDataPtr = 0;\r
573         scsiDev.dataLen = 0;\r
574         // transfer.lba = 0; // Needed in Request Sense to determine failure\r
575         transfer.blocks = 0;\r
576         transfer.currentBlock = 0;\r
577 \r
578         // Cancel long running commands!\r
579         if (transfer.inProgress == 1)\r
580         {\r
581                 if (transfer.dir == TRANSFER_WRITE)\r
582                 {\r
583                         sdCompleteWrite();\r
584                 }\r
585                 else\r
586                 {\r
587                         sdCompleteRead();\r
588                 }\r
589         }\r
590         transfer.inProgress = 0;\r
591         transfer.multiBlock = 0;\r
592 }\r
593 \r
594 void scsiDiskInit()\r
595 {\r
596         transfer.inProgress = 0;\r
597         scsiDiskReset();\r
598 \r
599         // Don't require the host to send us a START STOP UNIT command\r
600         blockDev.state = DISK_STARTED;\r
601         // WP pin not available for micro-sd\r
602         // TODO read card WP register\r
603         #if 0\r
604         if (SD_WP_Read())\r
605         {\r
606                 blockDev.state = blockDev.state | DISK_WP;\r
607         }\r
608         #endif\r
609 \r
610         if (SD_CD_Read() == 1)\r
611         {\r
612                 int retry;\r
613                 blockDev.state = blockDev.state | DISK_PRESENT;\r
614 \r
615                 // Wait up to 5 seconds for the SD card to wake up.\r
616                 for (retry = 0; retry < 5; ++retry)\r
617                 {\r
618                         if (doSdInit())\r
619                         {\r
620                                 break;\r
621                         }\r
622                         else\r
623                         {\r
624                                 CyDelay(1000);\r
625                         }\r
626                 }\r
627         }\r
628 }\r
629 \r