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/ide/arm/icside.c b/drivers/ide/arm/icside.c
new file mode 100644
index 0000000..308897e5
--- /dev/null
+++ b/drivers/ide/arm/icside.c
@@ -0,0 +1,870 @@
+/*
+ * linux/drivers/ide/arm/icside.c
+ *
+ * Copyright (c) 1996-2004 Russell King.
+ *
+ * Please note that this platform does not support 32-bit IDE IO.
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/errno.h>
+#include <linux/hdreg.h>
+#include <linux/ide.h>
+#include <linux/dma-mapping.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/scatterlist.h>
+
+#include <asm/dma.h>
+#include <asm/ecard.h>
+#include <asm/io.h>
+
+#define ICS_IDENT_OFFSET		0x2280
+
+#define ICS_ARCIN_V5_INTRSTAT		0x0000
+#define ICS_ARCIN_V5_INTROFFSET		0x0004
+#define ICS_ARCIN_V5_IDEOFFSET		0x2800
+#define ICS_ARCIN_V5_IDEALTOFFSET	0x2b80
+#define ICS_ARCIN_V5_IDESTEPPING	6
+
+#define ICS_ARCIN_V6_IDEOFFSET_1	0x2000
+#define ICS_ARCIN_V6_INTROFFSET_1	0x2200
+#define ICS_ARCIN_V6_INTRSTAT_1		0x2290
+#define ICS_ARCIN_V6_IDEALTOFFSET_1	0x2380
+#define ICS_ARCIN_V6_IDEOFFSET_2	0x3000
+#define ICS_ARCIN_V6_INTROFFSET_2	0x3200
+#define ICS_ARCIN_V6_INTRSTAT_2		0x3290
+#define ICS_ARCIN_V6_IDEALTOFFSET_2	0x3380
+#define ICS_ARCIN_V6_IDESTEPPING	6
+
+struct cardinfo {
+	unsigned int dataoffset;
+	unsigned int ctrloffset;
+	unsigned int stepping;
+};
+
+static struct cardinfo icside_cardinfo_v5 = {
+	.dataoffset	= ICS_ARCIN_V5_IDEOFFSET,
+	.ctrloffset	= ICS_ARCIN_V5_IDEALTOFFSET,
+	.stepping	= ICS_ARCIN_V5_IDESTEPPING,
+};
+
+static struct cardinfo icside_cardinfo_v6_1 = {
+	.dataoffset	= ICS_ARCIN_V6_IDEOFFSET_1,
+	.ctrloffset	= ICS_ARCIN_V6_IDEALTOFFSET_1,
+	.stepping	= ICS_ARCIN_V6_IDESTEPPING,
+};
+
+static struct cardinfo icside_cardinfo_v6_2 = {
+	.dataoffset	= ICS_ARCIN_V6_IDEOFFSET_2,
+	.ctrloffset	= ICS_ARCIN_V6_IDEALTOFFSET_2,
+	.stepping	= ICS_ARCIN_V6_IDESTEPPING,
+};
+
+struct icside_state {
+	unsigned int channel;
+	unsigned int enabled;
+	void __iomem *irq_port;
+	void __iomem *ioc_base;
+	unsigned int type;
+	/* parent device... until the IDE core gets one of its own */
+	struct device *dev;
+	ide_hwif_t *hwif[2];
+};
+
+#define ICS_TYPE_A3IN	0
+#define ICS_TYPE_A3USER	1
+#define ICS_TYPE_V6	3
+#define ICS_TYPE_V5	15
+#define ICS_TYPE_NOTYPE	((unsigned int)-1)
+
+/* ---------------- Version 5 PCB Support Functions --------------------- */
+/* Prototype: icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr)
+ * Purpose  : enable interrupts from card
+ */
+static void icside_irqenable_arcin_v5 (struct expansion_card *ec, int irqnr)
+{
+	struct icside_state *state = ec->irq_data;
+
+	writeb(0, state->irq_port + ICS_ARCIN_V5_INTROFFSET);
+}
+
+/* Prototype: icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr)
+ * Purpose  : disable interrupts from card
+ */
+static void icside_irqdisable_arcin_v5 (struct expansion_card *ec, int irqnr)
+{
+	struct icside_state *state = ec->irq_data;
+
+	readb(state->irq_port + ICS_ARCIN_V5_INTROFFSET);
+}
+
+static const expansioncard_ops_t icside_ops_arcin_v5 = {
+	.irqenable	= icside_irqenable_arcin_v5,
+	.irqdisable	= icside_irqdisable_arcin_v5,
+};
+
+
+/* ---------------- Version 6 PCB Support Functions --------------------- */
+/* Prototype: icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr)
+ * Purpose  : enable interrupts from card
+ */
+static void icside_irqenable_arcin_v6 (struct expansion_card *ec, int irqnr)
+{
+	struct icside_state *state = ec->irq_data;
+	void __iomem *base = state->irq_port;
+
+	state->enabled = 1;
+
+	switch (state->channel) {
+	case 0:
+		writeb(0, base + ICS_ARCIN_V6_INTROFFSET_1);
+		readb(base + ICS_ARCIN_V6_INTROFFSET_2);
+		break;
+	case 1:
+		writeb(0, base + ICS_ARCIN_V6_INTROFFSET_2);
+		readb(base + ICS_ARCIN_V6_INTROFFSET_1);
+		break;
+	}
+}
+
+/* Prototype: icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
+ * Purpose  : disable interrupts from card
+ */
+static void icside_irqdisable_arcin_v6 (struct expansion_card *ec, int irqnr)
+{
+	struct icside_state *state = ec->irq_data;
+
+	state->enabled = 0;
+
+	readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
+	readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
+}
+
+/* Prototype: icside_irqprobe(struct expansion_card *ec)
+ * Purpose  : detect an active interrupt from card
+ */
+static int icside_irqpending_arcin_v6(struct expansion_card *ec)
+{
+	struct icside_state *state = ec->irq_data;
+
+	return readb(state->irq_port + ICS_ARCIN_V6_INTRSTAT_1) & 1 ||
+	       readb(state->irq_port + ICS_ARCIN_V6_INTRSTAT_2) & 1;
+}
+
+static const expansioncard_ops_t icside_ops_arcin_v6 = {
+	.irqenable	= icside_irqenable_arcin_v6,
+	.irqdisable	= icside_irqdisable_arcin_v6,
+	.irqpending	= icside_irqpending_arcin_v6,
+};
+
+/*
+ * Handle routing of interrupts.  This is called before
+ * we write the command to the drive.
+ */
+static void icside_maskproc(ide_drive_t *drive, int mask)
+{
+	ide_hwif_t *hwif = HWIF(drive);
+	struct icside_state *state = hwif->hwif_data;
+	unsigned long flags;
+
+	local_irq_save(flags);
+
+	state->channel = hwif->channel;
+
+	if (state->enabled && !mask) {
+		switch (hwif->channel) {
+		case 0:
+			writeb(0, state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
+			readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
+			break;
+		case 1:
+			writeb(0, state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
+			readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
+			break;
+		}
+	} else {
+		readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_2);
+		readb(state->irq_port + ICS_ARCIN_V6_INTROFFSET_1);
+	}
+
+	local_irq_restore(flags);
+}
+
+#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
+
+#ifndef CONFIG_IDEDMA_ICS_AUTO
+#warning CONFIG_IDEDMA_ICS_AUTO=n support is obsolete, and will be removed soon.
+#endif
+
+/*
+ * SG-DMA support.
+ *
+ * Similar to the BM-DMA, but we use the RiscPCs IOMD DMA controllers.
+ * There is only one DMA controller per card, which means that only
+ * one drive can be accessed at one time.  NOTE! We do not enforce that
+ * here, but we rely on the main IDE driver spotting that both
+ * interfaces use the same IRQ, which should guarantee this.
+ */
+
+static void icside_build_sglist(ide_drive_t *drive, struct request *rq)
+{
+	ide_hwif_t *hwif = drive->hwif;
+	struct icside_state *state = hwif->hwif_data;
+	struct scatterlist *sg = hwif->sg_table;
+
+	ide_map_sg(drive, rq);
+
+	if (rq_data_dir(rq) == READ)
+		hwif->sg_dma_direction = DMA_FROM_DEVICE;
+	else
+		hwif->sg_dma_direction = DMA_TO_DEVICE;
+
+	hwif->sg_nents = dma_map_sg(state->dev, sg, hwif->sg_nents,
+				    hwif->sg_dma_direction);
+}
+
+/*
+ * Configure the IOMD to give the appropriate timings for the transfer
+ * mode being requested.  We take the advice of the ATA standards, and
+ * calculate the cycle time based on the transfer mode, and the EIDE
+ * MW DMA specs that the drive provides in the IDENTIFY command.
+ *
+ * We have the following IOMD DMA modes to choose from:
+ *
+ *	Type	Active		Recovery	Cycle
+ *	A	250 (250)	312 (550)	562 (800)
+ *	B	187		250		437
+ *	C	125 (125)	125 (375)	250 (500)
+ *	D	62		125		187
+ *
+ * (figures in brackets are actual measured timings)
+ *
+ * However, we also need to take care of the read/write active and
+ * recovery timings:
+ *
+ *			Read	Write
+ *  	Mode	Active	-- Recovery --	Cycle	IOMD type
+ *	MW0	215	50	215	480	A
+ *	MW1	80	50	50	150	C
+ *	MW2	70	25	25	120	C
+ */
+static int icside_set_speed(ide_drive_t *drive, u8 xfer_mode)
+{
+	int on = 0, cycle_time = 0, use_dma_info = 0;
+
+	/*
+	 * Limit the transfer speed to MW_DMA_2.
+	 */
+	if (xfer_mode > XFER_MW_DMA_2)
+		xfer_mode = XFER_MW_DMA_2;
+
+	switch (xfer_mode) {
+	case XFER_MW_DMA_2:
+		cycle_time = 250;
+		use_dma_info = 1;
+		break;
+
+	case XFER_MW_DMA_1:
+		cycle_time = 250;
+		use_dma_info = 1;
+		break;
+
+	case XFER_MW_DMA_0:
+		cycle_time = 480;
+		break;
+
+	case XFER_SW_DMA_2:
+	case XFER_SW_DMA_1:
+	case XFER_SW_DMA_0:
+		cycle_time = 480;
+		break;
+	}
+
+	/*
+	 * If we're going to be doing MW_DMA_1 or MW_DMA_2, we should
+	 * take care to note the values in the ID...
+	 */
+	if (use_dma_info && drive->id->eide_dma_time > cycle_time)
+		cycle_time = drive->id->eide_dma_time;
+
+	drive->drive_data = cycle_time;
+
+	if (cycle_time && ide_config_drive_speed(drive, xfer_mode) == 0)
+		on = 1;
+	else
+		drive->drive_data = 480;
+
+	printk("%s: %s selected (peak %dMB/s)\n", drive->name,
+		ide_xfer_verbose(xfer_mode), 2000 / drive->drive_data);
+
+	drive->current_speed = xfer_mode;
+
+	return on;
+}
+
+static int icside_dma_host_off(ide_drive_t *drive)
+{
+	return 0;
+}
+
+static int icside_dma_off_quietly(ide_drive_t *drive)
+{
+	drive->using_dma = 0;
+	return icside_dma_host_off(drive);
+}
+
+static int icside_dma_host_on(ide_drive_t *drive)
+{
+	return 0;
+}
+
+static int icside_dma_on(ide_drive_t *drive)
+{
+	drive->using_dma = 1;
+	return icside_dma_host_on(drive);
+}
+
+static int icside_dma_check(ide_drive_t *drive)
+{
+	struct hd_driveid *id = drive->id;
+	ide_hwif_t *hwif = HWIF(drive);
+	int xfer_mode = XFER_PIO_2;
+	int on;
+
+	if (!(id->capability & 1) || !hwif->autodma)
+		goto out;
+
+	/*
+	 * Consult the list of known "bad" drives
+	 */
+	if (__ide_dma_bad_drive(drive))
+		goto out;
+
+	/*
+	 * Enable DMA on any drive that has multiword DMA
+	 */
+	if (id->field_valid & 2) {
+		xfer_mode = ide_dma_speed(drive, 0);
+		goto out;
+	}
+
+	/*
+	 * Consult the list of known "good" drives
+	 */
+	if (__ide_dma_good_drive(drive)) {
+		if (id->eide_dma_time > 150)
+			goto out;
+		xfer_mode = XFER_MW_DMA_1;
+	}
+
+out:
+	on = icside_set_speed(drive, xfer_mode);
+
+	if (on)
+		return icside_dma_on(drive);
+	else
+		return icside_dma_off_quietly(drive);
+}
+
+static int icside_dma_end(ide_drive_t *drive)
+{
+	ide_hwif_t *hwif = HWIF(drive);
+	struct icside_state *state = hwif->hwif_data;
+
+	drive->waiting_for_dma = 0;
+
+	disable_dma(hwif->hw.dma);
+
+	/* Teardown mappings after DMA has completed. */
+	dma_unmap_sg(state->dev, hwif->sg_table, hwif->sg_nents,
+		     hwif->sg_dma_direction);
+
+	return get_dma_residue(hwif->hw.dma) != 0;
+}
+
+static void icside_dma_start(ide_drive_t *drive)
+{
+	ide_hwif_t *hwif = HWIF(drive);
+
+	/* We can not enable DMA on both channels simultaneously. */
+	BUG_ON(dma_channel_active(hwif->hw.dma));
+	enable_dma(hwif->hw.dma);
+}
+
+static int icside_dma_setup(ide_drive_t *drive)
+{
+	ide_hwif_t *hwif = HWIF(drive);
+	struct request *rq = hwif->hwgroup->rq;
+	unsigned int dma_mode;
+
+	if (rq_data_dir(rq))
+		dma_mode = DMA_MODE_WRITE;
+	else
+		dma_mode = DMA_MODE_READ;
+
+	/*
+	 * We can not enable DMA on both channels.
+	 */
+	BUG_ON(dma_channel_active(hwif->hw.dma));
+
+	icside_build_sglist(drive, rq);
+
+	/*
+	 * Ensure that we have the right interrupt routed.
+	 */
+	icside_maskproc(drive, 0);
+
+	/*
+	 * Route the DMA signals to the correct interface.
+	 */
+	writeb(hwif->select_data, hwif->config_data);
+
+	/*
+	 * Select the correct timing for this drive.
+	 */
+	set_dma_speed(hwif->hw.dma, drive->drive_data);
+
+	/*
+	 * Tell the DMA engine about the SG table and
+	 * data direction.
+	 */
+	set_dma_sg(hwif->hw.dma, hwif->sg_table, hwif->sg_nents);
+	set_dma_mode(hwif->hw.dma, dma_mode);
+
+	drive->waiting_for_dma = 1;
+
+	return 0;
+}
+
+static void icside_dma_exec_cmd(ide_drive_t *drive, u8 cmd)
+{
+	/* issue cmd to drive */
+	ide_execute_command(drive, cmd, ide_dma_intr, 2 * WAIT_CMD, NULL);
+}
+
+static int icside_dma_test_irq(ide_drive_t *drive)
+{
+	ide_hwif_t *hwif = HWIF(drive);
+	struct icside_state *state = hwif->hwif_data;
+
+	return readb(state->irq_port +
+		     (hwif->channel ?
+			ICS_ARCIN_V6_INTRSTAT_2 :
+			ICS_ARCIN_V6_INTRSTAT_1)) & 1;
+}
+
+static int icside_dma_timeout(ide_drive_t *drive)
+{
+	printk(KERN_ERR "%s: DMA timeout occurred: ", drive->name);
+
+	if (icside_dma_test_irq(drive))
+		return 0;
+
+	ide_dump_status(drive, "DMA timeout",
+		HWIF(drive)->INB(IDE_STATUS_REG));
+
+	return icside_dma_end(drive);
+}
+
+static int icside_dma_lostirq(ide_drive_t *drive)
+{
+	printk(KERN_ERR "%s: IRQ lost\n", drive->name);
+	return 1;
+}
+
+static void icside_dma_init(ide_hwif_t *hwif)
+{
+	int autodma = 0;
+
+#ifdef CONFIG_IDEDMA_ICS_AUTO
+	autodma = 1;
+#endif
+
+	printk("    %s: SG-DMA", hwif->name);
+
+	hwif->atapi_dma		= 1;
+	hwif->mwdma_mask	= 7; /* MW0..2 */
+	hwif->swdma_mask	= 7; /* SW0..2 */
+
+	hwif->dmatable_cpu	= NULL;
+	hwif->dmatable_dma	= 0;
+	hwif->speedproc		= icside_set_speed;
+	hwif->autodma		= autodma;
+
+	hwif->ide_dma_check	= icside_dma_check;
+	hwif->ide_dma_host_off	= icside_dma_host_off;
+	hwif->ide_dma_off_quietly = icside_dma_off_quietly;
+	hwif->ide_dma_host_on	= icside_dma_host_on;
+	hwif->ide_dma_on	= icside_dma_on;
+	hwif->dma_setup		= icside_dma_setup;
+	hwif->dma_exec_cmd	= icside_dma_exec_cmd;
+	hwif->dma_start		= icside_dma_start;
+	hwif->ide_dma_end	= icside_dma_end;
+	hwif->ide_dma_test_irq	= icside_dma_test_irq;
+	hwif->ide_dma_timeout	= icside_dma_timeout;
+	hwif->ide_dma_lostirq	= icside_dma_lostirq;
+
+	hwif->drives[0].autodma = hwif->autodma;
+	hwif->drives[1].autodma = hwif->autodma;
+
+	printk(" capable%s\n", hwif->autodma ? ", auto-enable" : "");
+}
+#else
+#define icside_dma_init(hwif)	(0)
+#endif
+
+static ide_hwif_t *icside_find_hwif(unsigned long dataport)
+{
+	ide_hwif_t *hwif;
+	int index;
+
+	for (index = 0; index < MAX_HWIFS; ++index) {
+		hwif = &ide_hwifs[index];
+		if (hwif->io_ports[IDE_DATA_OFFSET] == dataport)
+			goto found;
+	}
+
+	for (index = 0; index < MAX_HWIFS; ++index) {
+		hwif = &ide_hwifs[index];
+		if (!hwif->io_ports[IDE_DATA_OFFSET])
+			goto found;
+	}
+
+	hwif = NULL;
+found:
+	return hwif;
+}
+
+static ide_hwif_t *
+icside_setup(void __iomem *base, struct cardinfo *info, struct expansion_card *ec)
+{
+	unsigned long port = (unsigned long)base + info->dataoffset;
+	ide_hwif_t *hwif;
+
+	hwif = icside_find_hwif(port);
+	if (hwif) {
+		int i;
+
+		memset(&hwif->hw, 0, sizeof(hw_regs_t));
+
+		/*
+		 * Ensure we're using MMIO
+		 */
+		default_hwif_mmiops(hwif);
+		hwif->mmio = 2;
+
+		for (i = IDE_DATA_OFFSET; i <= IDE_STATUS_OFFSET; i++) {
+			hwif->hw.io_ports[i] = port;
+			hwif->io_ports[i] = port;
+			port += 1 << info->stepping;
+		}
+		hwif->hw.io_ports[IDE_CONTROL_OFFSET] = (unsigned long)base + info->ctrloffset;
+		hwif->io_ports[IDE_CONTROL_OFFSET] = (unsigned long)base + info->ctrloffset;
+		hwif->hw.irq  = ec->irq;
+		hwif->irq     = ec->irq;
+		hwif->noprobe = 0;
+		hwif->chipset = ide_acorn;
+		hwif->gendev.parent = &ec->dev;
+	}
+
+	return hwif;
+}
+
+static int __init
+icside_register_v5(struct icside_state *state, struct expansion_card *ec)
+{
+	ide_hwif_t *hwif;
+	void __iomem *base;
+
+	base = ioremap(ecard_resource_start(ec, ECARD_RES_MEMC),
+		       ecard_resource_len(ec, ECARD_RES_MEMC));
+	if (!base)
+		return -ENOMEM;
+
+	state->irq_port = base;
+
+	ec->irqaddr  = base + ICS_ARCIN_V5_INTRSTAT;
+	ec->irqmask  = 1;
+	ec->irq_data = state;
+	ec->ops      = &icside_ops_arcin_v5;
+
+	/*
+	 * Be on the safe side - disable interrupts
+	 */
+	icside_irqdisable_arcin_v5(ec, 0);
+
+	hwif = icside_setup(base, &icside_cardinfo_v5, ec);
+	if (!hwif) {
+		iounmap(base);
+		return -ENODEV;
+	}
+
+	state->hwif[0] = hwif;
+
+	probe_hwif_init(hwif);
+	create_proc_ide_interfaces();
+
+	return 0;
+}
+
+static int __init
+icside_register_v6(struct icside_state *state, struct expansion_card *ec)
+{
+	ide_hwif_t *hwif, *mate;
+	void __iomem *ioc_base, *easi_base;
+	unsigned int sel = 0;
+	int ret;
+
+	ioc_base = ioremap(ecard_resource_start(ec, ECARD_RES_IOCFAST),
+			   ecard_resource_len(ec, ECARD_RES_IOCFAST));
+	if (!ioc_base) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	easi_base = ioc_base;
+
+	if (ecard_resource_flags(ec, ECARD_RES_EASI)) {
+		easi_base = ioremap(ecard_resource_start(ec, ECARD_RES_EASI),
+				    ecard_resource_len(ec, ECARD_RES_EASI));
+		if (!easi_base) {
+			ret = -ENOMEM;
+			goto unmap_slot;
+		}
+
+		/*
+		 * Enable access to the EASI region.
+		 */
+		sel = 1 << 5;
+	}
+
+	writeb(sel, ioc_base);
+
+	ec->irq_data      = state;
+	ec->ops           = &icside_ops_arcin_v6;
+
+	state->irq_port   = easi_base;
+	state->ioc_base   = ioc_base;
+
+	/*
+	 * Be on the safe side - disable interrupts
+	 */
+	icside_irqdisable_arcin_v6(ec, 0);
+
+	/*
+	 * Find and register the interfaces.
+	 */
+	hwif = icside_setup(easi_base, &icside_cardinfo_v6_1, ec);
+	mate = icside_setup(easi_base, &icside_cardinfo_v6_2, ec);
+
+	if (!hwif || !mate) {
+		ret = -ENODEV;
+		goto unmap_port;
+	}
+
+	state->hwif[0]    = hwif;
+	state->hwif[1]    = mate;
+
+	hwif->maskproc    = icside_maskproc;
+	hwif->channel     = 0;
+	hwif->hwif_data   = state;
+	hwif->mate        = mate;
+	hwif->serialized  = 1;
+	hwif->config_data = (unsigned long)ioc_base;
+	hwif->select_data = sel;
+	hwif->hw.dma      = ec->dma;
+
+	mate->maskproc    = icside_maskproc;
+	mate->channel     = 1;
+	mate->hwif_data   = state;
+	mate->mate        = hwif;
+	mate->serialized  = 1;
+	mate->config_data = (unsigned long)ioc_base;
+	mate->select_data = sel | 1;
+	mate->hw.dma      = ec->dma;
+
+	if (ec->dma != NO_DMA && !request_dma(ec->dma, hwif->name)) {
+		icside_dma_init(hwif);
+		icside_dma_init(mate);
+	}
+
+	probe_hwif_init(hwif);
+	probe_hwif_init(mate);
+	create_proc_ide_interfaces();
+
+	return 0;
+
+ unmap_port:
+	if (easi_base != ioc_base)
+		iounmap(easi_base);
+ unmap_slot:
+	iounmap(ioc_base);
+ out:
+	return ret;
+}
+
+static int __devinit
+icside_probe(struct expansion_card *ec, const struct ecard_id *id)
+{
+	struct icside_state *state;
+	void __iomem *idmem;
+	int ret;
+
+	ret = ecard_request_resources(ec);
+	if (ret)
+		goto out;
+
+	state = kmalloc(sizeof(struct icside_state), GFP_KERNEL);
+	if (!state) {
+		ret = -ENOMEM;
+		goto release;
+	}
+
+	memset(state, 0, sizeof(state));
+	state->type	= ICS_TYPE_NOTYPE;
+	state->dev	= &ec->dev;
+
+	idmem = ioremap(ecard_resource_start(ec, ECARD_RES_IOCFAST),
+			ecard_resource_len(ec, ECARD_RES_IOCFAST));
+	if (idmem) {
+		unsigned int type;
+
+		type = readb(idmem + ICS_IDENT_OFFSET) & 1;
+		type |= (readb(idmem + ICS_IDENT_OFFSET + 4) & 1) << 1;
+		type |= (readb(idmem + ICS_IDENT_OFFSET + 8) & 1) << 2;
+		type |= (readb(idmem + ICS_IDENT_OFFSET + 12) & 1) << 3;
+		iounmap(idmem);
+
+		state->type = type;
+	}
+
+	switch (state->type) {
+	case ICS_TYPE_A3IN:
+		dev_warn(&ec->dev, "A3IN unsupported\n");
+		ret = -ENODEV;
+		break;
+
+	case ICS_TYPE_A3USER:
+		dev_warn(&ec->dev, "A3USER unsupported\n");
+		ret = -ENODEV;
+		break;
+
+	case ICS_TYPE_V5:
+		ret = icside_register_v5(state, ec);
+		break;
+
+	case ICS_TYPE_V6:
+		ret = icside_register_v6(state, ec);
+		break;
+
+	default:
+		dev_warn(&ec->dev, "unknown interface type\n");
+		ret = -ENODEV;
+		break;
+	}
+
+	if (ret == 0) {
+		ecard_set_drvdata(ec, state);
+		goto out;
+	}
+
+	kfree(state);
+ release:
+	ecard_release_resources(ec);
+ out:
+	return ret;
+}
+
+static void __devexit icside_remove(struct expansion_card *ec)
+{
+	struct icside_state *state = ecard_get_drvdata(ec);
+
+	switch (state->type) {
+	case ICS_TYPE_V5:
+		/* FIXME: tell IDE to stop using the interface */
+
+		/* Disable interrupts */
+		icside_irqdisable_arcin_v5(ec, 0);
+		break;
+
+	case ICS_TYPE_V6:
+		/* FIXME: tell IDE to stop using the interface */
+		if (ec->dma != NO_DMA)
+			free_dma(ec->dma);
+
+		/* Disable interrupts */
+		icside_irqdisable_arcin_v6(ec, 0);
+
+		/* Reset the ROM pointer/EASI selection */
+		writeb(0, state->ioc_base);
+		break;
+	}
+
+	ecard_set_drvdata(ec, NULL);
+	ec->ops = NULL;
+	ec->irq_data = NULL;
+
+	if (state->ioc_base)
+		iounmap(state->ioc_base);
+	if (state->ioc_base != state->irq_port)
+		iounmap(state->irq_port);
+
+	kfree(state);
+	ecard_release_resources(ec);
+}
+
+static void icside_shutdown(struct expansion_card *ec)
+{
+	struct icside_state *state = ecard_get_drvdata(ec);
+	unsigned long flags;
+
+	/*
+	 * Disable interrupts from this card.  We need to do
+	 * this before disabling EASI since we may be accessing
+	 * this register via that region.
+	 */
+	local_irq_save(flags);
+	ec->ops->irqdisable(ec, 0);
+	local_irq_restore(flags);
+
+	/*
+	 * Reset the ROM pointer so that we can read the ROM
+	 * after a soft reboot.  This also disables access to
+	 * the IDE taskfile via the EASI region.
+	 */
+	if (state->ioc_base)
+		writeb(0, state->ioc_base);
+}
+
+static const struct ecard_id icside_ids[] = {
+	{ MANU_ICS,  PROD_ICS_IDE  },
+	{ MANU_ICS2, PROD_ICS2_IDE },
+	{ 0xffff, 0xffff }
+};
+
+static struct ecard_driver icside_driver = {
+	.probe		= icside_probe,
+	.remove		= __devexit_p(icside_remove),
+	.shutdown	= icside_shutdown,
+	.id_table	= icside_ids,
+	.drv = {
+		.name	= "icside",
+	},
+};
+
+static int __init icside_init(void)
+{
+	return ecard_register_driver(&icside_driver);
+}
+
+MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ICS IDE driver");
+
+module_init(icside_init);