/*-
 * Copyright (c) 2002-2018 The UbixOS Project.
 * All rights reserved.
 *
 * This was developed by Christopher W. Olsen for the UbixOS Project.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1) Redistributions of source code must retain the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors in the documentation and/or
 *    other materials provided with the distribution.
 * 3) Neither the name of the UbixOS Project nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <isa/fdc.h>
#include <isa/8259.h>
#include <sys/video.h>
#include <sys/gdt.h>
#include <sys/idt.h>
#include <ubixos/spinlock.h>
#include <sys/io.h>
#include <sys/dma.h>
#include <sys/device.h>
#include <lib/kprintf.h>
#include <lib/kmalloc.h>
#include <devfs/devfs.h>

static struct spinLock fdcSpinLock = SPIN_LOCK_INITIALIZER;

static volatile bool done = FALSE;
static drvGeom geometry = { dg144Heads, dg144Tracks, dg144Spt };
static bool diskChange = FALSE;
static bool motor = FALSE;
static volatile Int8 fdcTrack = 0xff;
static Int8 sr0 = 0;
static volatile int timeOut = 0;
static Int8 statSize = 0;
static Int8 status[7] = { 0 };

unsigned long tbaddr = 0x80000L;

int fdcInit2(struct device_node *dev) {
  dev->devInfo->size = (1024 * 1450);
  return (0x0);
}

int fdc_init() {
  struct device_interface *devInfo = (struct device_interface *) kmalloc(sizeof(struct device_interface));
  setVector(floppyIsr, mVec + 6, (dInt + dPresent));
  irqEnable(6);
  reset();
  devInfo->major = 0x0;
  devInfo->init = (void *) &fdcInit2;
  devInfo->read = fdcRead;
  devInfo->write = fdcWrite;
  devInfo->reset = (void *) reset;

  device_add(0, 'c', devInfo);
  devfs_makeNode("fd0", 'b', 0x0, 0x0);
  return (0x0);
}

asm(
  ".globl floppyIsr      \n"
  "floppyIsr:            \n"
  "  pusha               \n"
  "  push %ss            \n"
  "  push %ds            \n"
  "  push %es            \n"
  "  push %fs            \n"
  "  push %gs            \n"
  "  call floppyIsrhndlr \n"
  "  pop %gs             \n"
  "  pop %fs             \n"
  "  pop %es             \n"
  "  pop %ds             \n"
  "  pop %ss             \n"
  "  popa                \n"
  "  iret                \n"
);

void floppyIsrhndlr() {
  done = TRUE;
  outportByte(0x20, 0x20);
}

void sendByte(int Int8) {
  volatile int msr;
  int tmo;
  for (tmo = 0; tmo < 128; tmo++) {
    msr = inportByte(fdcMsr);
    if ((msr & 0xc0) == 0x80) {
      outportByte(fdcData, Int8);
      return;
    }
    inportByte(0x80);
  }
}

int getByte() {
  volatile int msr;
  int tmo;
  for (tmo = 0; tmo < 128; tmo++) {
    msr = inportByte(fdcMsr);
    if ((msr & 0xd0) == 0xd0) {
      return inportByte(fdcData);
    }
    inportByte(0x80);
  }
  return (-1);
}

bool fdcRw(int block, unsigned char *blockBuffer, bool read, unsigned long numSectors) {
  int head = 0x0, track = 0x0, sector = 0x0, tries = 0x0, copyCount = 0x0;
  unsigned char *p_tbaddr = (unsigned char *) 0x80000;
  unsigned char *p_blockbuff = blockBuffer;
  //kprintf("Block: [%i]\n",block);
  block2Hts(block, &head, &track, &sector);
  motorOn();
  if (!read && blockBuffer) {
    /* copy data from data buffer into track buffer */
    for (copyCount = 0; copyCount < (numSectors * 512); copyCount++) {
      *p_tbaddr = *p_blockbuff;
      p_blockbuff++;
      p_tbaddr++;
    }
  }
  for (tries = 0; tries < 3; tries++) {
    if (inportByte(fdcDir) & 0x80) {
      diskChange = TRUE;
      seek(1); /* clear "disk change" status */
      recalibrate();
      motorOff();
      kprint("FDC: Disk change detected. Trying again.\n");
      return fdcRw(block, blockBuffer, read, numSectors);
    }
    if (!seek(track)) {
      motorOff();
      kprintf("FDC: Error seeking to track [%i]\n", block);
      return FALSE;
    }
    outportByte(fdcCcr, 0);
    if (read) {
      dmaXfer(2, tbaddr, numSectors * 512, FALSE);
      sendByte(cmdRead);
    }
    else {
      dmaXfer(2, tbaddr, numSectors * 512, TRUE);
      sendByte(cmdWrite);
    }
    sendByte(head << 2);
    sendByte(track);
    sendByte(head);
    sendByte(sector);
    sendByte(2); /* 512 Int8s/sector */
    sendByte(geometry.spt);
    if (geometry.spt == dg144Spt) {
      sendByte(dg144Gap3rw); /* gap 3 size for 1.44M read/write */
    }
    else {
      sendByte(dg168Gap3rw); /* 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 fdcRw(block, blockBuffer, read, numSectors);
    }
    if ((status[0] & 0xc0) == 0)
      break; /* worked! outta here! */
    recalibrate(); /* oops, try again... */
  }
  motorOff();
  if (read && blockBuffer) {
    p_blockbuff = blockBuffer;
    p_tbaddr = (unsigned char *) 0x80000;
    for (copyCount = 0x0; copyCount < (numSectors * 512); copyCount++) {
      *p_blockbuff = *p_tbaddr;
      p_blockbuff++;
      p_tbaddr++;
    }
  }
  return (tries != 3);
}

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 motorOn(void) {
  if (motor == FALSE) {
    outportByte(fdcDor, 0x1c);
    motor = TRUE;
  }
}

