diff mbox

[v2] ata: New PATA driver for Altera CompactFlash.

Message ID 1254967110-3495-1-git-send-email-thomas@wytron.com.tw
State Not Applicable
Delegated to: David Miller
Headers show

Commit Message

Thomas Chou Oct. 8, 2009, 1:58 a.m. UTC
This patch adds a new PATA driver to support the Altera SOPC Builder
CompactFlash component as a PATA device with support for insertion
and removal of CF cards.  It uses the platform driver model.

For each probed platform device, the driver spawns a kthread to
handle insertion and removal of CF cards by creating and removing the
underlying ATA host interface.  The Altera CompactFlash component
includes a register bit to indicate whether a CF card is detected
and an interrupt triggered when this bit changes.  The driver has a
handler for this interrupt which flags that something has changed and
wakes up the kthread to deal with it.

The platform device uses 2 IORESOURCE_MEM resources and 2
IORESOURCE_IRQ resources.  The first memory resource is for the
CompactFlash component's IDE registers and the second is for the
component's CF status and control registers.  The first IRQ resource
is for the IDE and the second is for the CF detection interrupt.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Thomas Chou <thomas@wytron.com.tw>
---
 drivers/ata/Kconfig          |   11 +
 drivers/ata/Makefile         |    1 +
 drivers/ata/pata_altera_cf.c |  582 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 594 insertions(+), 0 deletions(-)
 create mode 100644 drivers/ata/pata_altera_cf.c

Comments

Alan Cox Oct. 8, 2009, 9:55 a.m. UTC | #1
On Thu,  8 Oct 2009 09:58:30 +0800
Thomas Chou <thomas@wytron.com.tw> wrote:

> This patch adds a new PATA driver to support the Altera SOPC Builder
> CompactFlash component as a PATA device with support for insertion
> and removal of CF cards.  It uses the platform driver model.

Can you explain why you remove the interface rather than the device on
the interface when you detect a card removal ?

--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ian Abbott Oct. 8, 2009, 11:22 a.m. UTC | #2
On 08/10/09 10:55, Alan Cox wrote:
> On Thu,  8 Oct 2009 09:58:30 +0800
> Thomas Chou <thomas@wytron.com.tw> wrote:
> 
>> This patch adds a new PATA driver to support the Altera SOPC Builder
>> CompactFlash component as a PATA device with support for insertion
>> and removal of CF cards.  It uses the platform driver model.
> 
> Can you explain why you remove the interface rather than the device on
> the interface when you detect a card removal ?

As the originator of the driver, the only answer I have is "I don't know
how."  I was hoping for inspiration from some of the other PATA CF
drivers, but alas, I couldn't find it.  I admit that removing the whole
ATA host is a bit on the extreme side.
Alan Cox Oct. 8, 2009, 1:12 p.m. UTC | #3
On Thu, 8 Oct 2009 12:22:22 +0100
Ian Abbott <abbotti@mev.co.uk> wrote:

> On 08/10/09 10:55, Alan Cox wrote:
> > On Thu,  8 Oct 2009 09:58:30 +0800
> > Thomas Chou <thomas@wytron.com.tw> wrote:
> > 
> >> This patch adds a new PATA driver to support the Altera SOPC Builder
> >> CompactFlash component as a PATA device with support for insertion
> >> and removal of CF cards.  It uses the platform driver model.
> > 
> > Can you explain why you remove the interface rather than the device on
> > the interface when you detect a card removal ?
> 
> As the originator of the driver, the only answer I have is "I don't know
> how."  I was hoping for inspiration from some of the other PATA CF
> drivers, but alas, I couldn't find it.  I admit that removing the whole
> ATA host is a bit on the extreme side.

I think the bit you want is something like:

	struct ata_eh_info *ehi = &ap->link->eh_info;
	if (mumblemumble & whatever) {
		ata_ehi_clear_desc(ehi);
		ata_ehi_hotplugged(ehi);
		ata_ehi_push_desc(ehi, "CF connection changed");
		ata_port_freeze(ap);
	}

