/**************************************************************************************
 Copyright (c) 2002
      The UbixOS Project

 $Id: fdc.c,v 1.1 2002/04/27 12:21:54 reddawg Exp $
**************************************************************************************/

#include <ubixos/io.h>
#include <ubixos/dma.h>
#include <ubixos/video.h>
#include <ubixos/gdt.h>
#include <ubixos/idt.h>
#include <ubixos/8259.h>
#include <drivers/fdc.h>

/* globals */
static volatile BOOL done = FALSE;
static BOOL dchange = FALSE;
static BOOL motor = FALSE;
static int mtick = 0;
static volatile int tmout = 0;
static BYTE status[7] = { 0 };
static BYTE statsz = 0;
static BYTE sr0 = 0;
static BYTE fdc_track = 0xff;
static DrvGeom geometry = { DG144_HEADS,DG144_TRACKS,DG144_SPT };

unsigned long tbaddr = 0x80000L;    /* physical address of track buffer located below 1M */

/* prototypes */
extern void floppy_ISR();
extern void _int1c();

void sendbyte(int byte);
int getbyte();
void irq6(void);
void int1c(void);
BOOL waitfdc(BOOL sensei);
BOOL fdc_rw(int block,BYTE *blockbuff,BOOL read,unsigned long nosectors);

/* helper functions */


void initFloppy(void) {
  int i;
  set_vector(floppy_ISR, M_VEC+6, (D_INT+D_PRESENT+D_DPL1));
  enable_irq(6);
  set_vector(_int1c, 0x1c, (D_INT+D_PRESENT+D_DPL1));
  reset();
  sendbyte(CMD_VERSION);
  i = getbyte();
  kprint("Initialising Floppy driver...  ");
  if (i == 0x80) {
    kprint("NEC765 controller found\n");
    }
  else {
    kprint("Enhanced controller found\n");
    }
  }

/* sendbyte() routine from intel manual */
void sendbyte(int byte) {
  volatile int msr;
  int tmo;
  for (tmo = 0;tmo < 128;tmo++) {
    msr = inportb(FDC_MSR);
    if ((msr & 0xc0) == 0x80) {
      outportb(FDC_DATA,byte);
      return;
      }
    inportb(0x80);   /* delay */
    }
  }

/* getbyte() routine from intel manual */
int getbyte() {
  volatile int msr;
  int tmo;
  for (tmo = 0;tmo < 128;tmo++) {
    msr = inportb(FDC_MSR);
    if ((msr & 0xd0) == 0xd0) {
      return inportb(FDC_DATA);
      }
    inportb(0x80);   /* delay */
    }
  return -1;   /* read timeout */
  }

BOOL waitfdc(BOOL sensei) {
  tmout = 50000;   /* set timeout to 1 second */
  while (!done && tmout);
  statsz = 0;
  while ((statsz < 7) && (inportb(FDC_MSR) & (1<<4))) {
    status[statsz++] = getbyte();
    }
  if (sensei) {
    sendbyte(CMD_SENSEI);
    sr0 = getbyte();
    fdc_track = getbyte();
    }
  done = FALSE;
  if (!tmout) {
    if (inportb(FDC_DIR) & 0x80) {
      dchange = TRUE;
      }
    return FALSE;
    }
  else {
    return TRUE;
    }
  }

/* This is the IRQ6 handler */
void irq6(void) {
  /* signal operation finished */
  done = TRUE;
  /* EOI the PIC */
  outportb(0x20,0x20);
  }

/* This is the timer (int 1ch) handler */
void int1c(void) {
  if (tmout) --tmout;     /* bump timeout */
  if (mtick > 0)
     --mtick;
  else if (!mtick && motor) {
    outportb(FDC_DOR,0x0c);  /* turn off floppy motor */
    motor = FALSE;
    }
  }

void block2hts(int block,int *head,int *track,int *sector) {
  *head = (block % (geometry.spt * geometry.heads)) / (geometry.spt);
  *track = block / (geometry.spt * geometry.heads);
  *sector = block % geometry.spt + 1;
  }

void reset(void) {
  outportb(FDC_DOR,0);
  mtick = 0;
  motor = FALSE;
  outportb(FDC_DRS,0);
  outportb(FDC_DOR,0x0c);
  done = TRUE;
  waitfdc(TRUE);
  sendbyte(CMD_SPECIFY);
  sendbyte(0xdf);  /* SRT = 3ms, HUT = 240ms */
  sendbyte(0x02);  /* HLT = 16ms, ND = 0 */
  seek(1);
  recalibrate();
  dchange = FALSE;
  }

