/*
    exDOS - Experimental 32 Bit protected mode scripted operating system
    (C) 2005 Jason Hunt
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    Contact: nulluser@gmail.com
    
    File: floppy.c
*/

#include "stdio.h"
#include "system.h"
#include "floppy.h"
#include "interrupt.h"
#include "video.h"

/* Interrupt vector for driver to hook */
#define FLOPPY_INTR_NUM         0x26

/* Floppy drive controller registers */

#define FLOPPY_SRA      0x3f0	// r
#define FLOPPY_SRB      0x3f1	// r
#define FLOPPY_DOR	0x3f2	// w
#define FLOPPY_TDR      0x3f3	// r
#define FLOPPY_MSR 	0x3f4	// w
#define FLOPPY_DSR     	0x3f4	// r/w
#define FLOPPY_FIFO	0x3f5   // r
#define FLOPPY_DIR      0x3f7   // w
#define FLOPPY_CCR      0x3f7   // w

/* Flags */

#define FLOPPY_BUSY      0x10   // Operation in progress

/* Commands */

#define FLOPPY_SIS           0x08   // Sense interrupt status
#define FLOPPY_SPECIFY       0x03   // Specify command
#define FLOPPY_CONFIGURE     0x13   // Configure command
#define FLOPPY_SEEK          0x0f   // Seek command
#define FLOPPY_RECALIBRATE   0x07   // Recalibrate command
#define FLOPPY_READ          0x46   // Read command

/* Parameters */


#define FLOPPY_HEADS           2           // NUmber of heads
#define FLOPPY_SPT             18          // Sectors per track



/* Floppy drive global variables */

volatile bool floppy_done = 0;
volatile byte floppy_result[7];   // Status date from floppy drive
volatile byte floppy_num_result = 0;

volatile int floppy_track = -1; // Set to invalid track number
volatile int floppy_st0 = 0; // Status 0 of floppy_sense_status

volatile byte floppy_fail = 0;

volatile byte floppy_stop= 0;
volatile byte floppy_running = 0;
volatile word floppy_stop_count = 0;



/* Floppy drive interrupt */
void floppy_intr(void)
{
    floppy_done = 1;            // Set flag to indicate that the 
//    puts("Floppy int\n");     // Operation is done
}
/* End of floppy_intr */


/* Initilize the floppy drive controller */
void floppy_init( void )
{
    intr_set_vector(FLOPPY_INTR_NUM, floppy_intr_wrap);    // Install the floppy interrupt

    if (floppy_reset() == FLOPPY_ERROR)    
    {
        puts("Floppy: (init) Unable to setup floppy drive\n");
        return;
    }

    puts("Floppy: driver loaded\n");
}
/* End of floppy init */


/* Called by the timer interrupt */
void floppy_timer( void )
{
    if (floppy_stop)
    {
        floppy_stop_count--;

        if (floppy_stop_count == 0)
        {
         //   puts("Floppy idle\n");
            out_byte(FLOPPY_DOR, 0x0c); // motor 0 off
            floppy_running = 0;
            floppy_stop = 0;
        }
     }
}
/* End of floppy timer */


/* Reset the floppy drive controller to a known state */
int floppy_reset( void )
{
    floppy_fail = 0;
    floppy_track = -1;
    floppy_running = 0;
    floppy_stop = 0;

    out_byte(FLOPPY_DOR, 0);    // Reset the controller
    out_byte(FLOPPY_CCR, 0);     // Set data rate to 500k
    out_byte(FLOPPY_DOR, 0x0c);  // Enable interrutps

    // Wait for interrupt
    if (floppy_wait_done() == FLOPPY_ERROR)
    {
        puts("Floppy: (reset) Reset fail waiting for interrupt\n");
        return(FLOPPY_ERROR);
    }

    byte i = 4;

    // Get status for each drive
    while (i--)
    {
        // Read sense interrupt status
        if (floppy_sense_status() == FLOPPY_ERROR)
        {
            puts("Floppy: (reset) Unable to get floppy interrupt status\n");
            return(FLOPPY_ERROR);
        }
    }

    floppy_write_fifo(FLOPPY_SPECIFY);   // Specify command
    floppy_write_fifo(0xdf);             // SRT = 1.5, HUT = 240
    floppy_write_fifo(0x02);             // HLT = 2
   
    // Seek to track 1
    if (floppy_seek(1) == FLOPPY_ERROR)
    {
        puts("Floppy: (reset) Unable to seek to first track\n");
        return(FLOPPY_ERROR);
    }       

    // Recalibrate the drive
    if (floppy_recalibrate() == FLOPPY_ERROR)
    {
        puts("Floppy: (reset) Unable to recalibrate\n");
        return(FLOPPY_ERROR);
    } 

    return(0);
}
/* End of reset */