The core code also assumes your freeze method will mask any further
connection change reports until the port is unfrozen (so you don't get
multiple parallel "in/out/in/out" events at once)

Alan
--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik Nov. 19, 2009, 11:27 p.m. UTC | #4
On 10/07/2009 09:58 PM, Thomas Chou wrote:
> This patch adds a new PATA driver to support the Altera SOPC Builder
> CompactFlash component as a PATA device with support for insertion
> and removal of CF cards.  It uses the platform driver model.
>
> For each probed platform device, the driver spawns a kthread to
> handle insertion and removal of CF cards by creating and removing the
> underlying ATA host interface.  The Altera CompactFlash component
> includes a register bit to indicate whether a CF card is detected
> and an interrupt triggered when this bit changes.  The driver has a
> handler for this interrupt which flags that something has changed and
> wakes up the kthread to deal with it.
>
> The platform device uses 2 IORESOURCE_MEM resources and 2
> IORESOURCE_IRQ resources.  The first memory resource is for the
> CompactFlash component's IDE registers and the second is for the
> component's CF status and control registers.  The first IRQ resource
> is for the IDE and the second is for the CF detection interrupt.
>
> Signed-off-by: Ian Abbott<abbotti@mev.co.uk>
> Signed-off-by: Thomas Chou<thomas@wytron.com.tw>
> ---
>   drivers/ata/Kconfig          |   11 +
>   drivers/ata/Makefile         |    1 +
>   drivers/ata/pata_altera_cf.c |  582 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 594 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/ata/pata_altera_cf.c

Ping...  still waiting on v3, updated with Alan's feedback.


--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig
index f2df6e2..8d0bb6d 100644
--- a/drivers/ata/Kconfig
+++ b/drivers/ata/Kconfig
@@ -781,5 +781,16 @@  config PATA_BF54X
 
 	  If unsure, say N.
 
+config PATA_ALTERA_CF
+	tristate "Altera SOPC Builder CompactFlash support"
+	depends on EMBEDDED
+	help
+	  This option enables support for the CompactFlash core in an
+	  Altera SOPC Builder system.  It supports insertion and removal
+	  of CompactFlash cards.  It can be used instead of the generic
+	  PATA platform device support if this functionality is desired.
+
+	  If unsure, say N.
+
 endif # ATA_SFF
 endif # ATA
diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
index 01e126f..9be42c2 100644
--- a/drivers/ata/Makefile
+++ b/drivers/ata/Makefile
@@ -77,6 +77,7 @@  obj-$(CONFIG_PATA_PLATFORM)	+= pata_platform.o
 obj-$(CONFIG_PATA_AT91)	+= pata_at91.o
 obj-$(CONFIG_PATA_OF_PLATFORM)	+= pata_of_platform.o
 obj-$(CONFIG_PATA_ICSIDE)	+= pata_icside.o
+obj-$(CONFIG_PATA_ALTERA_CF)	+= pata_altera_cf.o
 # Should be last but two libata driver
 obj-$(CONFIG_PATA_ACPI)		+= pata_acpi.o
 # Should be last but one libata driver
