diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/acorn/block |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/acorn/block')
-rw-r--r-- | drivers/acorn/block/Kconfig | 36 | ||||
-rw-r--r-- | drivers/acorn/block/Makefile | 9 | ||||
-rw-r--r-- | drivers/acorn/block/fd1772.c | 1609 | ||||
-rw-r--r-- | drivers/acorn/block/fd1772dma.S | 100 | ||||
-rw-r--r-- | drivers/acorn/block/mfm.S | 162 | ||||
-rw-r--r-- | drivers/acorn/block/mfmhd.c | 1416 |
6 files changed, 3332 insertions, 0 deletions
diff --git a/drivers/acorn/block/Kconfig b/drivers/acorn/block/Kconfig new file mode 100644 index 000000000000..073add35e66f --- /dev/null +++ b/drivers/acorn/block/Kconfig @@ -0,0 +1,36 @@ +# +# Block device driver configuration +# + +menu "Acorn-specific block devices" + depends on ARCH_ACORN + +config BLK_DEV_FD1772 + tristate "Old Archimedes floppy (1772) support" + depends on ARCH_ARC || ARCH_A5K + help + Support the floppy drive on the Acorn Archimedes (A300, A4x0, A540, + R140 and R260) series of computers; it supports only 720K floppies + at the moment. If you don't have one of these machines just answer + N. + +config BLK_DEV_MFM + tristate "MFM harddisk support" + depends on ARCH_ARC || ARCH_A5K + help + Support the MFM hard drives on the Acorn Archimedes both + on-board the A4x0 motherboards and via the Acorn MFM podules. + Drives up to 64MB are supported. If you haven't got one of these + machines or drives just say N. + +config BLK_DEV_MFM_AUTODETECT + bool "Autodetect hard drive geometry" + depends on BLK_DEV_MFM + help + If you answer Y, the MFM code will attempt to automatically detect + the cylinders/heads/sectors count on your hard drive. WARNING: This + sometimes doesn't work and it also does some dodgy stuff which + potentially might damage your drive. + +endmenu + diff --git a/drivers/acorn/block/Makefile b/drivers/acorn/block/Makefile new file mode 100644 index 000000000000..38a9afe8e03f --- /dev/null +++ b/drivers/acorn/block/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the Acorn block device drivers. +# + +fd1772_mod-objs := fd1772.o fd1772dma.o +mfmhd_mod-objs := mfmhd.o mfm.o + +obj-$(CONFIG_BLK_DEV_FD1772) += fd1772_mod.o +obj-$(CONFIG_BLK_DEV_MFM) += mfmhd_mod.o diff --git a/drivers/acorn/block/fd1772.c b/drivers/acorn/block/fd1772.c new file mode 100644 index 000000000000..3cd2e968e96c --- /dev/null +++ b/drivers/acorn/block/fd1772.c @@ -0,0 +1,1609 @@ +/* + * linux/kernel/arch/arm/drivers/block/fd1772.c + * Based on ataflop.c in the m68k Linux + * Copyright (C) 1993 Greg Harp + * Atari Support by Bjoern Brauel, Roman Hodek + * Archimedes Support by Dave Gilbert (linux@treblig.org) + * + * Big cleanup Sep 11..14 1994 Roman Hodek: + * - Driver now works interrupt driven + * - Support for two drives; should work, but I cannot test that :-( + * - Reading is done in whole tracks and buffered to speed up things + * - Disk change detection and drive deselecting after motor-off + * similar to TOS + * - Autodetection of disk format (DD/HD); untested yet, because I + * don't have an HD drive :-( + * + * Fixes Nov 13 1994 Martin Schaller: + * - Autodetection works now + * - Support for 5 1/4" disks + * - Removed drive type (unknown on atari) + * - Do seeks with 8 Mhz + * + * Changes by Andreas Schwab: + * - After errors in multiple read mode try again reading single sectors + * (Feb 1995): + * - Clean up error handling + * - Set blk_size for proper size checking + * - Initialize track register when testing presence of floppy + * - Implement some ioctl's + * + * Changes by Torsten Lang: + * - When probing the floppies we should add the FDC1772CMDADD_H flag since + * the FDC1772 will otherwise wait forever when no disk is inserted... + * + * Things left to do: + * - Formatting + * - Maybe a better strategy for disk change detection (does anyone + * know one?) + * - There are some strange problems left: The strangest one is + * that, at least on my TT (4+4MB), the first 2 Bytes of the last + * page of the TT-Ram (!) change their contents (some bits get + * set) while a floppy DMA is going on. But there are no accesses + * to these memory locations from the kernel... (I tested that by + * making the page read-only). I cannot explain what's going on... + * - Sometimes the drive-change-detection stops to work. The + * function is still called, but the WP bit always reads as 0... + * Maybe a problem with the status reg mode or a timing problem. + * Note 10/12/94: The change detection now seems to work reliably. + * There is no proof, but I've seen no hang for a long time... + * + * ARCHIMEDES changes: (gilbertd@cs.man.ac.uk) + * 26/12/95 - Changed all names starting with FDC to FDC1772 + * Removed all references to clock speed of FDC - we're stuck with 8MHz + * Modified disk_type structure to remove HD formats + * + * 7/ 1/96 - Wrote FIQ code, removed most remaining atariisms + * + * 13/ 1/96 - Well I think its read a single sector; but there is a problem + * fd_rwsec_done which is called in FIQ mode starts another transfer + * off (in fd_rwsec) while still in FIQ mode. Because its still in + * FIQ mode it can't service the DMA and loses data. So need to + * heavily restructure. + * 14/ 1/96 - Found that the definitions of the register numbers of the + * FDC were multiplied by 2 in the header for the 16bit words + * of the atari so half the writes were going in the wrong place. + * Also realised that the FIQ entry didn't make any attempt to + * preserve registers or return correctly; now in assembler. + * + * 11/ 2/96 - Hmm - doesn't work on real machine. Auto detect doesn't + * and hacking that past seems to wait forever - check motor + * being turned on. + * + * 17/ 2/96 - still having problems - forcing track to -1 when selecting + * new drives seems to allow it to read first few sectors + * but then we get solid hangs at apparently random places + * which change depending what is happening. + * + * 9/ 3/96 - Fiddled a lot of stuff around to move to kernel 1.3.35 + * A lot of fiddling in DMA stuff. Having problems with it + * constnatly thinking its timeing out. Ah - its timeout + * was set to (6*HZ) rather than jiffies+(6*HZ). Now giving + * duff data! + * + * 5/ 4/96 - Made it use the new IOC_ macros rather than *ioc + * Hmm - giving unexpected FIQ and then timeouts + * 18/ 8/96 - Ran through indent -kr -i8 + * Some changes to disc change detect; don't know how well it + * works. + * 24/ 8/96 - Put all the track buffering code back in from the atari + * code - I wonder if it will still work... No :-) + * Still works if I turn off track buffering. + * 25/ 8/96 - Changed the timer expires that I'd added back to be + * jiffies + ....; and it all sprang to life! Got 2.8K/sec + * off a cp -r of a 679K disc (showed 94% cpu usage!) + * (PC gets 14.3K/sec - 0% CPU!) Hmm - hard drive corrupt! + * Also perhaps that compile was with cache off. + * changed cli in fd_readtrack_check to cliIF + * changed vmallocs to kmalloc (whats the difference!!) + * Removed the busy wait loop in do_fd_request and replaced + * by a routine on tq_immediate; only 11% cpu on a dd off the + * raw disc - but the speed is the same. + * 1/ 9/96 - Idea (failed!) - set the 'disable spin-up sequence' + * when we read the track if we know the motor is on; didn't + * help - perhaps we have to do it in stepping as well. + * Nope. Still doesn't help. + * Hmm - what seems to be happening is that fd_readtrack_check + * is never getting called. Its job is to terminate the read + * just after we think we should have got the data; otherwise + * the fdc takes 1 second to timeout; which is what's happening + * Now I can see 'readtrack_timer' being set (which should do the + * call); but it never seems to be called - hmm! + * OK - I've moved the check to my tq_immediate code - + * and it WORKS! 13.95K/second at 19% CPU. + * I wish I knew why that timer didn't work..... + * + * 16/11/96 - Fiddled and frigged for 2.0.18 + * + * DAG 30/01/99 - Started frobbing for 2.2.1 + * DAG 20/06/99 - A little more frobbing: + * Included include/asm/uaccess.h for get_user/put_user + * + * DAG 1/09/00 - Dusted off for 2.4.0-test7 + * MAX_SECTORS was name clashing so it is now FD1772_... + * Minor parameter, name layouts for 2.4.x differences + */ + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/workqueue.h> +#include <linux/fd.h> +#include <linux/fd1772.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/bitops.h> + +#include <asm/arch/oldlatches.h> +#include <asm/dma.h> +#include <asm/hardware.h> +#include <asm/hardware/ioc.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach-types.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/uaccess.h> + + +/* Note: FD_MAX_UNITS could be redefined to 2 for the Atari (with + * little additional rework in this file). But I'm not yet sure if + * some other code depends on the number of floppies... (It is defined + * in a public header!) + */ +#if 0 +#undef FD_MAX_UNITS +#define FD_MAX_UNITS 2 +#endif + +/* Ditto worries for Arc - DAG */ +#define FD_MAX_UNITS 4 +#define TRACKBUFFER 0 +/*#define DEBUG*/ + +#ifdef DEBUG +#define DPRINT(a) printk a +#else +#define DPRINT(a) +#endif + +static struct request_queue *floppy_queue; + +#define MAJOR_NR FLOPPY_MAJOR +#define FLOPPY_DMA 0 +#define DEVICE_NAME "floppy" +#define QUEUE (floppy_queue) +#define CURRENT elv_next_request(floppy_queue) + +/* Disk types: DD */ +static struct archy_disk_type { + const char *name; + unsigned spt; /* sectors per track */ + unsigned blocks; /* total number of blocks */ + unsigned stretch; /* track doubling ? */ +} disk_type[] = { + + { "d360", 9, 720, 0 }, /* 360kB diskette */ + { "D360", 9, 720, 1 }, /* 360kb in 720kb drive */ + { "D720", 9, 1440, 0 }, /* 720kb diskette (DD) */ + /*{ "D820", 10,1640, 0}, *//* DD disk with 82 tracks/10 sectors + - DAG - can't see how type detect can distinguish this + from 720K until it reads block 4 by which time its too late! */ +}; + +#define NUM_DISK_TYPES (sizeof(disk_type)/sizeof(*disk_type)) + +/* + * Maximum disk size (in kilobytes). This default is used whenever the + * current disk size is unknown. + */ +#define MAX_DISK_SIZE 720 + +static struct gendisk *disks[FD_MAX_UNIT]; + +/* current info on each unit */ +static struct archy_floppy_struct { + int connected; /* !=0 : drive is connected */ + int autoprobe; /* !=0 : do autoprobe */ + + struct archy_disk_type *disktype; /* current type of disk */ + + int track; /* current head position or -1 + * if unknown */ + unsigned int steprate; /* steprate setting */ + unsigned int wpstat; /* current state of WP signal + * (for disk change detection) */ +} unit[FD_MAX_UNITS]; + +/* DAG: On Arc we spin on a flag being cleared by fdc1772_comendhandler which + is an assembler routine */ +extern void fdc1772_comendhandler(void); /* Actually doens't have these parameters - see fd1772.S */ +extern volatile int fdc1772_comendstatus; +extern volatile int fdc1772_fdc_int_done; + +#define FDC1772BASE ((0x210000>>2)|0x80000000) + +#define FDC1772_READ(reg) inb(FDC1772BASE+(reg/2)) + +/* DAG: You wouldn't be silly to ask why FDC1772_WRITE is a function rather + than the #def below - well simple - the #def won't compile - and I + don't understand why (__outwc not defined) */ +/* NOTE: Reg is 0,2,4,6 as opposed to 0,1,2,3 or 0,4,8,12 to keep compatibility + with the ST version of fd1772.h */ +/*#define FDC1772_WRITE(reg,val) outw(val,(reg+FDC1772BASE)); */ +void FDC1772_WRITE(int reg, unsigned char val) +{ + if (reg == FDC1772REG_CMD) { + DPRINT(("FDC1772_WRITE new command 0x%x @ %d\n", val,jiffies)); + if (fdc1772_fdc_int_done) { + DPRINT(("FDC1772_WRITE: Hmm fdc1772_fdc_int_done true - resetting\n")); + fdc1772_fdc_int_done = 0; + }; + }; + outb(val, (reg / 2) + FDC1772BASE); +}; + +#define FD1772_MAX_SECTORS 22 + +unsigned char *DMABuffer; /* buffer for writes */ +/*static unsigned long PhysDMABuffer; *//* physical address */ +/* DAG: On Arc we just go straight for the DMA buffer */ +#define PhysDMABuffer DMABuffer + +#ifdef TRACKBUFFER +unsigned char *TrackBuffer; /* buffer for reads */ +#define PhysTrackBuffer TrackBuffer /* physical address */ +static int BufferDrive, BufferSide, BufferTrack; +static int read_track; /* non-zero if we are reading whole tracks */ + +#define SECTOR_BUFFER(sec) (TrackBuffer + ((sec)-1)*512) +#define IS_BUFFERED(drive,side,track) \ + (BufferDrive == (drive) && BufferSide == (side) && BufferTrack == (track)) +#endif + +/* + * These are global variables, as that's the easiest way to give + * information to interrupts. They are the data used for the current + * request. + */ +static int SelectedDrive = 0; +static int ReqCmd, ReqBlock; +static int ReqSide, ReqTrack, ReqSector, ReqCnt; +static int HeadSettleFlag = 0; +static unsigned char *ReqData, *ReqBuffer; +static int MotorOn = 0, MotorOffTrys; + +/* Synchronization of FDC1772 access. */ +static volatile int fdc_busy = 0; +static DECLARE_WAIT_QUEUE_HEAD(fdc_wait); + + +/* long req'd for set_bit --RR */ +static unsigned long changed_floppies = 0xff, fake_change = 0; +#define CHECK_CHANGE_DELAY HZ/2 + +/* DAG - increased to 30*HZ - not sure if this is the correct thing to do */ +#define FD_MOTOR_OFF_DELAY (10*HZ) +#define FD_MOTOR_OFF_MAXTRY (10*20) + +#define FLOPPY_TIMEOUT (6*HZ) +#define RECALIBRATE_ERRORS 4 /* After this many errors the drive + * will be recalibrated. */ +#define MAX_ERRORS 8 /* After this many errors the driver + * will give up. */ + +#define START_MOTOR_OFF_TIMER(delay) \ + do { \ + motor_off_timer.expires = jiffies + (delay); \ + add_timer( &motor_off_timer ); \ + MotorOffTrys = 0; \ + } while(0) + +#define START_CHECK_CHANGE_TIMER(delay) \ + do { \ + mod_timer(&fd_timer, jiffies + (delay)); \ + } while(0) + +#define START_TIMEOUT() \ + do { \ + mod_timer(&timeout_timer, jiffies+FLOPPY_TIMEOUT); \ + } while(0) + +#define STOP_TIMEOUT() \ + do { \ + del_timer( &timeout_timer ); \ + } while(0) + +#define ENABLE_IRQ() enable_irq(FIQ_FD1772+64); + +#define DISABLE_IRQ() disable_irq(FIQ_FD1772+64); + +static void fd1772_checkint(void); + +DECLARE_WORK(fd1772_tq, (void *)fd1772_checkint, NULL); +/* + * The driver is trying to determine the correct media format + * while Probing is set. fd_rwsec_done() clears it after a + * successful access. + */ +static int Probing = 0; + +/* This flag is set when a dummy seek is necessary to make the WP + * status bit accessible. + */ +static int NeedSeek = 0; + + +/***************************** Prototypes *****************************/ + +static void fd_select_side(int side); +static void fd_select_drive(int drive); +static void fd_deselect(void); +static void fd_motor_off_timer(unsigned long dummy); +static void check_change(unsigned long dummy); +static void floppy_irqconsequencehandler(void); +static void fd_error(void); +static void do_fd_action(int drive); +static void fd_calibrate(void); +static void fd_calibrate_done(int status); +static void fd_seek(void); +static void fd_seek_done(int status); +static void fd_rwsec(void); +#ifdef TRACKBUFFER +static void fd_readtrack_check( unsigned long dummy ); +#endif +static void fd_rwsec_done(int status); +static void fd_times_out(unsigned long dummy); +static void finish_fdc(void); +static void finish_fdc_done(int dummy); +static void floppy_off(unsigned int nr); +static void setup_req_params(int drive); +static void redo_fd_request(void); +static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int + cmd, unsigned long param); +static void fd_probe(int drive); +static int fd_test_drive_present(int drive); +static void config_types(void); +static int floppy_open(struct inode *inode, struct file *filp); +static int floppy_release(struct inode *inode, struct file *filp); +static void do_fd_request(request_queue_t *); + +/************************* End of Prototypes **************************/ + +static struct timer_list motor_off_timer = + TIMER_INITIALIZER(fd_motor_off_timer, 0, 0); + +#ifdef TRACKBUFFER +static struct timer_list readtrack_timer = + TIMER_INITIALIZER(fd_readtrack_check, 0, 0); +#endif + +static struct timer_list timeout_timer = + TIMER_INITIALIZER(fd_times_out, 0, 0); + +static struct timer_list fd_timer = + TIMER_INITIALIZER(check_change, 0, 0); + +/* DAG: Haven't got a clue what this is? */ +int stdma_islocked(void) +{ + return 0; +}; + +/* Select the side to use. */ + +static void fd_select_side(int side) +{ + oldlatch_aupdate(LATCHA_SIDESEL, side ? 0 : LATCHA_SIDESEL); +} + + +/* Select a drive, update the FDC1772's track register + */ + +static void fd_select_drive(int drive) +{ +#ifdef DEBUG + printk("fd_select_drive:%d\n", drive); +#endif + /* Hmm - nowhere do we seem to turn the motor on - I'm going to do it here! */ + oldlatch_aupdate(LATCHA_MOTOR | LATCHA_INUSE, 0); + + if (drive == SelectedDrive) + return; + + oldlatch_aupdate(LATCHA_FDSELALL, 0xf - (1 << drive)); + + /* restore track register to saved value */ + FDC1772_WRITE(FDC1772REG_TRACK, unit[drive].track); + udelay(25); + + SelectedDrive = drive; +} + + +/* Deselect both drives. */ + +static void fd_deselect(void) +{ + unsigned long flags; + + DPRINT(("fd_deselect\n")); + + oldlatch_aupdate(LATCHA_FDSELALL | LATCHA_MOTOR | LATCHA_INUSE, 0xf | LATCHA_MOTOR | LATCHA_INUSE); + + SelectedDrive = -1; +} + + +/* This timer function deselects the drives when the FDC1772 switched the + * motor off. The deselection cannot happen earlier because the FDC1772 + * counts the index signals, which arrive only if one drive is selected. + */ + +static void fd_motor_off_timer(unsigned long dummy) +{ + unsigned long flags; + unsigned char status; + int delay; + + del_timer(&motor_off_timer); + + if (SelectedDrive < 0) + /* no drive selected, needn't deselect anyone */ + return; + + save_flags(flags); + cli(); + + if (fdc_busy) /* was stdma_islocked */ + goto retry; + + status = FDC1772_READ(FDC1772REG_STATUS); + + if (!(status & 0x80)) { + /* + * motor already turned off by FDC1772 -> deselect drives + * In actual fact its this deselection which turns the motor + * off on the Arc, since the motor control is actually on + * Latch A + */ + DPRINT(("fdc1772: deselecting in fd_motor_off_timer\n")); + fd_deselect(); + MotorOn = 0; + restore_flags(flags); + return; + } + /* not yet off, try again */ + +retry: + restore_flags(flags); + /* Test again later; if tested too often, it seems there is no disk + * in the drive and the FDC1772 will leave the motor on forever (or, + * at least until a disk is inserted). So we'll test only twice + * per second from then on... + */ + delay = (MotorOffTrys < FD_MOTOR_OFF_MAXTRY) ? + (++MotorOffTrys, HZ / 20) : HZ / 2; + START_MOTOR_OFF_TIMER(delay); +} + + +/* This function is repeatedly called to detect disk changes (as good + * as possible) and keep track of the current state of the write protection. + */ + +static void check_change(unsigned long dummy) +{ + static int drive = 0; + + unsigned long flags; + int stat; + + if (fdc_busy) + return; /* Don't start poking about if the fdc is busy */ + + return; /* let's just forget it for the mo DAG */ + + if (++drive > 1 || !unit[drive].connected) + drive = 0; + + save_flags(flags); + cli(); + + if (!stdma_islocked()) { + stat = !!(FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_WPROT); + + /* The idea here is that if the write protect line has changed then + the disc must have changed */ + if (stat != unit[drive].wpstat) { + DPRINT(("wpstat[%d] = %d\n", drive, stat)); + unit[drive].wpstat = stat; + set_bit(drive, &changed_floppies); + } + } + restore_flags(flags); + + START_CHECK_CHANGE_TIMER(CHECK_CHANGE_DELAY); +} + + +/* Handling of the Head Settling Flag: This flag should be set after each + * seek operation, because we don't use seeks with verify. + */ + +static inline void set_head_settle_flag(void) +{ + HeadSettleFlag = FDC1772CMDADD_E; +} + +static inline int get_head_settle_flag(void) +{ + int tmp = HeadSettleFlag; + HeadSettleFlag = 0; + return (tmp); +} + + + + +/* General Interrupt Handling */ + +static inline void copy_buffer(void *from, void *to) +{ + ulong *p1 = (ulong *) from, *p2 = (ulong *) to; + int cnt; + + for (cnt = 512 / 4; cnt; cnt--) + *p2++ = *p1++; +} + +static void (*FloppyIRQHandler) (int status) = NULL; + +static void floppy_irqconsequencehandler(void) +{ + unsigned char status; + void (*handler) (int); + + fdc1772_fdc_int_done = 0; + + handler = FloppyIRQHandler; + FloppyIRQHandler = NULL; + + if (handler) { + nop(); + status = (unsigned char) fdc1772_comendstatus; + DPRINT(("FDC1772 irq, status = %02x handler = %08lx\n", (unsigned int) status, (unsigned long) handler)); + handler(status); + } else { + DPRINT(("FDC1772 irq, no handler status=%02x\n", fdc1772_comendstatus)); + } + DPRINT(("FDC1772 irq: end of floppy_irq\n")); +} + + +/* Error handling: If some error happened, retry some times, then + * recalibrate, then try again, and fail after MAX_ERRORS. + */ + +static void fd_error(void) +{ + printk("FDC1772: fd_error\n"); + /*panic("fd1772: fd_error"); *//* DAG tmp */ + if (!CURRENT) + return; + CURRENT->errors++; + if (CURRENT->errors >= MAX_ERRORS) { + printk("fd%d: too many errors.\n", SelectedDrive); + end_request(CURRENT, 0); + } else if (CURRENT->errors == RECALIBRATE_ERRORS) { + printk("fd%d: recalibrating\n", SelectedDrive); + if (SelectedDrive != -1) + unit[SelectedDrive].track = -1; + } + redo_fd_request(); +} + + + +#define SET_IRQ_HANDLER(proc) do { FloppyIRQHandler = (proc); } while(0) + + +/* do_fd_action() is the general procedure for a fd request: All + * required parameter settings (drive select, side select, track + * position) are checked and set if needed. For each of these + * parameters and the actual reading or writing exist two functions: + * one that starts the setting (or skips it if possible) and one + * callback for the "done" interrupt. Each done func calls the next + * set function to propagate the request down to fd_rwsec_done(). + */ + +static void do_fd_action(int drive) +{ + struct request *req; + DPRINT(("do_fd_action unit[drive].track=%d\n", unit[drive].track)); + +#ifdef TRACKBUFFER +repeat: + + if (IS_BUFFERED( drive, ReqSide, ReqTrack )) { + req = CURRENT; + if (ReqCmd == READ) { + copy_buffer( SECTOR_BUFFER(ReqSector), ReqData ); + if (++ReqCnt < req->current_nr_sectors) { + /* read next sector */ + setup_req_params( drive ); + goto repeat; + } else { + /* all sectors finished */ + req->nr_sectors -= req->current_nr_sectors; + req->sector += req->current_nr_sectors; + end_request(req, 1); + redo_fd_request(); + return; + } + } else { + /* cmd == WRITE, pay attention to track buffer + * consistency! */ + copy_buffer( ReqData, SECTOR_BUFFER(ReqSector) ); + } + } +#endif + + if (SelectedDrive != drive) { + /*unit[drive].track = -1; DAG */ + fd_select_drive(drive); + }; + + + if (unit[drive].track == -1) + fd_calibrate(); + else if (unit[drive].track != ReqTrack << unit[drive].disktype->stretch) + fd_seek(); + else + fd_rwsec(); +} + + +/* Seek to track 0 if the current track is unknown */ + +static void fd_calibrate(void) +{ + DPRINT(("fd_calibrate\n")); + if (unit[SelectedDrive].track >= 0) { + fd_calibrate_done(0); + return; + } + DPRINT(("fd_calibrate (after track compare)\n")); + SET_IRQ_HANDLER(fd_calibrate_done); + /* we can't verify, since the speed may be incorrect */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | unit[SelectedDrive].steprate); + + NeedSeek = 1; + MotorOn = 1; + START_TIMEOUT(); + /* wait for IRQ */ +} + + +static void fd_calibrate_done(int status) +{ + DPRINT(("fd_calibrate_done()\n")); + STOP_TIMEOUT(); + + /* set the correct speed now */ + if (status & FDC1772STAT_RECNF) { + printk("fd%d: restore failed\n", SelectedDrive); + fd_error(); + } else { + unit[SelectedDrive].track = 0; + fd_seek(); + } +} + + +/* Seek the drive to the requested track. The drive must have been + * calibrated at some point before this. + */ + +static void fd_seek(void) +{ + unsigned long flags; + DPRINT(("fd_seek() to track %d (unit[SelectedDrive].track=%d)\n", ReqTrack, + unit[SelectedDrive].track)); + if (unit[SelectedDrive].track == ReqTrack << + unit[SelectedDrive].disktype->stretch) { + fd_seek_done(0); + return; + } + FDC1772_WRITE(FDC1772REG_DATA, ReqTrack << + unit[SelectedDrive].disktype->stretch); + udelay(25); + save_flags(flags); + clf(); + SET_IRQ_HANDLER(fd_seek_done); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK | unit[SelectedDrive].steprate | + /* DAG */ + (MotorOn?FDC1772CMDADD_H:0)); + + restore_flags(flags); + MotorOn = 1; + set_head_settle_flag(); + START_TIMEOUT(); + /* wait for IRQ */ +} + + +static void fd_seek_done(int status) +{ + DPRINT(("fd_seek_done()\n")); + STOP_TIMEOUT(); + + /* set the correct speed */ + if (status & FDC1772STAT_RECNF) { + printk("fd%d: seek error (to track %d)\n", + SelectedDrive, ReqTrack); + /* we don't know exactly which track we are on now! */ + unit[SelectedDrive].track = -1; + fd_error(); + } else { + unit[SelectedDrive].track = ReqTrack << + unit[SelectedDrive].disktype->stretch; + NeedSeek = 0; + fd_rwsec(); + } +} + + +/* This does the actual reading/writing after positioning the head + * over the correct track. + */ + +#ifdef TRACKBUFFER +static int MultReadInProgress = 0; +#endif + + +static void fd_rwsec(void) +{ + unsigned long paddr, flags; + unsigned int rwflag, old_motoron; + unsigned int track; + + DPRINT(("fd_rwsec(), Sec=%d, Access=%c\n", ReqSector, ReqCmd == WRITE ? 'w' : 'r')); + if (ReqCmd == WRITE) { + /*cache_push( (unsigned long)ReqData, 512 ); */ + paddr = (unsigned long) ReqData; + rwflag = 0x100; + } else { + paddr = (unsigned long) PhysDMABuffer; +#ifdef TRACKBUFFER + if (read_track) + paddr = (unsigned long)PhysTrackBuffer; +#endif + rwflag = 0; + } + + DPRINT(("fd_rwsec() before sidesel rwflag=%d sec=%d trk=%d\n", rwflag, + ReqSector, FDC1772_READ(FDC1772REG_TRACK))); + fd_select_side(ReqSide); + + /*DPRINT(("fd_rwsec() before start sector \n")); */ + /* Start sector of this operation */ +#ifdef TRACKBUFFER + FDC1772_WRITE( FDC1772REG_SECTOR, !read_track ? ReqSector : 1 ); +#else + FDC1772_WRITE( FDC1772REG_SECTOR, ReqSector ); +#endif + + /* Cheat for track if stretch != 0 */ + if (unit[SelectedDrive].disktype->stretch) { + track = FDC1772_READ(FDC1772REG_TRACK); + FDC1772_WRITE(FDC1772REG_TRACK, track >> + unit[SelectedDrive].disktype->stretch); + } + udelay(25); + + DPRINT(("fd_rwsec() before setup DMA \n")); + /* Setup DMA - Heavily modified by DAG */ + save_flags(flags); + clf(); + disable_dma(FLOPPY_DMA); + set_dma_mode(FLOPPY_DMA, rwflag ? DMA_MODE_WRITE : DMA_MODE_READ); + set_dma_addr(FLOPPY_DMA, (long) paddr); /* DAG - changed from Atari specific */ +#ifdef TRACKBUFFER + set_dma_count(FLOPPY_DMA,(!read_track ? 1 : unit[SelectedDrive].disktype->spt)*512); +#else + set_dma_count(FLOPPY_DMA, 512); /* Block/sector size - going to have to change */ +#endif + SET_IRQ_HANDLER(fd_rwsec_done); + /* Turn on dma int */ + enable_dma(FLOPPY_DMA); + /* Now give it something to do */ + FDC1772_WRITE(FDC1772REG_CMD, (rwflag ? (FDC1772CMD_WRSEC | FDC1772CMDADD_P) : +#ifdef TRACKBUFFER + (FDC1772CMD_RDSEC | (read_track ? FDC1772CMDADD_M : 0) | + /* Hmm - the idea here is to stop the FDC spinning the disc + up when we know that we already still have it spinning */ + (MotorOn?FDC1772CMDADD_H:0)) +#else + FDC1772CMD_RDSEC +#endif + )); + + restore_flags(flags); + DPRINT(("fd_rwsec() after DMA setup flags=0x%08x\n", flags)); + /*sti(); *//* DAG - Hmm */ + /* Hmm - should do something DAG */ + old_motoron = MotorOn; + MotorOn = 1; + NeedSeek = 1; + + /* wait for interrupt */ + +#ifdef TRACKBUFFER + if (read_track) { + /* + * If reading a whole track, wait about one disk rotation and + * then check if all sectors are read. The FDC will even + * search for the first non-existant sector and need 1 sec to + * recognise that it isn't present :-( + */ + /* 1 rot. + 5 rot.s if motor was off */ + mod_timer(&readtrack_timer, jiffies + HZ/5 + (old_motoron ? 0 : HZ)); + DPRINT(("Setting readtrack_timer to %d @ %d\n", + readtrack_timer.expires,jiffies)); + MultReadInProgress = 1; + } +#endif + + /*DPRINT(("fd_rwsec() before START_TIMEOUT \n")); */ + START_TIMEOUT(); + /*DPRINT(("fd_rwsec() after START_TIMEOUT \n")); */ +} + + +#ifdef TRACKBUFFER + +static void fd_readtrack_check(unsigned long dummy) +{ + unsigned long flags, addr; + extern unsigned char *fdc1772_dataaddr; + + DPRINT(("fd_readtrack_check @ %d\n",jiffies)); + + save_flags(flags); + clf(); + + del_timer( &readtrack_timer ); + + if (!MultReadInProgress) { + /* This prevents a race condition that could arise if the + * interrupt is triggered while the calling of this timer + * callback function takes place. The IRQ function then has + * already cleared 'MultReadInProgress' when control flow + * gets here. + */ + restore_flags(flags); + return; + } + + /* get the current DMA address */ + addr=(unsigned long)fdc1772_dataaddr; /* DAG - ? */ + DPRINT(("fd_readtrack_check: addr=%x PhysTrackBuffer=%x\n",addr,PhysTrackBuffer)); + + if (addr >= (unsigned int)PhysTrackBuffer + unit[SelectedDrive].disktype->spt*512) { + /* already read enough data, force an FDC interrupt to stop + * the read operation + */ + SET_IRQ_HANDLER( NULL ); + restore_flags(flags); + DPRINT(("fd_readtrack_check(): done\n")); + FDC1772_WRITE( FDC1772REG_CMD, FDC1772CMD_FORCI ); + udelay(25); + + /* No error until now -- the FDC would have interrupted + * otherwise! + */ + fd_rwsec_done( 0 ); + } else { + /* not yet finished, wait another tenth rotation */ + restore_flags(flags); + DPRINT(("fd_readtrack_check(): not yet finished\n")); + readtrack_timer.expires = jiffies + HZ/5/10; + add_timer( &readtrack_timer ); + } +} + +#endif + +static void fd_rwsec_done(int status) +{ + unsigned int track; + + DPRINT(("fd_rwsec_done() status=%d @ %d\n", status,jiffies)); + +#ifdef TRACKBUFFER + if (read_track && !MultReadInProgress) + return; + + MultReadInProgress = 0; + + STOP_TIMEOUT(); + + if (read_track) + del_timer( &readtrack_timer ); +#endif + + + /* Correct the track if stretch != 0 */ + if (unit[SelectedDrive].disktype->stretch) { + track = FDC1772_READ(FDC1772REG_TRACK); + FDC1772_WRITE(FDC1772REG_TRACK, track << + unit[SelectedDrive].disktype->stretch); + } + if (ReqCmd == WRITE && (status & FDC1772STAT_WPROT)) { + printk("fd%d: is write protected\n", SelectedDrive); + goto err_end; + } + if ((status & FDC1772STAT_RECNF) +#ifdef TRACKBUFFER + /* RECNF is no error after a multiple read when the FDC + * searched for a non-existant sector! + */ + && !(read_track && + FDC1772_READ(FDC1772REG_SECTOR) > unit[SelectedDrive].disktype->spt) +#endif + ) { + if (Probing) { + if (unit[SelectedDrive].disktype > disk_type) { + /* try another disk type */ + unit[SelectedDrive].disktype--; + set_capacity(disks[SelectedDrive], + unit[SelectedDrive].disktype->blocks); + } else + Probing = 0; + } else { + /* record not found, but not probing. Maybe stretch wrong ? Restart probing */ + if (unit[SelectedDrive].autoprobe) { + unit[SelectedDrive].disktype = disk_type + NUM_DISK_TYPES - 1; + set_capacity(disks[SelectedDrive], + unit[SelectedDrive].disktype->blocks); + Probing = 1; + } + } + if (Probing) { + setup_req_params(SelectedDrive); +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + do_fd_action(SelectedDrive); + return; + } + printk("fd%d: sector %d not found (side %d, track %d)\n", + SelectedDrive, FDC1772_READ(FDC1772REG_SECTOR), ReqSide, ReqTrack); + goto err_end; + } + if (status & FDC1772STAT_CRC) { + printk("fd%d: CRC error (side %d, track %d, sector %d)\n", + SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); + goto err_end; + } + if (status & FDC1772STAT_LOST) { + printk("fd%d: lost data (side %d, track %d, sector %d)\n", + SelectedDrive, ReqSide, ReqTrack, FDC1772_READ(FDC1772REG_SECTOR)); + goto err_end; + } + Probing = 0; + + if (ReqCmd == READ) { +#ifdef TRACKBUFFER + if (!read_track) { + /*cache_clear (PhysDMABuffer, 512);*/ + copy_buffer (DMABuffer, ReqData); + } else { + /*cache_clear (PhysTrackBuffer, FD1772_MAX_SECTORS * 512);*/ + BufferDrive = SelectedDrive; + BufferSide = ReqSide; + BufferTrack = ReqTrack; + copy_buffer (SECTOR_BUFFER (ReqSector), ReqData); + } +#else + /*cache_clear( PhysDMABuffer, 512 ); */ + copy_buffer(DMABuffer, ReqData); +#endif + } + if (++ReqCnt < CURRENT->current_nr_sectors) { + /* read next sector */ + setup_req_params(SelectedDrive); + do_fd_action(SelectedDrive); + } else { + /* all sectors finished */ + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + end_request(CURRENT, 1); + redo_fd_request(); + } + return; + +err_end: +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + + fd_error(); +} + + +static void fd_times_out(unsigned long dummy) +{ + SET_IRQ_HANDLER(NULL); + /* If the timeout occurred while the readtrack_check timer was + * active, we need to cancel it, else bad things will happen */ + del_timer( &readtrack_timer ); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(25); + + printk("floppy timeout\n"); + STOP_TIMEOUT(); /* hmm - should we do this ? */ + fd_error(); +} + + +/* The (noop) seek operation here is needed to make the WP bit in the + * FDC1772 status register accessible for check_change. If the last disk + * operation would have been a RDSEC, this bit would always read as 0 + * no matter what :-( To save time, the seek goes to the track we're + * already on. + */ + +static void finish_fdc(void) +{ + /* DAG - just try without this dummy seek! */ + finish_fdc_done(0); + return; + + if (!NeedSeek) { + finish_fdc_done(0); + } else { + DPRINT(("finish_fdc: dummy seek started\n")); + FDC1772_WRITE(FDC1772REG_DATA, unit[SelectedDrive].track); + SET_IRQ_HANDLER(finish_fdc_done); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); + MotorOn = 1; + START_TIMEOUT(); + /* we must wait for the IRQ here, because the ST-DMA is + * released immediately afterwards and the interrupt may be + * delivered to the wrong driver. + */ + } +} + + +static void finish_fdc_done(int dummy) +{ + unsigned long flags; + + DPRINT(("finish_fdc_done entered\n")); + STOP_TIMEOUT(); + NeedSeek = 0; + + if (timer_pending(&fd_timer) && + time_after(jiffies + 5, fd_timer.expires)) + /* If the check for a disk change is done too early after this + * last seek command, the WP bit still reads wrong :-(( + */ + mod_timer(&fd_timer, jiffies + 5); + else { + /* START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ + }; + del_timer(&motor_off_timer); + START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); + + save_flags(flags); + cli(); + /* stdma_release(); - not sure if I should do something DAG */ + fdc_busy = 0; + wake_up(&fdc_wait); + restore_flags(flags); + + DPRINT(("finish_fdc() finished\n")); +} + + +/* Prevent "aliased" accesses. */ +static int fd_ref[4]; +static int fd_device[4]; + +/* dummy for blk.h */ +static void floppy_off(unsigned int nr) +{ +} + + +/* On the old arcs write protect depends on the particular model + of machine. On the A310, R140, and A440 there is a disc changed + detect, however on the A4x0/1 range there is not. There + is nothing to tell you which machine your on. + At the moment I'm just marking changed always. I've + left the Atari's 'change on write protect change' code in this + part (but nothing sets it). + RiscOS apparently checks the disc serial number etc. to detect changes + - but if it sees a disc change line go high (?) it flips to using + it. Well maybe I'll add that in the future (!?) +*/ +static int check_floppy_change(struct gendisk *disk) +{ + struct archy_floppy_struct *p = disk->private_data; + unsigned int drive = p - unit; + + if (test_bit(drive, &fake_change)) { + /* simulated change (e.g. after formatting) */ + return 1; + } + if (test_bit(drive, &changed_floppies)) { + /* surely changed (the WP signal changed at least once) */ + return 1; + } + if (p->wpstat) { + /* WP is on -> could be changed: to be sure, buffers should be + * invalidated... + */ + return 1; + } + return 1; /* DAG - was 0 */ +} + +static int floppy_revalidate(struct gendisk *disk) +{ + struct archy_floppy_struct *p = disk->private_data; + unsigned int drive = p - unit; + + if (test_bit(drive, &changed_floppies) || test_bit(drive, &fake_change) + || unit[drive].disktype == 0) { +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + clear_bit(drive, &fake_change); + clear_bit(drive, &changed_floppies); + p->disktype = 0; + } + return 0; +} + +/* This sets up the global variables describing the current request. */ + +static void setup_req_params(int drive) +{ + int block = ReqBlock + ReqCnt; + + ReqTrack = block / unit[drive].disktype->spt; + ReqSector = block - ReqTrack * unit[drive].disktype->spt + 1; + ReqSide = ReqTrack & 1; + ReqTrack >>= 1; + ReqData = ReqBuffer + 512 * ReqCnt; + +#ifdef TRACKBUFFER + read_track = (ReqCmd == READ && CURRENT->errors == 0); +#endif + + DPRINT(("Request params: Si=%d Tr=%d Se=%d Data=%08lx\n", ReqSide, + ReqTrack, ReqSector, (unsigned long) ReqData)); +} + + +static void redo_fd_request(void) +{ + int drive, type; + struct archy_floppy_struct *floppy; + + DPRINT(("redo_fd_request: CURRENT=%p dev=%s CURRENT->sector=%ld\n", + CURRENT, CURRENT ? CURRENT->rq_disk->disk_name : "", + CURRENT ? CURRENT->sector : 0)); + +repeat: + + if (!CURRENT) + goto the_end; + + floppy = CURRENT->rq_disk->private_data; + drive = floppy - unit; + type = fd_device[drive]; + + if (!floppy->connected) { + /* drive not connected */ + printk("Unknown Device: fd%d\n", drive); + end_request(CURRENT, 0); + goto repeat; + } + if (type == 0) { + if (!floppy->disktype) { + Probing = 1; + floppy->disktype = disk_type + NUM_DISK_TYPES - 1; + set_capacity(disks[drive], floppy->disktype->blocks); + floppy->autoprobe = 1; + } + } else { + /* user supplied disk type */ + --type; + if (type >= NUM_DISK_TYPES) { + printk("fd%d: invalid disk format", drive); + end_request(CURRENT, 0); + goto repeat; + } + floppy->disktype = &disk_type[type]; + set_capacity(disks[drive], floppy->disktype->blocks); + floppy->autoprobe = 0; + } + + if (CURRENT->sector + 1 > floppy->disktype->blocks) { + end_request(CURRENT, 0); + goto repeat; + } + /* stop deselect timer */ + del_timer(&motor_off_timer); + + ReqCnt = 0; + ReqCmd = CURRENT->cmd; + ReqBlock = CURRENT->sector; + ReqBuffer = CURRENT->buffer; + setup_req_params(drive); + do_fd_action(drive); + + return; + +the_end: + finish_fdc(); +} + +static void fd1772_checkint(void) +{ + extern int fdc1772_bytestogo; + + /*printk("fd1772_checkint %d\n",fdc1772_fdc_int_done);*/ + if (fdc1772_fdc_int_done) + floppy_irqconsequencehandler(); + if ((MultReadInProgress) && (fdc1772_bytestogo==0)) fd_readtrack_check(0); + if (fdc_busy) { + schedule_work(&fd1772_tq); + } +} + +static void do_fd_request(request_queue_t* q) +{ + unsigned long flags; + + DPRINT(("do_fd_request for pid %d\n", current->pid)); + if (fdc_busy) return; + save_flags(flags); + cli(); + while (fdc_busy) + sleep_on(&fdc_wait); + fdc_busy = 1; + ENABLE_IRQ(); + restore_flags(flags); + + fdc1772_fdc_int_done = 0; + + redo_fd_request(); + + schedule_work(&fd1772_tq); +} + + +static int invalidate_drive(struct block_device *bdev) +{ + struct archy_floppy_struct *p = bdev->bd_disk->private_data; + /* invalidate the buffer track to force a reread */ +#ifdef TRACKBUFFER + BufferDrive = -1; +#endif + + set_bit(p - unit, &fake_change); + return 0; +} + +static int fd_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long param) +{ + struct block_device *bdev = inode->i_bdev; + + switch (cmd) { + case FDFMTEND: + case FDFLUSH: + invalidate_drive(bdev); + check_disk_change(bdev); + case FDFMTBEG: + return 0; + default: + return -EINVAL; + } +} + + +/* Initialize the 'unit' variable for drive 'drive' */ + +static void fd_probe(int drive) +{ + unit[drive].connected = 0; + unit[drive].disktype = NULL; + + if (!fd_test_drive_present(drive)) + return; + + unit[drive].connected = 1; + unit[drive].track = -1; /* If we put the auto detect back in this can go to 0 */ + unit[drive].steprate = FDC1772STEP_6; + MotorOn = 1; /* from probe restore operation! */ +} + + +/* This function tests the physical presence of a floppy drive (not + * whether a disk is inserted). This is done by issuing a restore + * command, waiting max. 2 seconds (that should be enough to move the + * head across the whole disk) and looking at the state of the "TR00" + * signal. This should now be raised if there is a drive connected + * (and there is no hardware failure :-) Otherwise, the drive is + * declared absent. + */ + +static int fd_test_drive_present(int drive) +{ + unsigned long timeout; + unsigned char status; + int ok; + + printk("fd_test_drive_present %d\n", drive); + if (drive > 1) + return (0); + return (1); /* Simple hack for the moment - the autodetect doesn't seem to work on arc */ + fd_select_drive(drive); + + /* disable interrupt temporarily */ + DISABLE_IRQ(); + FDC1772_WRITE(FDC1772REG_TRACK, 0x00); /* was ff00 why? */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_RESTORE | FDC1772CMDADD_H | FDC1772STEP_6); + + /*printk("fd_test_drive_present: Going into timeout loop\n"); */ + for (ok = 0, timeout = jiffies + 2 * HZ + HZ / 2; time_before(jiffies, timeout);) { + /* What does this piece of atariism do? - query for an interrupt? */ + /* if (!(mfp.par_dt_reg & 0x20)) + break; */ + /* Well this is my nearest guess - quit when we get an FDC interrupt */ + if (ioc_readb(IOC_FIQSTAT) & 2) + break; + } + + /*printk("fd_test_drive_present: Coming out of timeout loop\n"); */ + status = FDC1772_READ(FDC1772REG_STATUS); + ok = (status & FDC1772STAT_TR00) != 0; + + /*printk("fd_test_drive_present: ok=%d\n",ok); */ + /* force interrupt to abort restore operation (FDC1772 would try + * about 50 seconds!) */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(500); + status = FDC1772_READ(FDC1772REG_STATUS); + udelay(20); + /*printk("fd_test_drive_present: just before OK code %d\n",ok); */ + + if (ok) { + /* dummy seek command to make WP bit accessible */ + FDC1772_WRITE(FDC1772REG_DATA, 0); + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_SEEK); + printk("fd_test_drive_present: just before wait for int\n"); + /* DAG: Guess means wait for interrupt */ + while (!(ioc_readb(IOC_FIQSTAT) & 2)); + printk("fd_test_drive_present: just after wait for int\n"); + status = FDC1772_READ(FDC1772REG_STATUS); + } + printk("fd_test_drive_present: just before ENABLE_IRQ\n"); + ENABLE_IRQ(); + printk("fd_test_drive_present: about to return\n"); + return (ok); +} + + +/* Look how many and which kind of drives are connected. If there are + * floppies, additionally start the disk-change and motor-off timers. + */ + +static void config_types(void) +{ + int drive, cnt = 0; + + printk("Probing floppy drive(s):\n"); + for (drive = 0; drive < FD_MAX_UNITS; drive++) { + fd_probe(drive); + if (unit[drive].connected) { + printk("fd%d\n", drive); + ++cnt; + } + } + + if (FDC1772_READ(FDC1772REG_STATUS) & FDC1772STAT_BUSY) { + /* If FDC1772 is still busy from probing, give it another FORCI + * command to abort the operation. If this isn't done, the FDC1772 + * will interrupt later and its IRQ line stays low, because + * the status register isn't read. And this will block any + * interrupts on this IRQ line :-( + */ + FDC1772_WRITE(FDC1772REG_CMD, FDC1772CMD_FORCI); + udelay(500); + FDC1772_READ(FDC1772REG_STATUS); + udelay(20); + } + if (cnt > 0) { + START_MOTOR_OFF_TIMER(FD_MOTOR_OFF_DELAY); + if (cnt == 1) + fd_select_drive(0); + /*START_CHECK_CHANGE_TIMER( CHECK_CHANGE_DELAY ); */ + } +} + +/* + * 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 = iminor(inode) & 3; + int type = iminor(inode) >> 2; + int old_dev = fd_device[drive]; + + if (fd_ref[drive] && old_dev != type) + return -EBUSY; + + if (fd_ref[drive] == -1 || (fd_ref[drive] && filp->f_flags & O_EXCL)) + return -EBUSY; + + if (filp->f_flags & O_EXCL) + fd_ref[drive] = -1; + else + fd_ref[drive]++; + + fd_device[drive] = type; + + if (filp->f_flags & O_NDELAY) + return 0; + + if (filp->f_mode & 3) { + check_disk_change(inode->i_bdev); + if (filp->f_mode & 2) { + if (unit[drive].wpstat) { + floppy_release(inode, filp); + return -EROFS; + } + } + } + return 0; +} + + +static int floppy_release(struct inode *inode, struct file *filp) +{ + int drive = iminor(inode) & 3; + + if (fd_ref[drive] < 0) + fd_ref[drive] = 0; + else if (!fd_ref[drive]--) { + printk("floppy_release with fd_ref == 0"); + fd_ref[drive] = 0; + } + + return 0; +} + +static struct block_device_operations floppy_fops = +{ + .open = floppy_open, + .release = floppy_release, + .ioctl = fd_ioctl, + .media_changed = check_floppy_change, + .revalidate_disk= floppy_revalidate, +}; + +static struct kobject *floppy_find(dev_t dev, int *part, void *data) +{ + int drive = *part & 3; + if ((*part >> 2) > NUM_DISK_TYPES || drive >= FD_MAX_UNITS) + return NULL; + *part = 0; + return get_disk(disks[drive]); +} + +int fd1772_init(void) +{ + static DEFINE_SPINLOCK(lock); + int i, err = -ENOMEM; + + if (!machine_is_archimedes()) + return 0; + + for (i = 0; i < FD_MAX_UNITS; i++) { + disks[i] = alloc_disk(1); + if (!disks[i]) + goto err_disk; + } + + err = register_blkdev(MAJOR_NR, "fd"); + if (err) + goto err_disk; + + err = -EBUSY; + if (request_dma(FLOPPY_DMA, "fd1772")) { + printk("Unable to grab DMA%d for the floppy (1772) driver\n", FLOPPY_DMA); + goto err_blkdev; + }; + + if (request_dma(FIQ_FD1772, "fd1772 end")) { + printk("Unable to grab DMA%d for the floppy (1772) driver\n", FIQ_FD1772); + goto err_dma1; + }; + + /* initialize variables */ + SelectedDrive = -1; +#ifdef TRACKBUFFER + BufferDrive = BufferSide = BufferTrack = -1; + /* Atari uses 512 - I want to eventually cope with 1K sectors */ + DMABuffer = (char *)kmalloc((FD1772_MAX_SECTORS+1)*512,GFP_KERNEL); + TrackBuffer = DMABuffer + 512; +#else + /* Allocate memory for the DMAbuffer - on the Atari this takes it + out of some special memory... */ + DMABuffer = (char *) kmalloc(2048); /* Copes with pretty large sectors */ +#endif + err = -ENOMEM; + if (!DMAbuffer) + goto err_dma2; + + enable_dma(FIQ_FD1772); /* This inserts a call to our command end routine */ + + floppy_queue = blk_init_queue(do_fd_request, &lock); + if (!floppy_queue) + goto err_queue; + + for (i = 0; i < FD_MAX_UNITS; i++) { + unit[i].track = -1; + disks[i]->major = MAJOR_NR; + disks[i]->first_minor = 0; + disks[i]->fops = &floppy_fops; + sprintf(disks[i]->disk_name, "fd%d", i); + disks[i]->private_data = &unit[i]; + disks[i]->queue = floppy_queue; + set_capacity(disks[i], MAX_DISK_SIZE * 2); + } + blk_register_region(MKDEV(MAJOR_NR, 0), 256, THIS_MODULE, + floppy_find, NULL, NULL); + + for (i = 0; i < FD_MAX_UNITS; i++) + add_disk(disks[i]); + + config_types(); + + return 0; + + err_queue: + kfree(DMAbuffer); + err_dma2: + free_dma(FIQ_FD1772); + + err_dma1: + free_dma(FLOPPY_DMA); + + err_blkdev: + unregister_blkdev(MAJOR_NR, "fd"); + + err_disk: + while (i--) + put_disk(disks[i]); + return err; +} diff --git a/drivers/acorn/block/fd1772dma.S b/drivers/acorn/block/fd1772dma.S new file mode 100644 index 000000000000..7964435443ec --- /dev/null +++ b/drivers/acorn/block/fd1772dma.S @@ -0,0 +1,100 @@ +#include <asm/hardware.h> + +@ Code for DMA with the 1772 fdc +.text + + + .global fdc1772_dataaddr +fdc1772_fiqdata: +@ Number of bytes left to DMA + .global fdc1772_bytestogo +fdc1772_bytestogo: + .word 0 +@ Place to put/get data from in DMA + .global fdc1772_dataaddr +fdc1772_dataaddr: + .word 0 + + .global fdc1772_fdc_int_done +fdc1772_fdc_int_done: + .word 0 + .global fdc1772_comendstatus +fdc1772_comendstatus: + .word 0 + +@ We hang this off DMA channel 1 + .global fdc1772_comendhandler +fdc1772_comendhandler: + mov r8,#IOC_BASE + ldrb r9,[r8,#0x34] @ IOC FIQ status + tst r9,#2 + subeqs pc,r14,#4 @ should I leave a space here + orr r9,r8,#0x10000 @ FDC base + adr r8,fdc1772_fdc_int_done + ldrb r10,[r9,#0] @ FDC status + mov r9,#1 @ Got a FIQ flag + stmia r8,{r9,r10} + subs pc,r14,#4 + + + .global fdc1772_dma_read +fdc1772_dma_read: + mov r8,#IOC_BASE + ldrb r9,[r8,#0x34] @ IOC FIQ status + tst r9,#1 + beq fdc1772_dma_read_notours + orr r8,r8,#0x10000 @ FDC base + ldrb r10,[r8,#0xc] @ Read from FDC data reg (also clears interrupt) + ldmia r11,{r8,r9} + subs r8,r8,#1 @ One less byte to go + @ If there was somewhere for this data to go then store it and update pointers + strplb r10,[r9],#1 @ Store the data and increment the pointer + stmplia r11,{r8,r9} @ Update count/pointers + @ Handle any other interrupts if there are any +fdc1772_dma_read_notours: + @ Cant branch because this code has been copied down to the FIQ vector + ldr pc,[pc,#-4] + .word fdc1772_comendhandler + .global fdc1772_dma_read_end +fdc1772_dma_read_end: + + .global fdc1772_dma_write +fdc1772_dma_write: + mov r8,#IOC_BASE + ldrb r9,[r8,#0x34] @ IOC FIQ status + tst r9,#1 + beq fdc1772_dma_write_notours + orr r8,r8,#0x10000 @ FDC base + ldmia r11,{r9,r10} + subs r9,r9,#1 @ One less byte to go + @ If there really is some data then get it, store it and update count + ldrplb r12,[r10],#1 + strplb r12,[r8,#0xc] @ write it to FDC data reg + stmplia r11,{r9,r10} @ Update count and pointer - should clear interrupt + @ Handle any other interrupts +fdc1772_dma_write_notours: + @ Cant branch because this code has been copied down to the FIQ vector + ldr pc,[pc,#-4] + .word fdc1772_comendhandler + + .global fdc1772_dma_write_end +fdc1772_dma_write_end: + + +@ Setup the FIQ R11 to point to the data and store the count, address +@ for this dma +@ R0=count +@ R1=address + .global fdc1772_setupdma +fdc1772_setupdma: + @ The big job is flipping in and out of FIQ mode + adr r2,fdc1772_fiqdata @ This is what we really came here for + stmia r2,{r0,r1} + mov r3, pc + teqp pc,#0x0c000001 @ Disable FIQs, IRQs and switch to FIQ mode + mov r0,r0 @ NOP + mov r11,r2 + teqp r3,#0 @ Normal mode + mov r0,r0 @ NOP + mov pc,r14 + diff --git a/drivers/acorn/block/mfm.S b/drivers/acorn/block/mfm.S new file mode 100644 index 000000000000..c90cbd41ce21 --- /dev/null +++ b/drivers/acorn/block/mfm.S @@ -0,0 +1,162 @@ +@ Read/Write DMA code for the ST506/MFM hard drive controllers on the A400 Acorn Archimedes +@ motherboard and on ST506 expansion podules. +@ (c) David Alan Gilbert (linux@treblig.org) 1996-1999 + +#include <asm/assembler.h> + +hdc63463_irqdata: +@ Controller base address + .global hdc63463_baseaddress +hdc63463_baseaddress: + .word 0 + + .global hdc63463_irqpolladdress +hdc63463_irqpolladdress: + .word 0 + + .global hdc63463_irqpollmask +hdc63463_irqpollmask: + .word 0 + +@ where to read/write data from the kernel data space + .global hdc63463_dataptr +hdc63463_dataptr: + .word 0 + +@ Number of bytes left to transfer + .global hdc63463_dataleft +hdc63463_dataleft: + .word 0 + +@ ------------------------------------------------------------------------- +@ hdc63463_writedma: DMA from host to controller +@ internal reg usage: r0=hdc base address, r1=irq poll address, r2=poll mask +@ r3=data ptr, r4=data left, r5,r6=temporary + .global hdc63463_writedma +hdc63463_writedma: + stmfd sp!,{r4-r7} + adr r5,hdc63463_irqdata + ldmia r5,{r0,r1,r2,r3,r4} + +writedma_again: + + @ test number of remaining bytes to transfer + cmp r4,#0 + beq writedma_end + bmi writedma_end + + @ Check the hdc is interrupting + ldrb r5,[r1,#0] + tst r5,r2 + beq writedma_end + + @ Transfer a block of upto 256 bytes + cmp r4,#256 + movlt r7,r4 + movge r7,#256 + + @ Check the hdc is still busy and command has not ended and no errors + ldr r5,[r0,#32] @ Status reg - 16 bit - its the top few bits which are status + @ think we should continue DMA until it drops busy - perhaps this was + @ the main problem with corrected errors causing a hang + @tst r5,#0x3c00 @ Test for things which should be off + @bne writedma_end + and r5,r5,#0x8000 @ This is test for things which should be on: Busy + cmp r5,#0x8000 + bne writedma_end + + @ Bytes remaining at end + sub r4,r4,r7 + + @ HDC Write register location + add r0,r0,#32+8 + +writedma_loop: + @ OK - pretty sure we should be doing this + + ldr r5,[r3],#4 @ Get a word to be written + @ get bottom half to be sent first + mov r6,r5,lsl#16 @ Separate the first 2 bytes + orr r2,r6,r6,lsr #16 @ Duplicate them in the bottom half of the word + @ now the top half + mov r6,r5,lsr#16 @ Get 2nd 2 bytes + orr r6,r6,r6,lsl#16 @ Duplicate + @str r6,[r0] @ to hdc + stmia r0,{r2,r6} + subs r7,r7,#4 @ Dec. number of bytes left + bne writedma_loop + + @ If we were too slow we had better go through again - DAG - took out with new interrupt routine + @ sub r0,r0,#32+8 + @ adr r2,hdc63463_irqdata + @ ldr r2,[r2,#8] + @ b writedma_again + +writedma_end: + adr r5,hdc63463_irqdata+12 + stmia r5,{r3,r4} + ldmfd sp!,{r4-r7} + RETINSTR(mov,pc,lr) + +@ ------------------------------------------------------------------------- +@ hdc63463_readdma: DMA from controller to host +@ internal reg usage: r0=hdc base address, r1=irq poll address, r2=poll mask +@ r3=data ptr, r4=data left, r5,r6=temporary + .global hdc63463_readdma +hdc63463_readdma: + stmfd sp!,{r4-r7} + adr r5,hdc63463_irqdata + ldmia r5,{r0,r1,r2,r3,r4} + +readdma_again: + @ test number of remaining bytes to transfer + cmp r4,#0 + beq readdma_end + bmi readdma_end + + @ Check the hdc is interrupting + ldrb r5,[r1,#0] + tst r5,r2 + beq readdma_end + + @ Check the hdc is still busy and command has not ended and no errors + ldr r5,[r0,#32] @ Status reg - 16 bit - its the top few bits which are status + @ think we should continue DMA until it drops busy - perhaps this was + @ the main problem with corrected errors causing a hang + @tst r5,#0x3c00 @ Test for things which should be off + @bne readdma_end + and r5,r5,#0x8000 @ This is test for things which should be on: Busy + cmp r5,#0x8000 + bne readdma_end + + @ Transfer a block of upto 256 bytes + cmp r4,#256 + movlt r7,r4 + movge r7,#256 + + @ Bytes remaining at end + sub r4,r4,r7 + + @ Set a pointer to the data register in the HDC + add r0,r0,#8 +readdma_loop: + @ OK - pretty sure we should be doing this + ldmia r0,{r5,r6} + mov r5,r5,lsl#16 + mov r6,r6,lsl#16 + orr r6,r6,r5,lsr #16 + str r6,[r3],#4 + subs r7,r7,#4 @ Decrement bytes to go + bne readdma_loop + + @ Try reading multiple blocks - if this was fast enough then I do not think + @ this should help - NO taken out DAG - new interrupt handler has + @ non-consecutive memory blocks + @ sub r0,r0,#8 + @ b readdma_again + +readdma_end: + adr r5,hdc63463_irqdata+12 + stmia r5,{r3,r4} + ldmfd sp!,{r4-r7} + RETINSTR(mov,pc,lr) diff --git a/drivers/acorn/block/mfmhd.c b/drivers/acorn/block/mfmhd.c new file mode 100644 index 000000000000..4b65f74d66b1 --- /dev/null +++ b/drivers/acorn/block/mfmhd.c @@ -0,0 +1,1416 @@ +/* + * linux/arch/arm/drivers/block/mfmhd.c + * + * Copyright (C) 1995, 1996 Russell King, Dave Alan Gilbert (gilbertd@cs.man.ac.uk) + * + * MFM hard drive code [experimental] + */ + +/* + * Change list: + * + * 3/2/96:DAG: Started a change list :-) + * Set the hardsect_size pointers up since we are running 256 byte + * sectors + * Added DMA code, put it into the rw_intr + * Moved RCAL out of generic interrupt code - don't want to do it + * while DMA'ing - its now in individual handlers. + * Took interrupt handlers off task queue lists and called + * directly - not sure of implications. + * + * 18/2/96:DAG: Well its reading OK I think, well enough for image file code + * to find the image file; but now I've discovered that I actually + * have to put some code in for image files. + * + * Added stuff for image files; seems to work, but I've not + * got a multisegment image file (I don't think!). + * Put in a hack (yep a real hack) for multiple cylinder reads. + * Not convinced its working. + * + * 5/4/96:DAG: Added asm/hardware.h and use IOC_ macros + * Rewrote dma code in mfm.S (again!) - now takes a word at a time + * from main RAM for speed; still doesn't feel speedy! + * + * 20/4/96:DAG: After rewriting mfm.S a heck of a lot of times and speeding + * things up, I've finally figured out why its so damn slow. + * Linux is only reading a block at a time, and so you never + * get more than 1K per disc revoloution ~=60K/second. + * + * 27/4/96:DAG: On Russell's advice I change ll_rw_blk.c to ask it to + * join adjacent blocks together. Everything falls flat on its + * face. + * Four hours of debugging later; I hadn't realised that + * ll_rw_blk would be so generous as to join blocks whose + * results aren't going into consecutive buffers. + * + * OK; severe rehacking of mfm_rw_interrupt; now end_request's + * as soon as its DMA'd each request. Odd thing is that + * we are sometimes getting interrupts where we are not transferring + * any data; why? Is that what happens when you miss? I doubt + * it; are we too fast? No - its just at command ends. Got 240K/s + * better than before, but RiscOS hits 480K/s + * + * 25/6/96:RMK: Fixed init code to allow the MFM podule to work. Increased the + * number of errors for my Miniscribe drive (8425). + * + * 30/6/96:DAG: Russell suggested that a check drive 0 might turn the LEDs off + * - so in request_done just before it clears Busy it sends a + * check drive 0 - and the LEDs go off!!!! + * + * Added test for mainboard controller. - Removes need for separate + * define. + * + * 13/7/96:DAG: Changed hardware sectore size to 512 in attempt to make + * IM drivers work. + * 21/7/96:DAG: Took out old image file stuff (accessing it now produces an IO + * error.) + * + * 17/8/96:DAG: Ran through indent -kr -i8; evil - all my nice 2 character indents + * gone :-( Hand modified afterwards. + * Took out last remains of the older image map system. + * + * 22/9/96:DAG: Changed mfm.S so it will carry on DMA'ing til; BSY is dropped + * Changed mfm_rw_intr so that it doesn't follow the error + * code until BSY is dropped. Nope - still broke. Problem + * may revolve around when it reads the results for the error + * number? + * + *16/11/96:DAG: Modified for 2.0.18; request_irq changed + * + *17/12/96:RMK: Various cleanups, reorganisation, and the changes for new IO system. + * Improved probe for onboard MFM chip - it was hanging on my A5k. + * Added autodetect CHS code such that we don't rely on the presence + * of an ADFS boot block. Added ioport resource manager calls so + * that we don't clash with already-running hardware (eg. RiscPC Ether + * card slots if someone tries this)! + * + * 17/1/97:RMK: Upgraded to 2.1 kernels. + * + * 4/3/98:RMK: Changed major number to 21. + * + * 27/6/98:RMK: Changed asm/delay.h to linux/delay.h for mdelay(). + */ + +/* + * Possible enhancements: + * Multi-thread the code so that it is possible that while one drive + * is seeking, the other one can be reading data/seeking as well. + * This would be a performance boost with dual drive systems. + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/genhd.h> +#include <linux/major.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/blkpg.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include <asm/dma.h> +#include <asm/hardware.h> +#include <asm/ecard.h> +#include <asm/hardware/ioc.h> + +static void (*do_mfm)(void) = NULL; +static struct request_queue *mfm_queue; +static DEFINE_SPINLOCK(mfm_lock); + +#define MAJOR_NR MFM_ACORN_MAJOR +#define QUEUE (mfm_queue) +#define CURRENT elv_next_request(mfm_queue) +/* + * This sort of stuff should be in a header file shared with ide.c, hd.c, xd.c etc + */ +#ifndef HDIO_GETGEO +#define HDIO_GETGEO 0x301 +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + unsigned long start; +}; +#endif + + +/* + * Configuration section + * + * This is the maximum number of drives that we accept + */ +#define MFM_MAXDRIVES 2 +/* + * Linux I/O address of onboard MFM controller or 0 to disable this + */ +#define ONBOARD_MFM_ADDRESS ((0x002d0000 >> 2) | 0x80000000) +/* + * Uncomment this to enable debugging in the MFM driver... + */ +#ifndef DEBUG +/*#define DEBUG */ +#endif +/* + * End of configuration + */ + + +/* + * This structure contains all information to do with a particular physical + * device. + */ +struct mfm_info { + unsigned char sectors; + unsigned char heads; + unsigned short cylinders; + unsigned short lowcurrent; + unsigned short precomp; +#define NO_TRACK -1 +#define NEED_1_RECAL -2 +#define NEED_2_RECAL -3 + int cylinder; + struct { + char recal; + char report; + char abort; + } errors; +} mfm_info[MFM_MAXDRIVES]; + +#define MFM_DRV_INFO mfm_info[raw_cmd.dev] + +/* Stuff from the assembly routines */ +extern unsigned int hdc63463_baseaddress; /* Controller base address */ +extern unsigned int hdc63463_irqpolladdress; /* Address to read to test for int */ +extern unsigned int hdc63463_irqpollmask; /* Mask for irq register */ +extern unsigned int hdc63463_dataptr; /* Pointer to kernel data space to DMA */ +extern int hdc63463_dataleft; /* Number of bytes left to transfer */ + + + + +static int lastspecifieddrive; +static unsigned Busy; + +static unsigned int PartFragRead; /* The number of sectors which have been read + during a partial read split over two + cylinders. If 0 it means a partial + read did not occur. */ + +static unsigned int PartFragRead_RestartBlock; /* Where to restart on a split access */ +static unsigned int PartFragRead_SectorsLeft; /* Where to restart on a split access */ + +static int Sectors256LeftInCurrent; /* i.e. 256 byte sectors left in current */ +static int SectorsLeftInRequest; /* i.e. blocks left in the thing mfm_request was called for */ +static int Copy_Sector; /* The 256 byte sector we are currently at - fragments need to know + where to take over */ +static char *Copy_buffer; + + +static void mfm_seek(void); +static void mfm_rerequest(void); +static void mfm_request(void); +static void mfm_specify (void); +static void issue_request(unsigned int block, unsigned int nsect, + struct request *req); + +static unsigned int mfm_addr; /* Controller address */ +static unsigned int mfm_IRQPollLoc; /* Address to read for IRQ information */ +static unsigned int mfm_irqenable; /* Podule IRQ enable location */ +static unsigned char mfm_irq; /* Interrupt number */ +static int mfm_drives = 0; /* drives available */ +static int mfm_status = 0; /* interrupt status */ +static int *errors; + +static struct rawcmd { + unsigned int dev; + unsigned int cylinder; + unsigned int head; + unsigned int sector; + unsigned int cmdtype; + unsigned int cmdcode; + unsigned char cmddata[16]; + unsigned int cmdlen; +} raw_cmd; + +static unsigned char result[16]; + +static struct cont { + void (*interrupt) (void); /* interrupt handler */ + void (*error) (void); /* error handler */ + void (*redo) (void); /* redo handler */ + void (*done) (int st); /* done handler */ +} *cont = NULL; + +#if 0 +static struct tq_struct mfm_tq = {0, 0, (void (*)(void *)) NULL, 0}; +#endif + +int number_mfm_drives = 1; + +/* ------------------------------------------------------------------------------------------ */ +/* + * From the HD63463 data sheet from Hitachi Ltd. + */ + +#define MFM_COMMAND (mfm_addr + 0) +#define MFM_DATAOUT (mfm_addr + 1) +#define MFM_STATUS (mfm_addr + 8) +#define MFM_DATAIN (mfm_addr + 9) + +#define CMD_ABT 0xF0 /* Abort */ +#define CMD_SPC 0xE8 /* Specify */ +#define CMD_TST 0xE0 /* Test */ +#define CMD_RCLB 0xC8 /* Recalibrate */ +#define CMD_SEK 0xC0 /* Seek */ +#define CMD_WFS 0xAB /* Write Format Skew */ +#define CMD_WFM 0xA3 /* Write Format */ +#define CMD_MTB 0x90 /* Memory to buffer */ +#define CMD_CMPD 0x88 /* Compare data */ +#define CMD_WD 0x87 /* Write data */ +#define CMD_RED 0x70 /* Read erroneous data */ +#define CMD_RIS 0x68 /* Read ID skew */ +#define CMD_FID 0x61 /* Find ID */ +#define CMD_RID 0x60 /* Read ID */ +#define CMD_BTM 0x50 /* Buffer to memory */ +#define CMD_CKD 0x48 /* Check data */ +#define CMD_RD 0x40 /* Read data */ +#define CMD_OPBW 0x38 /* Open buffer write */ +#define CMD_OPBR 0x30 /* Open buffer read */ +#define CMD_CKV 0x28 /* Check drive */ +#define CMD_CKE 0x20 /* Check ECC */ +#define CMD_POD 0x18 /* Polling disable */ +#define CMD_POL 0x10 /* Polling enable */ +#define CMD_RCAL 0x08 /* Recall */ + +#define STAT_BSY 0x8000 /* Busy */ +#define STAT_CPR 0x4000 /* Command Parameter Rejection */ +#define STAT_CED 0x2000 /* Command end */ +#define STAT_SED 0x1000 /* Seek end */ +#define STAT_DER 0x0800 /* Drive error */ +#define STAT_ABN 0x0400 /* Abnormal end */ +#define STAT_POL 0x0200 /* Polling */ + +/* ------------------------------------------------------------------------------------------ */ +#ifdef DEBUG +static void console_printf(const char *fmt,...) +{ + static char buffer[2048]; /* Arbitary! */ + extern void console_print(const char *); + unsigned long flags; + va_list ap; + + local_irq_save(flags); + + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + console_print(buffer); + va_end(fmt); + + local_irq_restore(flags); +}; /* console_printf */ + +#define DBG(x...) console_printf(x) +#else +#define DBG(x...) +#endif + +static void print_status(void) +{ + char *error; + static char *errors[] = { + "no error", + "command aborted", + "invalid command", + "parameter error", + "not initialised", + "rejected TEST", + "no useld", + "write fault", + "not ready", + "no scp", + "in seek", + "invalid NCA", + "invalid step rate", + "seek error", + "over run", + "invalid PHA", + "data field EEC error", + "data field CRC error", + "error corrected", + "data field fatal error", + "no data am", + "not hit", + "ID field CRC error", + "time over", + "no ID am", + "not writable" + }; + if (result[1] < 0x65) + error = errors[result[1] >> 2]; + else + error = "unknown"; + printk("("); + if (mfm_status & STAT_BSY) printk("BSY "); + if (mfm_status & STAT_CPR) printk("CPR "); + if (mfm_status & STAT_CED) printk("CED "); + if (mfm_status & STAT_SED) printk("SED "); + if (mfm_status & STAT_DER) printk("DER "); + if (mfm_status & STAT_ABN) printk("ABN "); + if (mfm_status & STAT_POL) printk("POL "); + printk(") SSB = %X (%s)\n", result[1], error); + +} + +/* ------------------------------------------------------------------------------------- */ + +static void issue_command(int command, unsigned char *cmdb, int len) +{ + int status; +#ifdef DEBUG + int i; + console_printf("issue_command: %02X: ", command); + for (i = 0; i < len; i++) + console_printf("%02X ", cmdb[i]); + console_printf("\n"); +#endif + + do { + status = inw(MFM_STATUS); + } while (status & (STAT_BSY | STAT_POL)); + DBG("issue_command: status after pol/bsy loop: %02X:\n ", status >> 8); + + if (status & (STAT_CPR | STAT_CED | STAT_SED | STAT_DER | STAT_ABN)) { + outw(CMD_RCAL, MFM_COMMAND); + while (inw(MFM_STATUS) & STAT_BSY); + } + status = inw(MFM_STATUS); + DBG("issue_command: status before parameter issue: %02X:\n ", status >> 8); + + while (len > 0) { + outw(cmdb[1] | (cmdb[0] << 8), MFM_DATAOUT); + len -= 2; + cmdb += 2; + } + status = inw(MFM_STATUS); + DBG("issue_command: status before command issue: %02X:\n ", status >> 8); + + outw(command, MFM_COMMAND); + status = inw(MFM_STATUS); + DBG("issue_command: status immediately after command issue: %02X:\n ", status >> 8); +} + +static void wait_for_completion(void) +{ + while ((mfm_status = inw(MFM_STATUS)) & STAT_BSY); +} + +static void wait_for_command_end(void) +{ + int i; + + while (!((mfm_status = inw(MFM_STATUS)) & STAT_CED)); + + for (i = 0; i < 16;) { + int in; + in = inw(MFM_DATAIN); + result[i++] = in >> 8; + result[i++] = in; + } + outw (CMD_RCAL, MFM_COMMAND); +} + +/* ------------------------------------------------------------------------------------- */ + +static void mfm_rw_intr(void) +{ + int old_status; /* Holds status on entry, we read to see if the command just finished */ +#ifdef DEBUG + console_printf("mfm_rw_intr...dataleft=%d\n", hdc63463_dataleft); + print_status(); +#endif + + /* Now don't handle the error until BSY drops */ + if ((mfm_status & (STAT_DER | STAT_ABN)) && ((mfm_status&STAT_BSY)==0)) { + /* Something has gone wrong - let's try that again */ + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (cont) { + DBG("mfm_rw_intr: DER/ABN err\n"); + cont->error(); + cont->redo(); + }; + return; + }; + + /* OK so what ever happened it's not an error, now I reckon we are left between + a choice of command end or some data which is ready to be collected */ + /* I think we have to transfer data while the interrupt line is on and its + not any other type of interrupt */ + if (CURRENT->cmd == WRITE) { + extern void hdc63463_writedma(void); + if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { + printk("mfm_rw_intr: Apparent DMA write request when no more to DMA\n"); + if (cont) { + cont->error(); + cont->redo(); + }; + return; + }; + hdc63463_writedma(); + } else { + extern void hdc63463_readdma(void); + if ((hdc63463_dataleft <= 0) && (!(mfm_status & STAT_CED))) { + printk("mfm_rw_intr: Apparent DMA read request when no more to DMA\n"); + if (cont) { + cont->error(); + cont->redo(); + }; + return; + }; + DBG("Going to try read dma..............status=0x%x, buffer=%p\n", mfm_status, hdc63463_dataptr); + hdc63463_readdma(); + }; /* Read */ + + if (hdc63463_dataptr != ((unsigned int) Copy_buffer + 256)) { + /* If we didn't actually manage to get any data on this interrupt - but why? We got the interrupt */ + /* Ah - well looking at the status its just when we get command end; so no problem */ + /*console_printf("mfm: dataptr mismatch. dataptr=0x%08x Copy_buffer+256=0x%08p\n", + hdc63463_dataptr,Copy_buffer+256); + print_status(); */ + } else { + Sectors256LeftInCurrent--; + Copy_buffer += 256; + Copy_Sector++; + + /* We have come to the end of this request */ + if (!Sectors256LeftInCurrent) { + DBG("mfm: end_request for CURRENT=0x%p CURRENT(sector=%d current_nr_sectors=%d nr_sectors=%d)\n", + CURRENT, CURRENT->sector, CURRENT->current_nr_sectors, CURRENT->nr_sectors); + + CURRENT->nr_sectors -= CURRENT->current_nr_sectors; + CURRENT->sector += CURRENT->current_nr_sectors; + SectorsLeftInRequest -= CURRENT->current_nr_sectors; + + end_request(CURRENT, 1); + if (SectorsLeftInRequest) { + hdc63463_dataptr = (unsigned int) CURRENT->buffer; + Copy_buffer = CURRENT->buffer; + Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; + errors = &(CURRENT->errors); + /* These should match the present calculations of the next logical sector + on the device + Copy_Sector=CURRENT->sector*2; */ + + if (Copy_Sector != CURRENT->sector * 2) +#ifdef DEBUG + /*console_printf*/printk("mfm: Copy_Sector mismatch. Copy_Sector=%d CURRENT->sector*2=%d\n", + Copy_Sector, CURRENT->sector * 2); +#else + printk("mfm: Copy_Sector mismatch! Eek!\n"); +#endif + }; /* CURRENT */ + }; /* Sectors256LeftInCurrent */ + }; + + old_status = mfm_status; + mfm_status = inw(MFM_STATUS); + if (mfm_status & (STAT_DER | STAT_ABN)) { + /* Something has gone wrong - let's try that again */ + if (cont) { + DBG("mfm_rw_intr: DER/ABN error\n"); + cont->error(); + cont->redo(); + }; + return; + }; + + /* If this code wasn't entered due to command_end but there is + now a command end we must read the command results out. If it was + entered like this then mfm_interrupt_handler would have done the + job. */ + if ((!((old_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) && + ((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR)) { + int len = 0; + while (len < 16) { + int in; + in = inw(MFM_DATAIN); + result[len++] = in >> 8; + result[len++] = in; + }; + }; /* Result read */ + + /*console_printf ("mfm_rw_intr nearexit [%02X]\n", __raw_readb(mfm_IRQPollLoc)); */ + + /* If end of command move on */ + if (mfm_status & (STAT_CED)) { + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + /* End of command - trigger the next command */ + if (cont) { + cont->done(1); + } + DBG("mfm_rw_intr: returned from cont->done\n"); + } else { + /* Its going to generate another interrupt */ + do_mfm = mfm_rw_intr; + }; +} + +static void mfm_setup_rw(void) +{ + DBG("setting up for rw...\n"); + + do_mfm = mfm_rw_intr; + issue_command(raw_cmd.cmdcode, raw_cmd.cmddata, raw_cmd.cmdlen); +} + +static void mfm_recal_intr(void) +{ +#ifdef DEBUG + console_printf("recal intr - status = "); + print_status(); +#endif + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (mfm_status & (STAT_DER | STAT_ABN)) { + printk("recal failed\n"); + MFM_DRV_INFO.cylinder = NEED_2_RECAL; + if (cont) { + cont->error(); + cont->redo(); + } + return; + } + /* Thats seek end - we are finished */ + if (mfm_status & STAT_SED) { + issue_command(CMD_POD, NULL, 0); + MFM_DRV_INFO.cylinder = 0; + mfm_seek(); + return; + } + /* Command end without seek end (see data sheet p.20) for parallel seek + - we have to send a POL command to wait for the seek */ + if (mfm_status & STAT_CED) { + do_mfm = mfm_recal_intr; + issue_command(CMD_POL, NULL, 0); + return; + } + printk("recal: unknown status\n"); +} + +static void mfm_seek_intr(void) +{ +#ifdef DEBUG + console_printf("seek intr - status = "); + print_status(); +#endif + outw(CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + if (mfm_status & (STAT_DER | STAT_ABN)) { + printk("seek failed\n"); + MFM_DRV_INFO.cylinder = NEED_2_RECAL; + if (cont) { + cont->error(); + cont->redo(); + } + return; + } + if (mfm_status & STAT_SED) { + issue_command(CMD_POD, NULL, 0); + MFM_DRV_INFO.cylinder = raw_cmd.cylinder; + mfm_seek(); + return; + } + if (mfm_status & STAT_CED) { + do_mfm = mfm_seek_intr; + issue_command(CMD_POL, NULL, 0); + return; + } + printk("seek: unknown status\n"); +} + +/* IDEA2 seems to work better - its what RiscOS sets my + * disc to - on its SECOND call to specify! + */ +#define IDEA2 +#ifndef IDEA2 +#define SPEC_SL 0x16 +#define SPEC_SH 0xa9 /* Step pulse high=21, Record Length=001 (256 bytes) */ +#else +#define SPEC_SL 0x00 /* OM2 - SL - step pulse low */ +#define SPEC_SH 0x21 /* Step pulse high=4, Record Length=001 (256 bytes) */ +#endif + +static void mfm_setupspecify (int drive, unsigned char *cmdb) +{ + cmdb[0] = 0x1f; /* OM0 - !SECT,!MOD,!DIF,PADP,ECD,CRCP,CRCI,ACOR */ + cmdb[1] = 0xc3; /* OM1 - DTM,BRST,!CEDM,!SEDM,!DERM,0,AMEX,PSK */ + cmdb[2] = SPEC_SL; /* OM2 - SL - step pulse low */ + cmdb[3] = (number_mfm_drives == 1) ? 0x02 : 0x06; /* 1 or 2 drives */ + cmdb[4] = 0xfc | ((mfm_info[drive].cylinders - 1) >> 8);/* RW time over/high part of number of cylinders */ + cmdb[5] = mfm_info[drive].cylinders - 1; /* low part of number of cylinders */ + cmdb[6] = mfm_info[drive].heads - 1; /* Number of heads */ + cmdb[7] = mfm_info[drive].sectors - 1; /* Number of sectors */ + cmdb[8] = SPEC_SH; + cmdb[9] = 0x0a; /* gap length 1 */ + cmdb[10] = 0x0d; /* gap length 2 */ + cmdb[11] = 0x0c; /* gap length 3 */ + cmdb[12] = (mfm_info[drive].precomp - 1) >> 8; /* pre comp cylinder */ + cmdb[13] = mfm_info[drive].precomp - 1; + cmdb[14] = (mfm_info[drive].lowcurrent - 1) >> 8; /* Low current cylinder */ + cmdb[15] = mfm_info[drive].lowcurrent - 1; +} + +static void mfm_specify (void) +{ + unsigned char cmdb[16]; + + DBG("specify...dev=%d lastspecified=%d\n", raw_cmd.dev, lastspecifieddrive); + mfm_setupspecify (raw_cmd.dev, cmdb); + + issue_command (CMD_SPC, cmdb, 16); + /* Ensure that we will do another specify if we move to the other drive */ + lastspecifieddrive = raw_cmd.dev; + wait_for_completion(); +} + +static void mfm_seek(void) +{ + unsigned char cmdb[4]; + + DBG("seeking...\n"); + if (MFM_DRV_INFO.cylinder < 0) { + do_mfm = mfm_recal_intr; + DBG("mfm_seek: about to call specify\n"); + mfm_specify (); /* DAG added this */ + + cmdb[0] = raw_cmd.dev + 1; + cmdb[1] = 0; + + issue_command(CMD_RCLB, cmdb, 2); + return; + } + if (MFM_DRV_INFO.cylinder != raw_cmd.cylinder) { + cmdb[0] = raw_cmd.dev + 1; + cmdb[1] = 0; /* raw_cmd.head; DAG: My data sheet says this should be 0 */ + cmdb[2] = raw_cmd.cylinder >> 8; + cmdb[3] = raw_cmd.cylinder; + + do_mfm = mfm_seek_intr; + issue_command(CMD_SEK, cmdb, 4); + } else + mfm_setup_rw(); +} + +static void mfm_initialise(void) +{ + DBG("init...\n"); + mfm_seek(); +} + +static void request_done(int uptodate) +{ + DBG("mfm:request_done\n"); + if (uptodate) { + unsigned char block[2] = {0, 0}; + + /* Apparently worked - let's check bytes left to DMA */ + if (hdc63463_dataleft != (PartFragRead_SectorsLeft * 256)) { + printk("mfm: request_done - dataleft=%d - should be %d - Eek!\n", hdc63463_dataleft, PartFragRead_SectorsLeft * 256); + end_request(CURRENT, 0); + Busy = 0; + }; + /* Potentially this means that we've done; but we might be doing + a partial access, (over two cylinders) or we may have a number + of fragments in an image file. First let's deal with partial accesss + */ + if (PartFragRead) { + /* Yep - a partial access */ + + /* and issue the remainder */ + issue_request(PartFragRead_RestartBlock, PartFragRead_SectorsLeft, CURRENT); + return; + } + + /* ah well - perhaps there is another fragment to go */ + + /* Increment pointers/counts to start of next fragment */ + if (SectorsLeftInRequest > 0) printk("mfm: SectorsLeftInRequest>0 - Eek! Shouldn't happen!\n"); + + /* No - its the end of the line */ + /* end_request's should have happened at the end of sector DMAs */ + /* Turns Drive LEDs off - may slow it down? */ + if (!elv_next_request(QUEUE)) + issue_command(CMD_CKV, block, 2); + + Busy = 0; + DBG("request_done: About to mfm_request\n"); + /* Next one please */ + mfm_request(); /* Moved from mfm_rw_intr */ + DBG("request_done: returned from mfm_request\n"); + } else { + printk("mfm:request_done: update=0\n"); + end_request(CURRENT, 0); + Busy = 0; + } +} + +static void error_handler(void) +{ + printk("error detected... status = "); + print_status(); + (*errors)++; + if (*errors > MFM_DRV_INFO.errors.abort) + cont->done(0); + if (*errors > MFM_DRV_INFO.errors.recal) + MFM_DRV_INFO.cylinder = NEED_2_RECAL; +} + +static void rw_interrupt(void) +{ + printk("rw_interrupt\n"); +} + +static struct cont rw_cont = +{ + rw_interrupt, + error_handler, + mfm_rerequest, + request_done +}; + +/* + * Actually gets round to issuing the request - note everything at this + * point is in 256 byte sectors not Linux 512 byte blocks + */ +static void issue_request(unsigned int block, unsigned int nsect, + struct request *req) +{ + struct gendisk *disk = req->rq_disk; + struct mfm_info *p = disk->private_data; + int track, start_head, start_sector; + int sectors_to_next_cyl; + dev = p - mfm_info; + + track = block / p->sectors; + start_sector = block % p->sectors; + start_head = track % p->heads; + + /* First get the number of whole tracks which are free before the next + track */ + sectors_to_next_cyl = (p->heads - (start_head + 1)) * p->sectors; + /* Then add in the number of sectors left on this track */ + sectors_to_next_cyl += (p->sectors - start_sector); + + DBG("issue_request: mfm_info[dev].sectors=%d track=%d\n", p->sectors, track); + + raw_cmd.dev = dev; + raw_cmd.sector = start_sector; + raw_cmd.head = start_head; + raw_cmd.cylinder = track / p->heads; + raw_cmd.cmdtype = CURRENT->cmd; + raw_cmd.cmdcode = CURRENT->cmd == WRITE ? CMD_WD : CMD_RD; + raw_cmd.cmddata[0] = dev + 1; /* DAG: +1 to get US */ + raw_cmd.cmddata[1] = raw_cmd.head; + raw_cmd.cmddata[2] = raw_cmd.cylinder >> 8; + raw_cmd.cmddata[3] = raw_cmd.cylinder; + raw_cmd.cmddata[4] = raw_cmd.head; + raw_cmd.cmddata[5] = raw_cmd.sector; + + /* Was == and worked - how the heck??? */ + if (lastspecifieddrive != raw_cmd.dev) + mfm_specify (); + + if (nsect <= sectors_to_next_cyl) { + raw_cmd.cmddata[6] = nsect >> 8; + raw_cmd.cmddata[7] = nsect; + PartFragRead = 0; /* All in one */ + PartFragRead_SectorsLeft = 0; /* Must set this - used in DMA calcs */ + } else { + raw_cmd.cmddata[6] = sectors_to_next_cyl >> 8; + raw_cmd.cmddata[7] = sectors_to_next_cyl; + PartFragRead = sectors_to_next_cyl; /* only do this many this time */ + PartFragRead_RestartBlock = block + sectors_to_next_cyl; /* Where to restart from */ + PartFragRead_SectorsLeft = nsect - sectors_to_next_cyl; + } + raw_cmd.cmdlen = 8; + + /* Setup DMA pointers */ + hdc63463_dataptr = (unsigned int) Copy_buffer; + hdc63463_dataleft = nsect * 256; /* Better way? */ + + DBG("mfm%c: %sing: CHS=%d/%d/%d, sectors=%d, buffer=0x%08lx (%p)\n", + raw_cmd.dev + 'a', (CURRENT->cmd == READ) ? "read" : "writ", + raw_cmd.cylinder, + raw_cmd.head, + raw_cmd.sector, nsect, (unsigned long) Copy_buffer, CURRENT); + + cont = &rw_cont; + errors = &(CURRENT->errors); +#if 0 + mfm_tq.routine = (void (*)(void *)) mfm_initialise; + queue_task(&mfm_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +#else + mfm_initialise(); +#endif +} /* issue_request */ + +/* + * Called when an error has just happened - need to trick mfm_request + * into thinking we weren't busy + * + * Turn off ints - mfm_request expects them this way + */ +static void mfm_rerequest(void) +{ + DBG("mfm_rerequest\n"); + cli(); + Busy = 0; + mfm_request(); +} + +static struct gendisk *mfm_gendisk[2]; + +static void mfm_request(void) +{ + DBG("mfm_request CURRENT=%p Busy=%d\n", CURRENT, Busy); + + /* If we are still processing then return; we will get called again */ + if (Busy) { + /* Again seems to be common in 1.3.45 */ + /*DBG*/printk("mfm_request: Exiting due to busy\n"); + return; + } + Busy = 1; + + while (1) { + unsigned int block, nsect; + struct gendisk *disk; + + DBG("mfm_request: loop start\n"); + sti(); + + DBG("mfm_request: before !CURRENT\n"); + + if (!CURRENT) { + printk("mfm_request: Exiting due to empty queue (pre)\n"); + do_mfm = NULL; + Busy = 0; + return; + } + + DBG("mfm_request: before arg extraction\n"); + + disk = CURRENT->rq_disk; + block = CURRENT->sector; + nsect = CURRENT->nr_sectors; + if (block >= get_capacity(disk) || + block+nsect > get_capacity(disk)) { + printk("%s: bad access: block=%d, count=%d, nr_sects=%ld\n", + disk->disk_name, block, nsect, get_capacity(disk)); + printk("mfm: continue 1\n"); + end_request(CURRENT, 0); + Busy = 0; + continue; + } + + /* DAG: Linux doesn't cope with this - even though it has an array telling + it the hardware block size - silly */ + block <<= 1; /* Now in 256 byte sectors */ + nsect <<= 1; /* Ditto */ + + SectorsLeftInRequest = nsect >> 1; + Sectors256LeftInCurrent = CURRENT->current_nr_sectors * 2; + Copy_buffer = CURRENT->buffer; + Copy_Sector = CURRENT->sector << 1; + + DBG("mfm_request: block after offset=%d\n", block); + + if (CURRENT->cmd != READ && CURRENT->cmd != WRITE) { + printk("unknown mfm-command %d\n", CURRENT->cmd); + end_request(CURRENT, 0); + Busy = 0; + printk("mfm: continue 4\n"); + continue; + } + issue_request(block, nsect, CURRENT); + + break; + } + DBG("mfm_request: Dropping out bottom\n"); +} + +static void do_mfm_request(request_queue_t *q) +{ + DBG("do_mfm_request: about to mfm_request\n"); + mfm_request(); +} + +static void mfm_interrupt_handler(int unused, void *dev_id, struct pt_regs *regs) +{ + void (*handler) (void) = do_mfm; + + do_mfm = NULL; + + DBG("mfm_interrupt_handler (handler=0x%p)\n", handler); + + mfm_status = inw(MFM_STATUS); + + /* If CPR (Command Parameter Reject) and not busy it means that the command + has some return message to give us */ + if ((mfm_status & (STAT_CPR | STAT_BSY)) == STAT_CPR) { + int len = 0; + while (len < 16) { + int in; + in = inw(MFM_DATAIN); + result[len++] = in >> 8; + result[len++] = in; + } + } + if (handler) { + handler(); + return; + } + outw (CMD_RCAL, MFM_COMMAND); /* Clear interrupt condition */ + printk ("mfm: unexpected interrupt - status = "); + print_status (); + while (1); +} + + + + + +/* + * Tell the user about the drive if we decided it exists. + */ +static void mfm_geometry(int drive) +{ + struct mfm_info *p = mfm_info + drive; + struct gendisk *disk = mfm_gendisk[drive]; + disk->private_data = p; + if (p->cylinders) + printk ("%s: %dMB CHS=%d/%d/%d LCC=%d RECOMP=%d\n", + disk->disk_name, + p->cylinders * p->heads * p->sectors / 4096, + p->cylinders, p->heads, p->sectors, + p->lowcurrent, p->precomp); + set_capacity(disk, p->cylinders * p->heads * p->sectors / 2); +} + +#ifdef CONFIG_BLK_DEV_MFM_AUTODETECT +/* + * Attempt to detect a drive and find its geometry. The drive has already been + * specified... + * + * We first recalibrate the disk, then try to probe sectors, heads and then + * cylinders. NOTE! the cylinder probe may break drives. The xd disk driver + * does something along these lines, so I assume that most drives are up to + * this mistreatment... + */ +static int mfm_detectdrive (int drive) +{ + unsigned int mingeo[3], maxgeo[3]; + unsigned int attribute, need_recal = 1; + unsigned char cmdb[8]; + + memset (mingeo, 0, sizeof (mingeo)); + maxgeo[0] = mfm_info[drive].sectors; + maxgeo[1] = mfm_info[drive].heads; + maxgeo[2] = mfm_info[drive].cylinders; + + cmdb[0] = drive + 1; + cmdb[6] = 0; + cmdb[7] = 1; + for (attribute = 0; attribute < 3; attribute++) { + while (mingeo[attribute] != maxgeo[attribute]) { + unsigned int variable; + + variable = (maxgeo[attribute] + mingeo[attribute]) >> 1; + cmdb[1] = cmdb[2] = cmdb[3] = cmdb[4] = cmdb[5] = 0; + + if (need_recal) { + int tries = 5; + + do { + issue_command (CMD_RCLB, cmdb, 2); + wait_for_completion (); + wait_for_command_end (); + if (result[1] == 0x20) + break; + } while (result[1] && --tries); + if (result[1]) { + outw (CMD_RCAL, MFM_COMMAND); + return 0; + } + need_recal = 0; + } + + switch (attribute) { + case 0: + cmdb[5] = variable; + issue_command (CMD_CMPD, cmdb, 8); + break; + case 1: + cmdb[1] = variable; + cmdb[4] = variable; + issue_command (CMD_CMPD, cmdb, 8); + break; + case 2: + cmdb[2] = variable >> 8; + cmdb[3] = variable; + issue_command (CMD_SEK, cmdb, 4); + break; + } + wait_for_completion (); + wait_for_command_end (); + + switch (result[1]) { + case 0x00: + case 0x50: + mingeo[attribute] = variable + 1; + break; + + case 0x20: + outw (CMD_RCAL, MFM_COMMAND); + return 0; + + case 0x24: + need_recal = 1; + default: + maxgeo[attribute] = variable; + break; + } + } + } + mfm_info[drive].cylinders = mingeo[2]; + mfm_info[drive].lowcurrent = mingeo[2]; + mfm_info[drive].precomp = mingeo[2] / 2; + mfm_info[drive].heads = mingeo[1]; + mfm_info[drive].sectors = mingeo[0]; + outw (CMD_RCAL, MFM_COMMAND); + return 1; +} +#endif + +/* + * Initialise all drive information for this controller. + */ +static int mfm_initdrives(void) +{ + int drive; + + if (number_mfm_drives > MFM_MAXDRIVES) { + number_mfm_drives = MFM_MAXDRIVES; + printk("No. of ADFS MFM drives is greater than MFM_MAXDRIVES - you can't have that many!\n"); + } + + for (drive = 0; drive < number_mfm_drives; drive++) { + mfm_info[drive].lowcurrent = 1; + mfm_info[drive].precomp = 1; + mfm_info[drive].cylinder = -1; + mfm_info[drive].errors.recal = 0; + mfm_info[drive].errors.report = 0; + mfm_info[drive].errors.abort = 4; + +#ifdef CONFIG_BLK_DEV_MFM_AUTODETECT + mfm_info[drive].cylinders = 1024; + mfm_info[drive].heads = 8; + mfm_info[drive].sectors = 64; + { + unsigned char cmdb[16]; + + mfm_setupspecify (drive, cmdb); + cmdb[1] &= ~0x81; + issue_command (CMD_SPC, cmdb, 16); + wait_for_completion (); + if (!mfm_detectdrive (drive)) { + mfm_info[drive].cylinders = 0; + mfm_info[drive].heads = 0; + mfm_info[drive].sectors = 0; + } + cmdb[0] = cmdb[1] = 0; + issue_command (CMD_CKV, cmdb, 2); + } +#else + mfm_info[drive].cylinders = 1; /* its going to have to figure it out from the partition info */ + mfm_info[drive].heads = 4; + mfm_info[drive].sectors = 32; +#endif + } + return number_mfm_drives; +} + + + +/* + * The 'front' end of the mfm driver follows... + */ + +static int mfm_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) +{ + struct mfm_info *p = inode->i_bdev->bd_disk->private_data; + struct hd_geometry *geo = (struct hd_geometry *) arg; + if (cmd != HDIO_GETGEO) + return -EINVAL; + if (!arg) + return -EINVAL; + if (put_user (p->heads, &geo->heads)) + return -EFAULT; + if (put_user (p->sectors, &geo->sectors)) + return -EFAULT; + if (put_user (p->cylinders, &geo->cylinders)) + return -EFAULT; + if (put_user (get_start_sect(inode->i_bdev), &geo->start)) + return -EFAULT; + return 0; +} + +/* + * This is to handle various kernel command line parameters + * specific to this driver. + */ +void mfm_setup(char *str, int *ints) +{ + return; +} + +/* + * Set the CHS from the ADFS boot block if it is present. This is not ideal + * since if there are any non-ADFS partitions on the disk, this won't work! + * Hence, I want to get rid of this... + */ +void xd_set_geometry(struct block_device *bdev, unsigned char secsptrack, + unsigned char heads, unsigned int secsize) +{ + struct mfm_info *p = bdev->bd_disk->private_data; + int drive = p - mfm_info; + unsigned long disksize = bdev->bd_inode->i_size; + + if (p->cylinders == 1) { + p->sectors = secsptrack; + p->heads = heads; + p->cylinders = discsize / (secsptrack * heads * secsize); + + if ((heads < 1) || (p->cylinders > 1024)) { + printk("%s: Insane disc shape! Setting to 512/4/32\n", + bdev->bd_disk->disk_name); + + /* These values are fairly arbitary, but are there so that if your + * lucky you can pick apart your disc to find out what is going on - + * I reckon these figures won't hurt MOST drives + */ + p->sectors = 32; + p->heads = 4; + p->cylinders = 512; + } + if (raw_cmd.dev == drive) + mfm_specify (); + mfm_geometry (drive); + } +} + +static struct block_device_operations mfm_fops = +{ + .owner = THIS_MODULE, + .ioctl = mfm_ioctl, +}; + +/* + * See if there is a controller at the address presently at mfm_addr + * + * We check to see if the controller is busy - if it is, we abort it first, + * and check that the chip is no longer busy after at least 180 clock cycles. + * We then issue a command and check that the BSY or CPR bits are set. + */ +static int mfm_probecontroller (unsigned int mfm_addr) +{ + if (inw (MFM_STATUS) & STAT_BSY) { + outw (CMD_ABT, MFM_COMMAND); + udelay (50); + if (inw (MFM_STATUS) & STAT_BSY) + return 0; + } + + if (inw (MFM_STATUS) & STAT_CED) + outw (CMD_RCAL, MFM_COMMAND); + + outw (CMD_SEK, MFM_COMMAND); + + if (inw (MFM_STATUS) & (STAT_BSY | STAT_CPR)) { + unsigned int count = 2000; + while (inw (MFM_STATUS) & STAT_BSY) { + udelay (500); + if (!--count) + return 0; + } + + outw (CMD_RCAL, MFM_COMMAND); + } + return 1; +} + +static int mfm_do_init(unsigned char irqmask) +{ + int i, ret; + + printk("mfm: found at address %08X, interrupt %d\n", mfm_addr, mfm_irq); + + ret = -EBUSY; + if (!request_region (mfm_addr, 10, "mfm")) + goto out1; + + ret = register_blkdev(MAJOR_NR, "mfm"); + if (ret) + goto out2; + + /* Stuff for the assembler routines to get to */ + hdc63463_baseaddress = ioaddr(mfm_addr); + hdc63463_irqpolladdress = mfm_IRQPollLoc; + hdc63463_irqpollmask = irqmask; + + mfm_queue = blk_init_queue(do_mfm_request, &mfm_lock); + if (!mfm_queue) + goto out2a; + + Busy = 0; + lastspecifieddrive = -1; + + mfm_drives = mfm_initdrives(); + if (!mfm_drives) { + ret = -ENODEV; + goto out3; + } + + for (i = 0; i < mfm_drives; i++) { + struct gendisk *disk = alloc_disk(64); + if (!disk) + goto Enomem; + disk->major = MAJOR_NR; + disk->first_minor = i << 6; + disk->fops = &mfm_fops; + sprintf(disk->disk_name, "mfm%c", 'a'+i); + mfm_gendisk[i] = disk; + } + + printk("mfm: detected %d hard drive%s\n", mfm_drives, + mfm_drives == 1 ? "" : "s"); + ret = request_irq(mfm_irq, mfm_interrupt_handler, SA_INTERRUPT, "MFM harddisk", NULL); + if (ret) { + printk("mfm: unable to get IRQ%d\n", mfm_irq); + goto out4; + } + + if (mfm_irqenable) + outw(0x80, mfm_irqenable); /* Required to enable IRQs from MFM podule */ + + for (i = 0; i < mfm_drives; i++) { + mfm_geometry(i); + mfm_gendisk[i]->queue = mfm_queue; + add_disk(mfm_gendisk[i]); + } + return 0; + +out4: + for (i = 0; i < mfm_drives; i++) + put_disk(mfm_gendisk[i]); +out3: + blk_cleanup_queue(mfm_queue); +out2a: + unregister_blkdev(MAJOR_NR, "mfm"); +out2: + release_region(mfm_addr, 10); +out1: + return ret; +Enomem: + while (i--) + put_disk(mfm_gendisk[i]); + goto out3; +} + +static void mfm_do_exit(void) +{ + int i; + + free_irq(mfm_irq, NULL); + for (i = 0; i < mfm_drives; i++) { + del_gendisk(mfm_gendisk[i]); + put_disk(mfm_gendisk[i]); + } + blk_cleanup_queue(mfm_queue); + unregister_blkdev(MAJOR_NR, "mfm"); + if (mfm_addr) + release_region(mfm_addr, 10); +} + +static int __devinit mfm_probe(struct expansion_card *ec, struct ecard_id *id) +{ + if (mfm_addr) + return -EBUSY; + + mfm_addr = ecard_address(ec, ECARD_IOC, ECARD_MEDIUM) + 0x800; + mfm_IRQPollLoc = ioaddr(mfm_addr + 0x400); + mfm_irqenable = mfm_IRQPollLoc; + mfm_irq = ec->irq; + + return mfm_do_init(0x08); +} + +static void __devexit mfm_remove(struct expansion_card *ec) +{ + outw (0, mfm_irqenable); /* Required to enable IRQs from MFM podule */ + mfm_do_exit(); +} + +static const struct ecard_id mfm_cids[] = { + { MANU_ACORN, PROD_ACORN_MFM }, + { 0xffff, 0xffff }, +}; + +static struct ecard_driver mfm_driver = { + .probe = mfm_probe, + .remove = __devexit(mfm_remove), + .id_table = mfm_cids, + .drv = { + .name = "mfm", + }, +}; + +/* + * Look for a MFM controller - first check the motherboard, then the podules + * The podules have an extra interrupt enable that needs to be played with + * + * The HDC is accessed at MEDIUM IOC speeds. + */ +static int __init mfm_init (void) +{ + unsigned char irqmask; + + if (mfm_probecontroller(ONBOARD_MFM_ADDRESS)) { + mfm_addr = ONBOARD_MFM_ADDRESS; + mfm_IRQPollLoc = IOC_IRQSTATB; + mfm_irqenable = 0; + mfm_irq = IRQ_HARDDISK; + return mfm_do_init(0x08); /* IL3 pin */ + } else { + return ecard_register_driver(&mfm_driver); + } +} + +static void __exit mfm_exit(void) +{ + if (mfm_addr == ONBOARD_MFM_ADDRESS) + mfm_do_exit(); + else + ecard_unregister_driver(&mfm_driver); +} + +module_init(mfm_init) +module_exit(mfm_exit) +MODULE_LICENSE("GPL"); |