3 * - By John Hodge (thePowersGang)
15 #define MOTOR_ON_DELAY 500
16 #define MOTOR_OFF_DELAY 2000
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)
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
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);
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);
53 int FDD_int_StartMotor(int Disk);
54 int FDD_int_StopMotor(int Disk);
55 void FDD_int_StopMotorCallback(void *Ptr);
57 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0);
58 Uint16 FDD_int_GetBase(int Disk, int *Drive);
60 void FDD_int_ClearIRQ(void);
61 int FDD_int_WaitIRQ(void);
62 void FDD_int_IRQHandler(int IRQ, void *Ptr);
66 * \brief Marker for IRQ6
67 * \todo Convert into a semaphore?
71 * \brief Protector for DMA and IRQ6
77 * \brief Set up FDC IO
78 * \return Boolean failure
80 * Registers the IRQ handler and resets the controller
84 // Install IRQ6 Handler
85 IRQ_AddHandler(6, FDD_int_IRQHandler, NULL);
89 // TODO: All controllers
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
102 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer)
106 Uint16 base = FDD_int_GetBase(Disk, &_disk);
107 int cyl = Track >> 1, head = Track & 1;
109 ENTER("iDisk iTrack ibWrite pBuffer", Disk, Track, bWrite, Buffer);
111 Mutex_Acquire( &gFDD_IOMutex );
113 // Initialise DMA for read/write
114 // TODO: Support non 1.44MiB FDs
115 DMA_SetChannel(2, BYTES_PER_TRACK, !bWrite);
119 cmd = CMD_WRITE_DATA | 0xC0;
121 cmd = CMD_READ_DATA | 0xC0;
123 LOG("cmd = 0x%x", cmd);
126 if( FDD_int_SeekToTrack(Disk, Track) ) {
127 Mutex_Release( &gFDD_IOMutex );
131 LOG("Track seek done");
133 for( i = 0; i < 20; i ++ )
135 LOG("Starting motor");
136 FDD_int_StartMotor(Disk);
140 DMA_WriteData(2, BYTES_PER_TRACK, Buffer);
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 - ?
153 LOG("Waiting for IRQ");
156 // No Sense Interrupt
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
169 FDD_int_HandleST0Error(__func__, Disk, st0);
174 Log_Debug("FDD", "Disk %i is not writable", Disk);
175 Mutex_Release( &gFDD_IOMutex );
181 Log_Debug("FDD", "FDD_int_ReadWriteTrack: Drive not ready");
187 Log_Debug("FDD", "FDD_int_ReadWriteTrack: End of cylinder");
191 if( st1 & (0x20|0x10|0x04|0x01) ) {
192 Log_Debug("FDD", "FDD_int_ReadWriteTrack: st1 = 0x%x", st1);
196 if( st2 & (0x40|0x20|0x10|0x04|0x01) ) {
197 Log_Debug("FDD", "FDD_int_ReadWriteTrack: st2 = 0x%x", st2);
202 Log_Debug("FDD", "Wanted bps = 2 (512), got %i", bps);
208 DMA_ReadData(2, BYTES_PER_TRACK, Buffer);
210 LOG("All data done");
211 FDD_int_StopMotor(Disk);
212 Mutex_Release( &gFDD_IOMutex );
217 Log_Debug("FDD", "%i retries exhausted", i);
218 FDD_int_StopMotor(Disk);
219 Mutex_Release( &gFDD_IOMutex );
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
230 int FDD_int_SeekToTrack(int Disk, int Track)
232 Uint8 st0=0, res_cyl=0;
235 Uint16 base = FDD_int_GetBase(Disk, &_disk);;
237 ENTER("iDisk iTrack", Disk, Track);
242 LOG("cyl = %i, head = %i", cyl, head);
244 FDD_int_StartMotor(Disk);
246 for( int i = 0; i < 10; i ++ )
248 LOG("Sending command");
250 FDD_int_WriteData(base, CMD_SEEK);
251 FDD_int_WriteData(base, (head << 2) + _disk);
252 FDD_int_WriteData(base, cyl);
254 LOG("Waiting for IRQ");
256 FDD_int_SenseInterrupt(base, &st0, &res_cyl);
260 FDD_int_HandleST0Error(__func__, Disk, st0);
264 if( res_cyl == cyl ) {
265 FDD_int_StopMotor(Disk);
271 Log_Error("FDD", "FDD_int_SeekToTrack: 10 retries exhausted\n");
272 FDD_int_StopMotor(Disk);
278 * \brief Calibrate a drive
279 * \param Disk Global disk number
281 int FDD_int_Calibrate(int Disk)
284 Uint16 base = FDD_int_GetBase(Disk, &_disk);
285 FDD_int_StartMotor(Disk);
287 for( int i = 0; i < 10; i ++ )
289 Uint8 st0=0, cyl = -1;
292 FDD_int_WriteData(base, CMD_RECALIBRATE);
293 FDD_int_WriteData(base, _disk);
297 FDD_int_SenseInterrupt(base, &st0, &cyl);
300 FDD_int_HandleST0Error(__func__, Disk, st0);
306 FDD_int_StopMotor(Disk);
311 Log_Error("FDD", "FDD_int_Calibrate: Retries exhausted");
317 * \brief Reset a controller
318 * \param Base Controller base address
320 int FDD_int_Reset(int Disk)
324 Uint16 base = FDD_int_GetBase(Disk, &_disk);
326 tmp = inb(base + FDC_DOR) & 0xF0;
327 outb( base + FDC_DOR, 0x00 );
329 outb( base + FDC_DOR, tmp | 0x0C );
331 FDD_int_SenseInterrupt(base, NULL, NULL);
333 outb(base + FDC_CCR, 0x00); // 500KB/s
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
339 // TODO: Recalibrate all present disks
340 FDD_int_Calibrate(Disk);
345 * \brief Write a byte to the FIFO
347 int FDD_int_WriteData(Uint16 Base, Uint8 Data)
349 for( int i = 0; i < 100; i ++ )
351 if( inb(Base + FDC_MSR) & 0x80 )
353 outb(Base + FDC_FIFO, Data);
358 Log_Error("FDD", "Write timeout");
363 * \brief Read a byte from the FIFO
365 int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
367 for( int i = 0; i < 100; i ++ )
369 if( inb(Base + FDC_MSR) & 0x80 )
371 Uint8 tmp = inb(Base + FDC_FIFO);
372 if(Data) *Data = tmp;
377 Log_Error("FDD", "Read timeout");
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
387 void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
389 FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
390 FDD_int_ReadData(Base, ST0);
391 FDD_int_ReadData(Base, Cyl);
395 * \brief Start the motor on a disk
397 int FDD_int_StartMotor(int Disk)
400 Uint16 base = FDD_int_GetBase(Disk, &_disk);
402 // Clear the motor off timer
403 Time_RemoveTimer(gaFDD_Disks[Disk].Timer);
404 gaFDD_Disks[Disk].Timer = NULL;
406 // Check if the motor is already on
407 if( gaFDD_Disks[Disk].MotorState == MOTOR_ATSPEED )
411 outb(base + FDC_DOR, inb(base+FDC_DOR) | (1 << (_disk + 4)));
413 // Wait for it to reach speed
414 Time_Delay(MOTOR_ON_DELAY);
416 gaFDD_Disks[Disk].MotorState = MOTOR_ATSPEED;
422 * \brief Schedule the motor to stop
424 int FDD_int_StopMotor(int Disk)
426 if( gaFDD_Disks[Disk].MotorState != MOTOR_ATSPEED )
428 if( gaFDD_Disks[Disk].Timer != NULL )
431 gaFDD_Disks[Disk].Timer = Time_CreateTimer(MOTOR_OFF_DELAY, FDD_int_StopMotorCallback, (void*)(tVAddr)Disk);
437 * \brief Actually stop the motor
438 * \param Ptr Actaully the global disk number
440 void FDD_int_StopMotorCallback(void *Ptr)
442 int Disk = (tVAddr)Ptr;
444 Uint16 base = FDD_int_GetBase(Disk, &_disk);
446 gaFDD_Disks[Disk].Timer = NULL;
447 gaFDD_Disks[Disk].MotorState = MOTOR_OFF;
449 outb(base + FDC_DOR, inb(base+FDC_DOR) & ~(1 << (_disk + 4)));
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
460 Uint16 FDD_int_GetBase(int Disk, int *Drive)
462 if(Drive) *Drive = Disk & 3;
465 case 0: return 0x3F0;
466 case 1: return 0x370;
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
479 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0)
481 static const char *status_type[] = {
482 0, "Error", "Invalid", "Drive Error"
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
492 * \brief Clear the IRQ fired flag
494 void FDD_int_ClearIRQ(void)
500 * \brief Wait for an IRQ to fire
502 int FDD_int_WaitIRQ(void)
504 while(gbFDD_IRQ6Fired == 0)
511 * \param IRQ IRQ Number (unused)
512 * \param Ptr Data Pointer (unused)
514 void FDD_int_IRQHandler(int IRQ, void *Ptr)