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