/*
 * Here is code taken from the modern linux kernel:
 * 
 * Linux 2.4.30 - 2005
 *
 *  linux/drivers/block/floppy.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *  Copyright (C) 1993, 1994  Alain Knaff
 *  Copyright (C) 1998 Alan Cox
 *  Licensed under the GPL
 */
/*
 * As with hd.c, all routines within this file can (and will) be called
 * by interrupts, so extreme caution is needed. A hardware interrupt
 * handler may not sleep, or a kernel panic will happen. Thus I cannot
 * call "floppy-on" directly, but have to set a special timer interrupt
 * etc.
 */

/*
 * 28.02.92 - made track-buffering routines, based on the routines written
 * by entropy@wintermute.wpi.edu (Lawrence Foard). Linus.
 */

/*
 * Automatic floppy-detection and formatting written by Werner Almesberger
 * (almesber@nessie.cs.id.ethz.ch), who also corrected some problems with
 * the floppy-change signal detection.
 */

/*
 * 1992/7/22 -- Hennus Bergman: Added better error reporting, fixed
 * FDC data overrun bug, added some preliminary stuff for vertical
 * recording support.
 *
 * 1992/9/17: Added DMA allocation & DMA functions. -- hhb.
 *
 * TODO: Errors are still not counted properly.
 */

/* 1992/9/20
 * Modifications for ``Sector Shifting'' by Rob Hooft (hooft@chem.ruu.nl)
 * modeled after the freeware MS-DOS program fdformat/88 V1.8 by
 * Christoph H. Hochst\"atter.
 * I have fixed the shift values to the ones I always use. Maybe a new
 * ioctl() should be created to be able to modify them.
 * There is a bug in the driver that makes it impossible to format a
 * floppy as the first thing after bootup.
 */

/*
 * 1993/4/29 -- Linus -- cleaned up the timer handling in the kernel, and
 * this helped the floppy driver as well. Much cleaner, and still seems to
 * work.
 */

/* 1994/6/24 --bbroad-- added the floppy table entries and made
 * minor modifications to allow 2.88 floppies to be run.
 */

/* 1994/7/13 -- Paul Vojta -- modified the probing code to allow three or more
 * disk types.
 */

/*
 * 1994/8/8 -- Alain Knaff -- Switched to fdpatch driver: Support for bigger
 * format bug fixes, but unfortunately some new bugs too...
 */

/* 1994/9/17 -- Koen Holtman -- added logging of physical floppy write
 * errors to allow safe writing by specialized programs.
 */

/* 1995/4/24 -- Dan Fandrich -- added support for Commodore 1581 3.5" disks
 * by defining bit 1 of the "stretch" parameter to mean put sectors on the
 * opposite side of the disk, leaving the sector IDs alone (i.e. Commodore's
 * drives are "upside-down").
 */

/*
 * 1995/8/26 -- Andreas Busse -- added Mips support.
 */

/*
 * 1995/10/18 -- Ralf Baechle -- Portability cleanup; move machine dependent
 * features to asm/floppy.h.
 */

/*
 * 1998/05/07 -- Russell King -- More portability cleanups; moved definition of
 * interrupt and dma channel to asm/floppy.h. Cleaned up some formatting &
 * use of '0' for NULL.
 */
 
/*
 * 1998/06/07 -- Alan Cox -- Merged the 2.0.34 fixes for resource allocation
 * failures.
 */

/*
 * 1998/09/20 -- David Weinehall -- Added slow-down code for buggy PS/2-drives.
 */

/*
 * 1999/08/13 -- Paul Slootman -- floppy stopped working on Alpha after 24
 * days, 6 hours, 32 minutes and 32 seconds (i.e. MAXINT jiffies; ints were
 * being used to store jiffies, which are unsigned longs).
 */

/*
 * 2000/08/28 -- Arnaldo Carvalho de Melo <acme@conectiva.com.br>
 * - get rid of check_region
 * - s/suser/capable/
 */

/*
 * 2001/08/26 -- Paul Gortmaker - fix insmod oops on machines with no
 * floppy controller (lingering task on list after module is gone... boom.)
 */

/*
 * 2002/02/07 -- Anton Altaparmakov - Fix io ports reservation to correct range
 * (0x3f2-0x3f5, 0x3f7). This fix is a bit of a hack but the proper fix
 * requires many non-obvious changes in arch dependent code.
 */

/*
 * PS/2 floppies have much slower step rates than regular floppies.
 * It's been recommended that take about 1/4 of the default speed
 * in some more extreme cases.
 */

/* =======
 * can use virtual DMA:
 * 0 = use of virtual DMA disallowed by config
 * 1 = use of virtual DMA prescribed by config
 * 2 = no virtual DMA preference configured.  By default try hard DMA,
 * but fall back on virtual DMA when not enough memory available
 */

/* =======
 * use virtual DMA
 * 0 using hard DMA
 * 1 using virtual DMA
 * This variable is set to virtual when a DMA mem problem arises, and
 * reset back in floppy_grab_irq_and_dma.
 * It is not safe to reset it in other circumstances, because the floppy
 * driver may have several buffers in use at once, and we do currently not
 * record each buffers capabilities
 */

/* the following is the mask of allowed drives. By default units 2 and
 * 3 of both floppy controllers are disabled, because switching on the
 * motor of these drives causes system hangs on some PCI computers. drive
 * 0 is the low bit (0x1), and drive 7 is the high bit (0x80). Bits are on if
 * a drive is allowed.
 *
 * NOTE: This must come before we include the arch floppy header because
 *       some ports reference this variable from there. -DaveM
 */

/*
 * Maximum disk size (in kilobytes). This default is used whenever the
 * current disk size is unknown.
 * [Now it is rather a minimum]
 */

/*
 * globals used by 'result()'
 */
/* NOTE: the time values in jiffies should be in msec!
 CMOS drive type
  |     Maximum data rate supported by drive type
  |     |   Head load time, msec
  |     |   |   Head unload time, msec (not used)
  |     |   |   |     Step rate interval, usec
  |     |   |   |     |       Time needed for spinup time (jiffies)
  |     |   |   |     |       |      Timeout for spinning down (jiffies)
  |     |   |   |     |       |      |   Spindown offset (where disk stops)
  |     |   |   |     |       |      |   |     Select delay
  |     |   |   |     |       |      |   |     |     RPS
  |     |   |   |     |       |      |   |     |     |    Max number of tracks
  |     |   |   |     |       |      |   |     |     |    |     Interrupt timeout
  |     |   |   |     |       |      |   |     |     |    |     |   Max nonintlv. sectors
  |     |   |   |     |       |      |   |     |     |    |     |   | -Max Errors- flags */
{{0,  500, 16, 16, 8000,    1*HZ, 3*HZ,  0, SEL_DLY, 5,  80, 3*HZ, 20, {3,1,2,0,2}, 0,
      0, { 7, 4, 8, 2, 1, 5, 3,10}, 3*HZ/2, 0 }, "unknown" },