diff --git a/drivers/ata/pata_altera_cf.c b/drivers/ata/pata_altera_cf.c
new file mode 100644
index 0000000..3f6d71f
--- /dev/null
+++ b/drivers/ata/pata_altera_cf.c
@@ -0,0 +1,582 @@ 
+/*
+ *  pata_altera_cf.c - PATA driver for Altera SOPC Builder CompactFlash core.
+ *
+ *  Copyright (C) 2009 MEV Limited <http://www.mev.co.uk>
+ *  Author: Ian Abbott <abbotti@mev.co.uk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  This file was based on/inspired by:
+ *    drivers/ata/pata_platform.c
+ *    drivers/ata/pata_pcmcia.c
+ *    drivers/ata/pata_ixp4xx_cf.c
+ *    altcf.c from the Microtronix Nios II Linux distribution.
+ *
+ *  This is a platform device driver and expects two memory resources and
+ *  two IRQ resources per platform device in the following:
+ *
+ *    IDE memory resource -- 64 byte memory area consisting of IDE registers
+ *                           on 4-byte boundaries.
+ *    IDE IRQ resource    -- IRQ resource consisting of IRQ number and desired
+ *                           IRQ flags for the IDE interface.  The IRQ number
+ *                           can be set to 0 to avoid setting up the IRQ.
+ *    CF CONTROL memory resource -- 16 byte resource consisting of the CF
+ *                           CONTROL registers on 4-byte boundaries.
+ *    CF CONTROL IRQ resource -- IRQ resource consisting of IRQ number and
+ *                           desired IRQ flags for the CF CONTROL interface.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/blkdev.h>
+#include <scsi/scsi_host.h>
+#include <linux/ata.h>
+#include <linux/libata.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+
+#define DRV_NAME "pata_altera_cf"
+#define DRV_VERSION "0.01"
+
+
+#define ALTCF_SHIFT		2
+
+/* IDE registers */
+#define ALTCF_IDE_LEN		(16 << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_DATA	(ATA_REG_DATA << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_ERR	(ATA_REG_ERR << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_NSECT	(ATA_REG_NSECT << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_LBAL	(ATA_REG_LBAL << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_LBAM	(ATA_REG_LBAM << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_LBAH	(ATA_REG_LBAH << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_DEVICE	(ATA_REG_DEVICE << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_STATUS	(ATA_REG_STATUS << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_FEATURE	(ATA_REG_FEATURE << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_CMD	(ATA_REG_CMD << ALTCF_SHIFT)
+#define ALTCF_IDE_REG_CTL	(14 << ALTCF_SHIFT)
+
+/* CF CONTROL registers */
+#define ALTCF_CFC_LEN		(4 << ALTCF_SHIFT)
+#define ALTCF_CFC_REG_CFCTL	(0 << ALTCF_SHIFT)
+#define ALTCF_CFC_REG_IDECTL	(1 << ALTCF_SHIFT)
+
+/* CFCTL register bitmasks */
+#define ALTCF_CFCTL_DET		0x01	/* CF detected */
+#define ALTCF_CFCTL_PWR		0x02	/* power */
+#define ALTCF_CFCTL_RST		0x04	/* reset */
+#define ALTCF_CFCTL_IDET	0x08	/* "CF detect change" intr enable */
+
+/* IDECTL register bitmasks */
+#define ALTCF_IDECTL_IIDE	0x01	/* IDE interrupt enable */
+
+/* Macros to read/write CF CONTROL registers. */
+#define READ_CFCTL(altcf)	readl((altcf)->cfc_base + ALTCF_CFC_REG_CFCTL)
+#define WRITE_CFCTL(altcf, d)	\
+	writel((d), (altcf)->cfc_base + ALTCF_CFC_REG_CFCTL)
+#define READ_IDECTL(altcf)	readl((altcf)->cfc_base + ALTCF_CFC_REG_IDECTL)
+#define WRITE_IDECTL(altcf, d)	\
+	writel((d), (altcf)->cfc_base + ALTCF_CFC_REG_IDECTL)
+
+/*
+ * This is the driver's private data for the device.  The struct device's
+ * private data pointer is used by the libata core so we can't use it ourselves.
+ * Instead, we use the devres functions to access it.  This is a lot slower
+ * than using the device's private data pointer but we shouldn't have to do it
+ * very often.
+ */
+struct altcf_private {
+	struct device *dev;		/* point back to device */
+	resource_size_t ide_raw_base;	/* unmapped IDE registers */
+	unsigned char __iomem *ide_base;/* mapped IDE registers */
+	unsigned char __iomem *cfc_base;/* mapped CF CONTROL registers */
+	struct task_struct *cf_thread;	/* kthread to handle CF events */
+	int cfc_irq;			/* IRQ for CF CONTROL */
+	int ide_irq;			/* IRQ for IDE */
+	unsigned long ide_irqflags;	/* IRQ flags for IDE */
+	unsigned long event;		/* event/state bits */
+	spinlock_t cfctl_lock;		/* spinlock to change CFCTL */
+	unsigned char cfctl;		/* CFCTL register */
+	void *ata_group_id;		/* devres group ID of ATA resources */
+};
+
+/* Event/state bits. */
+enum altcf_event {
+	EV_CHANGE,
+	EV_HOLD,
+	EV_DEAD
+};
+
+/* Update the CFCTL register. */
+static void update_cfctl(struct altcf_private *altcf,
+		unsigned char mask, unsigned char set)
+{
+	unsigned long flags;
+
+	set &= mask;
+	spin_lock_irqsave(&altcf->cfctl_lock, flags);
+	altcf->cfctl = (altcf->cfctl & ~mask) | set;
+	WRITE_CFCTL(altcf, altcf->cfctl);
+	spin_unlock_irqrestore(&altcf->cfctl_lock, flags);
+}
+
+/* IRQ handler to handle CF detection state changes. */
+static irqreturn_t altcf_cf_interrupt(int irq, void *context)
+{
+	struct altcf_private *altcf = context;
+
+	READ_CFCTL(altcf);	/* Clear interrupt */
+	/* Record the fact that something changed. */
+	set_bit(EV_CHANGE, &altcf->event);
+	smp_mb();
+	/* Wake up the kthread to deal with it. */
+	wake_up_process(altcf->cf_thread);
+	return IRQ_HANDLED;
+}
+
+/* Provide our own set_mode(). */
+static int altcf_set_mode(struct ata_link *link, struct ata_device **error)
+{
+	struct ata_device *dev;
+
+	ata_for_each_dev(dev, link, ENABLED) {
+		ata_dev_printk(dev, KERN_INFO, "configured for PIO0\n");
+		dev->pio_mode = XFER_PIO_0;
+		dev->xfer_mode = XFER_PIO_0;
+		dev->xfer_shift = ATA_SHIFT_PIO;
+		dev->flags |= ATA_DFLAG_PIO;
+	}
+	return 0;
+}
+
+static struct scsi_host_template altcf_sht = {
+	ATA_PIO_SHT(DRV_NAME),
+};
+
+static struct ata_port_operations altcf_port_ops = {
+	.inherits		= &ata_sff_port_ops,
+	.sff_data_xfer		= ata_sff_data_xfer_noirq,
+	.cable_detect		= ata_cable_40wire,
+	.set_mode		= altcf_set_mode,
+	.port_start		= ATA_OP_NULL,
+};
+
+/* Checks if CF present and sets up ATA host. */
+static void altcf_detect_cf(struct altcf_private *altcf)
+{
+	struct ata_host *host;
+	struct ata_port *ap;
+	void *group_id;
+
+	if ((READ_CFCTL(altcf) & ALTCF_CFCTL_DET) == 0)
+		return;
+
+	dev_dbg(altcf->dev, "CF detected\n");
+	/* Hold in reset powered down for 0.5 seconds. */
+	update_cfctl(altcf, ALTCF_CFCTL_RST | ALTCF_CFCTL_PWR, ALTCF_CFCTL_RST);
+	msleep(500);
+	if (test_bit(EV_CHANGE, &altcf->event)
+			|| test_bit(EV_DEAD, &altcf->event)) {
+		goto fail_reset_sleep;
+	}
+	/* Remove reset, power up and wait 0.5 seconds. */
+	update_cfctl(altcf, ALTCF_CFCTL_RST | ALTCF_CFCTL_PWR, ALTCF_CFCTL_PWR);
+	msleep(500);
+	if (test_bit(EV_CHANGE, &altcf->event)
+			|| test_bit(EV_DEAD, &altcf->event)) {
+		goto fail_power_sleep;
+	}
+
+	/* Create devres group to manage ATA host resources. */
+	group_id = devres_open_group(altcf->dev, NULL, GFP_KERNEL);
+	if (group_id == NULL) {
+		dev_warn(altcf->dev, "failed to allocate ATA group\n");
+		goto fail_open_group;
+	}
+
+	/* Allocate ATA host with single port. */
+	host = ata_host_alloc(altcf->dev, 1);
+	if (host == NULL) {
+		dev_warn(altcf->dev, "failed to allocate ATA host\n");
+		goto fail_ata_host_alloc;
+	}
+
+	ap = host->ports[0];
+	ap->ops = &altcf_port_ops;
+	ap->pio_mask = 0x1f;	/* PIO4 */
+	ap->flags = ATA_FLAG_NO_LEGACY | ATA_FLAG_MMIO;
+
+	/* Use polling mode if there's no IRQ. */
+	if (!altcf->ide_irq) {
+		ap->flags |= ATA_FLAG_PIO_POLLING;
+		ata_port_desc(ap, "no IRQ, using PIO polling");
+	}
+
+	/* Set up register addresses. */
+	/* command section */
+	ap->ioaddr.cmd_addr = altcf->ide_base;
+	ap->ioaddr.data_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_DATA;
+	ap->ioaddr.error_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_ERR;
+	ap->ioaddr.feature_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_FEATURE;
+	ap->ioaddr.nsect_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_NSECT;
+	ap->ioaddr.lbal_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_LBAL;
+	ap->ioaddr.lbam_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_LBAM;
+	ap->ioaddr.lbah_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_LBAH;
+	ap->ioaddr.device_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_DEVICE;
+	ap->ioaddr.status_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_STATUS;
+	ap->ioaddr.command_addr = ap->ioaddr.cmd_addr + ALTCF_IDE_REG_CMD;
+	/* control section */
+	ap->ioaddr.ctl_addr = altcf->ide_base + ALTCF_IDE_REG_CTL;
+	ap->ioaddr.altstatus_addr = ap->ioaddr.ctl_addr;
+	ata_port_desc(ap, "mmio cmd 0x%llx ctl 0x%llx",
+			(unsigned long long)altcf->ide_raw_base,
+			(unsigned long long)altcf->ide_raw_base
+			+ ALTCF_IDE_REG_CTL);
+
+	/* Enable the IDE IRQ if using it. */
+	if (altcf->ide_irq)
+		WRITE_IDECTL(altcf, ALTCF_IDECTL_IIDE);
+
+	/* Activate the ATA host. */
+	if (ata_host_activate(host, altcf->ide_irq,
+				altcf->ide_irq ? ata_sff_interrupt : NULL,
+				altcf->ide_irqflags, &altcf_sht)) {
+		dev_warn(altcf->dev, "failed to activate ATA host\n");
+		goto fail_ata_host_activate;
+	}
+
+	/* Success! */
+	altcf->ata_group_id = group_id;
+	devres_close_group(altcf->dev, group_id);
+	return;
+
+fail_ata_host_activate:
+
+	/* Disable IDE IRQ. */
+	WRITE_IDECTL(altcf, 0);
+fail_ata_host_alloc:
+
+	devres_release_group(altcf->dev, group_id);
+fail_open_group:
+fail_power_sleep:
+
+	/* Hold in reset powered down. */
+	update_cfctl(altcf, ALTCF_CFCTL_RST | ALTCF_CFCTL_PWR, ALTCF_CFCTL_RST);
+fail_reset_sleep:
+
+	dev_dbg(altcf->dev, "CF detection cancelled\n");
+}
+
+/* Get rid of old ATA host, if any. */
+static void altcf_remove_ata(struct altcf_private *altcf)
+{
+	void *group_id;
+	struct ata_host *host;
+
+	/* Disable IDE IRQ. */
+	WRITE_IDECTL(altcf, 0);
+	/* Look for ATA resources. */
+	group_id = altcf->ata_group_id;
+	altcf->ata_group_id = NULL;
+	if (group_id) {
+		dev_dbg(altcf->dev, "removing old CF\n");
+		/* Look for ATA host and detach it. */
+		host = dev_get_drvdata(altcf->dev);
+		if (host)
+			ata_host_detach(host);
+		/* Release ATA resources. */
+		devres_release_group(altcf->dev, group_id);
+	}
+	/* Hold CF in reset powered down. */
+	update_cfctl(altcf, ALTCF_CFCTL_RST | ALTCF_CFCTL_PWR, ALTCF_CFCTL_RST);
+}
+
+/*
+ * Kthread function to handle CF detection state changes.
+ * It is responsible for adding and removing the ATA host.
+ */
+static int altcf_thread(void *context)
+{
+	struct altcf_private *altcf = context;
+
+	for (;;) {
+		dev_dbg(altcf->dev, "altcf_thread loop\n");
+		set_current_state(TASK_INTERRUPTIBLE);
+		while (!kthread_should_stop()
+				&& (test_bit(EV_HOLD, &altcf->event)
+					|| (!test_and_clear_bit(EV_CHANGE,
+							&altcf->event)
+						&& !test_bit(EV_DEAD,
+							&altcf->event)))) {
+			dev_dbg(altcf->dev, "altcf_thread sleeping\n");
+			schedule();
+			dev_dbg(altcf->dev, "altcf_thread woken\n");
+			set_current_state(TASK_INTERRUPTIBLE);
+		}
+		__set_current_state(TASK_RUNNING);
+		if (kthread_should_stop()) {
+			dev_dbg(altcf->dev, "altcf_thread stopping\n");
+			break;
+		}
+		/* State changed, so remove ATA host if previously set up. */
+		altcf_remove_ata(altcf);
+		if (!test_bit(EV_DEAD, &altcf->event)) {
+			/* Check if CF is present and set it up. */
+			altcf_detect_cf(altcf);
+		}
+	}
+	/* Finally, remove ATA host if set up. */
+	altcf_remove_ata(altcf);
+	dev_dbg(altcf->dev, "altcf_thread exit\n");
+	return 0;
+}
+
+/* This is the devres release function for our private data. */
+static void altcf_private_release(struct device *dev, void *res)
+{
+	/* no op */
+}
+
+/* Add a device. */
+static int __devinit altcf_probe(struct device *dev,
+		struct resource *ide_mem_res, struct resource *ide_irq_res,
+		struct resource *cfc_mem_res, struct resource *cfc_irq_res)
+{
+	struct altcf_private *altcf;
+	int rc = -ENXIO;
+
+	/* Check resources */
+	if (resource_type(ide_mem_res) != IORESOURCE_MEM) {
+		dev_err(dev, "IDE region wrong type\n");
+		return rc;
+	}
+	if (resource_size(ide_mem_res) < ALTCF_IDE_LEN) {
+		dev_err(dev, "IDE region too small");
+		return rc;
+	}
+	if (resource_type(ide_irq_res) != IORESOURCE_IRQ) {
+		dev_err(dev, "IDE IRQ wrong type\n");
+		return rc;
+	}
+	if (resource_type(cfc_mem_res) != IORESOURCE_MEM) {
+		dev_err(dev, "CF CONTROL region wrong type\n");
+		return rc;
+	}
+	if (resource_size(cfc_mem_res) < ALTCF_CFC_LEN) {
+		dev_err(dev, "CF CONTROL region too small");
+		return rc;
+	}
+
+	/* Allocate private data and add it to the device. */
+	rc = -ENOMEM;
+	altcf = devres_alloc(altcf_private_release,
+			sizeof(struct altcf_private), GFP_KERNEL);
+	if (!altcf) {
+		dev_err(dev, "failed to alloc private data\n");
+		return rc;
+	}
+	altcf->dev = dev;	/* point back to device */
+	devres_add(dev, altcf);
+	/* Some more initialization. */
+	altcf->ide_irq = (int)ide_irq_res->start;
+	altcf->ide_irqflags = ide_irq_res->flags & IORESOURCE_BITS;
+	altcf->cfc_irq = (int)cfc_irq_res->start;
+	spin_lock_init(&altcf->cfctl_lock);
+
+	/* Map registers. */
+	altcf->ide_raw_base = ide_mem_res->start;
+	altcf->ide_base = devm_ioremap_nocache(dev, ide_mem_res->start,
+			ALTCF_IDE_LEN);
+	if (!altcf->ide_base) {
+		dev_err(dev, "failed to remap IDE registers\n");
+		return rc;
+	}
+	altcf->cfc_base = devm_ioremap_nocache(dev, cfc_mem_res->start,
+			ALTCF_CFC_LEN);
+	if (!altcf->cfc_base) {
+		dev_err(dev, "failed to remap CF CONTROL registers\n");
+		return rc;
+	}
+
+	/* Initialize CF CONTROL registers. */
+	altcf->cfctl = ALTCF_CFCTL_RST;
+	WRITE_CFCTL(altcf, altcf->cfctl);
+	WRITE_IDECTL(altcf, 0);
+	READ_CFCTL(altcf);	/* Clear "CF detect change" interrupt. */
+
+	/* Set event to hold off processing of CF state changes. */
+	set_bit(EV_HOLD, &altcf->event);
+	smp_mb();
+
+	/* Create kthread to handle CF state changes. */
+	altcf->cf_thread = kthread_create(altcf_thread, altcf,
+			DRV_NAME "/%s", dev_name(dev));
+	if (IS_ERR(altcf->cf_thread)) {
+		rc = PTR_ERR(altcf->cf_thread);
+		altcf->cf_thread = NULL;
+		dev_err(dev, "failed to create thread (%d)\n", rc);
+		return rc;
+	}
+
+	/* Request IRQ handler to handle "CF detect change" interrupt. */
+	rc = request_irq(altcf->cfc_irq, altcf_cf_interrupt,
+			(cfc_irq_res->flags & IORESOURCE_BITS),
+			DRV_NAME, altcf);
+	if (rc) {
+		dev_err(dev, "failed to request CF CONTROL IRQ %d flags 0x%lx"
+				" (%d)\n",
+				altcf->cfc_irq,
+				(cfc_irq_res->flags & IORESOURCE_BITS), rc);
+		goto fail_request_irq_cf;
+	}
+
+	/* Enable the "CF detect change" interrupt. */
+	update_cfctl(altcf, ALTCF_CFCTL_IDET, ALTCF_CFCTL_IDET);
+
+	/* Generate initial CF state change event. */
+	set_bit(EV_CHANGE, &altcf->event);
+	smp_mb__before_clear_bit();
+	clear_bit(EV_HOLD, &altcf->event);
+	smp_mb__after_clear_bit();
+	wake_up_process(altcf->cf_thread);
+
+	return 0;
+
+#ifdef unused
+	/* Disable the "CF detect change" interrupt and free the IRQ. */
+	update_cfctl(altcf, ALTCF_CFCTL_IDET, 0);
+	free_irq(altcf->cfc_irq, altcf);
+#endif
+fail_request_irq_cf:
+
+	/* Stop the thread. */
+	kthread_stop(altcf->cf_thread);
+	/* Reinitialize CF CONTROL registers. */
+	altcf->cfctl = ALTCF_CFCTL_RST;
+	WRITE_CFCTL(altcf, altcf->cfctl);
+	WRITE_IDECTL(altcf, 0);
+	/* Everything else is removed by devres. */
+	return rc;
+}
+
+/* Remove a device. */
+static int __devexit altcf_remove(struct device *dev)
+{
+	struct altcf_private *altcf;
+
+	/* Get pointer to our private data resource. */
+	altcf = devres_find(dev, altcf_private_release, NULL, NULL);
+	if (altcf == NULL)
+		return -ENXIO;
+	/* Hold off CF detect thread processing and mark device as dead. */
+	set_bit(EV_HOLD, &altcf->event);
+	set_bit(EV_DEAD, &altcf->event);
+	smp_mb();
+	/* Disable the "CF detect change" interrupt and free the IRQ. */
+	update_cfctl(altcf, ALTCF_CFCTL_IDET, 0);
+	free_irq(altcf->cfc_irq, altcf);
+	/* Stop the thread. */
+	if (altcf->cf_thread) {
+		kthread_stop(altcf->cf_thread);
+		altcf->cf_thread = NULL;
+	}
+	/* Reinitialize CF CONTROL registers. */
+	altcf->cfctl = ALTCF_CFCTL_RST;
+	WRITE_CFCTL(altcf, altcf->cfctl);
+	WRITE_IDECTL(altcf, 0);
+	/* Everything else is removed by devres. */
+	return 0;
+}
+
+/* Add a platform device. */
+static int __devinit altcf_platform_probe(struct platform_device *pdev)
+{
+	struct resource *ide_mem_res;
+	struct resource *ide_irq_res;
+	struct resource *cfc_mem_res;
+	struct resource *cfc_irq_res;
+
+	if (pdev->num_resources != 4) {
+		dev_err(&pdev->dev, "invalid number of resources\n");
+		return -EINVAL;
+	}
+
+	/* Get the IDE MMIO resource. */
+	ide_mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(ide_mem_res == NULL)) {
+		dev_err(&pdev->dev, "no IDE mem resource\n");
+		return -EINVAL;
+	}
+
+	/* Get the IDE IRQ resource. */
+	ide_irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if ((unlikely(ide_irq_res == NULL))) {
+		dev_err(&pdev->dev, "no IDE IRQ resource\n");
+		return -EINVAL;
+	}
+
+	/* Get the CF CONTROL MMIO resource. */
+	cfc_mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (unlikely(ide_mem_res == NULL)) {
+		dev_err(&pdev->dev, "no CF CONTROL mem resource\n");
+		return -EINVAL;
+	}
+
+	/* Get the CF CONTROL IRQ resource. */
+	cfc_irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+	if ((unlikely(cfc_irq_res == NULL))) {
+		dev_err(&pdev->dev, "no CF CONTROL IRQ resource\n");
+		return -EINVAL;
+	}
+
+	return altcf_probe(&pdev->dev, ide_mem_res, ide_irq_res,
+			cfc_mem_res, cfc_irq_res);
+}
+
+/* Remove a platform device. */
+static int __devexit altcf_platform_remove(struct platform_device *pdev)
+{
+	return altcf_remove(&pdev->dev);
+}
+
+/* We are a platform device driver. */
+static struct platform_driver altcf_platform_driver = {
+	.probe		= altcf_platform_probe,
+	.remove		= __devexit_p(altcf_platform_remove),
+	.driver = {
+		.name		= DRV_NAME,
+		.owner		= THIS_MODULE,
+	},
+};
+
+/* module_init function */
+static int __init altcf_init(void)
+{
+	return platform_driver_register(&altcf_platform_driver);
+}
+
+/* module_exit function */
+static void __exit altcf_exit(void)
+{
+	platform_driver_unregister(&altcf_platform_driver);
+}
+
+MODULE_AUTHOR("Ian Abbott");
+MODULE_DESCRIPTION("low-level driver for Altera SOPC Builder CompactFlash ATA");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+MODULE_ALIAS("platform:" DRV_NAME);
+
+module_init(altcf_init);
+module_exit(altcf_exit);