bdd98732072aa3fb2be48a7fb09f03acffe5fcbd
[SCSI2SD-V6.git] / software / SCSI2SD / src / scsiPhy.c
1 //      Copyright (C) 2013 Michael McMaster <michael@codesrc.com>\r
2 //\r
3 //      This file is part of SCSI2SD.\r
4 //\r
5 //      SCSI2SD is free software: you can redistribute it and/or modify\r
6 //      it under the terms of the GNU General Public License as published by\r
7 //      the Free Software Foundation, either version 3 of the License, or\r
8 //      (at your option) any later version.\r
9 //\r
10 //      SCSI2SD is distributed in the hope that it will be useful,\r
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 //      GNU General Public License for more details.\r
14 //\r
15 //      You should have received a copy of the GNU General Public License\r
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.\r
17 #pragma GCC push_options\r
18 #pragma GCC optimize("-flto")\r
19 \r
20 #include "device.h"\r
21 #include "scsi.h"\r
22 #include "scsiPhy.h"\r
23 #include "bits.h"\r
24 #include "trace.h"\r
25 \r
26 #define scsiTarget_AUX_CTL (* (reg8 *) scsiTarget_datapath__DP_AUX_CTL_REG)\r
27 \r
28 // DMA controller can't handle any more bytes.\r
29 #define MAX_DMA_BYTES 4095\r
30 \r
31 // Private DMA variables.\r
32 static int dmaInProgress = 0;\r
33 // used when transferring > MAX_DMA_BYTES.\r
34 static uint8_t* dmaBuffer = NULL;\r
35 static uint32_t dmaSentCount = 0;\r
36 static uint32_t dmaTotalCount = 0;\r
37 \r
38 static uint8 scsiDmaRxChan = CY_DMA_INVALID_CHANNEL;\r
39 static uint8 scsiDmaTxChan = CY_DMA_INVALID_CHANNEL;\r
40 \r
41 // DMA descriptors\r
42 static uint8 scsiDmaRxTd[1] = { CY_DMA_INVALID_TD };\r
43 static uint8 scsiDmaTxTd[1] = { CY_DMA_INVALID_TD };\r
44 \r
45 // Source of dummy bytes for DMA reads\r
46 static uint8 dummyBuffer = 0xFF;\r
47 \r
48 volatile uint8_t scsiRxDMAComplete;\r
49 volatile uint8_t scsiTxDMAComplete;\r
50 \r
51 CY_ISR_PROTO(scsiRxCompleteISR);\r
52 CY_ISR(scsiRxCompleteISR)\r
53 {\r
54         traceIrq(trace_scsiRxCompleteISR);\r
55         scsiRxDMAComplete = 1;\r
56 }\r
57 \r
58 CY_ISR_PROTO(scsiTxCompleteISR);\r
59 CY_ISR(scsiTxCompleteISR)\r
60 {\r
61         traceIrq(trace_scsiTxCompleteISR);\r
62         scsiTxDMAComplete = 1;\r
63 }\r
64 \r
65 CY_ISR_PROTO(scsiResetISR);\r
66 CY_ISR(scsiResetISR)\r
67 {\r
68         traceIrq(trace_scsiResetISR);\r
69         scsiDev.resetFlag = 1;\r
70 }\r
71 \r
72 uint8_t\r
73 scsiReadDBxPins()\r
74 {\r
75         return\r
76                 (SCSI_ReadPin(SCSI_In_DBx_DB7) << 7) |\r
77                 (SCSI_ReadPin(SCSI_In_DBx_DB6) << 6) |\r
78                 (SCSI_ReadPin(SCSI_In_DBx_DB5) << 5) |\r
79                 (SCSI_ReadPin(SCSI_In_DBx_DB4) << 4) |\r
80                 (SCSI_ReadPin(SCSI_In_DBx_DB3) << 3) |\r
81                 (SCSI_ReadPin(SCSI_In_DBx_DB2) << 2) |\r
82                 (SCSI_ReadPin(SCSI_In_DBx_DB1) << 1) |\r
83                 SCSI_ReadPin(SCSI_In_DBx_DB0);\r
84 }\r
85 \r
86 uint8_t\r
87 scsiReadByte(void)\r
88 {\r
89         trace(trace_spinPhyTxFifo);\r
90         while (unlikely(scsiPhyTxFifoFull()) && likely(!scsiDev.resetFlag)) {}\r
91         scsiPhyTx(0);\r
92 \r
93         trace(trace_spinPhyRxFifo);\r
94         while (scsiPhyRxFifoEmpty() && likely(!scsiDev.resetFlag)) {}\r
95         uint8_t val = scsiPhyRx();\r
96         scsiDev.parityError = scsiDev.parityError || SCSI_Parity_Error_Read();\r
97 \r
98         trace(trace_spinTxComplete);\r
99         while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE) && likely(!scsiDev.resetFlag)) {}\r
100 \r
101         return val;\r
102 }\r
103 \r
104 static void\r
105 scsiReadPIO(uint8* data, uint32 count)\r
106 {\r
107         int prep = 0;\r
108         int i = 0;\r
109 \r
110         while (i < count && likely(!scsiDev.resetFlag))\r
111         {\r
112                 uint8_t status = scsiPhyStatus();\r
113 \r
114                 if (prep < count && (status & SCSI_PHY_TX_FIFO_NOT_FULL))\r
115                 {\r
116                         scsiPhyTx(0);\r
117                         ++prep;\r
118                 }\r
119                 if (status & SCSI_PHY_RX_FIFO_NOT_EMPTY)\r
120                 {\r
121                         data[i] = scsiPhyRx();\r
122                         ++i;\r
123                 }\r
124         }\r
125         scsiDev.parityError = scsiDev.parityError || SCSI_Parity_Error_Read();\r
126         while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE) && likely(!scsiDev.resetFlag)) {}\r
127 }\r
128 \r
129 static void\r
130 doRxSingleDMA(uint8* data, uint32 count)\r
131 {\r
132         // Prepare DMA transfer\r
133         dmaInProgress = 1;\r
134         trace(trace_doRxSingleDMA);\r
135 \r
136         CyDmaTdSetConfiguration(\r
137                 scsiDmaTxTd[0],\r
138                 count,\r
139                 CY_DMA_DISABLE_TD, // Disable the DMA channel when TD completes count bytes\r
140                 SCSI_TX_DMA__TD_TERMOUT_EN // Trigger interrupt when complete\r
141                 );\r
142         CyDmaTdSetConfiguration(\r
143                 scsiDmaRxTd[0],\r
144                 count,\r
145                 CY_DMA_DISABLE_TD, // Disable the DMA channel when TD completes count bytes\r
146                 TD_INC_DST_ADR |\r
147                         SCSI_RX_DMA__TD_TERMOUT_EN // Trigger interrupt when complete\r
148                 );\r
149 \r
150         CyDmaTdSetAddress(\r
151                 scsiDmaTxTd[0],\r
152                 LO16((uint32)&dummyBuffer),\r
153                 LO16((uint32)scsiTarget_datapath__F0_REG));\r
154         CyDmaTdSetAddress(\r
155                 scsiDmaRxTd[0],\r
156                 LO16((uint32)scsiTarget_datapath__F1_REG),\r
157                 LO16((uint32)data)\r
158                 );\r
159 \r
160         CyDmaChSetInitialTd(scsiDmaTxChan, scsiDmaTxTd[0]);\r
161         CyDmaChSetInitialTd(scsiDmaRxChan, scsiDmaRxTd[0]);\r
162 \r
163         // The DMA controller is a bit trigger-happy. It will retain\r
164         // a drq request that was triggered while the channel was\r
165         // disabled.\r
166         CyDmaClearPendingDrq(scsiDmaTxChan);\r
167         CyDmaClearPendingDrq(scsiDmaRxChan);\r
168 \r
169         scsiTxDMAComplete = 0;\r
170         scsiRxDMAComplete = 0;\r
171 \r
172         CyDmaChEnable(scsiDmaRxChan, 1);\r
173         CyDmaChEnable(scsiDmaTxChan, 1);\r
174 }\r
175 \r
176 void\r
177 scsiReadDMA(uint8* data, uint32 count)\r
178 {\r
179         dmaSentCount = 0;\r
180         dmaTotalCount = count;\r
181         dmaBuffer = data;\r
182 \r
183         uint32_t singleCount = (count > MAX_DMA_BYTES) ? MAX_DMA_BYTES : count;\r
184         doRxSingleDMA(data, singleCount);\r
185         dmaSentCount += count;\r
186 }\r
187 \r
188 int\r
189 scsiReadDMAPoll()\r
190 {\r
191         if (scsiTxDMAComplete && scsiRxDMAComplete)\r
192         {\r
193                 // Wait until our scsi signals are consistent. This should only be\r
194                 // a few cycles.\r
195                 trace(trace_spinTxComplete);\r
196                 while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE)) {}\r
197 \r
198                 if (likely(dmaSentCount == dmaTotalCount))\r
199                 {\r
200                         dmaInProgress = 0;\r
201                         scsiDev.parityError = scsiDev.parityError || SCSI_Parity_Error_Read();\r
202                         return 1;\r
203                 }\r
204                 else\r
205                 {\r
206                         // Transfer was too large for a single DMA transfer. Continue\r
207                         // to send remaining bytes.\r
208                         uint32_t count = dmaTotalCount - dmaSentCount;\r
209                         if (unlikely(count > MAX_DMA_BYTES)) count = MAX_DMA_BYTES;\r
210                         doRxSingleDMA(dmaBuffer + dmaSentCount, count);\r
211                         dmaSentCount += count;\r
212                         return 0;\r
213                 }\r
214         }\r
215         else\r
216         {\r
217                 return 0;\r
218         }\r
219 }\r
220 \r
221 void\r
222 scsiRead(uint8_t* data, uint32_t count)\r
223 {\r
224         if (count < 8)\r
225         {\r
226                 scsiReadPIO(data, count);\r
227         }\r
228         else\r
229         {\r
230                 scsiReadDMA(data, count);\r
231 \r
232                 // Wait for the next DMA interrupt (or the 1ms systick)\r
233                 // It's beneficial to halt the processor to\r
234                 // give the DMA controller more memory bandwidth to work with.\r
235                 __WFI();\r
236 \r
237                 trace(trace_spinReadDMAPoll);\r
238                 while (!scsiReadDMAPoll() && likely(!scsiDev.resetFlag)) {};\r
239         }\r
240 }\r
241 \r
242 void\r
243 scsiWriteByte(uint8 value)\r
244 {\r
245         trace(trace_spinPhyTxFifo);\r
246         while (unlikely(scsiPhyTxFifoFull()) && likely(!scsiDev.resetFlag)) {}\r
247         scsiPhyTx(value);\r
248 \r
249         trace(trace_spinTxComplete);\r
250         while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE) && likely(!scsiDev.resetFlag)) {}\r
251         scsiPhyRxFifoClear();\r
252 }\r
253 \r
254 static void\r
255 scsiWritePIO(const uint8_t* data, uint32_t count)\r
256 {\r
257         int i = 0;\r
258 \r
259         while (i < count && likely(!scsiDev.resetFlag))\r
260         {\r
261                 if (!scsiPhyTxFifoFull())\r
262                 {\r
263                         scsiPhyTx(data[i]);\r
264                         ++i;\r
265                 }\r
266         }\r
267 \r
268         trace(trace_spinTxComplete);\r
269         while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE) && likely(!scsiDev.resetFlag)) {}\r
270         scsiPhyRxFifoClear();\r
271 }\r
272 \r
273 static void\r
274 doTxSingleDMA(const uint8* data, uint32 count)\r
275 {\r
276         // Prepare DMA transfer\r
277         dmaInProgress = 1;\r
278         trace(trace_doTxSingleDMA);\r
279 \r
280         CyDmaTdSetConfiguration(\r
281                 scsiDmaTxTd[0],\r
282                 count,\r
283                 CY_DMA_DISABLE_TD, // Disable the DMA channel when TD completes count bytes\r
284                 TD_INC_SRC_ADR |\r
285                         SCSI_TX_DMA__TD_TERMOUT_EN // Trigger interrupt when complete\r
286                 );\r
287         CyDmaTdSetAddress(\r
288                 scsiDmaTxTd[0],\r
289                 LO16((uint32)data),\r
290                 LO16((uint32)scsiTarget_datapath__F0_REG));\r
291         CyDmaChSetInitialTd(scsiDmaTxChan, scsiDmaTxTd[0]);\r
292 \r
293         // The DMA controller is a bit trigger-happy. It will retain\r
294         // a drq request that was triggered while the channel was\r
295         // disabled.\r
296         CyDmaClearPendingDrq(scsiDmaTxChan);\r
297 \r
298         scsiTxDMAComplete = 0;\r
299         scsiRxDMAComplete = 1;\r
300 \r
301         CyDmaChEnable(scsiDmaTxChan, 1);\r
302 }\r
303 \r
304 void\r
305 scsiWriteDMA(const uint8* data, uint32 count)\r
306 {\r
307         dmaSentCount = 0;\r
308         dmaTotalCount = count;\r
309         dmaBuffer = data;\r
310 \r
311         uint32_t singleCount = (count > MAX_DMA_BYTES) ? MAX_DMA_BYTES : count;\r
312         doTxSingleDMA(data, singleCount);\r
313         dmaSentCount += count;\r
314 }\r
315 \r
316 int\r
317 scsiWriteDMAPoll()\r
318 {\r
319         if (scsiTxDMAComplete)\r
320         {\r
321                 // Wait until our scsi signals are consistent. This should only be\r
322                 // a few cycles.\r
323                 trace(trace_spinTxComplete);\r
324                 while (!(scsiPhyStatus() & SCSI_PHY_TX_COMPLETE)) {}\r
325 \r
326                 if (likely(dmaSentCount == dmaTotalCount))\r
327                 {\r
328                         scsiPhyRxFifoClear();\r
329                         dmaInProgress = 0;\r
330                         return 1;\r
331                 }\r
332                 else\r
333                 {\r
334                         // Transfer was too large for a single DMA transfer. Continue\r
335                         // to send remaining bytes.\r
336                         uint32_t count = dmaTotalCount - dmaSentCount;\r
337                         if (unlikely(count > MAX_DMA_BYTES)) count = MAX_DMA_BYTES;\r
338                         doTxSingleDMA(dmaBuffer + dmaSentCount, count);\r
339                         dmaSentCount += count;\r
340                         return 0;\r
341                 }\r
342         }\r
343         else\r
344         {\r
345                 return 0;\r
346         }\r
347 }\r
348 \r
349 void\r
350 scsiWrite(const uint8_t* data, uint32_t count)\r
351 {\r
352         if (count < 8)\r
353         {\r
354                 scsiWritePIO(data, count);\r
355         }\r
356         else\r
357         {\r
358                 scsiWriteDMA(data, count);\r
359 \r
360                 // Wait for the next DMA interrupt (or the 1ms systick)\r
361                 // It's beneficial to halt the processor to\r
362                 // give the DMA controller more memory bandwidth to work with.\r
363                 __WFI();\r
364 \r
365                 trace(trace_spinWriteDMAPoll);\r
366                 while (!scsiWriteDMAPoll() && likely(!scsiDev.resetFlag)) {};\r
367         }\r
368 }\r
369 \r
370 static inline void busSettleDelay(void)\r
371 {\r
372         // Data Release time (switching IO) = 400ns\r
373         // + Bus Settle time (switching phase) = 400ns.\r
374         CyDelayUs(1); // Close enough.\r
375 }\r
376 \r
377 void scsiEnterPhase(int phase)\r
378 {\r
379         int newPhase = phase > 0 ? phase : 0;\r
380         if (newPhase != SCSI_CTL_PHASE_Read())\r
381         {\r
382                 SCSI_CTL_PHASE_Write(phase > 0 ? phase : 0);\r
383                 busSettleDelay();\r
384         }\r
385 }\r
386 \r
387 void scsiPhyReset()\r
388 {\r
389         trace(trace_scsiPhyReset);\r
390         if (dmaInProgress)\r
391         {\r
392                 dmaInProgress = 0;\r
393                 dmaBuffer = NULL;\r
394                 dmaSentCount = 0;\r
395                 dmaTotalCount = 0;\r
396                 CyDmaChSetRequest(scsiDmaTxChan, CY_DMA_CPU_TERM_CHAIN);\r
397                 CyDmaChSetRequest(scsiDmaRxChan, CY_DMA_CPU_TERM_CHAIN);\r
398                 \r
399                 // CyDmaChGetRequest returns 0 for the relevant bit once the\r
400                 // request is completed.\r
401                 trace(trace_spinDMAReset);\r
402                 while (CyDmaChGetRequest(scsiDmaTxChan) & CY_DMA_CPU_TERM_CHAIN) {}\r
403                 while (CyDmaChGetRequest(scsiDmaRxChan) & CY_DMA_CPU_TERM_CHAIN) {}\r
404 \r
405                 CyDmaChDisable(scsiDmaTxChan);\r
406                 CyDmaChDisable(scsiDmaRxChan);\r
407         }\r
408 \r
409         // Set the Clear bits for both SCSI device FIFOs\r
410         scsiTarget_AUX_CTL = scsiTarget_AUX_CTL | 0x03;\r
411 \r
412         // Trigger RST outselves.  It is connected to the datapath and will\r
413         // ensure it returns to the idle state.  The datapath runs at the BUS clk\r
414         // speed (ie. same as the CPU), so we can be sure it is active for a sufficient\r
415         // duration.\r
416         SCSI_RST_ISR_Disable();\r
417         SCSI_SetPin(SCSI_Out_RST);\r
418 \r
419         SCSI_CTL_PHASE_Write(0);\r
420         SCSI_ClearPin(SCSI_Out_ATN);\r
421         SCSI_ClearPin(SCSI_Out_BSY);\r
422         SCSI_ClearPin(SCSI_Out_ACK);\r
423         SCSI_ClearPin(SCSI_Out_RST);\r
424         SCSI_ClearPin(SCSI_Out_SEL);\r
425         SCSI_ClearPin(SCSI_Out_REQ);\r
426 \r
427         // Allow the FIFOs to fill up again.\r
428         SCSI_ClearPin(SCSI_Out_RST);\r
429         SCSI_RST_ISR_Enable();\r
430         scsiTarget_AUX_CTL = scsiTarget_AUX_CTL & ~(0x03);\r
431 \r
432         SCSI_Parity_Error_Read(); // clear sticky bits\r
433 }\r
434 \r
435 static void scsiPhyInitDMA()\r
436 {\r
437         // One-time init only.\r
438         if (scsiDmaTxChan == CY_DMA_INVALID_CHANNEL)\r
439         {\r
440                 scsiDmaRxChan =\r
441                         SCSI_RX_DMA_DmaInitialize(\r
442                                 1, // Bytes per burst\r
443                                 1, // request per burst\r
444                                 HI16(CYDEV_PERIPH_BASE),\r
445                                 HI16(CYDEV_SRAM_BASE)\r
446                                 );\r
447 \r
448                 scsiDmaTxChan =\r
449                         SCSI_TX_DMA_DmaInitialize(\r
450                                 1, // Bytes per burst\r
451                                 1, // request per burst\r
452                                 HI16(CYDEV_SRAM_BASE),\r
453                                 HI16(CYDEV_PERIPH_BASE)\r
454                                 );\r
455                 \r
456                 CyDmaChDisable(scsiDmaRxChan);\r
457                 CyDmaChDisable(scsiDmaTxChan);\r
458 \r
459                 scsiDmaRxTd[0] = CyDmaTdAllocate();\r
460                 scsiDmaTxTd[0] = CyDmaTdAllocate();\r
461 \r
462                 SCSI_RX_DMA_COMPLETE_StartEx(scsiRxCompleteISR);\r
463                 SCSI_TX_DMA_COMPLETE_StartEx(scsiTxCompleteISR);\r
464         }\r
465 }\r
466 \r
467 \r
468 void scsiPhyInit()\r
469 {\r
470         scsiPhyInitDMA();\r
471 \r
472         SCSI_RST_ISR_StartEx(scsiResetISR);\r
473 }\r
474 \r
475 // 1 = DBx error\r
476 // 2 = Parity error\r
477 // 4 = MSG error\r
478 // 8 = CD error\r
479 // 16 = IO error\r
480 // 32 = other error\r
481 int scsiSelfTest()\r
482 {\r
483         int result = 0;\r
484 \r
485         // TEST DBx and DBp\r
486         int i;\r
487         SCSI_Out_Ctl_Write(1); // Write bits manually.\r
488         SCSI_CTL_PHASE_Write(__scsiphase_io); // Needed for parity generation\r
489         for (i = 0; i < 256; ++i)\r
490         {\r
491                 SCSI_Out_Bits_Write(i);\r
492                 scsiDeskewDelay();\r
493                 if (scsiReadDBxPins() != (i & 0xff))\r
494                 {\r
495                         result |= 1;\r
496                 }\r
497                 if (Lookup_OddParity[i & 0xff] != SCSI_ReadPin(SCSI_In_DBP))\r
498                 {\r
499                         result |= 2;\r
500                 }\r
501         }\r
502         SCSI_Out_Ctl_Write(0); // Write bits normally.\r
503 \r
504         // TEST MSG, CD, IO\r
505         for (i = 0; i < 8; ++i)\r
506         {\r
507                 SCSI_CTL_PHASE_Write(i);\r
508                 scsiDeskewDelay();\r
509 \r
510                 if (SCSI_ReadPin(SCSI_In_MSG) != !!(i & __scsiphase_msg))\r
511                 {\r
512                         result |= 4;\r
513                 }\r
514                 if (SCSI_ReadPin(SCSI_In_CD) != !!(i & __scsiphase_cd))\r
515                 {\r
516                         result |= 8;\r
517                 }\r
518                 if (SCSI_ReadPin(SCSI_In_IO) != !!(i & __scsiphase_io))\r
519                 {\r
520                         result |= 16;\r
521                 }\r
522         }\r
523         SCSI_CTL_PHASE_Write(0);\r
524 \r
525         uint32_t signalsOut[] = { SCSI_Out_ATN, SCSI_Out_BSY, SCSI_Out_RST, SCSI_Out_SEL };\r
526         uint32_t signalsIn[] = { SCSI_Filt_ATN, SCSI_Filt_BSY, SCSI_Filt_RST, SCSI_Filt_SEL };\r
527 \r
528         for (i = 0; i < 4; ++i)\r
529         {\r
530                 SCSI_SetPin(signalsOut[i]);\r
531                 scsiDeskewDelay();\r
532 \r
533                 int j;\r
534                 for (j = 0; j < 4; ++j)\r
535                 {\r
536                         if (i == j)\r
537                         {\r
538                                 if (! SCSI_ReadFilt(signalsIn[j]))\r
539                                 {\r
540                                         result |= 32;\r
541                                 }\r
542                         }\r
543                         else\r
544                         {\r
545                                 if (SCSI_ReadFilt(signalsIn[j]))\r
546                                 {\r
547                                         result |= 32;\r
548                                 }\r
549                         }\r
550                 }\r
551                 SCSI_ClearPin(signalsOut[i]);\r
552         }\r
553         return result;\r
554 }\r
555 \r
556 \r
557 #pragma GCC pop_options\r