void motorOff(void) {
  if (motor == TRUE) {
    //outportByte(fdcDor,0x0);
    //outportByte(fdcDor,0x0C);
    motor = FALSE;
  }
}

bool seek(int track) {
  if (fdcTrack == track) {
    return (TRUE);
  }
  sendByte(cmdSeek);
  sendByte(0);
  sendByte(track);
  if (!waitFdc(TRUE)) {
    kprintf("wait fdc failed\n");
    return (FALSE);
  }
  if ((sr0 != 0x20) || (fdcTrack != track)) {
    return (FALSE);
  }
  else {
    return (TRUE);
  }
}

bool readBlock(int block, Int8 *blockBuffer, unsigned long numSectors) {
  int result = 0x0, loop = 0x0;
  if (numSectors > 1) {
    for (loop = 0; loop < numSectors; loop++) {
      result = fdcRw(block + loop, blockBuffer + (loop * 512), TRUE, 1);
    }
    return result;
  }
  return fdcRw(block, blockBuffer, TRUE, numSectors);
}

bool writeBlock(int block, Int8 *blockBuffer, unsigned long numSectors) {
  return fdcRw(block, blockBuffer, FALSE, numSectors);
}

bool waitFdc(bool sensei) {
  timeOut = 50000;
  while (!done && timeOut)
    ;
  statSize = 0;
  while ((statSize < 7) && (inportByte(fdcMsr) & (1 << 4))) {
    status[(int) statSize++] = getByte();
  }
  if (sensei) {
    sendByte(cmdSensei);
    sr0 = getByte();
    fdcTrack = getByte();
  }
  done = FALSE;
  if (!timeOut) {
    if (inportByte(fdcDir) & 0x80) {
      diskChange = TRUE;
    }
    return (FALSE);
  }
  else {
    return (TRUE);
  }
}

void recalibrate(void) {
  motorOn();
  sendByte(cmdRecal);
  sendByte(0);
  waitFdc(TRUE);
  motorOff();
}

void reset(void) {
  outportByte(fdcDor, 0);
  motor = FALSE;
  outportByte(fdcDor, 0x0c);
  done = TRUE;
  waitFdc(TRUE);
  sendByte(cmdSpecify);
  sendByte(0xdf);
  sendByte(0x02);
  seek(1);
  recalibrate();
  diskChange = FALSE;
  return;
}

