/*
 *  linux/kernel/blk_drv/floppy.c
 *
 *  Copyright (C) 1993  Greg Harp
 *  Atari Support by Bj”rn Brauel
 *
 */

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/fdreg.h>
#include <linux/fd.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <asm/system.h>

#include <linux/atarihw.h>
#include <linux/atariints.h>

#define MAJOR_NR FLOPPY_MAJOR
#include "blk.h"

/*
 *  Defines
 */
#define MAX_SECTORS	22
#define RAW_BUF 	900000

/*
 *  Error codes
 */
#define FD_OK		0	/* operation succeeded */
#define FD_ERROR	-1	/* general error (seek, read, write, etc) */
#define FD_NOUNIT	1	/* unit does not exist */
#define FD_UNITBUSY	2	/* unit already active */
#define FD_NOTACTIVE	3	/* unit is not active */
#define FD_NOTREADY	4	/* unit is not ready (motor not on/no disk) */

/*
 *  Floppy ID values
 */
#define FD_NODRIVE	0x00000000  /* response when no unit is present */
#define FD_HD_3 	0xaaaaaaaa  /* high-density 3.5" (1760K) drive */



static struct fd_drive_type drive_types[] = {
/*  code	name	   tr he   rdsz   wrsz sm pc1 pc2    sd     st    st*/
 { FD_HD_3,	"HD 3.5", 160, 2, 25000, 25000, 2, 80,161, 3000, 18000, 1000},
 { FD_NODRIVE, "No Drive",  0, 0,     0,     0, 0,  0,  0,    0,     0,    0}
};
static int num_dr_types = sizeof(drive_types) / sizeof(drive_types[0]);

static struct fd_data_type data_types[] = {
  { "Atari", 18 }
};
static int num_da_types = sizeof(data_types) / sizeof(data_types[0]);

#if 0
  {2880, 18,   2, 80, "IBM High Density 3.5"},      /* 1.44M */
#endif

/* current info on each unit */
static struct floppy_struct unit[FD_MAX_UNITS];

/* track buffer */
int savedtrack = -1;
unsigned char trackdata[512];

/*
 * These are global variables, as that's the easiest way to give
 * information to interrupts. They are the data used for the current
 * request.
 */
char block_flag = 0;
int selected = 0;
struct wait_queue *wait_on_floppy_select = NULL;
struct wait_queue *wait_fd_block = NULL;

/* Synchronization of FDC access. */
static volatile int fdc_busy = 0;
static struct wait_queue *fdc_wait = NULL;
static struct wait_queue *motor_wait = NULL;

#if 0
static unsigned int changed_floppies = 0;
#endif

/*======================================================================
  Select the side to use for a particular drive.
  The drive must have been calibrated at some point before this.
  The drive must also be active and the motor must be running.
======================================================================*/
static void fd_select_side(int drive, int side)
{
  if(side==0)
   {
    sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
    sound_ym.wd_data = sound_ym.wd_data & 0xff;
   }
  else
   {
    sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
    sound_ym.wd_data = sound_ym.wd_data & 0xfe;
   }

}


static struct timer_list motor_on_timer;
static struct timer_list motor_off_timer[FD_MAX_UNITS];
static int on_attempts;

static void wait_func(void)
 {
  int i;
  for(i=0;i<30;i++)
   i=i;
 }  

static void fd_select (int drive,unsigned short tr)
{
  int oldtr;
	if (drive == selected)
		return;
	oldtr=tr/2;
	selected = drive;
  if(oldtr*2==tr)
   {
    sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
    sound_ym.wd_data = 0xfd;     /* Select Side 0 and Drive A */
   }
  else
   {
    sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
    sound_ym.wd_data = 0xfc;     /* Select Side 1 and Drive A */
   }
}

static void fd_deselect (int drive)
{
	if (drive != selected)
		return;

  sound_ym.rd_data_reg_sel=14; /* Select PSG Port A */
  sound_ym.wd_data |= DSKDRVNONE;

	selected=-1;
}