{{1,  300, 16, 16, 8000,    1*HZ, 3*HZ,  0, SEL_DLY, 5,  40, 3*HZ, 17, {3,1,2,0,2}, 0,
      0, { 1, 0, 0, 0, 0, 0, 0, 0}, 3*HZ/2, 1 }, "360K PC" }, /*5 1/4 360 KB PC*/

{{2,  500, 16, 16, 6000, 4*HZ/10, 3*HZ, 14, SEL_DLY, 6,  83, 3*HZ, 17, {3,1,2,0,2}, 0,
      0, { 2, 5, 6,23,10,20,12, 0}, 3*HZ/2, 2 }, "1.2M" }, /*5 1/4 HD AT*/

{{3,  250, 16, 16, 3000,    1*HZ, 3*HZ,  0, SEL_DLY, 5,  83, 3*HZ, 20, {3,1,2,0,2}, 0,
      0, { 4,22,21,30, 3, 0, 0, 0}, 3*HZ/2, 4 }, "720k" }, /*3 1/2 DD*/

{{4,  500, 16, 16, 4000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5,  83, 3*HZ, 20, {3,1,2,0,2}, 0,
      0, { 7, 4,25,22,31,21,29,11}, 3*HZ/2, 7 }, "1.44M" }, /*3 1/2 HD*/

