Linux-2.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!
diff --git a/drivers/acorn/README b/drivers/acorn/README
new file mode 100644
index 0000000..d399c09
--- /dev/null
+++ b/drivers/acorn/README
@@ -0,0 +1 @@
+Drivers for the ACORN "podule" ARM specific bus.
diff --git a/drivers/acorn/block/Kconfig b/drivers/acorn/block/Kconfig
new file mode 100644
index 0000000..073add3
--- /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 0000000..38a9afe
--- /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 0000000..3cd2e96
--- /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 0000000..7964435
--- /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 0000000..c90cbd4
--- /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 0000000..4b65f74
--- /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");
diff --git a/drivers/acorn/char/Makefile b/drivers/acorn/char/Makefile
new file mode 100644
index 0000000..2fa9a8b
--- /dev/null
+++ b/drivers/acorn/char/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the acorn character device drivers.
+#
+
+obj-$(CONFIG_ARCH_ACORN)	+= i2c.o pcf8583.o
+obj-$(CONFIG_L7200_KEYB)	+= defkeymap-l7200.o keyb_l7200.o
diff --git a/drivers/acorn/char/defkeymap-l7200.c b/drivers/acorn/char/defkeymap-l7200.c
new file mode 100644
index 0000000..9e18ce7
--- /dev/null
+++ b/drivers/acorn/char/defkeymap-l7200.c
@@ -0,0 +1,386 @@
+/*
+ * linux/drivers/acorn/char/defkeymap-l7200.c
+ *
+ * Default keyboard maps for LinkUp Systems L7200 board
+ *
+ * Copyright (C) 2000 Steve Hill (sjhill@cotw.com)
+ *
+ * Changelog:
+ *   08-04-2000 SJH     Created file
+ */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+/* Normal (maps 1:1 with no processing) */
+#define KTn	0xF0
+/* Function keys */
+#define KTf	0xF1
+/* Special (Performs special house-keeping funcs) */
+#define KTs	0xF2
+#define KIGNORE		K(KTs, 0)	/* Ignore */
+#define KENTER		K(KTs, 1)	/* Enter */
+#define KREGS		K(KTs, 2)	/* Regs */
+#define KMEM		K(KTs, 3)	/* Mem */
+#define KSTAT		K(KTs, 4)	/* State */
+#define KINTR		K(KTs, 5)	/* Intr */
+#define Ksl	6	/* Last console */
+#define KCAPSLK		K(KTs, 7)	/* Caps lock */
+#define KNUMLK		K(KTs, 8)	/* Num-lock */
+#define KSCRLLK		K(KTs, 9)	/* Scroll-lock */
+#define KSCRLFOR	K(KTs,10)	/* Scroll forward */
+#define KSCRLBAK	K(KTs,11)	/* Scroll back */
+#define KREBOOT		K(KTs,12)	/* Reboot */
+#define KCAPSON		K(KTs,13)	/* Caps on */
+#define KCOMPOSE	K(KTs,14)	/* Compose */
+#define KSAK		K(KTs,15)	/* SAK */
+#define CONS_DEC	K(KTs,16)	/* Dec console */
+#define CONS_INC	K(KTs,17)	/* Incr console */
+#define KFLOPPY		K(KTs,18)	/* Floppy */
+/* Key pad (0-9 = digits, 10=+, 11=-, 12=*, 13=/, 14=enter, 16=., 17=# */
+#define KTp	0xF3
+#define KPAD_0		K(KTp, 0 )
+#define KPAD_1  	K(KTp, 1 )
+#define KPAD_2		K(KTp, 2 )
+#define KPAD_3		K(KTp, 3 )
+#define KPAD_4		K(KTp, 4 )
+#define KPAD_5		K(KTp, 5 )
+#define KPAD_6		K(KTp, 6 )
+#define KPAD_7		K(KTp, 7 )
+#define KPAD_8		K(KTp, 8 )
+#define KPAD_9		K(KTp, 9 )
+#define KPAD_PL		K(KTp,10 )
+#define KPAD_MI		K(KTp,11 )
+#define KPAD_ML		K(KTp,12 )
+#define KPAD_DV		K(KTp,13 )
+#define KPAD_EN		K(KTp,14 )
+#define KPAD_DT		K(KTp,16 )
+#define KPAD_HS		K(KTp,20 )
+/* Console switching */
+#define KCn	0xF5
+/* Cursor */
+#define KTc	0xF6
+#define Kcd	0	/* Cursor down */
+#define Kcl	1	/* Cursor left */
+#define Kcr	2	/* Cursor right */
+#define Kcu	3	/* Cursor up */
+/* Shift/alt modifiers etc */
+#define KMd	0xF7
+#define KSHIFT		K(KMd, 0 )
+#define KALTGR		K(KMd, 1 )
+#define KCTRL		K(KMd, 2 )
+#define KALT		K(KMd, 3 )
+/* Meta */
+#define KMt	0xF8
+#define KAs	0xF9
+#define KPADA_0		K(KAs, 0 )
+#define KPADA_1		K(KAs, 1 )
+#define KPADA_2		K(KAs, 2 )
+#define KPADA_3		K(KAs, 3 )
+#define KPADA_4		K(KAs, 4 )
+#define KPADA_5		K(KAs, 5 )
+#define KPADA_6		K(KAs, 6 )
+#define KPADA_7		K(KAs, 7 )
+#define KPADA_8		K(KAs, 8 )
+#define KPADA_9		K(KAs, 9 )
+#define KPADB_0		K(KAs,10 )
+#define KPADB_1		K(KAs,11 )
+#define KPADB_2		K(KAs,12 )
+#define KPADB_3		K(KAs,13 )
+#define KPADB_4		K(KAs,14 )
+#define KPADB_5		K(KAs,15 )
+#define KPADB_6		K(KAs,16 )
+#define KPADB_7		K(KAs,17 )
+#define KPADB_8		K(KAs,18 )
+#define KPADB_9		K(KAs,19 )
+/* Locking keys */
+#define KLk	0xFA
+/* Letters */
+#define KTl	0xFB
+
+/*
+ * Here is the layout of the keys for the Fujitsu QWERTY
+ * style keyboard:
+ *
+ *	static char Fujitsu_Key_Table[] =
+ *	{
+ *        KALT, '`' , KNUL, KCTL, KFUN, KESC, '1' , '2' ,
+ *	  '9' , '0' , '-' , '=' , KNUL, KBSP, KNUL, KNUL,
+ *	  KNUL, KBSL, KSHF, KNUL, KNUL, KDEL, KNUL, 't' ,
+ *	  'y' , 'u' , 'i' , KRET, KSHF, KPGD, KNUL, KNUL,
+ *	  KNUL, KTAB, KNUL, KNUL, KNUL, 'q' , 'w' , 'e' ,
+ *	  'r' , 'o' , 'p' , '[' , KNUL, ']' , KNUL, KNUL,
+ *	  KNUL, 'z' , KNUL, KNUL, KNUL, KSHL, KNUL, KNUL,
+ *	  'k' , 'l' , ';' , KSQT, KNUL, KPGU, KNUL, KNUL,
+ *	  KNUL, 'a' , KNUL, KNUL, KNUL, 's' , 'd' , 'f' ,
+ *	  'g' , 'h' , 'j' , '/' , KNUL, KHME, KNUL, KNUL,
+ *	  KNUL, 'x' , KNUL, KNUL, KNUL, 'c' , 'v' , 'b' ,
+ *	  'n' , 'm' , ',' , '.' , KNUL, ' ' , KNUL, KNUL,
+ *	  KNUL, KNUL, KNUL, KNUL, KNUL, '3' , '4' , '5' ,
+ *	  '6' , '7' , '8' , KNUL, KPRG, KNUL, KEND, KNUL,
+ *	};
+ */
+
+u_short plain_map[NR_KEYS]=
+{
+	0xf703, 0xf060, 0xf200, 0xf702, 0xf200, 0xf01b, 0xf031, 0xf032,
+	0xf039, 0xf030, 0xf02d, 0xf03d, 0xf200, 0xf07f, 0xf200, 0xf200,
+	0xf200, 0xf05c, 0xf700, 0xf200, 0xf200, 0xf116, 0xf000, 0xfb74,
+	0xfb79, 0xfb75, 0xfb69, 0xf201, 0xf700, 0xf600, 0xf200, 0xf200,
+	0xf200, 0xf009, 0xf200, 0xf200, 0xf200, 0xfb71, 0xfb77, 0xfb65,
+	0xfb72, 0xfb6f, 0xfb70, 0xf05b, 0xf200, 0xf05d, 0xf200, 0xf200,
+	0xf200, 0xfb7a, 0xf200, 0xf200, 0xf200, 0xf207, 0xf200, 0xf200,
+	0xfb6b, 0xfb6c, 0xf03b, 0xf027, 0xf200, 0xf603, 0xf200, 0xf200,
+	0xf200, 0xfb61, 0xf200, 0xf200, 0xf200, 0xfb73, 0xfb64, 0xfb66,
+	0xfb67, 0xfb68, 0xfb6a, 0xf02f, 0xf200, 0xf601, 0xf200, 0xf200,
+	0xf200, 0xfb78, 0xf200, 0xf200, 0xf200, 0xfb63, 0xfb76, 0xfb62,
+	0xfb6e, 0xfb6d, 0xf02c, 0xf02e, 0xf200, 0xf020, 0xf200, 0xf200,
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf033, 0xf034, 0xf035, 
+	0xf036, 0xf037, 0xf038, 0xf200, 0xf200, 0xf200, 0xf602, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 
+};
+
+u_short shift_map[NR_KEYS]=
+{
+	0xf703, 0xf07e, 0xf200, 0xf702, 0xf200, 0xf01b, 0xf021, 0xf040,
+	0xf028, 0xf029, 0xf05f, 0xf02b, 0xf200, 0xf07f, 0xf200, 0xf200,
+	0xf200, 0xf07c, 0xf700, 0xf200, 0xf200, 0xf116, 0xf000, 0xfb54,
+	0xfb59, 0xfb55, 0xfb49, 0xf201, 0xf700, 0xf600, 0xf200, 0xf200,
+ 	0xf200, 0xf009, 0xf200, 0xf200, 0xf200, 0xfb51, 0xfb57, 0xfb45,
+	0xfb52, 0xfb4f, 0xfb50, 0xf07b, 0xf200, 0xf07d, 0xf200, 0xf200,
+	0xf200, 0xfb5a, 0xf200, 0xf200, 0xf200, 0xf207, 0xf200, 0xf200,
+	0xfb4b, 0xfb4c, 0xf03a, 0xf022, 0xf200, 0xf603, 0xf200, 0xf200,
+	0xf200, 0xfb41, 0xf200, 0xf200, 0xf200, 0xfb53, 0xfb44, 0xfb46,
+	0xfb47, 0xfb48, 0xfb4a, 0xf03f, 0xf200, 0xf601, 0xf200, 0xf200,
+	0xf200, 0xfb58, 0xf200, 0xf200, 0xf200, 0xfb43, 0xfb56, 0xfb42,
+	0xfb4e, 0xfb4d, 0xf03c, 0xf03e, 0xf200, 0xf020, 0xf200, 0xf200,
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf023, 0xf024, 0xf025, 
+	0xf05e, 0xf026, 0xf02a, 0xf200, 0xf200, 0xf200, 0xf602, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 
+};
+
+u_short altgr_map[NR_KEYS]=
+{
+  KIGNORE   ,K(KCn,12 ),K(KCn,13 ),K(KCn,14  ),K(KCn,15 ),K(KCn,16 ),K(KCn,17  ),K(KCn, 18),
+  K(KCn, 19),K(KCn,20 ),K(KCn,21 ),K(KCn,22  ),K(KCn,23 ),KIGNORE   ,KREGS      ,KINTR     ,
+  KIGNORE   ,KIGNORE   ,K(KTn,'@'),KIGNORE    ,K(KTn,'$'),KIGNORE   ,KIGNORE    ,K(KTn,'{'),
+  K(KTn,'['),K(KTn,']'),K(KTn,'}'),K(KTn,'\\'),KIGNORE   ,KIGNORE   ,KIGNORE    ,K(KTf,21 ),
+  K(KTf,20 ),K(KTf,24 ),KNUMLK    ,KPAD_DV    ,KPAD_ML   ,KPAD_HS   ,KIGNORE    ,K(KTl,'q'),
+  K(KTl,'w'),K(KTl,'e'),K(KTl,'r'),K(KTl,'t' ),K(KTl,'y'),K(KTl,'u'),K(KTl,'i' ),K(KTl,'o'),
+  K(KTl,'p'),KIGNORE   ,K(KTn,'~'),KIGNORE    ,K(KTf,22 ),K(KTf,23 ),K(KTf,25  ),KPADB_7   ,
+  KPADB_8   ,KPADB_9   ,KPAD_MI   ,KCTRL      ,K(KAs,20 ),K(KTl,'s'),K(KAs,23  ),K(KAs,25 ),
+  K(KTl,'g'),K(KTl,'h'),K(KTl,'j'),K(KTl,'k' ),K(KTl,'l'),KIGNORE   ,KIGNORE    ,KENTER    ,
+  KPADB_4   ,KPADB_5   ,KPADB_6   ,KPAD_PL    ,KSHIFT    ,KIGNORE   ,K(KTl,'z' ),K(KTl,'x'),
+  K(KAs,22 ),K(KTl,'v'),K(KTl,21 ),K(KTl,'n' ),K(KTl,'m'),KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KSHIFT    ,K(KTc,Kcu),KPADB_1   ,KPADB_2    ,KPADB_3   ,KCAPSLK   ,KALT       ,KIGNORE   ,
+  KALTGR    ,KCTRL     ,K(KTc,Kcl),K(KTc,Kcd ),K(KTc,Kcr),KPADB_0   ,KPAD_DT    ,KPAD_EN   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+};
+
+u_short ctrl_map[NR_KEYS]=
+{
+	0xf703, 0xf200, 0xf200, 0xf702, 0xf200, 0xf200, 0xf001, 0xf002,
+	0xf009, 0xf000, 0xf031, 0xf200, 0xf200, 0xf07f, 0xf200, 0xf200,
+	0xf200, 0xf01c, 0xf700, 0xf200, 0xf200, 0xf116, 0xf000, 0xf020,
+	0xf019, 0xf015, 0xf009, 0xf201, 0xf700, 0xf600, 0xf200, 0xf200,
+	0xf200, 0xf009, 0xf200, 0xf200, 0xf200, 0xf011, 0xf017, 0xf005,
+	0xf012, 0xf00f, 0xf010, 0xf01b, 0xf200, 0xf01d, 0xf200, 0xf200,
+	0xf200, 0xf01a, 0xf200, 0xf200, 0xf200, 0xf207, 0xf200, 0xf200,
+	0xf00b, 0xf00c, 0xf200, 0xf007, 0xf200, 0xf603, 0xf200, 0xf200,
+	0xf200, 0xf001, 0xf200, 0xf200, 0xf200, 0xf001, 0xf013, 0xf006,
+	0xf007, 0xf008, 0xf00a, 0xf07f, 0xf200, 0xf601, 0xf200, 0xf200,
+	0xf200, 0xf018, 0xf200, 0xf200, 0xf200, 0xf003, 0xf016, 0xf002,
+	0xf00e, 0xf00d, 0xf200, 0xf200, 0xf200, 0xf000, 0xf200, 0xf200,
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf01b, 0xf01c, 0xf01d, 
+	0xf036, 0xf037, 0xf038, 0xf200, 0xf200, 0xf200, 0xf602, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 
+	0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf602, 0xf200, 
+};
+
+u_short shift_ctrl_map[NR_KEYS]=
+{
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KFLOPPY    ,KINTR     ,
+  KIGNORE   ,KIGNORE   ,K(KTn, 0 ),KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,K(KTn,31  ),KIGNORE   ,KIGNORE   ,KIGNORE    ,K(KTf,21 ),
+  K(KTf,20 ),K(KTf,24 ),KNUMLK    ,KPAD_DV    ,KPAD_ML   ,KPAD_HS   ,KIGNORE    ,K(KTn,17 ),
+  K(KTn,23 ),K(KTn, 5 ),K(KTn,18 ),K(KTn,20  ),K(KTn,25 ),K(KTn,21 ),K(KTn, 9  ),K(KTn,15 ),
+  K(KTn,16 ),KIGNORE   ,KIGNORE   ,KIGNORE    ,K(KTf,22 ),K(KTf,23 ),K(KTf,25  ),KPAD_7    ,
+  KPAD_8    ,KPAD_9    ,KPAD_MI   ,KCTRL      ,K(KTn, 1 ),K(KTn,19 ),K(KTn, 4  ),K(KTn, 6 ),
+  K(KTn, 7 ),K(KTn, 8 ),K(KTn,10 ),K(KTn,11  ),K(KTn,12 ),KIGNORE   ,K(KTn, 7  ),KENTER    ,
+  KPAD_4    ,KPAD_5    ,KPAD_6    ,KPAD_PL    ,KSHIFT    ,KIGNORE   ,K(KTn,26  ),K(KTn,24 ),
+  K(KTn, 3 ),K(KTn,22 ),K(KTn, 2 ),K(KTn,14  ),K(KTn,13 ),KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KSHIFT    ,K(KTc,Kcu),KPAD_1    ,KPAD_2     ,KPAD_3    ,KCAPSLK   ,KALT       ,K(KTn, 0 ),
+  KALTGR    ,KCTRL     ,K(KTc,Kcl),K(KTc,Kcd ),K(KTc,Kcr),KPAD_0    ,KPAD_DT    ,KPAD_EN   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+};
+
+u_short alt_map[NR_KEYS]=
+{
+  K(KMt,27 ),K(KCn, 0 ),K(KCn, 1 ),K(KCn, 2  ),K(KCn, 3 ),K(KCn, 4 ),K(KCn, 5  ),K(KCn, 6 ),
+  K(KCn, 7 ),K(KCn, 8 ),K(KCn, 9 ),K(KCn,10  ),K(KCn,11 ),KIGNORE   ,KSCRLLK    ,KINTR     ,
+  K(KMt,'`'),K(KMt,'1'),K(KMt,'2'),K(KMt,'3' ),K(KMt,'4'),K(KMt,'5'),K(KMt,'6' ),K(KMt,'7'),
+  K(KMt,'8'),K(KMt,'9'),K(KMt,'0'),K(KMt,'-' ),K(KMt,'='),K(KMt,'£'),K(KMt,127 ),K(KTf,21 ),
+  K(KTf,20 ),K(KTf,24 ),KNUMLK    ,KPAD_DV    ,KPAD_ML   ,KPAD_HS   ,K(KMt, 9  ),K(KMt,'q'),
+  K(KMt,'w'),K(KMt,'e'),K(KMt,'r'),K(KMt,'t' ),K(KMt,'y'),K(KMt,'u'),K(KMt,'i' ),K(KMt,'o'),
+  K(KMt,'p'),K(KMt,'['),K(KMt,']'),K(KMt,'\\'),K(KTf,22 ),K(KTf,23 ),K(KTf,25  ),KPADA_7   ,
+  KPADA_8   ,KPADA_9   ,KPAD_MI   ,KCTRL      ,K(KMt,'a'),K(KMt,'s'),K(KMt,'d' ),K(KMt,'f'),
+  K(KMt,'g'),K(KMt,'h'),K(KMt,'j'),K(KMt,'k' ),K(KMt,'l'),K(KMt,';'),K(KMt,'\''),K(KMt,13 ),
+  KPADA_4   ,KPADA_5   ,KPADA_6   ,KPAD_PL    ,KSHIFT    ,KIGNORE   ,K(KMt,'z' ),K(KMt,'x'),
+  K(KMt,'c'),K(KMt,'v'),K(KMt,'b'),K(KMt,'n' ),K(KMt,'m'),K(KMt,','),K(KMt,'.' ),KIGNORE   ,
+  KSHIFT    ,K(KTc,Kcu),KPADA_1   ,KPADA_2    ,KPADA_3   ,KCAPSLK   ,KALT       ,K(KMt,' '),
+  KALTGR    ,KCTRL     ,CONS_DEC  ,K(KTc,Kcd ),CONS_INC  ,KPADA_0   ,KPAD_DT    ,KPAD_EN   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+};
+
+u_short ctrl_alt_map[NR_KEYS]=
+{
+  KIGNORE   ,K(KCn, 0 ),K(KCn, 1 ),K(KCn, 2  ),K(KCn, 3 ),K(KCn, 4 ),K(KCn, 5  ),K(KCn, 6 ),
+  K(KCn, 7 ),K(KCn, 8 ),K(KCn, 9 ),K(KCn,10  ),K(KCn,11 ),KIGNORE   ,KIGNORE    ,KINTR     ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,K(KTf,21 ),
+  K(KTf,20 ),K(KTf,24 ),KNUMLK    ,KPAD_DV    ,KPAD_ML   ,KPAD_HS   ,KIGNORE    ,K(KMt,17 ),
+  K(KMt,23 ),K(KMt, 5 ),K(KMt,18 ),K(KMt,20  ),K(KMt,25 ),K(KMt,21 ),K(KMt, 9  ),K(KMt,15 ),
+  K(KMt,16 ),KIGNORE   ,KIGNORE   ,KIGNORE    ,KREBOOT   ,K(KTf,23 ),K(KTf,25  ),KPAD_7    ,
+  KPAD_8    ,KPAD_9    ,KPAD_MI   ,KCTRL      ,K(KMt, 1 ),K(KMt,19 ),K(KMt, 4  ),K(KMt, 6 ),
+  K(KMt, 7 ),K(KMt, 8 ),K(KMt,10 ),K(KMt,11  ),K(KMt,12 ),KIGNORE   ,KIGNORE    ,KENTER    ,
+  KPAD_4    ,KPAD_5    ,KPAD_6    ,KPAD_PL    ,KSHIFT    ,KIGNORE   ,K(KMt,26  ),K(KMt,24 ),
+  K(KMt, 3 ),K(KMt,22 ),K(KMt, 2 ),K(KMt,14  ),K(KMt,13 ),KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KSHIFT    ,K(KTc,Kcu),KPAD_1    ,KPAD_2     ,KPAD_3    ,KCAPSLK   ,KALT       ,KIGNORE   ,
+  KALTGR    ,KCTRL     ,K(KTc,Kcl),K(KTc,Kcd ),K(KTc,Kcr),KPAD_0    ,KREBOOT    ,KPAD_EN   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+  KIGNORE   ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,KIGNORE   ,KIGNORE    ,KIGNORE   ,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+	plain_map, shift_map, altgr_map, 0,
+	ctrl_map, shift_ctrl_map, 0, 0,
+	alt_map, 0, 0, 0,
+	ctrl_alt_map,	0
+};
+
+unsigned int keymap_count = 7;
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+	'\033', '[', '[', 'A', 0,
+	'\033', '[', '[', 'B', 0,
+	'\033', '[', '[', 'C', 0,
+	'\033', '[', '[', 'D', 0,
+	'\033', '[', '[', 'E', 0,
+	'\033', '[', '1', '7', '~', 0,
+	'\033', '[', '1', '8', '~', 0,
+	'\033', '[', '1', '9', '~', 0,
+	'\033', '[', '2', '0', '~', 0,
+	'\033', '[', '2', '1', '~', 0,
+	'\033', '[', '2', '3', '~', 0,
+	'\033', '[', '2', '4', '~', 0,
+	'\033', '[', '2', '5', '~', 0,
+	'\033', '[', '2', '6', '~', 0,
+	'\033', '[', '2', '8', '~', 0,
+	'\033', '[', '2', '9', '~', 0,
+	'\033', '[', '3', '1', '~', 0,
+	'\033', '[', '3', '2', '~', 0,
+	'\033', '[', '3', '3', '~', 0,
+	'\033', '[', '3', '4', '~', 0,
+	'\033', '[', '1', '~', 0,
+	'\033', '[', '2', '~', 0,
+	'\033', '[', '3', '~', 0,
+	'\033', '[', '4', '~', 0,
+	'\033', '[', '5', '~', 0,
+	'\033', '[', '6', '~', 0,
+	'\033', '[', 'M', 0,
+	'\033', '[', 'P', 0,
+};
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0;          /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+	func_buf + 0,
+	func_buf + 5,
+	func_buf + 10,
+	func_buf + 15,
+	func_buf + 20,
+	func_buf + 25,
+	func_buf + 31,
+	func_buf + 37,
+	func_buf + 43,
+	func_buf + 49,
+	func_buf + 55,
+	func_buf + 61,
+	func_buf + 67,
+	func_buf + 73,
+	func_buf + 79,
+	func_buf + 85,
+	func_buf + 91,
+	func_buf + 97,
+	func_buf + 103,
+	func_buf + 109,
+	func_buf + 115,
+	func_buf + 120,
+	func_buf + 125,
+	func_buf + 130,
+	func_buf + 135,
+	func_buf + 140,
+	func_buf + 145,
+	0,
+	0,
+	func_buf + 149,
+	0,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+	{'`', 'A', '\300'},	{'`', 'a', '\340'},
+	{'\'', 'A', '\301'},	{'\'', 'a', '\341'},
+	{'^', 'A', '\302'},	{'^', 'a', '\342'},
+	{'~', 'A', '\303'},	{'~', 'a', '\343'},
+	{'"', 'A', '\304'},	{'"', 'a', '\344'},
+	{'O', 'A', '\305'},	{'o', 'a', '\345'},
+	{'0', 'A', '\305'},	{'0', 'a', '\345'},
+	{'A', 'A', '\305'},	{'a', 'a', '\345'},
+	{'A', 'E', '\306'},	{'a', 'e', '\346'},
+	{',', 'C', '\307'},	{',', 'c', '\347'},
+	{'`', 'E', '\310'},	{'`', 'e', '\350'},
+	{'\'', 'E', '\311'},	{'\'', 'e', '\351'},
+	{'^', 'E', '\312'},	{'^', 'e', '\352'},
+	{'"', 'E', '\313'},	{'"', 'e', '\353'},
+	{'`', 'I', '\314'},	{'`', 'i', '\354'},
+	{'\'', 'I', '\315'},	{'\'', 'i', '\355'},
+	{'^', 'I', '\316'},	{'^', 'i', '\356'},
+	{'"', 'I', '\317'},	{'"', 'i', '\357'},
+	{'-', 'D', '\320'},	{'-', 'd', '\360'},
+	{'~', 'N', '\321'},	{'~', 'n', '\361'},
+	{'`', 'O', '\322'},	{'`', 'o', '\362'},
+	{'\'', 'O', '\323'},	{'\'', 'o', '\363'},
+	{'^', 'O', '\324'},	{'^', 'o', '\364'},
+	{'~', 'O', '\325'},	{'~', 'o', '\365'},
+	{'"', 'O', '\326'},	{'"', 'o', '\366'},
+	{'/', 'O', '\330'},	{'/', 'o', '\370'},
+	{'`', 'U', '\331'},	{'`', 'u', '\371'},
+	{'\'', 'U', '\332'},	{'\'', 'u', '\372'},
+	{'^', 'U', '\333'},	{'^', 'u', '\373'},
+	{'"', 'U', '\334'},	{'"', 'u', '\374'},
+	{'\'', 'Y', '\335'},	{'\'', 'y', '\375'},
+	{'T', 'H', '\336'},	{'t', 'h', '\376'},
+	{'s', 's', '\337'},	{'"', 'y', '\377'},
+	{'s', 'z', '\337'},	{'i', 'j', '\377'},
+};
+
+unsigned int accent_table_size = 68;
diff --git a/drivers/acorn/char/i2c.c b/drivers/acorn/char/i2c.c
new file mode 100644
index 0000000..c22bb9d
--- /dev/null
+++ b/drivers/acorn/char/i2c.c
@@ -0,0 +1,369 @@
+/*
+ *  linux/drivers/acorn/char/i2c.c
+ *
+ *  Copyright (C) 2000 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  ARM IOC/IOMD i2c driver.
+ *
+ *  On Acorn machines, the following i2c devices are on the bus:
+ *	- PCF8583 real time clock & static RAM
+ */
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <linux/miscdevice.h>
+#include <linux/rtc.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/fs.h>
+
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/hardware/ioc.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include "pcf8583.h"
+
+extern int (*set_rtc)(void);
+
+static struct i2c_client *rtc_client;
+static const unsigned char days_in_mon[] = 
+	{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+#define CMOS_CHECKSUM	(63)
+
+/*
+ * Acorn machines store the year in the static RAM at
+ * location 128.
+ */
+#define CMOS_YEAR	(64 + 128)
+
+static inline int rtc_command(int cmd, void *data)
+{
+	int ret = -EIO;
+
+	if (rtc_client)
+		ret = rtc_client->driver->command(rtc_client, cmd, data);
+
+	return ret;
+}
+
+/*
+ * Update the century + year bytes in the CMOS RAM, ensuring
+ * that the check byte is correctly adjusted for the change.
+ */
+static int rtc_update_year(unsigned int new_year)
+{
+	unsigned char yr[2], chk;
+	struct mem cmos_year  = { CMOS_YEAR, sizeof(yr), yr };
+	struct mem cmos_check = { CMOS_CHECKSUM, 1, &chk };
+	int ret;
+
+	ret = rtc_command(MEM_READ, &cmos_check);
+	if (ret)
+		goto out;
+	ret = rtc_command(MEM_READ, &cmos_year);
+	if (ret)
+		goto out;
+
+	chk -= yr[1] + yr[0];
+
+	yr[1] = new_year / 100;
+	yr[0] = new_year % 100;
+
+	chk += yr[1] + yr[0];
+
+	ret = rtc_command(MEM_WRITE, &cmos_year);
+	if (ret == 0)
+		ret = rtc_command(MEM_WRITE, &cmos_check);
+ out:
+	return ret;
+}
+
+/*
+ * Read the current RTC time and date, and update xtime.
+ */
+static void get_rtc_time(struct rtc_tm *rtctm, unsigned int *year)
+{
+	unsigned char ctrl, yr[2];
+	struct mem rtcmem = { CMOS_YEAR, sizeof(yr), yr };
+	int real_year, year_offset;
+
+	/*
+	 * Ensure that the RTC is running.
+	 */
+	rtc_command(RTC_GETCTRL, &ctrl);
+	if (ctrl & 0xc0) {
+		unsigned char new_ctrl = ctrl & ~0xc0;
+
+		printk(KERN_WARNING "RTC: resetting control %02x -> %02x\n",
+		       ctrl, new_ctrl);
+
+		rtc_command(RTC_SETCTRL, &new_ctrl);
+	}
+
+	if (rtc_command(RTC_GETDATETIME, rtctm) ||
+	    rtc_command(MEM_READ, &rtcmem))
+		return;
+
+	real_year = yr[0];
+
+	/*
+	 * The RTC year holds the LSB two bits of the current
+	 * year, which should reflect the LSB two bits of the
+	 * CMOS copy of the year.  Any difference indicates
+	 * that we have to correct the CMOS version.
+	 */
+	year_offset = rtctm->year_off - (real_year & 3);
+	if (year_offset < 0)
+		/*
+		 * RTC year wrapped.  Adjust it appropriately.
+		 */
+		year_offset += 4;
+
+	*year = real_year + year_offset + yr[1] * 100;
+}
+
+static int set_rtc_time(struct rtc_tm *rtctm, unsigned int year)
+{
+	unsigned char leap;
+	int ret;
+
+	leap = (!(year % 4) && (year % 100)) || !(year % 400);
+
+	if (rtctm->mon > 12 || rtctm->mon == 0 || rtctm->mday == 0)
+		return -EINVAL;
+
+	if (rtctm->mday > (days_in_mon[rtctm->mon] + (rtctm->mon == 2 && leap)))
+		return -EINVAL;
+
+	if (rtctm->hours >= 24 || rtctm->mins >= 60 || rtctm->secs >= 60)
+		return -EINVAL;
+
+	/*
+	 * The RTC's own 2-bit year must reflect the least
+	 * significant two bits of the CMOS year.
+	 */
+	rtctm->year_off = (year % 100) & 3;
+
+	ret = rtc_command(RTC_SETDATETIME, rtctm);
+	if (ret == 0)
+		ret = rtc_update_year(year);
+
+	return ret;
+}
+
+/*
+ * Set the RTC time only.  Note that
+ * we do not touch the date.
+ */
+static int k_set_rtc_time(void)
+{
+	struct rtc_tm new_rtctm, old_rtctm;
+	unsigned long nowtime = xtime.tv_sec;
+
+	if (rtc_command(RTC_GETDATETIME, &old_rtctm))
+		return 0;
+
+	new_rtctm.cs    = xtime.tv_nsec / 10000000;
+	new_rtctm.secs  = nowtime % 60;	nowtime /= 60;
+	new_rtctm.mins  = nowtime % 60;	nowtime /= 60;
+	new_rtctm.hours = nowtime % 24;
+
+	/*
+	 * avoid writing when we're going to change the day
+	 * of the month.  We will retry in the next minute.
+	 * This basically means that if the RTC must not drift
+	 * by more than 1 minute in 11 minutes.
+	 *
+	 * [ rtc: 1/1/2000 23:58:00, real 2/1/2000 00:01:00,
+	 *   rtc gets set to 1/1/2000 00:01:00 ]
+	 */
+	if ((old_rtctm.hours == 23 && old_rtctm.mins == 59) ||
+	    (new_rtctm.hours == 23 && new_rtctm.mins == 59))
+		return 1;
+
+	return rtc_command(RTC_SETTIME, &new_rtctm);
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	unsigned int year;
+	struct rtc_time rtctm;
+	struct rtc_tm rtc_raw;
+
+	switch (cmd) {
+	case RTC_ALM_READ:
+	case RTC_ALM_SET:
+		break;
+
+	case RTC_RD_TIME:
+		memset(&rtctm, 0, sizeof(struct rtc_time));
+		get_rtc_time(&rtc_raw, &year);
+		rtctm.tm_sec  = rtc_raw.secs;
+		rtctm.tm_min  = rtc_raw.mins;
+		rtctm.tm_hour = rtc_raw.hours;
+		rtctm.tm_mday = rtc_raw.mday;
+		rtctm.tm_mon  = rtc_raw.mon - 1; /* month starts at 0 */
+		rtctm.tm_year = year - 1900; /* starts at 1900 */
+		return copy_to_user((void *)arg, &rtctm, sizeof(rtctm))
+				 ? -EFAULT : 0;
+
+	case RTC_SET_TIME:
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&rtctm, (void *)arg, sizeof(rtctm)))
+			return -EFAULT;
+		rtc_raw.secs     = rtctm.tm_sec;
+		rtc_raw.mins     = rtctm.tm_min;
+		rtc_raw.hours    = rtctm.tm_hour;
+		rtc_raw.mday     = rtctm.tm_mday;
+		rtc_raw.mon      = rtctm.tm_mon + 1;
+		year             = rtctm.tm_year + 1900;
+		return set_rtc_time(&rtc_raw, year);
+		break;
+
+	case RTC_EPOCH_READ:
+		return put_user(1900, (unsigned long *)arg);
+
+	}
+	return -EINVAL;
+}
+
+static struct file_operations rtc_fops = {
+	.ioctl	= rtc_ioctl,
+};
+
+static struct miscdevice rtc_dev = {
+	.minor	= RTC_MINOR,
+	.name	= "rtc",
+	.fops	= &rtc_fops,
+};
+
+/* IOC / IOMD i2c driver */
+
+#define FORCE_ONES	0xdc
+#define SCL		0x02
+#define SDA		0x01
+
+/*
+ * We must preserve all non-i2c output bits in IOC_CONTROL.
+ * Note also that we need to preserve the value of SCL and
+ * SDA outputs as well (which may be different from the
+ * values read back from IOC_CONTROL).
+ */
+static u_int force_ones;
+
+static void ioc_setscl(void *data, int state)
+{
+	u_int ioc_control = ioc_readb(IOC_CONTROL) & ~(SCL | SDA);
+	u_int ones = force_ones;
+
+	if (state)
+		ones |= SCL;
+	else
+		ones &= ~SCL;
+
+	force_ones = ones;
+
+ 	ioc_writeb(ioc_control | ones, IOC_CONTROL);
+}
+
+static void ioc_setsda(void *data, int state)
+{
+	u_int ioc_control = ioc_readb(IOC_CONTROL) & ~(SCL | SDA);
+	u_int ones = force_ones;
+
+	if (state)
+		ones |= SDA;
+	else
+		ones &= ~SDA;
+
+	force_ones = ones;
+
+ 	ioc_writeb(ioc_control | ones, IOC_CONTROL);
+}
+
+static int ioc_getscl(void *data)
+{
+	return (ioc_readb(IOC_CONTROL) & SCL) != 0;
+}
+
+static int ioc_getsda(void *data)
+{
+	return (ioc_readb(IOC_CONTROL) & SDA) != 0;
+}
+
+static struct i2c_algo_bit_data ioc_data = {
+	.setsda		= ioc_setsda,
+	.setscl		= ioc_setscl,
+	.getsda		= ioc_getsda,
+	.getscl		= ioc_getscl,
+	.udelay		= 80,
+	.mdelay		= 80,
+	.timeout	= 100
+};
+
+static int ioc_client_reg(struct i2c_client *client)
+{
+	if (client->driver->id == I2C_DRIVERID_PCF8583 &&
+	    client->addr == 0x50) {
+		struct rtc_tm rtctm;
+		unsigned int year;
+		struct timespec tv;
+
+		rtc_client = client;
+		get_rtc_time(&rtctm, &year);
+
+		tv.tv_nsec = rtctm.cs * 10000000;
+		tv.tv_sec  = mktime(year, rtctm.mon, rtctm.mday,
+				    rtctm.hours, rtctm.mins, rtctm.secs);
+		do_settimeofday(&tv);
+		set_rtc = k_set_rtc_time;
+	}
+
+	return 0;
+}
+
+static int ioc_client_unreg(struct i2c_client *client)
+{
+	if (client == rtc_client) {
+		set_rtc = NULL;
+		rtc_client = NULL;
+	}
+
+	return 0;
+}
+
+static struct i2c_adapter ioc_ops = {
+	.id			= I2C_HW_B_IOC,
+	.algo_data		= &ioc_data,
+	.client_register	= ioc_client_reg,
+	.client_unregister	= ioc_client_unreg,
+};
+
+static int __init i2c_ioc_init(void)
+{
+	int ret;
+
+	force_ones = FORCE_ONES | SCL | SDA;
+
+	ret = i2c_bit_add_bus(&ioc_ops);
+
+	if (ret >= 0){
+		ret = misc_register(&rtc_dev);
+		if(ret < 0)
+			i2c_bit_del_bus(&ioc_ops);
+	}
+
+	return ret;
+}
+
+__initcall(i2c_ioc_init);
diff --git a/drivers/acorn/char/pcf8583.c b/drivers/acorn/char/pcf8583.c
new file mode 100644
index 0000000..ad7ae7a
--- /dev/null
+++ b/drivers/acorn/char/pcf8583.c
@@ -0,0 +1,239 @@
+/*
+ *  linux/drivers/acorn/char/pcf8583.c
+ *
+ *  Copyright (C) 2000 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Driver for PCF8583 RTC & RAM chip
+ */
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/mc146818rtc.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/bcd.h>
+
+#include "pcf8583.h"
+
+static struct i2c_driver pcf8583_driver;
+
+static unsigned short ignore[] = { I2C_CLIENT_END };
+static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
+
+static struct i2c_client_address_data addr_data = {
+	.normal_i2c		= normal_addr,
+	.normal_i2c_range	= ignore,
+	.probe			= ignore,
+	.probe_range		= ignore,
+	.ignore			= ignore,
+	.ignore_range		= ignore,
+	.force			= ignore,
+};
+
+#define DAT(x) ((unsigned int)(x->dev.driver_data))
+
+static int
+pcf8583_attach(struct i2c_adapter *adap, int addr, int kind)
+{
+	struct i2c_client *c;
+	unsigned char buf[1], ad[1] = { 0 };
+	struct i2c_msg msgs[2] = {
+		{ addr, 0,        1, ad  },
+		{ addr, I2C_M_RD, 1, buf }
+	};
+
+	c = kmalloc(sizeof(*c), GFP_KERNEL);
+	if (!c)
+		return -ENOMEM;
+
+	memset(c, 0, sizeof(*c));
+	c->addr		= addr;
+	c->adapter	= adap;
+	c->driver	= &pcf8583_driver;
+
+	if (i2c_transfer(c->adapter, msgs, 2) == 2)
+		DAT(c) = buf[0];
+
+	return i2c_attach_client(c);
+}
+
+static int
+pcf8583_probe(struct i2c_adapter *adap)
+{
+	return i2c_probe(adap, &addr_data, pcf8583_attach);
+}
+
+static int
+pcf8583_detach(struct i2c_client *client)
+{
+	i2c_detach_client(client);
+	kfree(client);
+	return 0;
+}
+
+static int
+pcf8583_get_datetime(struct i2c_client *client, struct rtc_tm *dt)
+{
+	unsigned char buf[8], addr[1] = { 1 };
+	struct i2c_msg msgs[2] = {
+		{ client->addr, 0,        1, addr },
+		{ client->addr, I2C_M_RD, 6, buf  }
+	};
+	int ret = -EIO;
+
+	memset(buf, 0, sizeof(buf));
+
+	ret = i2c_transfer(client->adapter, msgs, 2);
+	if (ret == 2) {
+		dt->year_off = buf[4] >> 6;
+		dt->wday     = buf[5] >> 5;
+
+		buf[4] &= 0x3f;
+		buf[5] &= 0x1f;
+
+		dt->cs       = BCD_TO_BIN(buf[0]);
+		dt->secs     = BCD_TO_BIN(buf[1]);
+		dt->mins     = BCD_TO_BIN(buf[2]);
+		dt->hours    = BCD_TO_BIN(buf[3]);
+		dt->mday     = BCD_TO_BIN(buf[4]);
+		dt->mon      = BCD_TO_BIN(buf[5]);
+
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int
+pcf8583_set_datetime(struct i2c_client *client, struct rtc_tm *dt, int datetoo)
+{
+	unsigned char buf[8];
+	int ret, len = 6;
+
+	buf[0] = 0;
+	buf[1] = DAT(client) | 0x80;
+	buf[2] = BIN_TO_BCD(dt->cs);
+	buf[3] = BIN_TO_BCD(dt->secs);
+	buf[4] = BIN_TO_BCD(dt->mins);
+	buf[5] = BIN_TO_BCD(dt->hours);
+
+	if (datetoo) {
+		len = 8;
+		buf[6] = BIN_TO_BCD(dt->mday) | (dt->year_off << 6);
+		buf[7] = BIN_TO_BCD(dt->mon)  | (dt->wday << 5);
+	}
+
+	ret = i2c_master_send(client, (char *)buf, len);
+	if (ret == len)
+		ret = 0;
+
+	buf[1] = DAT(client);
+	i2c_master_send(client, (char *)buf, 2);
+
+	return ret;
+}
+
+static int
+pcf8583_get_ctrl(struct i2c_client *client, unsigned char *ctrl)
+{
+	*ctrl = DAT(client);
+	return 0;
+}
+
+static int
+pcf8583_set_ctrl(struct i2c_client *client, unsigned char *ctrl)
+{
+	unsigned char buf[2];
+
+	buf[0] = 0;
+	buf[1] = *ctrl;
+	DAT(client) = *ctrl;
+
+	return i2c_master_send(client, (char *)buf, 2);
+}
+
+static int
+pcf8583_read_mem(struct i2c_client *client, struct mem *mem)
+{
+	unsigned char addr[1];
+	struct i2c_msg msgs[2] = {
+		{ client->addr, 0,        1, addr },
+		{ client->addr, I2C_M_RD, 0, mem->data }
+	};
+
+	if (mem->loc < 8)
+		return -EINVAL;
+
+	addr[0] = mem->loc;
+	msgs[1].len = mem->nr;
+
+	return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
+}
+
+static int
+pcf8583_write_mem(struct i2c_client *client, struct mem *mem)
+{
+	unsigned char addr[1];
+	struct i2c_msg msgs[2] = {
+		{ client->addr, 0, 1, addr },
+		{ client->addr, 0, 0, mem->data }
+	};
+
+	if (mem->loc < 8)
+		return -EINVAL;
+
+	addr[0] = mem->loc;
+	msgs[1].len = mem->nr;
+
+	return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
+}
+
+static int
+pcf8583_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+	switch (cmd) {
+	case RTC_GETDATETIME:
+		return pcf8583_get_datetime(client, arg);
+		
+	case RTC_SETTIME:
+		return pcf8583_set_datetime(client, arg, 0);
+
+	case RTC_SETDATETIME:
+		return pcf8583_set_datetime(client, arg, 1);
+
+	case RTC_GETCTRL:
+		return pcf8583_get_ctrl(client, arg);
+
+	case RTC_SETCTRL:
+		return pcf8583_set_ctrl(client, arg);
+
+	case MEM_READ:
+		return pcf8583_read_mem(client, arg);
+
+	case MEM_WRITE:
+		return pcf8583_write_mem(client, arg);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct i2c_driver pcf8583_driver = {
+	.name		= "PCF8583",
+	.id		= I2C_DRIVERID_PCF8583,
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= pcf8583_probe,
+	.detach_client	= pcf8583_detach,
+	.command	= pcf8583_command
+};
+
+static __init int pcf8583_init(void)
+{
+	return i2c_add_driver(&pcf8583_driver);
+}
+
+__initcall(pcf8583_init);
diff --git a/drivers/acorn/char/pcf8583.h b/drivers/acorn/char/pcf8583.h
new file mode 100644
index 0000000..847f7fd
--- /dev/null
+++ b/drivers/acorn/char/pcf8583.h
@@ -0,0 +1,41 @@
+/*
+ *  linux/drivers/acorn/char/pcf8583.h
+ *
+ *  Copyright (C) 2000 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+struct rtc_tm {
+	unsigned char	cs;
+	unsigned char	secs;
+	unsigned char	mins;
+	unsigned char	hours;
+	unsigned char	mday;
+	unsigned char	mon;
+	unsigned char	year_off;
+	unsigned char	wday;
+};
+
+struct mem {
+	unsigned int	loc;
+	unsigned int	nr;
+	unsigned char	*data;
+};
+
+#define RTC_GETDATETIME	0
+#define RTC_SETTIME	1
+#define RTC_SETDATETIME	2
+#define RTC_GETCTRL	3
+#define RTC_SETCTRL	4
+#define MEM_READ	5
+#define MEM_WRITE	6
+
+#define CTRL_STOP	0x80
+#define CTRL_HOLD	0x40
+#define CTRL_32KHZ	0x00
+#define CTRL_MASK	0x08
+#define CTRL_ALARMEN	0x04
+#define CTRL_ALARM	0x02
+#define CTRL_TIMER	0x01