static fd_nobusy(void)
{
 unsigned char fd_status;
 
 do
  {
   fd_status=mfp.par_dt_reg;
   fd_status = fd_status & 0x20;
  }
 while(fd_status != 0);

}

/*======================================================================
  Seek the drive to track 0.
  The drive must be active and the motor must be running.
  Returns standard floppy error code.
======================================================================*/
static int fd_calibrate(int drive)
{

  fd_select(drive,0);
  dma_wd.dma_mode_status=FDCSELREG_STP;
  dma_wd.fdc_acces_seccount=FDCCMD_RESTORE | FDCCMDADD_V ;
  
  fd_nobusy();
  fd_deselect(drive);
  
	return 1;
}

/*======================================================================
  Seek the drive to the requested cylinder.
  The drive must have been calibrated at some point before this.
  The drive must also be active and the motor must be running.
======================================================================*/
static int fd_seek(unsigned short drive, unsigned short track)
{
  unsigned short stat;
  dma_wd.dma_mode_status=FDCSELREG_DTA;
  nop();
  dma_wd.fdc_acces_seccount=track;
  wait_func();
  dma_wd.dma_mode_status=FDCSELREG_STP;
  nop();
  dma_wd.fdc_acces_seccount=FDCCMD_SEEK | FDCCMDADD_V ;
  wait_func();
  fd_nobusy();
    
	return 1;
}


struct header {
	unsigned char magic;
	unsigned char track;
	unsigned char sect;
	unsigned char ord;
	unsigned char labels[16];
	unsigned long hdrchk;
	unsigned long datachk;
};



/*==========================================================================
  atari_write converts track/labels data to raw track data
==========================================================================*/
static void atari_write(int dr, unsigned short tr, unsigned short sec)
{
  unsigned char dmaa;
  unsigned short oldtr;
  unsigned long phys_secbuf,flags;
  int i;
    
  sec=sec+1;
  
  phys_secbuf=VTOP((unsigned long)trackdata); /* DMA knows PhysAdr only ! */
  
  save_flags(flags);  
  cli();
  
  fd_select(dr,tr);
  
  tr=tr/2;

  /* Setup head pos. */
  fd_seek(dr,tr);


  /* Setup DMA */
  dma_wd.dma_lo=(unsigned char)phys_secbuf;
  phys_secbuf = phys_secbuf >> 8;
  dma_wd.dma_md=(unsigned char)phys_secbuf;
  phys_secbuf = phys_secbuf >> 8;
  dma_wd.dma_hi=(unsigned char)phys_secbuf;
  

  /* Clear FIFO and switch DMA to read-mode ! */  
  dma_wd.dma_mode_status=0x190;  
  dma_wd.dma_mode_status=0x90;  
  dma_wd.dma_mode_status=0x190;  

  /* Transmit only 1 Sector */
  dma_wd.fdc_acces_seccount=0x01;
  wait_func();  
  
  
  dma_wd.dma_mode_status=FDCSELREG_SEC | 0x100 ;
  nop();
  dma_wd.fdc_acces_seccount=sec;   
  wait_func();
  
  /* Start Write */
  dma_wd.dma_mode_status=FDCSELREG_STP | 0x100 ;
  nop();
  dma_wd.fdc_acces_seccount=FDCCMD_WRSEC ;
  wait_func();;      
  
  fd_nobusy();  /* Wait for IRQ 5 */
  
  restore_flags(flags);
  
  fd_deselect(dr);
  
  for(i=0;i<20000;i++)
   {
    i=i;
   }
 }