{{5, 1000, 15,  8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5,  83, 3*HZ, 40, {3,1,2,0,2}, 0,
      0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M AMI BIOS" }, /*3 1/2 ED*/

{{6, 1000, 15,  8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5,  83, 3*HZ, 40, {3,1,2,0,2}, 0,
      0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M" } /*3 1/2 ED*/
/*    |  --autodetected formats---    |      |      |
 *    read_track                      |      |    Name printed when booting
 *                                    |     Native format
 *                  Frequency of disk change checks */
};

/*
 * This struct defines the different floppy types.
 *
 * Bit 0 of 'stretch' tells if the tracks need to be doubled for some
 * types (e.g. 360kB diskette in 1.2MB drive, etc.).  Bit 1 of 'stretch'
 * tells if the disk is in Commodore 1581 format, which means side 0 sectors
 * are located on side 1 of the disk but with a side 0 ID, and vice-versa.
 * This is the same as the Sharp MZ-80 5.25" CP/M disk format, except that the
 * 1581's logical side 0 is on physical side 1, whereas the Sharp's logical
 * side 0 is on physical side 0 (but with the misnamed sector IDs).
 * 'stretch' should probably be renamed to something more general, like
 * 'options'.  Other parameters should be self-explanatory (see also
 * setfdprm(8)).
 */
/*
            Size
             |  Sectors per track
             |  | Head
             |  | |  Tracks
             |  | |  | Stretch
             |  | |  | |  Gap 1 size
             |  | |  | |    |  Data rate, | 0x40 for perp
             |  | |  | |    |    |  Spec1 (stepping rate, head unload
             |  | |  | |    |    |    |    /fmt gap (gap2) */
static struct floppy_struct floppy_type[32] = {
        {    0, 0,0, 0,0,0x00,0x00,0x00,0x00,NULL    }, /*  0 no testing    */
        {  720, 9,2,40,0,0x2A,0x02,0xDF,0x50,"d360"  }, /*  1 360KB PC      */
        { 2400,15,2,80,0,0x1B,0x00,0xDF,0x54,"h1200" }, /*  2 1.2MB AT      */
        {  720, 9,1,80,0,0x2A,0x02,0xDF,0x50,"D360"  }, /*  3 360KB SS 3.5" */
        { 1440, 9,2,80,0,0x2A,0x02,0xDF,0x50,"D720"  }, /*  4 720KB 3.5"    */
        {  720, 9,2,40,1,0x23,0x01,0xDF,0x50,"h360"  }, /*  5 360KB AT      */
        { 1440, 9,2,80,0,0x23,0x01,0xDF,0x50,"h720"  }, /*  6 720KB AT      */
        { 2880,18,2,80,0,0x1B,0x00,0xCF,0x6C,"H1440" }, /*  7 1.44MB 3.5"   */
        { 5760,36,2,80,0,0x1B,0x43,0xAF,0x54,"E2880" }, /*  8 2.88MB 3.5"   */
        { 6240,39,2,80,0,0x1B,0x43,0xAF,0x28,"E3120" }, /*  9 3.12MB 3.5"   */

        { 2880,18,2,80,0,0x25,0x00,0xDF,0x02,"h1440" }, /* 10 1.44MB 5.25"  */
        { 3360,21,2,80,0,0x1C,0x00,0xCF,0x0C,"H1680" }, /* 11 1.68MB 3.5"   */
        {  820,10,2,41,1,0x25,0x01,0xDF,0x2E,"h410"  }, /* 12 410KB 5.25"   */
        { 1640,10,2,82,0,0x25,0x02,0xDF,0x2E,"H820"  }, /* 13 820KB 3.5"    */
        { 2952,18,2,82,0,0x25,0x00,0xDF,0x02,"h1476" }, /* 14 1.48MB 5.25"  */
        { 3444,21,2,82,0,0x25,0x00,0xDF,0x0C,"H1722" }, /* 15 1.72MB 3.5"   */
        {  840,10,2,42,1,0x25,0x01,0xDF,0x2E,"h420"  }, /* 16 420KB 5.25"   */
        { 1660,10,2,83,0,0x25,0x02,0xDF,0x2E,"H830"  }, /* 17 830KB 3.5"    */
        { 2988,18,2,83,0,0x25,0x00,0xDF,0x02,"h1494" }, /* 18 1.49MB 5.25"  */
        { 3486,21,2,83,0,0x25,0x00,0xDF,0x0C,"H1743" }, /* 19 1.74 MB 3.5"  */

        { 1760,11,2,80,0,0x1C,0x09,0xCF,0x00,"h880"  }, /* 20 880KB 5.25"   */
        { 2080,13,2,80,0,0x1C,0x01,0xCF,0x00,"D1040" }, /* 21 1.04MB 3.5"   */
        { 2240,14,2,80,0,0x1C,0x19,0xCF,0x00,"D1120" }, /* 22 1.12MB 3.5"   */
        { 3200,20,2,80,0,0x1C,0x20,0xCF,0x2C,"h1600" }, /* 23 1.6MB 5.25"   */
        { 3520,22,2,80,0,0x1C,0x08,0xCF,0x2e,"H1760" }, /* 24 1.76MB 3.5"   */
        { 3840,24,2,80,0,0x1C,0x20,0xCF,0x00,"H1920" }, /* 25 1.92MB 3.5"   */
        { 6400,40,2,80,0,0x25,0x5B,0xCF,0x00,"E3200" }, /* 26 3.20MB 3.5"   */
        { 7040,44,2,80,0,0x25,0x5B,0xCF,0x00,"E3520" }, /* 27 3.52MB 3.5"   */
        { 7680,48,2,80,0,0x25,0x63,0xCF,0x00,"E3840" }, /* 28 3.84MB 3.5"   */

        { 3680,23,2,80,0,0x1C,0x10,0xCF,0x00,"H1840" }, /* 29 1.84MB 3.5"   */
        { 1600,10,2,80,0,0x25,0x02,0xDF,0x2E,"D800"  }, /* 30 800KB 3.5"    */
        { 3200,20,2,80,0,0x1C,0x00,0xCF,0x2C,"H1600" }, /* 31 1.6MB 3.5"    */
};

/*
 * Rate is 0 for 500kb/s, 1 for 300kbps, 2 for 250kbps
 * Spec1 is 0xSH, where S is stepping rate (F=1ms, E=2ms, D=3ms etc),
 * H is head unload time (1=16ms, 2=32ms, etc)
 */

/*
 * Track buffer
 * Because these are written to by the DMA controller, they must
 * not contain a 64k byte boundary crossing, or data will be
 * corrupted/lost.
 */

/*
 * The "reset" variable should be tested whenever an interrupt is scheduled,
 * after the commands have been sent. This is to ensure that the driver doesn't
 * get wedged when the interrupt doesn't come because of a failed command.
 * reset doesn't need to be tested before sending commands, because
 * output_byte is automatically disabled when reset is set.
 */
/*
 * These are global variables, as that's the easiest way to give
 * information to interrupts. They are the data used for the current
 * request.
 */

/*
 * Bottom half floppy driver.
 * ==========================
 *
 * This part of the file contains the code talking directly to the hardware,
 * and also the main service loop (seek-configure-spinup-command)
 */

/*
 * disk change.
 * This routine is responsible for maintaining the FD_DISK_CHANGE flag,
 * and the last_checked date.
 *
 * last_checked is the date of the last check which showed 'no disk change'
 * FD_DISK_CHANGE is set under two conditions:
 * 1. The floppy has been changed after some i/o to that floppy already
 *    took place.
 * 2. No floppy disk is in the drive. This is done in order to ensure that
 *    requests are quickly flushed in case there is no disk in the drive. It
 *    follows that FD_DISK_CHANGE can only be cleared if there is a disk in
 *    the drive.
 *
 * For 1., maxblock is observed. Maxblock is 0 if no i/o has taken place yet.
 * For 2., FD_DISK_NEWCHANGE is watched. FD_DISK_NEWCHANGE is cleared on
 *  each seek. If a disk is present, the disk change line should also be
 *  cleared on each seek. Thus, if FD_DISK_NEWCHANGE is clear, but the disk
 *  change line is set, this means either that no disk is in the drive, or
 *  that it has been removed since the last seek.
 *
 * This means that we really have a third possibility too:
 *  The floppy has been changed after the last seek.
 */

static int disk_change(int drive)

static inline int is_selected(int dor, int unit)

static int set_dor(int fdc, char mask, char data)
static void twaddle(void)
{
        if (DP->select_delay)
                return;
        fd_outb(FDCS->dor & ~(0x10<<UNIT(current_drive)), FD_DOR);
        fd_outb(FDCS->dor, FD_DOR);
        DRS->select_date = jiffies;
}

/* reset all driver information about the current fdc. This is needed after
 * a reset, and after a raw command. */
static void reset_fdc_info(int mode)
{

}

/* selects the fdc and drive, and enables the fdc's input/dma. */
static void set_fdc(int drive)
{

static int _lock_fdc(int drive, int interruptible, int line)
{

}

#define lock_fdc(drive,interruptible) _lock_fdc(drive,interruptible, __LINE__)

#define LOCK_FDC(drive,interruptible) \
if (lock_fdc(drive,interruptible)) return -EINTR;


/* unlocks the driver */
static inline void unlock_fdc(void)

/* switches the motor off after a given timeout */
static void motor_off_callback(unsigned long nr)

/* schedules motor off */
static void floppy_off(unsigned int drive)

/*
 * cycle through all N_DRIVE floppy drives, for disk change testing.
 * stopping at current drive. This is done before any long operation, to
 * be sure to have up to date disk change information.
 */
static void scandrives(void)

static void schedule_bh( void (*handler)(void*) )

static struct timer_list fd_timer;

static void main_command_interrupt(void)
{
        del_timer(&fd_timer);
        cont->interrupt();
}

/* waits for a delay (spinup or select) to pass */
static int fd_wait_for_completion(unsigned long delay, timeout_fn function)
{

}

static spinlock_t floppy_hlt_lock = SPIN_LOCK_UNLOCKED;
static int hlt_disabled;
static void floppy_disable_hlt(void)

static void floppy_enable_hlt(void)
{
        unsigned long flags;

        spin_lock_irqsave(&floppy_hlt_lock, flags);
        if (hlt_disabled){
                hlt_disabled=0;
#ifdef HAVE_DISABLE_HLT
                enable_hlt();
#endif
        }
        spin_unlock_irqrestore(&floppy_hlt_lock, flags);
}


static void setup_DMA(void)

static void show_floppy(void);

/* waits until the fdc becomes ready */
static int wait_til_ready(void)

/* sends a command byte to the fdc */
static int output_byte(char byte)
{
        int status;

        if ((status = wait_til_ready()) < 0)
                return -1;
        if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY){
                fd_outb(byte,FD_DATA);
#ifdef FLOPPY_SANITY_CHECK
                output_log[output_log_pos].data = byte;
                output_log[output_log_pos].status = status;
                output_log[output_log_pos].jiffies = jiffies;
                output_log_pos = (output_log_pos + 1) % OLOGSIZE;
#endif
                return 0;
        }
        FDCS->reset = 1;
        if (!initialising) {
                DPRINT("Unable to send byte %x to FDC. Fdc=%x Status=%x\n",
                       byte, fdc, status);
                show_floppy();
        }
        return -1;
}
#define LAST_OUT(x) if (output_byte(x)<0){ reset_fdc();return;}

/* gets the response from the fdc */
static int result(void)

#define MORE_OUTPUT -2
/* does the fdc need more output? */
static int need_more_output(void)
{
        int status;
        if ((status = wait_til_ready()) < 0)
                return -1;
        if ((status & (STATUS_READY|STATUS_DIR|STATUS_DMA)) == STATUS_READY)
                return MORE_OUTPUT;
        return result();
}

/* Set perpendicular mode as required, based on data rate, if supported.
 * 82077 Now tested. 1Mbps data rate only possible with 82077-1.
 */
static inline void perpendicular_mode(void)
{
        unsigned char perp_mode;

        if (raw_cmd->rate & 0x40){
                switch(raw_cmd->rate & 3){
                        case 0:
                                perp_mode=2;
                                break;
                        case 3:
                                perp_mode=3;
                                break;
                        default:
                                DPRINT("Invalid data rate for perpendicular mode!\n");
                                cont->done(0);
                                FDCS->reset = 1; /* convenient way to return to
                                                  * redo without to much hassle (deep
                                                  * stack et al. */
                                return;
                }
        } else
                perp_mode = 0;

        if (FDCS->perp_mode == perp_mode)
                return;
        if (FDCS->version >= FDC_82077_ORIG) {
                output_byte(FD_PERPENDICULAR);
                output_byte(perp_mode);
                FDCS->perp_mode = perp_mode;
        } else if (perp_mode) {
                DPRINT("perpendicular mode not supported by this FDC.\n");
        }
} /* perpendicular_mode */

static int fifo_depth = 0xa;
static int no_fifo;

static int fdc_configure(void)
{
        /* Turn on FIFO */
        output_byte(FD_CONFIGURE);
        if (need_more_output() != MORE_OUTPUT)
                return 0;
        output_byte(0);
        output_byte(0x10 | (no_fifo & 0x20) | (fifo_depth & 0xf));
        output_byte(0); /* pre-compensation from track 
                           0 upwards */
        return 1;
}       

#define NOMINAL_DTR 500

/* Issue a "SPECIFY" command to set the step rate time, head unload time,
 * head load time, and DMA disable flag to values needed by floppy.
 *
 * The value "dtr" is the data transfer rate in Kbps.  It is needed
 * to account for the data rate-based scaling done by the 82072 and 82077
 * FDC types.  This parameter is ignored for other types of FDCs (i.e.
 * 8272a).
 *
 * Note that changing the data transfer rate has a (probably deleterious)
 * effect on the parameters subject to scaling for 82072/82077 FDCs, so
 * fdc_specify is called again after each data transfer rate
 * change.
 *
 * srt: 1000 to 16000 in microseconds
 * hut: 16 to 240 milliseconds
 * hlt: 2 to 254 milliseconds
 *
 * These values are rounded up to the next highest available delay time.
 */
static void fdc_specify(void)
{
        unsigned char spec1, spec2;
        unsigned long srt, hlt, hut;
        unsigned long dtr = NOMINAL_DTR;
        unsigned long scale_dtr = NOMINAL_DTR;
        int hlt_max_code = 0x7f;
        int hut_max_code = 0xf;

        if (FDCS->need_configure && FDCS->version >= FDC_82072A) {
                fdc_configure();
                FDCS->need_configure = 0;
                /*DPRINT("FIFO enabled\n");*/
        }

        switch (raw_cmd->rate & 0x03) {
                case 3:
                        dtr = 1000;
                        break;
                case 1:
                        dtr = 300;
                        if (FDCS->version >= FDC_82078) {
                                /* chose the default rate table, not the one
                                 * where 1 = 2 Mbps */
                                output_byte(FD_DRIVESPEC);
                                if (need_more_output() == MORE_OUTPUT) {
                                        output_byte(UNIT(current_drive));
                                        output_byte(0xc0);
                                }
                        }
                        break;
                case 2:
                        dtr = 250;
                        break;
        }

        if (FDCS->version >= FDC_82072) {
                scale_dtr = dtr;
                hlt_max_code = 0x00; /* 0==256msec*dtr0/dtr (not linear!) */
                hut_max_code = 0x0; /* 0==256msec*dtr0/dtr (not linear!) */
        }

        /* Convert step rate from microseconds to milliseconds and 4 bits */
        srt = 16 - (DP->srt*scale_dtr/1000 + NOMINAL_DTR - 1)/NOMINAL_DTR;
        if( slow_floppy ) {
                srt = srt / 4;
        }
        SUPBOUND(srt, 0xf);
        INFBOUND(srt, 0);

        hlt = (DP->hlt*scale_dtr/2 + NOMINAL_DTR - 1)/NOMINAL_DTR;
        if (hlt < 0x01)
                hlt = 0x01;
        else if (hlt > 0x7f)
                hlt = hlt_max_code;

        hut = (DP->hut*scale_dtr/16 + NOMINAL_DTR - 1)/NOMINAL_DTR;
        if (hut < 0x1)
                hut = 0x1;
        else if (hut > 0xf)
                hut = hut_max_code;

        spec1 = (srt << 4) | hut;
        spec2 = (hlt << 1) | (use_virtual_dma & 1);

        /* If these parameters did not change, just return with success */
        if (FDCS->spec1 != spec1 || FDCS->spec2 != spec2) {
                /* Go ahead and set spec1 and spec2 */
                output_byte(FD_SPECIFY);
                output_byte(FDCS->spec1 = spec1);
                output_byte(FDCS->spec2 = spec2);
        }
} /* fdc_specify */

/* Set the FDC's data transfer rate on behalf of the specified drive.
 * NOTE: with 82072/82077 FDCs, changing the data rate requires a reissue
 * of the specify command (i.e. using the fdc_specify function).
 */
static int fdc_dtr(void)
{
        /* If data rate not already set to desired value, set it. */
        if ((raw_cmd->rate & 3) == FDCS->dtr)
                return 0;

        /* Set dtr */
        fd_outb(raw_cmd->rate & 3, FD_DCR);

        /* TODO: some FDC/drive combinations (C&T 82C711 with TEAC 1.2MB)
         * need a stabilization period of several milliseconds to be
         * enforced after data rate changes before R/W operations.
         * Pause 5 msec to avoid trouble. (Needs to be 2 jiffies)
         */
        FDCS->dtr = raw_cmd->rate & 3;
        return(fd_wait_for_completion(jiffies+2UL*HZ/100,
                                   (timeout_fn) floppy_ready));
} /* fdc_dtr */

static void tell_sector(void)
{
        printk(": track %d, head %d, sector %d, size %d",
               R_TRACK, R_HEAD, R_SECTOR, R_SIZECODE);
} /* tell_sector */


/*
 * OK, this error interpreting routine is called after a
 * DMA read/write has succeeded
 * or failed, so we check the results, and copy any buffers.
 * hhb: Added better error reporting.
 * ak: Made this into a separate routine.
 */
static int interpret_errors(void)
{
        char bad;

        if (inr!=7) {
                DPRINT("-- FDC reply error");
                FDCS->reset = 1;
                return 1;
        }

        /* check IC to find cause of interrupt */
        switch (ST0 & ST0_INTR) {
                case 0x40:      /* error occurred during command execution */
                        if (ST1 & ST1_EOC)
                                return 0; /* occurs with pseudo-DMA */
                        bad = 1;
                        if (ST1 & ST1_WP) {
                                DPRINT("Drive is write protected\n");
                                CLEARF(FD_DISK_WRITABLE);
                                cont->done(0);
                                bad = 2;
                        } else if (ST1 & ST1_ND) {
                                SETF(FD_NEED_TWADDLE);
                        } else if (ST1 & ST1_OR) {
                                if (DP->flags & FTD_MSG)
                                        DPRINT("Over/Underrun - retrying\n");
                                bad = 0;
                        }else if (*errors >= DP->max_errors.reporting){
                                DPRINT("");
                                if (ST0 & ST0_ECE) {
                                        printk("Recalibrate failed!");
                                } else if (ST2 & ST2_CRC) {
                                        printk("data CRC error");
                                        tell_sector();
                                } else if (ST1 & ST1_CRC) {
                                        printk("CRC error");
                                        tell_sector();
                                } else if ((ST1 & (ST1_MAM|ST1_ND)) || (ST2 & ST2_MAM)) {
                                        if (!probing) {
                                                printk("sector not found");
                                                tell_sector();
                                        } else
                                                printk("probe failed...");
                                } else if (ST2 & ST2_WC) {      /* seek error */
                                        printk("wrong cylinder");
                                } else if (ST2 & ST2_BC) {      /* cylinder marked as bad */
                                        printk("bad cylinder");
                                } else {
                                        printk("unknown error. ST[0..2] are: 0x%x 0x%x 0x%x", ST0, ST1, ST2);
                                        tell_sector();
                                }
                                printk("\n");

                        }
                        if (ST2 & ST2_WC || ST2 & ST2_BC)
                                /* wrong cylinder => recal */
                                DRS->track = NEED_2_RECAL;
                        return bad;
                case 0x80: /* invalid command given */
                        DPRINT("Invalid FDC command given!\n");
                        cont->done(0);
                        return 2;
                case 0xc0:
                        DPRINT("Abnormal termination caused by polling\n");
                        cont->error();
                        return 2;
                default: /* (0) Normal command termination */
                        return 0;
        }
}

/*
 * This routine is called when everything should be correctly set up
 * for the transfer (i.e. floppy motor is on, the correct floppy is
 * selected, and the head is sitting on the right track).
 */
static void setup_rw_floppy(void)

static int blind_seek;

/*
 * This is the routine called after every seek (or recalibrate) interrupt
 * from the floppy controller.
 */
static void seek_interrupt(void)

static void check_wp(void)

static void seek_floppy(void)

static void recal_interrupt(void)

static void print_result(char *message, int inr)

/* interrupt handler. Note that this can be called externally on the Sparc */
void floppy_interrupt(int irq, void *dev_id, struct pt_regs * regs)

static void recalibrate_floppy(void)

/*
 * Must do 4 FD_SENSEIs after reset because of ``drive polling''.
 */
static void reset_interrupt(void)

/*
 * reset is done by pulling bit 2 of DOR low for a while (old FDCs),
 * or by setting the self clearing bit 7 of STATUS (newer FDCs)
 */
static void reset_fdc(void)

static void show_floppy(void)

static void floppy_shutdown(void)

/*typedef void (*timeout_fn)(unsigned long);*/

/* start motor, check media-changed condition and write protection */
static int start_motor(void (*function)(void) )

static void floppy_ready(void)

static void floppy_start(void)

/*
 * ========================================================================
 * here ends the bottom half. Exported routines are:
 * floppy_start, floppy_off, floppy_ready, lock_fdc, unlock_fdc, set_fdc,
 * start_motor, reset_fdc, reset_fdc_info, interpret_errors.
 * Initialization also uses output_byte, result, set_dor, floppy_interrupt
 * and set_dor.
 * ========================================================================
 */
/*
 * General purpose continuations.
 * ==============================
 */

static int wait_til_done(void (*handler)(void), int interruptible)

static void generic_done(int result)
static void generic_success(void)

static void generic_failure(void)

static void success_and_wakeup(void)

/*
 * formatting and rw support.
 * ==========================
 */
static int next_valid_format(void)

static void bad_flp_intr(void)

static void set_floppy(kdev_t device)

/*
 * formatting support.
 * ===================
 */
static void format_interrupt(void)


static void setup_format_params(int track)

static void redo_format(void)

static int do_format(kdev_t device, struct format_descr *tmp_format_req)

/*
 * Buffer read/write and support
 * =============================
 */
/* new request_done. Can handle physical sectors which are smaller than a
 * logical buffer */
static void request_done(int uptodate)

/* Interrupt handler evaluating the result of the r/w operation */
static void rw_interrupt(void)

/* Compute maximal contiguous buffer size. */
static int buffer_chain_size(void)

/* Compute the maximal transfer size */
static int transfer_size(int ssize, int max_sector, int max_size)

/*
 * Move data from/to the track buffer to/from the buffer cache.
 */
static void copy_buffer(int ssize, int max_sector, int max_sector_2)


/*
 * Formulate a read/write request.
 * this routine decides where to load the data (directly to buffer, or to
 * tmp floppy area), how much data to load (the size of the buffer, the whole
 * track, or a single sector)
 * All floppy_track_buffer handling goes in here. If we ever add track buffer
 * allocation on the fly, it should be done here. No other part should need
 * modification.
 */
static int make_raw_rw_request(void)

static void redo_fd_request(void)

static void do_fd_request(request_queue_t * q)

static struct cont_t poll_cont={
        success_and_wakeup,
        floppy_ready,
        generic_failure,
        generic_done };

static int poll_drive(int interruptible, int flag)
{
        int ret;
        /* no auto-sense, just clear dcl */
        raw_cmd = &default_raw_cmd;
        raw_cmd->flags= flag;
        raw_cmd->track=0;
        raw_cmd->cmd_count=0;
        cont = &poll_cont;
#ifdef DCL_DEBUG
        if (DP->flags & FD_DEBUG){
                DPRINT("setting NEWCHANGE in poll_drive\n");
        }
#endif
        SETF(FD_DISK_NEWCHANGE);
        WAIT(floppy_ready);
        return ret;
}

/*
 * Misc Ioctl's and support
 * ========================
 */
static inline int fd_copyout(void *param, const void *address, unsigned long size)
{
        return copy_to_user(param,address, size) ? -EFAULT : 0;
}

static inline int fd_copyin(void *param, void *address, unsigned long size)
{
        return copy_from_user(address, param, size) ? -EFAULT : 0;
}

static inline const char *drive_name(int type, int drive)

/* raw commands */
static void raw_cmd_done(int flag)

static inline int raw_cmd_copyout(int cmd, char *param,
                                  struct floppy_raw_cmd *ptr)

static void raw_cmd_free(struct floppy_raw_cmd **ptr)

static int raw_cmd_ioctl(int cmd, void *param)
static int invalidate_drive(kdev_t rdev)

static inline int set_geometry(unsigned int cmd, struct floppy_struct *g,
                               int drive, int type, kdev_t device)
{
        int cnt;

        /* sanity checking for parameters.*/
        if (g->sect <= 0 ||
            g->head <= 0 ||
            g->track <= 0 ||
            g->track > UDP->tracks>>STRETCH(g) ||
            /* check if reserved bits are set */
            (g->stretch&~(FD_STRETCH|FD_SWAPSIDES)) != 0)
                return -EINVAL;
        if (type){
                if (!capable(CAP_SYS_ADMIN))
                        return -EPERM;
                LOCK_FDC(drive,1);
                for (cnt = 0; cnt < N_DRIVE; cnt++){
                        if (ITYPE(drive_state[cnt].fd_device) == type &&
                            drive_state[cnt].fd_ref)
                                set_bit(drive, &fake_change);
                }
                floppy_type[type] = *g;
                floppy_type[type].name="user format";
                for (cnt = type << 2; cnt < (type << 2) + 4; cnt++)
                        floppy_sizes[cnt]= floppy_sizes[cnt+0x80]=
                                (floppy_type[type].size+1)>>1;
                process_fd_request();
                for (cnt = 0; cnt < N_DRIVE; cnt++){
                        if (ITYPE(drive_state[cnt].fd_device) == type &&
                            drive_state[cnt].fd_ref)
                                check_disk_change(
                                        MKDEV(FLOPPY_MAJOR,
                                              drive_state[cnt].fd_device));
                }
        } else {
                LOCK_FDC(drive,1);
                if (cmd != FDDEFPRM)
                        /* notice a disk change immediately, else
                         * we lose our settings immediately*/
                        CALL(poll_drive(1, FD_RAW_NEED_DISK));
                user_params[drive] = *g;
                if (buffer_drive == drive)
                        SUPBOUND(buffer_max, user_params[drive].sect);
                current_type[drive] = &user_params[drive];
                floppy_sizes[drive] = (user_params[drive].size+1) >> 1;
                if (cmd == FDDEFPRM)
                        DRS->keep_data = -1;
                else
                        DRS->keep_data = 1;
                /* invalidation. Invalidate only when needed, i.e.
                 * when there are already sectors in the buffer cache
                 * whose number will change. This is useful, because
                 * mtools often changes the geometry of the disk after
                 * looking at the boot block */
                if (DRS->maxblock > user_params[drive].sect || DRS->maxtrack)
                        invalidate_drive(device);
                else
                        process_fd_request();
        }
        return 0;
}

/* handle obsolete ioctl's */
static int ioctl_table[]= {
        FDCLRPRM,
        FDSETPRM,
        FDDEFPRM,
        FDGETPRM,
        FDMSGON,
        FDMSGOFF,
        FDFMTBEG,
        FDFMTTRK,
        FDFMTEND,
        FDSETEMSGTRESH,
        FDFLUSH,
        FDSETMAXERRS,
        FDGETMAXERRS,
        FDGETDRVTYP,
        FDSETDRVPRM,
        FDGETDRVPRM,
        FDGETDRVSTAT,
        FDPOLLDRVSTAT,
        FDRESET,
        FDGETFDCSTAT,
        FDWERRORCLR,
        FDWERRORGET,
        FDRAWCMD,
        FDEJECT,
        FDTWADDLE
};

static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
                    unsigned long param)
{
#define FD_IOCTL_ALLOWED ((filp) && (filp)->private_data)
#define OUT(c,x) case c: outparam = (const char *) (x); break
#define IN(c,x,tag) case c: *(x) = inparam. tag ; return 0

        int i,drive,type;
        kdev_t device;
        int ret;
        int size;
        union inparam {
                struct floppy_struct g; /* geometry */
                struct format_descr f;
                struct floppy_max_errors max_errors;
                struct floppy_drive_params dp;
        } inparam; /* parameters coming from user space */
        const char *outparam; /* parameters passed back to user space */

        device = inode->i_rdev;
        switch (cmd) {
                case BLKROSET:
                case BLKROGET:
                case BLKRASET:
                case BLKRAGET:
                case BLKFLSBUF:
                        return blk_ioctl(device, cmd, param);
        }
        type = TYPE(device);
        drive = DRIVE(device);

        /* convert compatibility eject ioctls into floppy eject ioctl.
         * We do this in order to provide a means to eject floppy disks before
         * installing the new fdutils package */
        if (cmd == CDROMEJECT || /* CD-ROM eject */
            cmd == 0x6470 /* SunOS floppy eject */) {
                DPRINT("obsolete eject ioctl\n");
                DPRINT("please use floppycontrol --eject\n");
                cmd = FDEJECT;
        }

        /* generic block device ioctls */
        switch(cmd) {
                /* the following have been inspired by the corresponding
                 * code for other block devices. */
                struct floppy_struct *g;
                case HDIO_GETGEO:
                {
                        struct hd_geometry loc;
                        ECALL(get_floppy_geometry(drive, type, &g));
                        loc.heads = g->head;
                        loc.sectors = g->sect;
                        loc.cylinders = g->track;
                        loc.start = 0;
                        return _COPYOUT(loc);
                }

                case BLKGETSIZE:
                        ECALL(get_floppy_geometry(drive, type, &g));
                        return put_user(g->size, (unsigned long *) param);

                case BLKGETSIZE64:
                        ECALL(get_floppy_geometry(drive, type, &g));
                        return put_user((u64)g->size << 9, (u64 *) param);
                /* BLKRRPART is not defined as floppies don't have
                 * partition tables */
        }

        /* convert the old style command into a new style command */
        if ((cmd & 0xff00) == 0x0200) {
                ECALL(normalize_ioctl(&cmd, &size));
        } else
                return -EINVAL;

        /* permission checks */
        if (((cmd & 0x40) && !FD_IOCTL_ALLOWED) ||
            ((cmd & 0x80) && !capable(CAP_SYS_ADMIN)))
                return -EPERM;

        /* copyin */
        CLEARSTRUCT(&inparam);
        if (_IOC_DIR(cmd) & _IOC_WRITE)
                ECALL(fd_copyin((void *)param, &inparam, size))

        switch (cmd) {
                case FDEJECT:
                        if (UDRS->fd_ref != 1)
                                /* somebody else has this drive open */
                                return -EBUSY;
                        LOCK_FDC(drive,1);

                        /* do the actual eject. Fails on
                         * non-Sparc architectures */
                        ret=fd_eject(UNIT(drive));

                        USETF(FD_DISK_CHANGED);
                        USETF(FD_VERIFY);
                        process_fd_request();
                        return ret;                     
                case FDCLRPRM:
                        LOCK_FDC(drive,1);
                        current_type[drive] = NULL;
                        floppy_sizes[drive] = MAX_DISK_SIZE;
                        UDRS->keep_data = 0;
                        return invalidate_drive(device);
                case FDSETPRM:
                case FDDEFPRM:
                        return set_geometry(cmd, & inparam.g,
                                            drive, type, device);
                case FDGETPRM:
                        ECALL(get_floppy_geometry(drive, type, 
                                                  (struct floppy_struct**)
                                                  &outparam));
                        break;

                case FDMSGON:
                        UDP->flags |= FTD_MSG;
                        return 0;
                case FDMSGOFF:
                        UDP->flags &= ~FTD_MSG;
                        return 0;

                case FDFMTBEG:
                        LOCK_FDC(drive,1);
                        CALL(poll_drive(1, FD_RAW_NEED_DISK));
                        ret = UDRS->flags;
                        process_fd_request();
                        if (ret & FD_VERIFY)
                                return -ENODEV;
                        if (!(ret & FD_DISK_WRITABLE))
                                return -EROFS;
                        return 0;
                case FDFMTTRK:
                        if (UDRS->fd_ref != 1)
                                return -EBUSY;
                        return do_format(device, &inparam.f);
                case FDFMTEND:
                case FDFLUSH:
                        LOCK_FDC(drive,1);
                        return invalidate_drive(device);

                case FDSETEMSGTRESH:
                        UDP->max_errors.reporting =
                                (unsigned short) (param & 0x0f);
                        return 0;
                OUT(FDGETMAXERRS, &UDP->max_errors);
                IN(FDSETMAXERRS, &UDP->max_errors, max_errors);

                case FDGETDRVTYP:
                        outparam = drive_name(type,drive);
                        SUPBOUND(size,strlen(outparam)+1);
                        break;

                IN(FDSETDRVPRM, UDP, dp);
                OUT(FDGETDRVPRM, UDP);

                case FDPOLLDRVSTAT:
                        LOCK_FDC(drive,1);
                        CALL(poll_drive(1, FD_RAW_NEED_DISK));
                        process_fd_request();
                        /* fall through */
                OUT(FDGETDRVSTAT, UDRS);

                case FDRESET:
                        return user_reset_fdc(drive, (int)param, 1);

                OUT(FDGETFDCSTAT,UFDCS);

                case FDWERRORCLR:
                        CLEARSTRUCT(UDRWE);
                        return 0;
                OUT(FDWERRORGET,UDRWE);

                case FDRAWCMD:
                        if (type)
                                return -EINVAL;
                        LOCK_FDC(drive,1);
                        set_floppy(device);
                        CALL(i = raw_cmd_ioctl(cmd,(void *) param));
                        process_fd_request();
                        return i;

                case FDTWADDLE:
                        LOCK_FDC(drive,1);
                        twaddle();
                        process_fd_request();
                        return 0;

                default:
                        return -EINVAL;
        }

        if (_IOC_DIR(cmd) & _IOC_READ)
                return fd_copyout((void *)param, outparam, size);
        else
                return 0;
#undef OUT
#undef IN
}

static int floppy_release(struct inode * inode, struct file * filp)

/*
 * 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.
 */
#define RETERR(x) do{floppy_release(inode,filp); return -(x);}while(0)

static int floppy_open(struct inode * inode, struct file * filp)

/*
 * Check if the disk has been changed or if a change has been faked.
 */
static int check_floppy_change(kdev_t dev)

/* revalidate the floppy disk, i.e. trigger format autodetection by reading
 * the bootblock (block 0). "Autodetection" is also needed to check whether
 * there is a disk in the drive at all... Thus we also do it for fixed
 * geometry formats */
static int floppy_revalidate(kdev_t dev)

static struct block_device_operations floppy_fops = {
        owner:                  THIS_MODULE,
        open:                   floppy_open,
        release:                floppy_release,
        ioctl:                  fd_ioctl,
        check_media_change:     check_floppy_change,
        revalidate:             floppy_revalidate,
};


/*
 * Floppy Driver initialization
 * =============================
 */

/* Determine the floppy disk controller type */
/* This routine was written by David C. Niemi */
static char __init get_fdc_version(void)
{
        int r;

        output_byte(FD_DUMPREGS);       /* 82072 and better know DUMPREGS */
        if (FDCS->reset)
                return FDC_NONE;
        if ((r = result()) <= 0x00)
                return FDC_NONE;        /* No FDC present ??? */
        if ((r==1) && (reply_buffer[0] == 0x80)){
                printk(KERN_INFO "FDC %d is an 8272A\n",fdc);
                return FDC_8272A;       /* 8272a/765 don't know DUMPREGS */
        }
        if (r != 10) {
                printk("FDC %d init: DUMPREGS: unexpected return of %d bytes.\n",
                       fdc, r);
                return FDC_UNKNOWN;
        }

        if (!fdc_configure()) {
                printk(KERN_INFO "FDC %d is an 82072\n",fdc);
                return FDC_82072;       /* 82072 doesn't know CONFIGURE */
        }

        output_byte(FD_PERPENDICULAR);
        if (need_more_output() == MORE_OUTPUT) {
                output_byte(0);
        } else {
                printk(KERN_INFO "FDC %d is an 82072A\n", fdc);
                return FDC_82072A;      /* 82072A as found on Sparcs. */
        }

        output_byte(FD_UNLOCK);
        r = result();
        if ((r == 1) && (reply_buffer[0] == 0x80)){
                printk(KERN_INFO "FDC %d is a pre-1991 82077\n", fdc);
                return FDC_82077_ORIG;  /* Pre-1991 82077, doesn't know 
                                         * LOCK/UNLOCK */
        }
        if ((r != 1) || (reply_buffer[0] != 0x00)) {
                printk("FDC %d init: UNLOCK: unexpected return of %d bytes.\n",
                       fdc, r);
                return FDC_UNKNOWN;
        }
        output_byte(FD_PARTID);
        r = result();
        if (r != 1) {
                printk("FDC %d init: PARTID: unexpected return of %d bytes.\n",
                       fdc, r);
                return FDC_UNKNOWN;
        }
        if (reply_buffer[0] == 0x80) {
                printk(KERN_INFO "FDC %d is a post-1991 82077\n",fdc);
                return FDC_82077;       /* Revised 82077AA passes all the tests */
        }
        switch (reply_buffer[0] >> 5) {
                case 0x0:
                        /* Either a 82078-1 or a 82078SL running at 5Volt */
                        printk(KERN_INFO "FDC %d is an 82078.\n",fdc);
                        return FDC_82078;
                case 0x1:
                        printk(KERN_INFO "FDC %d is a 44pin 82078\n",fdc);
                        return FDC_82078;
                case 0x2:
                        printk(KERN_INFO "FDC %d is a S82078B\n", fdc);
                        return FDC_S82078B;
                case 0x3:
                        printk(KERN_INFO "FDC %d is a National Semiconductor PC87306\n", fdc);
                        return FDC_87306;
                default:
                        printk(KERN_INFO "FDC %d init: 82078 variant with unknown PARTID=%d.\n",
                               fdc, reply_buffer[0] >> 5);
                        return FDC_82078_UNKN;
        }
} /* get_fdc_version */


static int __init floppy_setup(char *str)
{
        int i;
        int param;
        int ints[11];

        str = get_options(str,ARRAY_SIZE(ints),ints);
        if (str) {
                for (i=0; i< ARRAY_SIZE(config_params); i++){
                        if (strcmp(str,config_params[i].name) == 0){
                                if (ints[0])
                                        param = ints[1];
                                else
                                        param = config_params[i].def_param;
                                if (config_params[i].fn)
                                        config_params[i].
                                                fn(ints,param,
                                                   config_params[i].param2);
                                if (config_params[i].var) {
                                        DPRINT("%s=%d\n", str, param);
                                        *config_params[i].var = param;
                                }
                                return 1;
                        }
                }
        }
        if (str) {
                DPRINT("unknown floppy option [%s]\n", str);
                
                DPRINT("allowed options are:");
                for (i=0; i< ARRAY_SIZE(config_params); i++)
                        printk(" %s",config_params[i].name);
                printk("\n");
        } else
                DPRINT("botched floppy option\n");
        DPRINT("Read linux/Documentation/floppy.txt\n");
        return 0;
}


int __init floppy_init(void)
{
        int i,unit,drive;


        raw_cmd = NULL;
        devfs_handle = devfs_mk_dir (NULL, "floppy", NULL);
        if (devfs_register_blkdev(MAJOR_NR,"fd",&floppy_fops)) {
                printk("Unable to get major %d for floppy\n",MAJOR_NR);
                return -EBUSY;
        }

        for (i=0; i<256; i++) {
                if (ITYPE(i))
                        floppy_sizes[i] = (floppy_type[ITYPE(i)].size+1) >> 1;
                else
                        floppy_sizes[i] = MAX_DISK_SIZE;

                floppy_blocksizes[i] = 512;
                floppy_maxsectors[i] = 64;
        }

        blk_size[MAJOR_NR] = floppy_sizes;
        blksize_size[MAJOR_NR] = floppy_blocksizes;
        max_sectors[MAJOR_NR] = floppy_maxsectors;
        blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST);
        reschedule_timeout(MAXTIMEOUT, "floppy init", MAXTIMEOUT);
        config_types();

        for (i = 0; i < N_FDC; i++) {
                fdc = i;
                CLEARSTRUCT(FDCS);
                FDCS->dtr = -1;
                FDCS->dor = 0x4;
#if defined(__sparc__) || defined(__mc68000__)
                /*sparcs/sun3x don't have a DOR reset which we can fall back on to*/
#ifdef __mc68000__
                if(MACH_IS_SUN3X)
#endif
                        FDCS->version = FDC_82072A;             
#endif
        }

        use_virtual_dma = can_use_virtual_dma & 1;
        fdc_state[0].address = FDC1;
        if (fdc_state[0].address == -1) {
                devfs_unregister_blkdev(MAJOR_NR,"fd");
                del_timer(&fd_timeout);
                blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
                return -ENODEV;
        }
#if N_FDC > 1
        fdc_state[1].address = FDC2;
#endif

        fdc = 0; /* reset fdc in case of unexpected interrupt */
        if (floppy_grab_irq_and_dma()){
                del_timer(&fd_timeout);
                blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
                devfs_unregister_blkdev(MAJOR_NR,"fd");
                return -EBUSY;
        }

        /* initialise drive state */
        for (drive = 0; drive < N_DRIVE; drive++) {
                CLEARSTRUCT(UDRS);
                CLEARSTRUCT(UDRWE);
                USETF(FD_DISK_NEWCHANGE);
                USETF(FD_DISK_CHANGED);
                USETF(FD_VERIFY);
                UDRS->fd_device = -1;
                floppy_track_buffer = NULL;
                max_buffer_sectors = 0;
        }

        /*
         * Small 10 msec delay to let through any interrupt that
         * initialization might have triggered, to not
         * confuse detection:
         */
        current->state = TASK_UNINTERRUPTIBLE;
        schedule_timeout(HZ/100 + 1);

        for (i = 0; i < N_FDC; i++) {
                fdc = i;
                FDCS->driver_version = FD_DRIVER_VERSION;
                for (unit=0; unit<4; unit++)
                        FDCS->track[unit] = 0;
                if (FDCS->address == -1)
                        continue;
                FDCS->rawcmd = 2;
                if (user_reset_fdc(-1,FD_RESET_ALWAYS,0)){
                        /* free ioports reserved by floppy_grab_irq_and_dma() */
                        release_region(FDCS->address+2, 4);
                        release_region(FDCS->address+7, 1);
                        FDCS->address = -1;
                        FDCS->version = FDC_NONE;
                        continue;
                }
                /* Try to determine the floppy controller type */
                FDCS->version = get_fdc_version();
                if (FDCS->version == FDC_NONE){
                        /* free ioports reserved by floppy_grab_irq_and_dma() */
                        release_region(FDCS->address+2, 4);
                        release_region(FDCS->address+7, 1);
                        FDCS->address = -1;
                        continue;
                }
                if (can_use_virtual_dma == 2 && FDCS->version < FDC_82072A)
                        can_use_virtual_dma = 0;

                have_no_fdc = 0;
                /* Not all FDCs seem to be able to handle the version command
                 * properly, so force a reset for the standard FDC clones,
                 * to avoid interrupt garbage.
                 */
                user_reset_fdc(-1,FD_RESET_ALWAYS,0);
        }
        fdc=0;
        del_timer(&fd_timeout);
        current_drive = 0;
        floppy_release_irq_and_dma();
        initialising=0;
        if (have_no_fdc) 
        {
                DPRINT("no floppy controllers found\n");
                run_task_queue(&tq_immediate);
                if (usage_count)
                        floppy_release_irq_and_dma();
                blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
                devfs_unregister_blkdev(MAJOR_NR,"fd");
        }
        
        for (drive = 0; drive < N_DRIVE; drive++) {
                motor_off_timer[drive].data = drive;
                motor_off_timer[drive].function = motor_off_callback;
                if (!(allowed_drive_mask & (1 << drive)))
                        continue;
                if (fdc_state[FDC(drive)].version == FDC_NONE)
                        continue;
                for (i = 0; i<NUMBER(floppy_type); i++)
                        register_disk(NULL, MKDEV(MAJOR_NR,TOMINOR(drive)+i*4),
                                        1, &floppy_fops, 0);
        }
        return have_no_fdc;
}


static int floppy_grab_irq_and_dma(void)

static void floppy_release_irq_and_dma(void)

static void __init parse_floppy_cfg_string(char *cfg)

int init_module(void)

void cleanup_module(void)