/* Read the interrupt status */
int floppy_sense_status( void )
{
    if (floppy_write_fifo(FLOPPY_SIS) == FLOPPY_ERROR)  // Send sense interrupt status command
    {
        puts("Floppy: (sense_status) Floppy sense status write fail\n");
        return(FLOPPY_ERROR);
    }
        
    if ((floppy_st0 = floppy_read_fifo()) == FLOPPY_ERROR) 
    {
        puts("Floppy: (sense_status) Floppy sense status read st0 fail\n");
        return(FLOPPY_ERROR);
    }    

    if ((floppy_track = floppy_read_fifo()) == FLOPPY_ERROR) 
    {
        puts("Floppy: (sense_status) Floppy sense status read fifo fail\n");
        return(FLOPPY_ERROR);
    }    

    return(0);
}
/* End of floppy_sense_status */


/* Wait until the operation is done */
int floppy_wait_done( void )
{
    byte timeout = 200;

   // puts("Wait floppy.");

    while(!floppy_done && timeout--) // Wait for floppy interrupt
        timer_delay(10);     

    if (!timeout)
    {
        puts("Floppy: (wait_done) Timeout waiting for floppy interrupt\n");
        return(FLOPPY_ERROR);
    }
       
    floppy_done = 0;           // Reset floppy interrupt flag
    floppy_num_result = 0;     // Reset result size

    // Read the result phase
    // Make sure that the busy flag is set in main status register
    while ((floppy_num_result  < 7) && 
          (in_byte(FLOPPY_MSR) & FLOPPY_BUSY) && 
           !floppy_fail)
        floppy_result[floppy_num_result++] = floppy_read_fifo();

    return(0);   
}
/* End of floppy_sense_status */



/* Return  true if RQM is high and DIO is low */
//
// RQM is high when contorller is ready
// DIO needs to be LOW for a write
// 
bool floppy_write_fifo_ready( void )
{
    return((in_byte(FLOPPY_MSR) & 0xc0) == 0x80);       
}
/* End of floppy_write_fifo_ready */



/* Return true if both the RQM and DIO bits are high */
//
// RQM is high when contorller is ready
// DIO needs to be HIGH for a read
// 
bool floppy_read_fifo_ready( void )
{
    return((in_byte(FLOPPY_MSR) & 0xc0) == 0xc0);       
}
/* End of floppy_read_fifo_ready */



/* Send a byte to the floppy controller */
int floppy_write_fifo(byte d)
{
   byte timeout = 200;                  //  Try 100 times
   
   // puts("Attempt send\n"); 
    while(timeout--)
    {
        if (floppy_write_fifo_ready())       // Read to send
        {
         //  puts("Send okay\n");
           out_byte(FLOPPY_FIFO, d);    // Send date to FIFO
           return(0);
        }

        timer_delay(1); // Two timer tick delay
    }    
    floppy_fail = 1;
    puts("Floppy: (write_fifo) Unable to send floppy data\n");
    return(FLOPPY_ERROR); // Unable to write data  
}
/* End of floppy_write_fifo */


/* Read a byte from the floppy controller */
int floppy_read_fifo(void)
{
    byte timeout = 200;                         //  Try 100 times

   // puts("Attempt read\n");
    while(timeout--)
    {
        if (floppy_read_fifo_ready())                // Data ready
        {
        //   puts("Read okay\n");
           
           return(in_byte(FLOPPY_FIFO));        // Return the data
        }

        timer_delay(1); // Two timer tick delay
    }   

    floppy_fail = 1;
    puts("Floppy: (read_fifo) Unable to read floppy data\n");
    return(FLOPPY_ERROR); // Unable to read data
}
/* End of floppy_read_fifo */


/* Move the head to a new track */
int floppy_seek(int track_num)
{
    if (floppy_track == track_num) return(0);

    floppy_write_fifo(FLOPPY_SEEK);
    floppy_write_fifo(0);
    floppy_write_fifo(track_num);

    
    if (floppy_wait_done() == FLOPPY_ERROR)
    {
        puts("Floppy: (seek) Unable to seek\n");
        return(FLOPPY_ERROR);
    }

    timer_delay(2); // wait for head to move into position

    if (floppy_sense_status() == FLOPPY_ERROR)
    {
        puts("Floppy: (seek) Unable to get status\n");
        return(FLOPPY_ERROR);
    }

    if (floppy_st0 != 0x20)
    {
        puts("Floppy: (seek) Status register mismatch\n");
        return(FLOPPY_ERROR);
    }

    if (floppy_track != track_num)
    {
        puts("Floppy: (seek) Track number mismatch\n");
        return(FLOPPY_ERROR);
    }

    return(0);
}
/* End of floppy_seek */