BOOL diskchange(void) {
  return dchange;
 }

void motoron(void) {
  if (!motor) {
    mtick = -1;     /* stop motor kill countdown */
    outportb(FDC_DOR,0x1c);
//    delay(500); /* delay 500ms for motor to spin up */
    motor = TRUE;
    }
  }

void motoroff(void) {
  if (motor) {
    mtick = 13500;   /* start motor kill countdown: 36 ticks ~ 2s */
    }
  }

void recalibrate(void) {
  motoron();
  sendbyte(CMD_RECAL);
  sendbyte(0);
  waitfdc(TRUE);
  motoroff();
  }

BOOL seek(int track) {
  if (fdc_track == track)  /* already there? */
    return TRUE;
  sendbyte(CMD_SEEK);
  sendbyte(0);
  sendbyte(track);
  if (!waitfdc(TRUE))
    return FALSE;     /* timeout! */
//  delay(15);
  if ((sr0 != 0x20) || (fdc_track != track))
    return FALSE;
  else
    return TRUE;
  }

BOOL log_disk(DrvGeom *g) {
  reset();
  geometry.heads = DG168_HEADS;
  geometry.tracks = DG168_TRACKS;
  geometry.spt = DG168_SPT;
  if (read_block(20,NULL,1)) {
    if (g) {
      g->heads = geometry.heads;
      g->tracks = geometry.tracks;
      g->spt = geometry.spt;
      }
    return TRUE;
    }
  geometry.heads = DG144_HEADS;
  geometry.tracks = DG144_TRACKS;
  geometry.spt = DG144_SPT;
  if (read_block(17,NULL,1)) {
    if (g) {
      g->heads = geometry.heads;
      g->tracks = geometry.tracks;
      g->spt = geometry.spt;
      }
    return TRUE;
    }
  return FALSE;
  }

BOOL read_block(int block,BYTE *blockbuff, unsigned long nosectors) {
  int track=0, sector=0, head=0, track2=0, result=0, loop=0;
  block2hts(block, &head, &track, &sector);
  block2hts(block+nosectors, &head, &track2, &sector);
  if (track!=track2) {
    for (loop=0; loop<nosectors; loop++) {
      result = fdc_rw(block+loop, blockbuff+(loop*512), TRUE, 1);
      }
    return result;
    }
  return fdc_rw(block,blockbuff,TRUE,nosectors);
  }

BOOL write_block(int block,BYTE *blockbuff, unsigned long nosectors) {
  return fdc_rw(block,blockbuff,FALSE, nosectors);
  }

BOOL fdc_rw(int block,BYTE *blockbuff,BOOL read,unsigned long nosectors) {
  int head,track,sector,tries, copycount = 0;
  unsigned char *p_tbaddr = (char *)0x80000;
  unsigned char *p_blockbuff = blockbuff;
  block2hts(block,&head,&track,&sector);
  motoron();
  if (!read && blockbuff) {
    /* copy data from data buffer into track buffer */
    for (copycount=0; copycount<(nosectors*512); copycount++) {
      *p_tbaddr = *p_blockbuff;
      p_blockbuff++;
      p_tbaddr++;
      }
    }
  for (tries = 0;tries < 3;tries++) {
    if (inportb(FDC_DIR) & 0x80) {
      dchange = TRUE;
      seek(1);  /* clear "disk change" status */
      recalibrate();
      motoroff();
      kprint("FDC: Disk change detected. Trying again.\n");
      return fdc_rw(block, blockbuff, read, nosectors);
      }
    if (!seek(track)) {
      motoroff();
      kprint("FDC: Error seeking to track\n");
      return FALSE;
      }
    outportb(FDC_CCR,0);
    if (read) {
      dma_xfer(2,tbaddr,nosectors*512,FALSE);
      sendbyte(CMD_READ);
      }
    else {
      dma_xfer(2,tbaddr,nosectors*512,TRUE);
      sendbyte(CMD_WRITE);
      }
    sendbyte(head << 2);
    sendbyte(track);
    sendbyte(head);
    sendbyte(sector);
    sendbyte(2);               /* 512 bytes/sector */
    sendbyte(geometry.spt);
    if (geometry.spt == DG144_SPT) {
      sendbyte(DG144_GAP3RW);  /* gap 3 size for 1.44M read/write */
      }
    else {
      sendbyte(DG168_GAP3RW);  /* gap 3 size for 1.68M read/write */
      }
    sendbyte(0xff);            /* DTL = unused */
    if (!waitfdc(TRUE)) {
      kprint("Timed out, trying operation again after reset()\n");
      reset();
      return fdc_rw(block, blockbuff, read, nosectors);
      }
    if ((status[0] & 0xc0) == 0) break;   /* worked! outta here! */
    recalibrate();  /* oops, try again... */
    }
  motoroff();
  if (read && blockbuff) {
    p_blockbuff = blockbuff;
    p_tbaddr = (char *) 0x80000;
    for (copycount=0; copycount<(nosectors*512); copycount++) {
      *p_blockbuff = *p_tbaddr;
      p_blockbuff++;
      p_tbaddr++;
      }
    }
  return (tries != 3);
  }

