Sorting source tree a bit
[tpg/acess2.git] / KernelLand / Modules / Storage / FDDv2 / fdc.c
1 /*
2  * Acess2 82077AA FDC
3  * - By John Hodge (thePowersGang)
4  *
5  * fdc.c
6  * - FDC IO Functions
7  */
8 #define DEBUG   0
9 #include <acess.h>
10 #include "common.h"
11 #include <dma.h>
12 #include <timers.h>
13
14 // === CONSTANTS ===
15 #define MOTOR_ON_DELAY  500
16 #define MOTOR_OFF_DELAY 2000
17
18 enum eMotorState
19 {
20         MOTOR_OFF,
21         MOTOR_ATSPEED,
22 };
23
24 enum eFDC_Registers
25 {
26         FDC_DOR  = 0x02,        // Digital Output Register
27         FDC_MSR  = 0x04,        // Master Status Register (Read Only)
28         FDC_FIFO = 0x05,        // FIFO port
29         FDC_CCR  = 0x07         // Configuration Control Register (write only)
30 };
31
32 enum eFDC_Commands
33 {
34         CMD_SPECIFY = 3,            // Specify parameters
35         CMD_WRITE_DATA = 5,         // Write Data
36         CMD_READ_DATA = 6,          // Read Data
37         CMD_RECALIBRATE = 7,        // Recalibrate a drive
38         CMD_SENSE_INTERRUPT = 8,    // Sense (Ack) an interrupt
39         CMD_SEEK = 15,              // Seek to a track
40 };
41
42 // === PROTOTYPES ===
43  int    FDD_SetupIO(void);
44  int    FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer);
45  int    FDD_int_SeekToTrack(int Disk, int Track);
46  int    FDD_int_Calibrate(int Disk);
47  int    FDD_int_Reset(int Disk);
48 // --- FIFO
49  int    FDD_int_WriteData(Uint16 Base, Uint8 Data);
50  int    FDD_int_ReadData(Uint16 Base, Uint8 *Data);
51 void    FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl);
52 // --- Motor Control
53  int    FDD_int_StartMotor(int Disk);
54  int    FDD_int_StopMotor(int Disk);
55 void    FDD_int_StopMotorCallback(void *Ptr);
56 // --- Helpers
57  int    FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0);
58 Uint16  FDD_int_GetBase(int Disk, int *Drive);
59 // --- Interrupt
60 void    FDD_int_ClearIRQ(void);
61  int    FDD_int_WaitIRQ(void);
62 void    FDD_int_IRQHandler(int IRQ, void *Ptr);
63
64 // === GLOBALS ===
65 /**
66  * \brief Marker for IRQ6
67  * \todo Convert into a semaphore?
68  */
69  int    gbFDD_IRQ6Fired;
70 /**
71  * \brief Protector for DMA and IRQ6
72  */
73 tMutex  gFDD_IOMutex;
74
75 // === CODE ===
76 /**
77  * \brief Set up FDC IO
78  * \return Boolean failure
79  *
80  * Registers the IRQ handler and resets the controller
81  */
82 int FDD_SetupIO(void)
83 {
84         // Install IRQ6 Handler
85         IRQ_AddHandler(6, FDD_int_IRQHandler, NULL);
86         
87         // Reset controller
88         FDD_int_Reset(0);
89         // TODO: All controllers
90         
91         return 0;
92 }
93
94 /**
95  * \brief Read/Write data from/to a disk
96  * \param Disk Global disk number
97  * \param Track Track number (Cyl*2+Head)
98  * \param bWrite Toggle write mode
99  * \param Buffer Destination/Source buffer
100  * \return Boolean failure
101  */
102 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer)
103 {
104         Uint8   cmd;
105          int    i, _disk;
106         Uint16  base = FDD_int_GetBase(Disk, &_disk);
107          int    cyl = Track >> 1, head = Track & 1;
108
109         ENTER("iDisk iTrack ibWrite pBuffer", Disk, Track, bWrite, Buffer);
110
111         Mutex_Acquire( &gFDD_IOMutex );
112         
113         // Initialise DMA for read/write
114         // TODO: Support non 1.44MiB FDs
115         DMA_SetChannel(2, BYTES_PER_TRACK, !bWrite);
116         
117         // Select command
118         if( bWrite )
119                 cmd = CMD_WRITE_DATA | 0xC0;
120         else
121                 cmd = CMD_READ_DATA | 0xC0;
122
123         LOG("cmd = 0x%x", cmd);
124         
125         // Seek
126         if( FDD_int_SeekToTrack(Disk, Track) ) {
127                 Mutex_Release( &gFDD_IOMutex );
128                 LEAVE('i', -1);
129                 return -1;
130         }
131         LOG("Track seek done");
132
133         for( i = 0; i < 20; i ++ )
134         {
135                 LOG("Starting motor");
136                 FDD_int_StartMotor(Disk);
137
138                 // Write data
139                 if( bWrite )
140                         DMA_WriteData(2, BYTES_PER_TRACK, Buffer);      
141         
142                 LOG("Sending command stream");
143                 FDD_int_WriteData(base, cmd);
144                 FDD_int_WriteData(base, (head << 2) | _disk);
145                 FDD_int_WriteData(base, cyl);
146                 FDD_int_WriteData(base, head);
147                 FDD_int_WriteData(base, 1);     // First Sector
148                 FDD_int_WriteData(base, 2);     // Bytes per sector (128*2^n)
149                 FDD_int_WriteData(base, 18);    // 18 tracks (full disk) - TODO: Non 1.44
150                 FDD_int_WriteData(base, 0x1B);  // Gap length - TODO: again
151                 FDD_int_WriteData(base, 0xFF);  // Data length - ?
152         
153                 LOG("Waiting for IRQ");
154                 FDD_int_WaitIRQ();
155         
156                 // No Sense Interrupt
157                 
158                 LOG("Reading result");
159                 Uint8   st0=0, st1=0, st2=0, bps=0;
160                 FDD_int_ReadData(base, &st0);
161                 FDD_int_ReadData(base, &st1);   // st1
162                 FDD_int_ReadData(base, &st2);   // st2
163                 FDD_int_ReadData(base, NULL);   // rcy - Mutilated Cyl
164                 FDD_int_ReadData(base, NULL);   // rhe - Mutilated Head
165                 FDD_int_ReadData(base, NULL);   // rse - Mutilated sector
166                 FDD_int_ReadData(base, &bps);   // bps - Should be the same as above
167
168                 if( st0 & 0xc0 ) {
169                         FDD_int_HandleST0Error(__func__, Disk, st0);
170                         continue ;
171                 }
172         
173                 if( st2 & 0x02 ) {
174                         Log_Debug("FDD", "Disk %i is not writable", Disk);
175                         Mutex_Release( &gFDD_IOMutex );
176                         LEAVE('i', 2);
177                         return 2;
178                 }
179                 
180                 if( st0 & 0x08 ) {
181                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: Drive not ready");
182                         continue ;
183                 }
184
185
186                 if( st1 & 0x80 ) {
187                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: End of cylinder");
188                         continue ;
189                 }
190
191                 if( st1 & (0x20|0x10|0x04|0x01) ) {
192                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: st1 = 0x%x", st1);
193                         continue;
194                 }
195                 
196                 if( st2 & (0x40|0x20|0x10|0x04|0x01) ) {
197                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: st2 = 0x%x", st2);
198                         continue ;
199                 }
200                 
201                 if( bps != 0x2 ) {
202                         Log_Debug("FDD", "Wanted bps = 2 (512), got %i", bps);
203                         continue ;
204                 }
205
206                 // Read back data
207                 if( !bWrite )
208                         DMA_ReadData(2, BYTES_PER_TRACK, Buffer);
209                 
210                 LOG("All data done");
211                 FDD_int_StopMotor(Disk);
212                 Mutex_Release( &gFDD_IOMutex );
213                 LEAVE('i', 0);
214                 return 0;
215         }
216
217         Log_Debug("FDD", "%i retries exhausted", i);
218         FDD_int_StopMotor(Disk);
219         Mutex_Release( &gFDD_IOMutex );
220         LEAVE('i', 1);
221         return 1;
222 }
223
224 /**
225  * \brief Seek to a specific track
226  * \param Disk Global disk number
227  * \param Track Track number (Cyl*2+Head)
228  * \return Boolean failure
229  */
230 int FDD_int_SeekToTrack(int Disk, int Track)
231 {
232         Uint8   st0=0, res_cyl=0;
233          int    cyl, head;
234          int    _disk;
235         Uint16  base = FDD_int_GetBase(Disk, &_disk);;
236
237         ENTER("iDisk iTrack", Disk, Track);
238         
239         cyl = Track / 2;
240         head = Track % 2;
241
242         LOG("cyl = %i, head = %i", cyl, head);
243         
244         FDD_int_StartMotor(Disk);
245         
246         for( int i = 0; i < 10; i ++ )
247         {
248                 LOG("Sending command");
249                 FDD_int_ClearIRQ();
250                 FDD_int_WriteData(base, CMD_SEEK);
251                 FDD_int_WriteData(base, (head << 2) + _disk);
252                 FDD_int_WriteData(base, cyl);
253         
254                 LOG("Waiting for IRQ");
255                 FDD_int_WaitIRQ();
256                 FDD_int_SenseInterrupt(base, &st0, &res_cyl);
257         
258                 if( st0 & 0xC0 )
259                 {
260                         FDD_int_HandleST0Error(__func__, Disk, st0);
261                         continue ;
262                 }
263                 
264                 if( res_cyl == cyl ) {
265                         FDD_int_StopMotor(Disk);
266                         LEAVE('i', 0);
267                         return 0;
268                 }
269         }
270         
271         Log_Error("FDD", "FDD_int_SeekToTrack: 10 retries exhausted\n");
272         FDD_int_StopMotor(Disk);
273         LEAVE('i', 1);
274         return 1;
275 }
276
277 /**
278  * \brief Calibrate a drive
279  * \param Disk  Global disk number
280  */
281 int FDD_int_Calibrate(int Disk)
282 {
283          int    _disk;
284         Uint16  base = FDD_int_GetBase(Disk, &_disk);
285         FDD_int_StartMotor(Disk);
286         
287         for( int i = 0; i < 10; i ++ )
288         {
289                 Uint8   st0=0, cyl = -1;
290         
291                 FDD_int_ClearIRQ();     
292                 FDD_int_WriteData(base, CMD_RECALIBRATE);
293                 FDD_int_WriteData(base, _disk);
294                 
295                 FDD_int_WaitIRQ();
296         
297                 FDD_int_SenseInterrupt(base, &st0, &cyl);
298                 
299                 if( st0 & 0xC0 ) {
300                         FDD_int_HandleST0Error(__func__, Disk, st0);
301                         continue ;
302                 }
303                 
304                 if( cyl == 0 )
305                 {
306                         FDD_int_StopMotor(Disk);
307                         return 0;
308                 }
309         }
310         
311         Log_Error("FDD", "FDD_int_Calibrate: Retries exhausted");
312         
313         return 1;
314 }
315
316 /**
317  * \brief Reset a controller
318  * \param Base  Controller base address
319  */
320 int FDD_int_Reset(int Disk)
321 {
322         Uint8   tmp;
323          int    _disk;
324         Uint16  base = FDD_int_GetBase(Disk, &_disk);
325
326         tmp = inb(base + FDC_DOR) & 0xF0;
327         outb( base + FDC_DOR, 0x00 );
328         Time_Delay(1);
329         outb( base + FDC_DOR, tmp | 0x0C );
330
331         FDD_int_SenseInterrupt(base, NULL, NULL);
332
333         outb(base + FDC_CCR, 0x00);     // 500KB/s
334
335         FDD_int_WriteData(base, CMD_SPECIFY);   // Step and Head Load Times
336         FDD_int_WriteData(base, 0xDF);  // Step Rate Time, Head Unload Time (Nibble each)
337         FDD_int_WriteData(base, 0x02);  // Head Load Time >> 1
338
339         // TODO: Recalibrate all present disks
340         FDD_int_Calibrate(Disk);
341         return 0;
342 }
343
344 /**
345  * \brief Write a byte to the FIFO
346  */
347 int FDD_int_WriteData(Uint16 Base, Uint8 Data)
348 {
349         for( int i = 0; i < 100; i ++ )
350         {
351                 if( inb(Base + FDC_MSR) & 0x80 )
352                 {
353                         outb(Base + FDC_FIFO, Data);
354                         return 0;
355                 }
356                 Time_Delay(10);
357         }
358         Log_Error("FDD", "Write timeout");
359         return 1;
360 }
361
362 /**
363  * \brief Read a byte from the FIFO
364  */
365 int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
366 {
367         for( int i = 0; i < 100; i ++ )
368         {
369                 if( inb(Base + FDC_MSR) & 0x80 )
370                 {
371                         Uint8 tmp = inb(Base + FDC_FIFO);
372                         if(Data) *Data = tmp;
373                         return 0;
374                 }
375                 Time_Delay(10);
376         }
377         Log_Error("FDD", "Read timeout");
378         return 1;
379 }
380
381 /**
382  * \brief Acknowledge an interrupt
383  * \param Base  Controller base address
384  * \param ST0   Location to store the ST0 value
385  * \param Cyl   Current cylinder
386  */
387 void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
388 {
389         FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
390         FDD_int_ReadData(Base, ST0);
391         FDD_int_ReadData(Base, Cyl);
392 }
393
394 /**
395  * \brief Start the motor on a disk
396  */
397 int FDD_int_StartMotor(int Disk)
398 {
399          int    _disk;
400         Uint16  base = FDD_int_GetBase(Disk, &_disk);
401         
402         // Clear the motor off timer    
403         Time_RemoveTimer(gaFDD_Disks[Disk].Timer);
404         gaFDD_Disks[Disk].Timer = NULL;
405
406         // Check if the motor is already on
407         if( gaFDD_Disks[Disk].MotorState == MOTOR_ATSPEED )
408                 return 0;
409
410         // Turn motor on
411         outb(base + FDC_DOR, inb(base+FDC_DOR) | (1 << (_disk + 4)));
412
413         // Wait for it to reach speed
414         Time_Delay(MOTOR_ON_DELAY);
415
416         gaFDD_Disks[Disk].MotorState = MOTOR_ATSPEED;
417
418         return 0;
419 }
420
421 /**
422  * \brief Schedule the motor to stop
423  */
424 int FDD_int_StopMotor(int Disk)
425 {
426         if( gaFDD_Disks[Disk].MotorState != MOTOR_ATSPEED )
427                 return 0;
428         if( gaFDD_Disks[Disk].Timer != NULL )
429                 return 0;
430
431         gaFDD_Disks[Disk].Timer = Time_CreateTimer(MOTOR_OFF_DELAY, FDD_int_StopMotorCallback, (void*)(tVAddr)Disk);
432
433         return 0;
434 }
435
436 /**
437  * \brief Actually stop the motor
438  * \param Ptr   Actaully the global disk number
439  */
440 void FDD_int_StopMotorCallback(void *Ptr)
441 {
442          int    Disk = (tVAddr)Ptr;
443          int    _disk;
444         Uint16  base = FDD_int_GetBase(Disk, &_disk);
445
446         gaFDD_Disks[Disk].Timer = NULL;
447         gaFDD_Disks[Disk].MotorState = MOTOR_OFF;
448         
449         outb(base + FDC_DOR, inb(base+FDC_DOR) & ~(1 << (_disk + 4)));
450
451         return ;
452 }
453
454 /**
455  * \brief Converts a global disk number into a controller and drive
456  * \param Disk  Global disk number
457  * \param Drive Destination for controller disk number
458  * \return Controller base address
459  */
460 Uint16 FDD_int_GetBase(int Disk, int *Drive)
461 {
462         if(Drive)       *Drive = Disk & 3;
463         switch(Disk >> 2)
464         {
465         case 0: return 0x3F0;
466         case 1: return 0x370;
467         default:
468                 return 0;
469         }
470 }
471
472 /**
473  * \brief Convert a ST0 error value into a message
474  * \param Fcn   Calling function name
475  * \parma Disk  Global disk number
476  * \param ST0   ST0 Value
477  * \return Boolean failure
478  */
479 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0)
480 {
481         static const char *status_type[] = {
482                 0, "Error", "Invalid", "Drive Error"
483         };
484
485         Log_Debug("FDD", "%s: Disk %i ST0 Status = %s (0x%x & 0xC0 = 0x%x)",
486                 Fcn, Disk, status_type[ST0 >> 6], ST0, ST0 & 0xC0
487                 );
488         return 0;
489 }
490
491 /**
492  * \brief Clear the IRQ fired flag
493  */
494 void FDD_int_ClearIRQ(void)
495 {
496         gbFDD_IRQ6Fired = 0;
497 }
498
499 /**
500  * \brief Wait for an IRQ to fire
501  */
502 int FDD_int_WaitIRQ(void)
503 {
504         while(gbFDD_IRQ6Fired == 0)
505                 Threads_Yield();
506         return 0;
507 }
508
509 /**
510  * \brief IRQ Handler
511  * \param IRQ   IRQ Number (unused)
512  * \param Ptr   Data Pointer (unused)
513  */
514 void FDD_int_IRQHandler(int IRQ, void *Ptr)
515 {
516         gbFDD_IRQ6Fired = 1;
517 }
518

UCC git Repository :: git.ucc.asn.au