/*
 *  linux/kernel/blk_drv/floppy.c
 *
 *  Copyright (C) 1993  Greg Harp
 */

#include <linux/sched.h>
#include <linux/fs.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 <machine/chipregs.h>
#include <machine/interrupt.h>

#define MAJOR_NR 2
#include "blk.h"

/*
 *  Defines
 */
#define TRACK_BUFFERS	10
#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      0xffffffff  /* response when no unit is present */
#define FD_DD_3         0x00000000  /* double-density 3.5" (880K) drive */
#define FD_HD_3         0xaaaaaaaa  /* high-density 3.5" (1760K) drive */
#define FD_DD_5         0x55555555  /* double-density 5.25" (440K) drive */


/*
 *  Macros
 */
#define MOTOR_ON        *ciab_prb &= ~DSKMOTOR
#define MOTOR_OFF       *ciab_prb |= DSKMOTOR
#define SELECT(mask)    *ciab_prb &= ~mask
#define DESELECT(mask)  *ciab_prb |= mask

#define DRIVE(x) ((x) & 3)
#define PROBE(x) ((x) >> 2) & 1)
#define TYPE(x)  ((x) >> 3) & 2)
#define DATA(x)  ((x) >> 5) & 3)

static ushort dsksel[FD_MAX_UNITS] = { DSKSEL0, DSKSEL1, DSKSEL2, DSKSEL3 };