/* this formats a track, given a certain geometry */
BOOL format_track(BYTE track,DrvGeom *g) {
  int i,h,r,r_id,split;
  BYTE tmpbuff[256];
  unsigned char *p_tbaddr = (char *)0x8000;
  unsigned int copycount = 0;

  if (g->spt != DG144_SPT && g->spt != DG168_SPT) {
    return FALSE;
    }
  motoron();
  outportb(FDC_CCR,0);
  seek(track);  /* seek to track */
  split = g->spt / 2;
  if (g->spt & 1) {
    split++;
    }
  for (h = 0;h < g->heads;h++) {
    if (inportb(FDC_DIR) & 0x80) {
      dchange = TRUE;
      seek(1);  /* clear "disk change" status */
      recalibrate();
      motoroff();
      return FALSE;
      }
    i = 0;   /* reset buffer index */
    for (r = 0;r < g->spt;r++) {
      r_id = r / 2 + 1;
      if (r & 1) {
        r_id += split;
        }
      if (h & 1) {
        r_id -= 2;
        if (r_id < 1) r_id += g->spt;
        }
      if (track & 1) {
        r_id -= g->spt / 2;
        if (r_id < 1) {
          r_id += g->spt;
          }
	}
      tmpbuff[i++] = track;
      tmpbuff[i++] = h;
      tmpbuff[i++] = r_id;
      tmpbuff[i++] = 2;
      }
    for(copycount = 0; copycount<i; copycount++) {
      *p_tbaddr = tmpbuff[copycount];
      p_tbaddr++;
      }
    dma_xfer(2,tbaddr,i,TRUE);
    sendbyte(CMD_FORMAT);
    sendbyte(h << 2);
    sendbyte(2);
    sendbyte(g->spt);
    if (g->spt == DG144_SPT) {
      sendbyte(DG144_GAP3FMT);    /* gap3 size for 1.44M format */
      }
    else {
      sendbyte(DG168_GAP3FMT);    /* gap3 size for 1.68M format */
      }
    sendbyte(0);     /* filler byte */
    if (!waitfdc(FALSE)) {
      return FALSE;
      }
    if (status[0] & 0xc0) {
      motoroff();
      return FALSE;
      }
    }
  motoroff();
  return TRUE;
  }


asm (
  ".globl _int1c        \n"
  "_int1c:              \n"
  "   pusha               \n" /* Save all registers               */
  "   pushw %ds           \n" /* Set up the data segment          */
  "   pushw %es           \n"
  "   pushw %ss           \n" /* Note that ss is always valid     */
  "   pushw %ss           \n"
  "   popw %ds            \n"
  "   popw %es            \n"
  "                       \n"
  "   call int1c          \n"
  "                       \n"
  "   popw %es            \n"
  "   popw %ds            \n" /* Restore registers                */
  "   popa                \n"
  "   iret                \n" /* Exit interrupt                   */
  );

asm (
  ".globl floppy_ISR        \n"
  "floppy_ISR:              \n"
  "   pusha               \n" /* Save all registers               */
  "   pushw %ds           \n" /* Set up the data segment          */
  "   pushw %es           \n"
  "   pushw %ss           \n" /* Note that ss is always valid     */
  "   pushw %ss           \n"
  "   popw %ds            \n"
  "   popw %es            \n"
  "                       \n"
  "   call irq6           \n"
  "                       \n"
  "   popw %es            \n"
  "   popw %ds            \n" /* Restore registers                */
  "   popa                \n"
  "   iret                \n" /* Exit interrupt                   */
  );