/*==========================================================================
  atari_read reads a raw track of data into a track buffer
==========================================================================*/
static int atari_read(int dr, unsigned short tr, unsigned short sec)
{
  unsigned char dmaa;
  unsigned long phys_secbuf,flags;
  int i;
    
  phys_secbuf=VTOP((unsigned long)trackdata);
  
  sec=sec+1;
  
  save_flags(flags);  
  cli();
  
  fd_select(dr,tr);
  
  tr=tr/2;
  
  /* Setup head pos. */
  fd_seek(dr,tr);
  

  /* Setup DMA */
  dma_wd.dma_lo=(unsigned char)phys_secbuf;
  phys_secbuf = phys_secbuf >> 8;
  dma_wd.dma_md=(unsigned char)phys_secbuf;
  phys_secbuf = phys_secbuf >> 8;
  dma_wd.dma_hi=(unsigned char)phys_secbuf;
  

  /* Clear FIFO and switch DMA to read-mode ! */  
  dma_wd.dma_mode_status=0x90;  
  dma_wd.dma_mode_status=0x190;  
  dma_wd.dma_mode_status=0x90;  
  /* Transmit only 1 Sector */
  dma_wd.fdc_acces_seccount=0x01;
  wait_func();
  
  dma_wd.dma_mode_status=FDCSELREG_SEC;
  nop();
  dma_wd.fdc_acces_seccount=sec;   
  wait_func();
  
  /* Start READ */
  dma_wd.dma_mode_status=FDCSELREG_STP;
  nop();
  dma_wd.fdc_acces_seccount=FDCCMD_RDSEC ;
  wait_func();
    
  fd_nobusy();  /* Wait for IRQ 5 */
  
  restore_flags(flags);
  
  fd_deselect(dr);
  
	return 0;
}

/*
 * Note that MAX_ERRORS=X doesn't imply that we retry every bad read
 * max X times - some types of errors increase the errorcount by 2 or
 * even 3, so we might actually retry only X/2 times before giving up.
 */
#define MAX_ERRORS 12

/*
 * The driver is trying to determine the correct media format
 * while probing is set. rw_interrupt() clears it after a
 * successful access.
 */
static int probing = 0;

/* Prevent "aliased" accesses. */
static fd_ref[4] = { 0,0,0,0 };
static fd_device[4] = { 0,0,0,0 };

/*
 * Current device number. Taken either from the block header or from the
 * format request descriptor.
 */
#define CURRENT_DEVICE (CURRENT->dev)

/* Current error count. */
#define CURRENT_ERRORS (CURRENT->errors)

static void floppy_off (unsigned int nr)
{
}



/*
 * floppy-change is never called from an interrupt, so we can relax a bit
 * here, sleep etc. Note that floppy-on tries to set current_DOR to point
 * to the desired drive, but it will probably not survive the sleep if
 * several floppies are used at the same time: thus the loop.
 */
int floppy_change(struct buffer_head * bh)
{
	return 0;
}

static __inline__ void copy_buffer(void *from, void *to)
{
  ulong *p1,*p2;
  int cnt;

  p1 = (ulong *)from;
  p2 = (ulong *)to;

  for (cnt = 0; cnt < 512/4; cnt++)
    *p2++ = *p1++;
}


static void redo_fd_request(void)
{
	unsigned int block, track, sector;
	int device, drive, cnt , i;
	struct floppy_struct *floppy;
	char *data;

  
	if (CURRENT && CURRENT->dev < 0) return;

    repeat:
    
  if(!CURRENT)
    return;

	if (MAJOR(CURRENT->dev) != MAJOR_NR)
		panic(DEVICE_NAME ": request list destroyed");

	if (CURRENT->bh) {
		if (!CURRENT->bh->b_lock)
			panic(DEVICE_NAME ": block not locked");
	}

	probing = 0;
	device = MINOR(CURRENT_DEVICE);
		
	if (CURRENT_DEVICE == 0x200 ) {
		/* manual selection */
		drive = 0;
		floppy = unit + drive;
	} 
	 else
	{
	 printk("Unknown Device !\n");
	 return;
	}
	
	for (cnt = 0; cnt < 2; cnt++) {
		block = CURRENT->sector + cnt;
		if ((int)block > floppy->blocks) {
			end_request(0);
			goto repeat;
		}

		track = block / floppy->dtype->sects;
		sector = block - track * floppy->dtype->sects;
		data = CURRENT->buffer + 512 * cnt;
		

		switch (CURRENT->cmd) {
		    case READ:
       atari_read(device,track,sector);
       copy_buffer(trackdata,data);
			break;

		    case WRITE:
       copy_buffer(data,trackdata);
       atari_write(device,track,sector);
			break;

		    default:
			printk("do_fd_request: unknown command\n");
			end_request(0);
			goto repeat;
		}
	}

	end_request(1);
	goto repeat;
}