static struct fd_drive_type drive_types[] = {
  { FD_DD_3,    "DD 3.5",  80, 2, 13628, 99, 99, 3, 15, 1 },
  { FD_NODRIVE, "No Drive", 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[] = {
  { "Amiga", 11 }
};
static int num_da_types = sizeof(data_types) / sizeof(data_types[0]);

#if 0
  {1440,  9,   2, 80, "IBM Double Density 3.5"},    /* 720K */
  {2880, 18,   2, 80, "IBM High Density 3.5"},      /* 1.44M */
  { 880, 11,   2, 40, "Amiga Double Density 5.25"}, /* 440K */
  { 720,  9,   2, 40, "IBM Double Density 5.25"},   /* 360K */
#endif

/* current info on each unit */
static struct floppy_struct unit[FD_MAX_UNITS];

/* track buffers */
struct track_buf {
  int drive;
  int track;
  char data[MAX_SECTORS * 512];
  char labels[MAX_SECTORS * 16];
} track_bufs[TRACK_BUFFERS];

/*
 * Functions
 */

/*==========================================================================
 This is Very Bad Thing (TM).  It should be replaced by something that
 uses the timers and sleeps to allow other work to be done.
==========================================================================*/
extern unsigned long jiffies;
void delay(unsigned long time)
{
  int i;

  i = jiffies;
  while (jiffies < i + time + 1) { };
}

/*======================================================================
  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.
======================================================================*/
void fd_select_side(int drive, int side)
{
  /* select the requested side */
  if (side == 0)
    *ciab_prb &= ~DSKSIDE;
  else
    *ciab_prb |= DSKSIDE;

  unit[drive].side = side;
}

/*======================================================================
  Select the direction to use for the current particular drive.
======================================================================*/
void fd_select_dir(int drive, int dir)
{
  /* select the requested direction */
  if (dir == 0)
    *ciab_prb &= ~DSKDIREC;
  else
    *ciab_prb |= DSKDIREC;

  unit[drive].dir = dir;
}

/*======================================================================
  Turn on the motor of the given drive.  Unit must already be active.
  Returns standard floppy error code.
======================================================================*/
void fd_motor_on(int drive)
{
  int i;

  /* deselect all drives */
  for (i=0; i<FD_MAX_UNITS; i++)
    DESELECT(dsksel[i]);

  /* turn on the unit's motor */
  MOTOR_ON;
  delay(1);
  SELECT(dsksel[drive]);
  delay(2);

  /* wait for spinup */
  /*
    while (*ciaa_pra & DSKRDY)
    ;
    */
  /* we _should wait on DSKRDY here, but for some ready it never goes low */
}

/*======================================================================
  Turn off the motor of the given drive.  Unit must already be active.
  Returns standard floppy error code.
======================================================================*/
void fd_motor_off(int drive)
{
  int i;

  /* deselect all drives */
  for (i=0; i<FD_MAX_UNITS; i++)
    DESELECT(dsksel[i]);

  /* turn off the unit's motor */
  MOTOR_OFF;
  delay(1);
  SELECT(dsksel[drive]);
  delay(1);
  MOTOR_ON;
  delay(1);
  DESELECT(dsksel[drive]);
}

/*======================================================================
  Return unit ID number of given disk (1 == error)
======================================================================*/
static ulong fd_get_id(int drive)
{
  int i;
  ulong id = 0;
  ushort mask;

  /* get selection mask */
  mask = dsksel[drive];

  /* set up for ID */
  MOTOR_ON;
  SELECT(mask);
  DESELECT(mask);
  MOTOR_OFF;
  SELECT(mask);
  DESELECT(mask);

  /* loop and read disk ID */
  for (i=0; i<32; i++) {
    SELECT(mask);

    /* read and store value of DSKRDY */
    id <<= 1;
    id |= (*ciaa_pra & DSKRDY) ? 1 : 0;

    DESELECT(mask);
  }

  /* return the ID value */
  return (id);
}

/*======================================================================
  Step the drive once in its current direction
======================================================================*/
void fd_step(void)
{
  *ciab_prb &= ~DSKSTEP;
  *ciab_prb |= DSKSTEP;
}

/*======================================================================
  Seek the drive to track 0.  
  The drive must be active and the motor must be running.
  Returns standard floppy error code.
======================================================================*/
void fd_calibrate(int drive)
{
  fd_select_side(drive, 0);
  fd_select_dir(drive, 1);

  /* loop until we hit track 0 */
  while (*ciaa_pra & DSKTRACK0) {
    fd_step();
    delay(4);
  }

  /* set known values */
  unit[drive].cyl = 0;
}

/*======================================================================
  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.
======================================================================*/
void fd_seek(int drive, int track)
{
  int cyl, side;
  int dir, cnt;
  int delay_time;

  cyl = track >> 1;
  side = (track % 2) ^ 1;

  if (unit[drive].cyl == -1) {
    fd_motor_on(drive);
    fd_calibrate(drive);
    fd_motor_off(drive);
  }

  if (cyl < unit[drive].cyl) {
    dir = 1;
    cnt = unit[drive].cyl - cyl;
  } else {
    dir = 0;
    cnt = cyl - unit[drive].cyl;
  }

  fd_motor_on(drive);

  while (cnt) {
    delay_time = unit[drive].type->step_delay;
    if (dir != unit[drive].dir)
      delay_time += unit[drive].type->settle_time;
    fd_select_dir(drive, dir);
    fd_step();
    delay(delay_time);
    --cnt;
  }

  unit[drive].cyl = cyl;

  delay(unit[drive].type->settle_time);

  fd_select_side(drive, side);
  delay(unit[drive].type->side_time);
}

void encode(unsigned long data, unsigned long *dest, unsigned long *csum)
{
  unsigned long data2;

  data &= 0x55555555;
  data2 = data ^ 0x55555555;
  data |= ((data2 >> 1) | 0x80000000) & (data2 << 1);

  if (*(dest - 1) & 0x00000001)
    data &= 0x7FFFFFFF;

  *csum ^= data;
  *dest = data;
}

unsigned long encode_long(unsigned long data, unsigned long *dest)
{
  unsigned long csum;

  csum = 0;

  encode(data >> 1, dest, &csum);
  encode(data, dest + 1, &csum);

  return csum & 0x55555555;
}

void encode_block(unsigned long *dest, unsigned char *from, int len,
		  unsigned long *csum)
{
  int cnt, to_cnt = 0;
  unsigned long data;
  unsigned long *src = (unsigned long *)from;

  /* odd bits */
  for (cnt = 0; cnt < len / 4; cnt++) {
    data = src[cnt] >> 1;
    encode(data, dest + to_cnt++, csum);
  }

  /* even bits */
  for (cnt = 0; cnt < len / 4; cnt++) {
    data = src[cnt];
    encode(data, dest + to_cnt++, csum);
  }

  *csum &= 0x55555555;
}

void correct(unsigned long *raw)
{
  unsigned char *ptr = (unsigned char *)raw;
  unsigned char data;

  data = *ptr;
  if (*(ptr - 1) & 0x01) {
    *ptr = data & 0x7f;
    return;
  }

  if (data & 0x40) return;

  *ptr |= 0x80;
}

/*==========================================================================
  amiga_write converts track/labels data to raw track data
==========================================================================*/
void amiga_write(int disk, unsigned long *raw, unsigned char *data,
		 unsigned char *labels, int track)
{
  int cnt;
  unsigned long csum, format;

  /* gap space */
  for (cnt = 0; cnt < 414; cnt++)
    *raw++ = 0xaaaaaaaa;

  /* sectors */
  for (cnt = 0; cnt < 11; cnt++) {
    *raw = 0xaaaaaaaa;
    correct(raw);
    ++raw;

    *raw++ = 0x44894489;

    format = 0xff000000 | (track << 16) | (cnt << 8) | (11 - cnt);
    csum = encode_long(format,raw);
    raw += 2;

    encode_block(raw, labels + cnt * 16, 16, &csum);
    raw += 8;
    csum = encode_long(csum, raw);
    raw += 2;

    csum = 0;
    encode_block(raw+2, data + cnt * 512, 512, &csum);
    csum = encode_long(csum, raw);
    raw += 256 + 2;
  }
}

#define get_word(raw) (*((unsigned short *)(raw)))
#define get_long(raw) (*((unsigned long *)(raw)))

#define decode_long(raw) (((get_long(raw) & 0x55555555) << 1) | \
			  (get_long((raw)+4) & 0x55555555))

#define MFM_NOSYNC	1
#define MFM_HEADER	2
#define MFM_DATA	3
#define MFM_TRACK	4

/*==========================================================================
 scan_sync - looks for the next start of sector marked by a sync. d3 is the
		sector number (10..0). When d3 = 10, can't be certain of a
		starting sync.
==========================================================================*/
unsigned long scan_sync(int drive, unsigned long raw, unsigned long end,
			int sect)
{
  unsigned short data;

  if (sect != 10) {
    while (raw < end) {
      data = get_word(raw);
      if (data == 0x4489) break;
      raw += 2;
    }
    if (raw > end)
      return 0;
  }

  while (raw < end) {
    data = get_word(raw);
    if (data != 0x4489) break;
    raw += 2;
  }
  if (raw > end)
    return 0;

  return raw;
}

/*==========================================================================
  amiga_read reads a raw track of data into a track buffer
==========================================================================*/
int amiga_read(int drive, char *track_data, char *label_data,
	       unsigned long raw, int track)
{
  unsigned long end;
  int scnt, cnt;
  unsigned long val1, val2, csum, data_csum;
  int format, tnum, sect, snext;
  unsigned long *data,*labels;

  end = raw + unit[drive].type->raw_size;

  for (scnt = unit[drive].sects-1; scnt >= 0; scnt--) {
    if (!(raw = scan_sync(drive, raw, end, scnt)))
      return 1;

    val1 = decode_long(raw);

    format = (val1 >> 24) & 0xFF;
    tnum   = (val1 >> 16) & 0xFF;
    sect   = (val1 >>  8) & 0xFF;
    snext  = (val1)       & 0xFF;

    labels = (unsigned long *)(label_data + (sect << 4));

    csum = 0;
    val1 = get_long(raw);
    raw += 4;
    csum ^= val1;
    val1 = get_long(raw);
    raw += 4;
    csum ^= val1;

    for (cnt = 0; cnt < 4; cnt++) {
      val1 = get_long(raw+16);
      csum ^= val1;
      val1 &= 0x55555555;
      val2 = get_long(raw);
      raw += 4;
      csum ^= val2;
      val2 &= 0x55555555;
      val2 = val2 << 1;
      val1 |= val2;
      *labels++ = val1;
    }

    csum &= 0x55555555;
    raw += 16;
    val1 = decode_long(raw);
    raw += 8;
    if (val1 != csum) {
      printk("MFM_HEADER: %08x,%08x\n", val1, csum);
      return MFM_HEADER;
    }

    /* verify track */
    if (tnum != track) {
      printk("MFM_TRACK: %d, %d\n", tnum, track);
      return MFM_TRACK;
    }

    data_csum = decode_long(raw);
    raw += 8;
    data = (unsigned long *)(track_data + (sect << 9));

    csum = 0;
    for (cnt = 0; cnt < 128; cnt++) {
      val1 = get_long(raw + 512);
      csum ^= val1;
      val1 &= 0x55555555;
      val2 = get_long(raw);
      raw += 4;
      csum ^= val2;
      val2 &= 0x55555555;
      val2 = val2 << 1;
      val1 |= val2;
      *data++ = val1;
    }

    csum &= 0x55555555;
    raw += 512;

    if (data_csum != csum)
      return MFM_DATA;
  }

  return 0;
}

extern unsigned char current_DOR;

/*
 * 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 };

/* Synchronization of FDC access. */
static volatile int fdc_busy = 0;
static struct wait_queue *fdc_wait = NULL;

/*
 * 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)

/*
 * 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;
unsigned char selected = 0;
struct wait_queue *wait_on_floppy_select = NULL;
struct wait_queue *wait_fd_block = NULL;

#if 0
void floppy_deselect(unsigned int nr)
{
  if (nr != (current_DOR & 3))
    printk("floppy_deselect: drive not selected\n");
  selected = 0;
  wake_up(&wait_on_floppy_select);
}
#endif

void request_done(int uptodate)
{
  timer_active &= ~(1 << FLOPPY_TIMER);
  end_request(uptodate);
}

#if 0
/*
 * 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;
}
#endif

void copy_buffer(void *from, void *to)
{
  ulong *p1,*p2;
  int cnt;

  p1 = from;
  p2 = to;

  for (cnt = 0; cnt < BLOCK_SIZE/4; cnt++)
    *p2++ = *p1++;
}

/*
 * We try to read tracks, but if we get too many errors, we
 * go back to reading just one sector at a time.
 *
 * This means we should be able to read a sector even if there
 * are other bad sectors on this track.
 */
inline void setup_rw_floppy(void)
{
}

static void floppy_shutdown(void)
{
printk("floppy_shutdown:\n");
}

static void floppy_on_interrupt(void)
{
}

void do_read(char *ptrack, int len)
{
  *dskptr = (unsigned long)ptrack;

  *adkcon = 0x6200;
  *adkcon = 0x9500;

  *dmacon = 0x8210;

  *dsklen = len | 0x8000;
  *dsklen = len | 0x8000;

  block_flag = 1;
  *intena = 0x8002;

  *dsksync = 0x4489;

  while (block_flag == 1) { };

  *intena = 0x0002;
  *dmacon = 0x0010;
}

void do_write(char *ptrack, int len)
{
  *dskptr = (unsigned long)ptrack;

  *adkcon = 0x6600;
  *adkcon = 0x9100;

  *dmacon = 0x8210;

  *dsklen = len | 0xC000;
  *dsklen = len | 0xC000;

  block_flag = 2;
  *intena = 0x8002;

  while (block_flag == 2) { };

  *intena = 0x0002;
  *dmacon = 0x0010;
}

void raw_read(int drive, int track, char *ptrack)
{
  fd_seek(drive, track);

  do_read(ptrack, unit[drive].type->raw_size >> 1);
  fd_motor_off(drive);
}

void raw_write(int drive, int track, char *ptrack)
{
  fd_seek(drive, track);

  do_write(ptrack, unit[drive].type->raw_size >> 1);
  fd_motor_off(drive);
}

void do_test(int buf, int drive, int track)
{
  int res;
  unsigned short *data;

  /* set track_bufs[buf].data/label */

  amiga_write(drive, (unsigned long *)RAW_BUF, track_bufs[buf].data,
	      track_bufs[buf].labels, track);

  data = (unsigned short *)(track_bufs[buf].data);
  while (*data != 0x4489)
    ++data;
  res = amiga_read(drive, (unsigned char *)data, track_bufs[buf].labels,
		   RAW_BUF, track);
  if (res) {
    printk("do_test: failed %d\n", res);
  }
}

int get_track(int drive, int track)
{
  int free = -1;
  int cnt;
  struct track_buf *tbuf;

  for (cnt = TRACK_BUFFERS-1; cnt >= 0; cnt--) {
    tbuf = &track_bufs[cnt];
    if (tbuf->drive == -1) free = cnt;
    if (tbuf->drive == drive && tbuf->track == track)
      return cnt;
  }

  if (free == -1) {
    printk("Out of buffers!");
    return -1;
  }

  raw_read(drive, track, (char *)RAW_BUF);
  if (amiga_read(drive, track_bufs[free].data, track_bufs[free].labels,
		 RAW_BUF, track))
    return -1;

  track_bufs[free].drive = drive;
  track_bufs[free].track = track;

#if 0
  for (cnt = 0; cnt < TRACK_BUFFERS; cnt++)
    if (track_bufs[cnt].drive = -1) {
      do_test(cnt, drive, track);
      break;
    }
#endif

  return free;
}

static void redo_fd_request(void)
{
  unsigned int block, track, sector;
  int device, drive, buf;
  struct floppy_struct *floppy;

  if (CURRENT && CURRENT->dev < 0) return;

repeat:
  if (!CURRENT) {
    if (!fdc_busy)
      printk("FDC access conflict!");
    fdc_busy = 0;
    wake_up(&fdc_wait);
    CLEAR_INTR;
    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 (device > 3) {
    /* manual selection */
    drive = (device >> 2) - 1;
    floppy = unit + drive;
  } else {
    /* Auto-detection */
    panic("redo_fd_request: can't handle auto detect");
  }

  block = CURRENT->sector;
  if (block > floppy->blocks) {
    printk("past end? %d,%d\n",block,floppy->blocks);
    request_done(0);
    goto repeat;
  }

  track = block / floppy->dtype->sects;
  sector = block - track * floppy->dtype->sects;

  switch (CURRENT->cmd) {
  case READ:
    if ((buf = get_track(drive, track)) == -1) {
      end_request(0);
      goto repeat;
    }
    copy_buffer(track_bufs[buf].data + sector * 512, CURRENT->buffer);
    break;

  case WRITE:
    if ((buf = get_track(drive, track)) == -1) {
      end_request(0);
      goto repeat;
    }
    copy_buffer(CURRENT->buffer, track_bufs[buf].data + sector * 512);
    amiga_write(drive, (unsigned long *)RAW_BUF, track_bufs[buf].data,
		track_bufs[buf].labels, track);
    raw_write(drive, track, (char *)RAW_BUF);
    break;

  default:
    printk("do_fd_request: unknown command\n");
    request_done(0);
    goto repeat;
  }

  request_done(1);
  goto repeat;
}

void do_fd_request(void)
{
  sti();
  while (fdc_busy) sleep_on(&fdc_wait);
  fdc_busy = 1;
  redo_fd_request();
}

void floppy_off(unsigned int dev)
{
}

static int fd_ioctl(struct inode *inode, struct file *filp,
		    unsigned int cmd, unsigned int param)
{
  printk("fd_ioctl: %d\n", cmd);
  return 0;
}

void probe(int drive)
{
  unsigned long code;
  int type, data;

  code = fd_get_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("find_base: unsupported drive type %08x 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("probe: can't get data type\n");
    return;
  }

  unit[drive].type = &drive_types[type];
  unit[drive].dtype = &data_types[data];
  unit[drive].side = -1;
  unit[drive].dir = -1;
  unit[drive].cyl = -1;

  unit[drive].sects = data_types[data].sects;
  unit[drive].blocks = drive_types[type].heads * drive_types[drive].tracks *
			unit[drive].sects * 2;

  unit[drive].disk = -1;
  unit[drive].motor = -1;
  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++) {
    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 */
};

int fd_block_done(struct ISR_mask *mask, ushort sr)
{
  if (block_flag)
    *dsklen = 0x4000;
  block_flag = 0;

  return 0;
}

void floppy_init(void)
{
  int cnt;
  struct ISR_mask mask;

  if (register_blkdev(MAJOR_NR,"fd",&floppy_fops)) {
    printk("Unable to get major %d for floppy\n",MAJOR_NR);
    return;
  }

  /* blk_size[MAJOR_NR] = floppy_sizes; */
  blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;

  for (cnt = 0; cnt < TRACK_BUFFERS; cnt++)
    track_bufs[cnt].drive = -1;

  timer_table[FLOPPY_TIMER].fn = floppy_shutdown;
  timer_active &= ~(1 << FLOPPY_TIMER);

  config_types();

  mask.intreqr_mask = 0x0002;
  mask.ciaa_mask = 0x0;
  mask.ciab_mask = 0x0;
  mask.always = 0;
  install_ISR(1, fd_block_done, 0, &mask);
}