void fdcRead(void *info, void *baseAddr, uInt32 startSector, uInt32 sectorCount) {
  spinLock(&fdcSpinLock);
  readBlock(startSector, baseAddr, sectorCount);
  spinUnlock(&fdcSpinLock);
  return;
}
void fdcWrite(void *info, void *baseAddr, uInt32 startSector, uInt32 sectorCount) {
  writeBlock(startSector, baseAddr, sectorCount);
  return;
}

/***

 $Log: fdc.c,v $
 Revision 1.1.1.1  2006/06/01 12:46:12  reddawg
 ubix2

 Revision 1.2  2005/10/12 00:13:37  reddawg
 Removed

 Revision 1.1.1.1  2005/09/26 17:24:01  reddawg
 no message

 Revision 1.24  2004/09/07 21:54:38  reddawg
 ok reverted back to old scheduling for now....

 Revision 1.23  2004/09/06 15:13:25  reddawg
 Last commit before FreeBSD 6.0

 Revision 1.22  2004/08/21 20:06:28  reddawg
 ok check out exec.c

 Revision 1.21  2004/08/15 16:47:49  reddawg
 Fixed

 Revision 1.20  2004/08/15 00:33:02  reddawg
 Wow the ide driver works again

 Revision 1.19  2004/08/01 20:40:45  reddawg
 Net related fixes

 Revision 1.18  2004/07/29 21:32:16  reddawg
 My quick lunchs breaks worth of updates....

 Revision 1.17  2004/07/27 08:03:36  reddawg
 chg: stopped push all these extra registers I can't find a good reason as to why I was doing it

 Revision 1.16  2004/07/22 17:32:25  reddawg
 I broke it hopefully

 Revision 1.15  2004/07/21 10:02:09  reddawg
 devfs: renamed functions
 device system: renamed functions
 fdc: fixed a few potential bugs and cleaned up some unused variables
 strol: fixed definition
 endtask: made it print out freepage debug info
 kmalloc: fixed a huge memory leak we had some unhandled descriptor insertion so some descriptors were lost
 ld: fixed a pointer conversion
 file: cleaned up a few unused variables
 sched: broke task deletion
 kprintf: fixed ogPrintf definition

 Revision 1.14  2004/07/17 02:38:31  reddawg
 Fixed a few problems

 Revision 1.13  2004/07/14 12:42:46  reddawg
 fdc: fdcInit to fdc_init
 Changed Startup Routines

 Revision 1.12  2004/06/04 10:19:42  reddawg
 notes: we compile again, thank g-d anyways i was about to cry

 Revision 1.11  2004/05/20 22:51:09  reddawg
 Cleaned Up Warnings

 Revision 1.10  2004/05/19 23:36:52  reddawg
 Bug Fixes

 Revision 1.9  2004/05/19 15:31:27  reddawg
 Fixed up the rest of the references

 Revision 1.8  2004/05/19 15:26:33  reddawg
 Fixed reference issues due to changes in driver subsystem

 Revision 1.7  2004/05/10 02:23:24  reddawg
 Minor Changes To Source Code To Prepare It For Open Source Release

 Revision 1.6  2004/04/30 14:16:04  reddawg
 Fixed all the datatypes to be consistant uInt8,uInt16,uInt32,Int8,Int16,Int32

 Revision 1.5  2004/04/29 15:29:20  reddawg
 Fixed All Running Issues

 Revision 1.4  2004/04/28 02:22:54  reddawg
 This is a fiarly large commit but we are starting to use new driver model
 all around

 Revision 1.3  2004/04/26 22:22:33  reddawg
 DevFS now uses correct size of device

 Revision 1.2  2004/04/22 21:20:05  reddawg
 FDC now adds drives to the devfs

 Revision 1.1.1.1  2004/04/15 12:07:09  reddawg
 UbixOS v1.0

 Revision 1.6  2004/04/13 16:36:33  reddawg
 Changed our copyright, it is all now under a BSD-Style license

 END
 ***/