/* Start the floppy motor */
void floppy_start_motor( void )
{
    floppy_stop = 0;            // stop and stop sequence
  
    if (floppy_running) 
    {

        //puts("already running\n");
        return; // If it's already running, return
    }

    out_byte(FLOPPY_DOR, 0x1c); // motor 0 on
    timer_delay(100);            // Wait for spin up

    floppy_running = 1;         // Motor is now running
}
/* End of floppy start motor */


/* Stop the floppy motor */
void floppy_stop_motor( void )
{
    floppy_stop_count = 500;     // About one second buffer time
    floppy_stop = 1;            // Start the stop timer
}
/* End of floppy stop */


/* Recalibrate the drive */
int floppy_recalibrate( void )
{
    floppy_start_motor();
   
    if (floppy_write_fifo(FLOPPY_RECALIBRATE) == FLOPPY_ERROR)   // Send command
    {
        puts("Floppy: (recalibrate) Unable to send command\n");
        return(FLOPPY_ERROR);
    }

    if (floppy_write_fifo(0) == FLOPPY_ERROR)   // Drive 0
    {
        puts("Floppy: (recalibrate) Unable to send drive number\n");
        return(FLOPPY_ERROR);
    }

    if (floppy_wait_done() == FLOPPY_ERROR)   // Drive 0
    {
        puts("Floppy: (recalibrate) wait_done fail\n");
        return(FLOPPY_ERROR);
    }

    if (floppy_sense_status() == FLOPPY_ERROR)   // Drive 0
    {
        puts("Floppy: (recalibrate) sense_status_fail\n");
        return(FLOPPY_ERROR);
    }

    floppy_stop_motor ();

    return(0);
}
/* End of recalibrate */


/* Setup Dma for transfer to memory  */
void floppy_setup_dma ( byte * buffer)
{
    out_byte(0x08, 0x14);  // disable dma1

    out_byte(0x0b, 0x56); // set more 2 transfer
    out_byte(0x0c, 0);  // reset latch

    //word r = (dword)buffer;

    // setup target address
    out_byte(0x04, 0);//r & 0xff);
    out_byte(0x04, 0);//(r>> 8) & 0xff);
    out_byte(0x81,0);//( r >> 16) & 0xff);

    out_byte(0x0c, 0); // reset latch
    out_byte(0x05, 0xff); // counter low
    out_byte(0x05, 0x01); // counter high
    out_byte(0x0a, 0x02); // release channel
    out_byte(0x08, 0x10); // enable dma 1
}
/* end of floppy_setup_dma */


/* Read LBA address into buffer */
int floppy_read_lba( word lba, byte *buffer, word count )
{
    byte *dma_buffer = (byte *) 0;

    floppy_setup_dma(dma_buffer);

    floppy_start_motor();

    // convert LBA to CHS

    byte head_num  = lba / FLOPPY_SPT  % FLOPPY_HEADS;
    byte track_num  =  lba / (FLOPPY_SPT  * FLOPPY_HEADS);
    byte sector_num  = lba % FLOPPY_SPT  + 1;

    /*puts("head: "); vga_put_hex(head_num);
    puts(" track: "); vga_put_hex(track_num);
    puts(" sector: "); vga_put_hex(sector_num);
    putchar('\n');*/

// boot: (track 0, head 0 secto 1)

    if (floppy_seek(track_num) == FLOPPY_ERROR)
    {
        floppy_stop_motor();
        puts("Floppy: (read) Unable to seek to track\n");
        return(FLOPPY_ERROR);
    }

    floppy_write_fifo(FLOPPY_READ);

    floppy_write_fifo(head_num << 2);  // Drive zero, head num also here
    floppy_write_fifo(track_num);
    floppy_write_fifo(head_num);
    floppy_write_fifo(sector_num);

    floppy_write_fifo(2);  // 512 bytes

    floppy_write_fifo(80);  // End of track
    floppy_write_fifo(27); // Gap 3 length
    floppy_write_fifo(1); // read one sector 

    if (floppy_wait_done() == FLOPPY_ERROR)
    {
        floppy_stop_motor();
        puts("Floppy: (read) Unable to read data\n");
        return(FLOPPY_ERROR);
    }

    if ((floppy_result[0] & 0xc0) != 0)
    {
        floppy_stop_motor();
        puts("Floppy: (read) Unable to read data\n");
        return(FLOPPY_ERROR);
    }

    floppy_stop_motor();

    word i;  // Replace this crap with a memcpy

    if (count > 512) count = 512;

    for (i = 0; i < count; i++)
        buffer[i] = dma_buffer[i];

    return(0);
}
/* End of floppy_read_lba */

