void do_fd_request(void)
{ 
	redo_fd_request();
}

static int fd_ioctl(struct inode *inode, struct file *filp,
		    unsigned int cmd, unsigned long param)
{
	printk("fd_ioctl: %d\n", cmd);
	return 0;
}

/*======================================================================
  Return unit ID number of given disk
======================================================================*/
static unsigned long get_drive_id(int drive)
{
	return FD_HD_3;
}

static void fd_probe(int drive)
{
	unsigned long code;
	int type, data;

	code = get_drive_id(drive);

	/* get drive type */
	unit[drive].type = NULL;
	for (type = 0; type < num_dr_types; type++)
		if (drive_types[type].code == code)
			break;

	if (type >= num_dr_types) {
		printk("fd_probe: unsupported drive type %08lx found\n",
		       code);
		return;
	}

	/* get data type */
	unit[drive].dtype = NULL;
	for (data = 0; data < num_da_types; data++)
		break;

	if (data >= num_da_types) {
		printk("fd_probe: can't get data type\n");
		return;
	}

	unit[drive].type = &drive_types[type];
	unit[drive].dtype = &data_types[data];
	unit[drive].track = -1;

	unit[drive].sects = data_types[data].sects * drive_types[type].sect_mult;
	unit[drive].blocks = drive_types[type].heads * drive_types[drive].tracks *
		unit[drive].sects;

	unit[drive].disk = -1;
	unit[drive].motor = 0;
	unit[drive].busy = 0;
	unit[drive].status = -1;
}

static void config_types(void)
{
	int drive;

	printk("Probing floppy drive(s):\n");
	for (drive = 0; drive < FD_MAX_UNITS; drive++) {
		fd_probe(drive);
		if (unit[drive].type != NULL)
			printk("%d: %s\n", drive, unit[drive].type->name);
	}
}

/*
 * floppy_open check for aliasing (/dev/fd0 can be the same as
 * /dev/PS0 etc), and disallows simultaneous access to the same
 * drive with different device numbers.
 */
static int floppy_open(struct inode *inode, struct file *filp)
{
  int drive;
  int old_dev;

  drive = inode->i_rdev & 3;
  old_dev = fd_device[drive];

  if (fd_ref[drive])
    if (old_dev != inode->i_rdev)
      return -EBUSY;

  fd_ref[drive]++;
  fd_device[drive] = inode->i_rdev;

  if (old_dev && old_dev != inode->i_rdev)
    invalidate_buffers(old_dev);

  if (filp && filp->f_mode)
    check_disk_change(inode->i_rdev);


  return 0;
}

static void floppy_release(struct inode * inode, struct file * filp)
{
  sync_dev(inode->i_rdev);
  if (!fd_ref[inode->i_rdev & 3]--) {
    printk("floppy_release with fd_ref == 0");
    fd_ref[inode->i_rdev & 3] = 0;
  }
}

static struct file_operations floppy_fops = {
	NULL,			/* lseek - default */
	block_read,		/* read - general block-dev read */
	block_write,		/* write - general block-dev write */
	NULL,			/* readdir - bad */
	NULL,			/* select */
	fd_ioctl,		/* ioctl */
	NULL,			/* mmap */
	floppy_open,		/* open */
	floppy_release, 	/* release */
	block_fsync		/* fsync */
};

static void fd_block_done(struct intframe *fp, void *data)
{
}

void floppy_init(void)
{
  int i;

  if (register_blkdev(MAJOR_NR,"fd",&floppy_fops)) {
    printk("Unable to get major %d for floppy\n",MAJOR_NR);
    return;
  }
  /* initialize variables */
  selected=-1;


  for (i = 0; i < FD_MAX_UNITS; i++) {
	  unit[i].track = -1;
  }

  /* blk_size[MAJOR_NR] = floppy_sizes; */
  blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;


  config_types();


}
