3 * - By John Hodge (thePowersGang)
14 #define MOTOR_ON_DELAY 500
15 #define MOTOR_OFF_DELAY 2000
25 FDC_DOR = 0x02, // Digital Output Register
26 FDC_MSR = 0x04, // Master Status Register (Read Only)
27 FDC_FIFO = 0x05, // FIFO port
28 FDC_CCR = 0x07 // Configuration Control Register (write only)
33 CMD_SPECIFY = 3, // Specify parameters
34 CMD_WRITE_DATA = 5, // Write Data
35 CMD_READ_DATA = 6, // Read Data
36 CMD_RECALIBRATE = 7, // Recalibrate a drive
37 CMD_SENSE_INTERRUPT = 8, // Sense (Ack) an interrupt
38 CMD_SEEK = 15, // Seek to a track
42 int FDD_SetupIO(void);
43 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer);
44 int FDD_int_SeekToTrack(int Disk, int Track);
45 int FDD_int_Calibrate(int Disk);
46 int FDD_int_Reset(int Disk);
48 int FDD_int_WriteData(Uint16 Base, Uint8 Data);
49 int FDD_int_ReadData(Uint16 Base, Uint8 *Data);
50 void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl);
52 int FDD_int_StartMotor(int Disk);
53 int FDD_int_StopMotor(int Disk);
54 void FDD_int_StopMotorCallback(void *Ptr);
56 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0);
57 Uint16 FDD_int_GetBase(int Disk, int *Drive);
59 void FDD_int_ClearIRQ(void);
60 int FDD_int_WaitIRQ(void);
61 void FDD_int_IRQHandler(int IRQ, void *Ptr);
65 * \brief Marker for IRQ6
66 * \todo Convert into a semaphore?
70 * \brief Protector for DMA and IRQ6
76 * \brief Set up FDC IO
77 * \return Boolean failure
79 * Registers the IRQ handler and resets the controller
83 // Install IRQ6 Handler
84 IRQ_AddHandler(6, FDD_int_IRQHandler, NULL);
88 // TODO: All controllers
94 * \brief Read/Write data from/to a disk
95 * \param Disk Global disk number
96 * \param Track Track number (Cyl*2+Head)
97 * \param bWrite Toggle write mode
98 * \param Buffer Destination/Source buffer
99 * \return Boolean failure
101 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer)
105 Uint16 base = FDD_int_GetBase(Disk, &_disk);
106 int cyl = Track >> 1, head = Track & 1;
108 ENTER("iDisk iTrack ibWrite pBuffer", Disk, Track, bWrite, Buffer);
110 Mutex_Acquire( &gFDD_IOMutex );
112 // Initialise DMA for read/write
113 // TODO: Support non 1.44MiB FDs
114 DMA_SetChannel(2, BYTES_PER_TRACK, !bWrite);
118 cmd = CMD_WRITE_DATA | 0xC0;
120 cmd = CMD_READ_DATA | 0xC0;
122 LOG("cmd = 0x%x", cmd);
125 if( FDD_int_SeekToTrack(Disk, Track) ) {
126 Mutex_Release( &gFDD_IOMutex );
130 LOG("Track seek done");
132 for( i = 0; i < 20; i ++ )
134 LOG("Starting motor");
135 FDD_int_StartMotor(Disk);
139 DMA_WriteData(2, BYTES_PER_TRACK, Buffer);
141 LOG("Sending command stream");
142 FDD_int_WriteData(base, cmd);
143 FDD_int_WriteData(base, (head << 2) | _disk);
144 FDD_int_WriteData(base, cyl);
145 FDD_int_WriteData(base, head);
146 FDD_int_WriteData(base, 1); // First Sector
147 FDD_int_WriteData(base, 2); // Bytes per sector (128*2^n)
148 FDD_int_WriteData(base, 18); // 18 tracks (full disk) - TODO: Non 1.44
149 FDD_int_WriteData(base, 0x1B); // Gap length - TODO: again
150 FDD_int_WriteData(base, 0xFF); // Data length - ?
152 LOG("Waiting for IRQ");
155 // No Sense Interrupt
157 LOG("Reading result");
158 Uint8 st0=0, st1=0, st2=0, bps=0;
159 FDD_int_ReadData(base, &st0);
160 FDD_int_ReadData(base, &st1); // st1
161 FDD_int_ReadData(base, &st2); // st2
162 FDD_int_ReadData(base, NULL); // rcy - Mutilated Cyl
163 FDD_int_ReadData(base, NULL); // rhe - Mutilated Head
164 FDD_int_ReadData(base, NULL); // rse - Mutilated sector
165 FDD_int_ReadData(base, &bps); // bps - Should be the same as above
168 FDD_int_HandleST0Error(__func__, Disk, st0);
173 Log_Debug("FDD", "Disk %i is not writable", Disk);
174 Mutex_Release( &gFDD_IOMutex );
180 Log_Debug("FDD", "FDD_int_ReadWriteTrack: Drive not ready");
186 Log_Debug("FDD", "FDD_int_ReadWriteTrack: End of cylinder");
190 if( st1 & (0x20|0x10|0x04|0x01) ) {
191 Log_Debug("FDD", "FDD_int_ReadWriteTrack: st1 = 0x%x", st1);
195 if( st2 & (0x40|0x20|0x10|0x04|0x01) ) {
196 Log_Debug("FDD", "FDD_int_ReadWriteTrack: st2 = 0x%x", st2);
201 Log_Debug("FDD", "Wanted bps = 2 (512), got %i", bps);
207 DMA_ReadData(2, BYTES_PER_TRACK, Buffer);
209 LOG("All data done");
210 FDD_int_StopMotor(Disk);
211 Mutex_Release( &gFDD_IOMutex );
216 Log_Debug("FDD", "%i retries exhausted", i);
217 FDD_int_StopMotor(Disk);
218 Mutex_Release( &gFDD_IOMutex );
224 * \brief Seek to a specific track
225 * \param Disk Global disk number
226 * \param Track Track number (Cyl*2+Head)
227 * \return Boolean failure
229 int FDD_int_SeekToTrack(int Disk, int Track)
231 Uint8 st0=0, res_cyl=0;
234 Uint16 base = FDD_int_GetBase(Disk, &_disk);;
236 ENTER("iDisk iTrack", Disk, Track);
241 LOG("cyl = %i, head = %i", cyl, head);
243 FDD_int_StartMotor(Disk);
245 for( int i = 0; i < 10; i ++ )
247 LOG("Sending command");
249 FDD_int_WriteData(base, CMD_SEEK);
250 FDD_int_WriteData(base, (head << 2) + _disk);
251 FDD_int_WriteData(base, cyl);
253 LOG("Waiting for IRQ");
255 FDD_int_SenseInterrupt(base, &st0, &res_cyl);
259 FDD_int_HandleST0Error(__func__, Disk, st0);
263 if( res_cyl == cyl ) {
264 FDD_int_StopMotor(Disk);
270 Log_Error("FDD", "FDD_int_SeekToTrack: 10 retries exhausted\n");
271 FDD_int_StopMotor(Disk);
277 * \brief Calibrate a drive
278 * \param Disk Global disk number
280 int FDD_int_Calibrate(int Disk)
283 Uint16 base = FDD_int_GetBase(Disk, &_disk);
284 FDD_int_StartMotor(Disk);
286 for( int i = 0; i < 10; i ++ )
288 Uint8 st0=0, cyl = -1;
291 FDD_int_WriteData(base, CMD_RECALIBRATE);
292 FDD_int_WriteData(base, _disk);
296 FDD_int_SenseInterrupt(base, &st0, &cyl);
299 FDD_int_HandleST0Error(__func__, Disk, st0);
305 FDD_int_StopMotor(Disk);
310 Log_Error("FDD", "FDD_int_Calibrate: Retries exhausted");
316 * \brief Reset a controller
317 * \param Base Controller base address
319 int FDD_int_Reset(int Disk)
323 Uint16 base = FDD_int_GetBase(Disk, &_disk);
325 tmp = inb(base + FDC_DOR) & 0xF0;
326 outb( base + FDC_DOR, 0x00 );
328 outb( base + FDC_DOR, tmp | 0x0C );
330 FDD_int_SenseInterrupt(base, NULL, NULL);
332 outb(base + FDC_CCR, 0x00); // 500KB/s
334 FDD_int_WriteData(base, CMD_SPECIFY); // Step and Head Load Times
335 FDD_int_WriteData(base, 0xDF); // Step Rate Time, Head Unload Time (Nibble each)
336 FDD_int_WriteData(base, 0x02); // Head Load Time >> 1
338 // TODO: Recalibrate all present disks
339 FDD_int_Calibrate(Disk);
344 * \brief Write a byte to the FIFO
346 int FDD_int_WriteData(Uint16 Base, Uint8 Data)
348 for( int i = 0; i < 100; i ++ )
350 if( inb(Base + FDC_MSR) & 0x80 )
352 outb(Base + FDC_FIFO, Data);
357 Log_Error("FDD", "Write timeout");
362 * \brief Read a byte from the FIFO
364 int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
366 for( int i = 0; i < 100; i ++ )
368 if( inb(Base + FDC_MSR) & 0x80 )
370 Uint8 tmp = inb(Base + FDC_FIFO);
371 if(Data) *Data = tmp;
376 Log_Error("FDD", "Read timeout");
381 * \brief Acknowledge an interrupt
382 * \param Base Controller base address
383 * \param ST0 Location to store the ST0 value
384 * \param Cyl Current cylinder
386 void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
388 FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
389 FDD_int_ReadData(Base, ST0);
390 FDD_int_ReadData(Base, Cyl);
394 * \brief Start the motor on a disk
396 int FDD_int_StartMotor(int Disk)
399 Uint16 base = FDD_int_GetBase(Disk, &_disk);
401 // Clear the motor off timer
402 Time_RemoveTimer(gaFDD_Disks[Disk].Timer);
403 gaFDD_Disks[Disk].Timer = -1;
405 // Check if the motor is already on
406 if( gaFDD_Disks[Disk].MotorState == MOTOR_ATSPEED )
410 outb(base + FDC_DOR, inb(base+FDC_DOR) | (1 << (_disk + 4)));
412 // Wait for it to reach speed
413 Time_Delay(MOTOR_ON_DELAY);
415 gaFDD_Disks[Disk].MotorState = MOTOR_ATSPEED;
421 * \brief Schedule the motor to stop
423 int FDD_int_StopMotor(int Disk)
425 if( gaFDD_Disks[Disk].MotorState != MOTOR_ATSPEED )
427 if( gaFDD_Disks[Disk].Timer != -1 )
430 gaFDD_Disks[Disk].Timer = Time_CreateTimer(MOTOR_OFF_DELAY, FDD_int_StopMotorCallback, (void*)(tVAddr)Disk);
436 * \brief Actually stop the motor
437 * \param Ptr Actaully the global disk number
439 void FDD_int_StopMotorCallback(void *Ptr)
441 int Disk = (tVAddr)Ptr;
443 Uint16 base = FDD_int_GetBase(Disk, &_disk);
445 gaFDD_Disks[Disk].Timer = -1;
446 gaFDD_Disks[Disk].MotorState = MOTOR_OFF;
448 outb(base + FDC_DOR, inb(base+FDC_DOR) & ~(1 << (_disk + 4)));
454 * \brief Converts a global disk number into a controller and drive
455 * \param Disk Global disk number
456 * \param Drive Destination for controller disk number
457 * \return Controller base address
459 Uint16 FDD_int_GetBase(int Disk, int *Drive)
461 if(Drive) *Drive = Disk & 3;
464 case 0: return 0x3F0;
465 case 1: return 0x370;
472 * \brief Convert a ST0 error value into a message
473 * \param Fcn Calling function name
474 * \parma Disk Global disk number
475 * \param ST0 ST0 Value
476 * \return Boolean failure
478 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0)
480 static const char *status_type[] = {
481 0, "Error", "Invalid", "Drive Error"
484 Log_Debug("FDD", "%s: Disk %i ST0 Status = %s (0x%x & 0xC0 = 0x%x)",
485 Fcn, Disk, status_type[ST0 >> 6], ST0, ST0 & 0xC0
491 * \brief Clear the IRQ fired flag
493 void FDD_int_ClearIRQ(void)
499 * \brief Wait for an IRQ to fire
501 int FDD_int_WaitIRQ(void)
503 while(gbFDD_IRQ6Fired == 0)
510 * \param IRQ IRQ Number (unused)
511 * \param Ptr Data Pointer (unused)
513 void FDD_int_IRQHandler(int IRQ, void *Ptr)