Signed-off-by: Christian Pellegrin <chripell@fsfe.org>
---
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index b9d0efb..2d083a9 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -181,6 +181,13 @@ config SPI_S3C24XX_GPIO
 	  the inbuilt hardware cannot provide the transfer mode, or
 	  where the board is using non hardware connected pins.
 
+config SPI_S3C24XX_DMA
+       tristate "Samsung S3C24XX series SPI using DMA"
+       depends on SPI_MASTER && ARCH_S3C2410 && EXPERIMENTAL
+       help
+         SPI driver for Samsung S3C24XX series ARM SoCs using DMA to
+         perform the transfers.
+
 config SPI_SH_SCI
 	tristate "SuperH SCI SPI controller"
 	depends on SUPERH
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index ccf18de..679889a 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_MPC52xx_PSC)		+= mpc52xx_psc_spi.o
 obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)		+= spi_s3c24xx_gpio.o
 obj-$(CONFIG_SPI_S3C24XX)		+= spi_s3c24xx.o
+obj-$(CONFIG_SPI_S3C24XX_DMA)           += spi_s3c24xx_dma.o
 obj-$(CONFIG_SPI_TXX9)			+= spi_txx9.o
 obj-$(CONFIG_SPI_XILINX)		+= xilinx_spi.o
 obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.o
diff --git a/drivers/spi/spi_s3c24xx_dma.c b/drivers/spi/spi_s3c24xx_dma.c
new file mode 100644
index 0000000..04eafd9
--- /dev/null
+++ b/drivers/spi/spi_s3c24xx_dma.c
@@ -0,0 +1,905 @@
+/* DMA capable SPI driver for the S3C24xx Processors.
+ *
+ * Copyright (C) 2007 Nordnav Technologies AB
+ *     Christer Weinigel <[EMAIL PROTECTED]>
+ *
+ * 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.
+ */
+
+#define NODMA 1
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include <linux/spi/spi.h>
+
+#include <asm/dma.h>
+
+#include <mach/regs-gpio.h>
+#include <mach/spi-gpio.h>
+#include <mach/hardware.h>
+#include <asm/plat-s3c24xx/regs-spi.h>
+#include <mach/spi.h>
+
+
+/* Setting up a DMA transfer and doing the DMA mapping is a rather
+ * heavy operation (takes about 20-30 usecs on a 200 MHz S3C24A0) so
+ * for short transfers it's a waste of CPU to do DMA.  Add a tunable
+ * that tells the driver at what size to start using use DMA
+ * transfers.
+ *
+ * To disable DMA entirely, set use_dma to 0.
+ */
+#ifdef NODMA
+static unsigned use_dma = 0;
+#else
+static unsigned use_dma = 32;
+module_param(use_dma, int, 0444);
+MODULE_PARM_DESC(use_dma,
+		 "0: no DMA, >=1: Use DMA for transfers above this size");
+#endif
+
+/* For fast bitrates using interrupts is really wasteful because the
+ * interrupt latency is much longer than the time it takes to transfer
+ * a byte, so most time is spent entering and exiting the interrupt
+ * handler.  It's faster to just poll the status register until the
+ * READY bit goes high.  This option tells the driver to use polling
+ * transfers above the specified frequency.
+ *
+ * For a 200 MHz S3C24A0 the interrupt latency seems to be around 4
+ * us, so it's probably worth turning on polling at bitrates above
+ * 8 bits / 4 us = 2 MBit/s.
+ *
+ * To disable polling entirely, set it to a ridiculously large value.
+ */
+static unsigned use_poll = 2000000;
+module_param(use_poll, int, 0444);
+MODULE_PARM_DESC(use_poll,
+		 "Use polling for transfers above this frequency");
+
+/* This setting just limits the number of times the driver tries to
+ * poll the status register for READY before it falls back to IRQ
+ * driven transfers.
+ *
+ * If you have set use_poll to a too low value and the max_poll check
+ * triggers, you want to either increase use_poll so that it uses
+ * interrupt transfers instear or increase max_poll so that the driver
+ * shuts up.  But increasing max_poll will waste a lot of CPU, so you
+ * really don't want to do that.
+ */
+static unsigned max_poll = 1000;
+module_param(max_poll, int, 0444);
+MODULE_PARM_DESC(max_poll,
+		 "Maximum number of times to poll the status register before"
+		 "falling back to IRQ driven transfers");
+
+#ifndef NODMA
+static struct s3c2410_dma_client spi_s3c24xx_dma_client = {
+	.name = "spi_s3c24xx_dma"
+};
+#endif
+
+struct spi_s3c24xx_dma_hw {
+	/* the base of SPI registers for this port */
+	void *regs;
+
+	/* the base value for spcon, i.e. including the CPOL and CPHA
+	 * bits and but excluding the SMOD and TAGD bits */
+	unsigned spcon;
+
+	/* state used during transfers */
+	int active;
+	size_t count;
+	unsigned char *rx_ptr;
+	const unsigned char *tx_ptr;
+
+#ifndef NODMA
+	/* state used during DMA transfers */
+	dma_addr_t xfer_dma_addr;
+	enum dma_data_direction xfer_direction;
+#endif
+
+	/* state used for activating and deactivating a board */
+	int cs;                 /* last chip selected or -1 if none */
+	int cs_pol;             /* cs polarity for last cs */
+	u32 speed_hz;           /* last selected speed */
+
+	/* callbacks optionally defined by the platform data */
+	void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);
+	struct s3c2410_spi_info *pdata;
+
+	/* queue of messages waiting to be processed */
+	struct list_head queue;
+	/* message queue lock */
+	spinlock_t queue_lock;
+
+	/* the current transfer */
+	struct spi_transfer *transfer;
+
+	/* resource handling */
+	struct device *dev;
+	struct clk *clk;
+	struct resource *iomem;
+	struct resource *irq;
+	dmach_t dmach;
+	int have_dma;
+
+	/* for PM */
+	u32                     save_SPPRE, save_SPPIN, save_SPCON;
+};
+
+static void spi_s3c24xx_dma_start_transfer(struct spi_s3c24xx_dma_hw *hw);
+static void spi_s3c24xx_dma_wake(struct spi_s3c24xx_dma_hw *hw);
+
+static void spi_s3c24xx_dma_default_set_cs(struct s3c2410_spi_info *spi,
+					   int cs, int pol)
+{
+	s3c2410_gpio_setpin(cs, pol);
+}
+
+static void spi_s3c24xx_dma_activate(struct spi_s3c24xx_dma_hw *hw,
+				     struct spi_device *spi,
+				     struct spi_transfer *transfer)
+{
+	unsigned long hz;
+	unsigned long actual_hz;
+	unsigned sppre;
+
+	dev_dbg(hw->dev, "act %d %d\n", hw->cs, spi->chip_select);
+	if (hw->cs != spi->chip_select) {
+		if (hw->cs != -1)
+			hw->set_cs(hw->pdata, hw->cs, !hw->cs_pol);
+
+		hw->spcon = S3C2410_SPCON_ENSCK | S3C2410_SPCON_MSTR;
+
+		hw->cs = spi->chip_select;
+		hw->cs_pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
+
+		hw->set_cs(hw->pdata, hw->cs, hw->cs_pol);
+	}
+
+	if (spi->mode & SPI_CPOL)
+		hw->spcon |= S3C2410_SPCON_CPOL_HIGH;
+	else
+		hw->spcon &= ~S3C2410_SPCON_CPOL_HIGH;
+
+	if (spi->mode & SPI_CPHA)
+		hw->spcon |= S3C2410_SPCON_CPHA_FMTA;
+	else
+		hw->spcon &= ~S3C2410_SPCON_CPHA_FMTB;
+
+	writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+	       hw->regs + S3C2410_SPCON);
+
+	hz = transfer->speed_hz;
+	if (hz == 0 || hz > spi->max_speed_hz)
+		hz = spi->max_speed_hz;
+
+	if (hw->speed_hz != hz) {
+		sppre = ((clk_get_rate(hw->clk) + hz - 1) / hz + 1) / 2 - 1;
+		if (sppre < 0)
+			sppre = 0;
+		if (sppre > 255)
+			sppre = 255;
+
+		writel(sppre, hw->regs + S3C2410_SPPRE);
+
+		actual_hz = clk_get_rate(hw->clk) / 2 / (sppre + 1);
+		dev_dbg(hw->dev,
+			"bus speed %lu Hz (%u), requested %lu Hz\n",
+			actual_hz, sppre, hz);
+
+		hw->speed_hz = hz;
+	}
+}
+
+static void spi_s3c24xx_dma_deactivate(struct spi_s3c24xx_dma_hw *hw)
+{
+	dev_dbg(hw->dev, "de %d\n", hw->cs);
+	if (hw->cs != -1) {
+		hw->set_cs(hw->pdata, hw->cs, !hw->cs_pol);
+		hw->cs = -1;
+	}
+}
+
+static void spi_s3c24xx_dma_transfer_done(struct spi_s3c24xx_dma_hw *hw)
+{
+	struct spi_message *message;
+	struct spi_transfer *transfer;
+	unsigned long flags;
+
+	writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+	       hw->regs + S3C2410_SPCON);
+
+	message = list_first_entry(&hw->queue,
+				   struct spi_message, queue);
+
+	transfer = hw->transfer;
+
+	dev_dbg(hw->dev, "done\n");
+
+	/* TODO maybe compensate for the time it takes to get here, If
+	 * the interrupt latency and everything else is large, it
+	 * might be a good idea to subtract that time. */
+
+	/* TODO How large is the delay?  If it's large, we might have
+	 * to use a timer instead of udelay. */
+
+	if (transfer->delay_usecs) {
+		dev_dbg(hw->dev, "delay %u usecs\n", transfer->delay_usecs);
+		udelay(transfer->delay_usecs);
+	}
+
+	/*if (transfer->cs_change)*/
+	spi_s3c24xx_dma_deactivate(hw);
+
+#ifdef DEBUG
+	if (transfer->rx_buf) {
+		int i;
+		printk(KERN_DEBUG "receive 0x%x bytes:", transfer->len);
+		for (i = 0; i < transfer->len; i++)
+			printk(" %02x",
+			       ((unsigned char *)transfer->rx_buf)[i]);
+		printk("\n");
+	}
+#endif
+
+	message->actual_length += transfer->len;
+
+	if (list_is_last(&transfer->transfer_list, &message->transfers)) {
+		spin_lock_irqsave(&hw->queue_lock, flags);
+		hw->transfer = NULL;
+		list_del(&message->queue);
+		spin_unlock_irqrestore(&hw->queue_lock, flags);
+
+		spi_s3c24xx_dma_wake(hw);
+
+		if (message->complete) {
+			message->status = 0;
+			message->complete(message->context);
+		}
+	} else {
+		hw->transfer = list_entry(transfer->transfer_list.next,
+					  struct spi_transfer,
+					  transfer_list);
+
+		spi_s3c24xx_dma_start_transfer(hw);
+	}
+}
+
+static int spi_s3c24xx_dma_poll(struct spi_s3c24xx_dma_hw *hw)
+{
+	unsigned status;
+	unsigned i;
+	static int poll_warned;
+
+	do {
+		for (i = 0; i <= max_poll; i++) {
+			status = readl(hw->regs + S3C2410_SPSTA);
+			if (status & S3C2410_SPSTA_DCOL)
+				dev_warn(hw->dev, "collision\n");
+			if (status & S3C2410_SPSTA_MULD)
+				dev_warn(hw->dev, "multi master error\n");
+			if (status & S3C2410_SPSTA_READY)
+				goto ready;
+		}
+
+		if (poll_warned < 5) {
+			dev_warn(hw->dev, "poll timeout (%d attempts), "
+				 "falling back to interrupts\n", i);
+			poll_warned++;
+		}
+
+		/* there is more data to transfer, so return 1 */
+		return 1;
+
+	ready:
+		pr_debug("%s: poll loops %d, count %d\n",
+			 __func__, i, hw->count);
+
+		if (hw->rx_ptr)
+			*hw->rx_ptr++ = readl(hw->regs + S3C2410_SPRDAT);
+
+		if (!hw->count) {
+			spi_s3c24xx_dma_transfer_done(hw);
+			return 0;
+		}
+
+		if (hw->tx_ptr)
+			writel(*hw->tx_ptr++,
+			       hw->regs + S3C2410_SPTDAT);
+		else
+			writel(0xff, hw->regs + S3C2410_SPTDAT);
+		hw->count--;
+	} while (use_poll <= hw->speed_hz);
+
+	/* there's more work to be done */
+	return 1;
+}
+
+static irqreturn_t spi_s3c24xx_dma_irq(int irq, void *dev_id)
+{
+	struct spi_s3c24xx_dma_hw *hw = dev_id;
+	unsigned status;
+
+	if (!hw->active) {
+		dev_warn(hw->dev, "%s: called when not active\n", __func__);
+		writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+		       hw->regs + S3C2410_SPCON);
+		return IRQ_HANDLED;
+	}
+
+	status = readl(hw->regs + S3C2410_SPSTA);
+
+	if (status != S3C2410_SPSTA_READY) {
+		dev_warn(hw->dev, "%s: unexpected status 0x%x, count %d\n",
+			 __func__, status, hw->count);
+	}
+
+	pr_debug("%s: count %d,  status 0x%x\n", __func__, hw->count, status);
+
+	writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+	       hw->regs + S3C2410_SPCON);
+
+	if (spi_s3c24xx_dma_poll(hw)) {
+		writel(hw->spcon | S3C2410_SPCON_SMOD_INT,
+		       hw->regs + S3C2410_SPCON);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void spi_s3c24xx_dma_start_int(struct spi_s3c24xx_dma_hw *hw)
+{
+	pr_debug("%s: count = %u\n", __func__, hw->count);
+
+	writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+	       hw->regs + S3C2410_SPCON);
+
+	if (spi_s3c24xx_dma_poll(hw)) {
+		pr_debug("%s: enabling irq\n", __func__);
+		writel(hw->spcon | S3C2410_SPCON_SMOD_INT,
+		       hw->regs + S3C2410_SPCON);
+	}
+}
+
+#ifndef NODMA
+static void spi_s3c24xx_dma_callback(struct s3c2410_dma_chan *dma_ch,
+				     void *buf_id, int size,
+				     enum s3c2410_dma_buffresult result)
+{
+	struct spi_s3c24xx_dma_hw *hw = buf_id;
+	struct spi_message *message =
+		list_first_entry(&hw->queue, struct spi_message, queue);
+	struct spi_transfer *transfer = hw->transfer;
+
+	pr_debug("%s: count %u, size %u, result %u\n",
+		 __func__, hw->count, size, result);
+
+	if (!hw->active) {
+		dev_warn(hw->dev, "%s: called when not active\n", __func__);
+		writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+		       hw->regs + S3C2410_SPCON);
+		return;
+	}
+
+	if (size > hw->count) {
+		dev_err(hw->dev, "%s: DMA size %u larger expected count %u\n",
+			__func__, size, hw->count);
+		/* things are messed up for this to happen, so don't
+		 * do anything here, just return and hope nothing bad
+		 * has happened */
+		return;
+	}
+
+	hw->count -= size;
+
+	if (hw->rx_ptr)
+		hw->rx_ptr += size;
+	if (hw->tx_ptr)
+		hw->tx_ptr += size;
+
+	if (!message->is_dma_mapped) {
+		dma_unmap_single(hw->dev, hw->xfer_dma_addr,
+				 transfer->len, hw->xfer_direction);
+	}
+
+	spi_s3c24xx_dma_start_int(hw);
+}
+#endif
+
+static int spi_s3c24xx_dma_start_dma(struct spi_s3c24xx_dma_hw *hw)
+{
+#ifndef NODMA
+	struct spi_message *message =
+		list_first_entry(&hw->queue, struct spi_message, queue);
+	struct spi_transfer *transfer = hw->transfer;
+	void *cpu_addr;
+
+	pr_debug("%s: count = %u, is_dma_mapped %d\n", __func__,
+		 hw->count, message->is_dma_mapped);
+
+	if (message->is_dma_mapped) {
+		/* TODO how does rx_dma work with the last byte which
+		 * is transferred using polling?  If the memory is
+		 * allocated with dma_alloc_coherent, it ought to
+		 * work, if the memory is allocated with
+		 * dma_alloc_noncoherent and the caller is doing
+		 * dma_sync by hand, it'll most probably break
+		 * horribly.  OTOH, in that case any polled I/O,
+		 * i.e. the INT transfers, will break too. */
+		if (hw->rx_ptr)
+			hw->xfer_dma_addr = transfer->rx_dma;
+		else
+			hw->xfer_dma_addr = transfer->tx_dma;
+	} else {
+		if (hw->rx_ptr) {
+			cpu_addr = transfer->rx_buf;
+			hw->xfer_direction = DMA_FROM_DEVICE;
+		} else {
+			cpu_addr = (void *)transfer->tx_buf;
+			hw->xfer_direction = DMA_TO_DEVICE;
+		}
+
+		hw->xfer_dma_addr = dma_map_single(hw->dev,
+						   cpu_addr,
+						   transfer->len,
+						   hw->xfer_direction);
+
+		if (dma_mapping_error(hw->xfer_dma_addr)) {
+			dev_warn(hw->dev,
+				 "dma_map_single(%p, %u, %u) failed\n",
+				 cpu_addr, transfer->len,
+				 hw->xfer_direction);
+			return 0;
+		}
+	}
+
+	if (hw->rx_ptr) {
+		s3c2410_dma_devconfig(hw->dmach, S3C2410_DMASRC_HW,
+				      S3C2410_DISRCC_INC | S3C2410_DISRCC_APB,
+				      hw->iomem->start + S3C2410_SPRDAT);
+		writel(hw->spcon | S3C2410_SPCON_SMOD_DMA | S3C2410_SPCON_TAGD,
+		       hw->regs + S3C2410_SPCON);
+	} else {
+		s3c2410_dma_devconfig(hw->dmach, S3C2410_DMASRC_MEM,
+				      S3C2410_DISRCC_INC | S3C2410_DISRCC_APB,
+				      hw->iomem->start + S3C2410_SPTDAT);
+		writel(hw->spcon | S3C2410_SPCON_SMOD_DMA,
+		       hw->regs + S3C2410_SPCON);
+	}
+
+	s3c2410_dma_enqueue(hw->dmach, hw, hw->xfer_dma_addr, hw->count);
+
+	return 1;
+#else  /* nodma */
+	return 0;		/* always failed */
+#endif
+}
+
+static int spi_s3c24xx_dma_setup(struct spi_device *spi)
+{
+	struct spi_master *master = spi->master;
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+
+	dev_info(hw->dev,
+		 "setup %s, mode 0x%x, bits %u, hz %d\n",
+		 spi->dev.bus_id,
+		 spi->mode, spi->bits_per_word, spi->max_speed_hz);
+
+	if (spi->mode & ~(SPI_CS_HIGH | SPI_CPOL | SPI_CPHA)) {
+		dev_err(hw->dev, "setup %s, unsupported mode 0x%x\n",
+			spi->dev.bus_id, spi->mode);
+		return -EINVAL;
+	}
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	if (spi->bits_per_word != 8) {
+		dev_err(hw->dev, "setup %s, unsupported bits_per_word (%u)\n",
+			spi->dev.bus_id, spi->bits_per_word);
+		return -EINVAL;
+	}
+
+	if (!spi->max_speed_hz) {
+		dev_err(hw->dev, "setup %s, no max speed set, "
+			"you have to supply a sensible value\n",
+			spi->dev.bus_id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void spi_s3c24xx_dma_start_transfer(struct spi_s3c24xx_dma_hw *hw)
+{
+	struct spi_message *message;
+	struct spi_transfer *transfer;
+
+	transfer = hw->transfer;
+
+	dev_dbg(hw->dev, "start: %s%s, hz %d, bits %d, len %d, cs %d\n",
+		transfer->rx_buf ? "R":"", transfer->tx_buf ? "W":"",
+		transfer->speed_hz, transfer->bits_per_word,
+		transfer->len, transfer->cs_change);
+
+	hw->count = transfer->len;
+
+	hw->rx_ptr = transfer->rx_buf;
+	hw->tx_ptr = transfer->tx_buf;
+
+#ifdef DEBUG
+	if (transfer->tx_buf) {
+		int i;
+		printk(KERN_DEBUG "transmit 0x%x bytes:", transfer->len);
+		for (i = 0; i < transfer->len; i++)
+			printk(" %02x",
+			       ((unsigned char *)transfer->tx_buf)[i]);
+		printk("\n");
+	}
+#endif
+
+	message = list_first_entry(&hw->queue,
+				   struct spi_message, queue);
+
+	spi_s3c24xx_dma_activate(hw, message->spi, transfer);
+
+	if (hw->rx_ptr) {
+		/* Write the first byte of TX data to transfer the
+		 * first byte and to fill the read FIFO. */
+		writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+		       hw->regs + S3C2410_SPCON);
+
+		if (hw->tx_ptr)
+			writel(*hw->tx_ptr++, hw->regs + S3C2410_SPTDAT);
+		else
+			writel(0xff, hw->regs + S3C2410_SPTDAT);
+		hw->count--;
+	}
+
+	/* Since there is only one DMA channel available per SPI
+	 * controller, we can only use DMA when doing a transfer in
+	 * one direction, simultaneous transmit and receive would need
+	 * two DMA channels.
+	 */
+	if ((hw->rx_ptr && hw->tx_ptr) || /* if transfer is bidirectional */
+	    !hw->have_dma ||              /* or DMA is disabled */
+	    hw->count < use_dma || /* or transfer is too small  */
+	    !spi_s3c24xx_dma_start_dma(hw)) /* or dma mapping failed */
+		spi_s3c24xx_dma_start_int(hw); /* fall back to int transfer */
+}
+
+static void spi_s3c24xx_dma_wake(struct spi_s3c24xx_dma_hw *hw)
+{
+	struct spi_message *message = NULL;
+	unsigned long flags;
+
+	dev_dbg(hw->dev, "%s\n", __func__);
+
+	spin_lock_irqsave(&hw->queue_lock, flags);
+	if (!hw->transfer && !list_empty(&hw->queue)) {
+		message = list_first_entry(&hw->queue,
+					   struct spi_message,
+					   queue);
+		hw->transfer = list_first_entry(&message->transfers,
+						struct spi_transfer,
+						transfer_list);
+	}
+	spin_unlock_irqrestore(&hw->queue_lock, flags);
+
+	if (message)
+		spi_s3c24xx_dma_start_transfer(hw);
+}
+
+static int spi_s3c24xx_dma_transfer(struct spi_device *spi,
+				    struct spi_message *message)
+{
+	struct spi_master *master = spi->master;
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+	struct spi_transfer *transfer;
+	unsigned long flags;
+
+	dev_dbg(hw->dev, "%s\n", __func__);
+
+	message->actual_length = 0;
+	message->status = -EINPROGRESS;
+
+	list_for_each_entry (transfer, &message->transfers, transfer_list) {
+		dev_dbg(&spi->dev, "%s%s, hz %d, bits %d, len %d, cs %d\n",
+			transfer->rx_buf ? "R":"", transfer->tx_buf ? "W":"",
+			transfer->speed_hz, transfer->bits_per_word,
+			transfer->len, transfer->cs_change);
+
+		if (!transfer->rx_buf && !transfer->tx_buf)
+			return -EINVAL;
+
+		if (transfer->bits_per_word && transfer->bits_per_word != 8)
+			return -EINVAL;
+	}
+
+	spin_lock_irqsave(&hw->queue_lock, flags);
+	list_add_tail(&message->queue, &hw->queue);
+	spin_unlock_irqrestore(&hw->queue_lock, flags);
+
+	spi_s3c24xx_dma_wake(hw);
+
+	return 0;
+}
+
+static void spi_s3c24xx_dma_free(struct spi_master *master)
+{
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+
+	dev_set_drvdata(hw->dev, NULL);
+
+	hw->active = 0;
+
+	if (hw->have_dma) {
+		int value;
+
+#ifndef NODMA
+		/* TODO it seems this cleanup can sometimes leave a
+		 * DMA request pending, is there any way to do a
+		 * better cleanup.  */
+
+		s3c2410_dma_ctrl(hw->dmach, S3C2410_DMAOP_FLUSH);
+		msleep(100);
+		s3c2410_dma_ctrl(hw->dmach, S3C2410_DMAOP_STOP);
+		msleep(100);
+#endif
+
+		writel(S3C2410_SPCON_SMOD_POLL, hw->regs + S3C2410_SPCON);
+		value = readb(hw->regs + S3C2410_SPRDAT);
+
+#ifndef NODMA
+		s3c2410_dma_free(hw->dmach, &spi_s3c24xx_dma_client);
+#endif
+	}
+	if (hw->irq)
+		free_irq(hw->irq->start, hw);
+	if (hw->regs)
+		iounmap(hw->regs);
+	if (hw->iomem) {
+		release_resource(hw->iomem);
+		kfree(hw->iomem);
+	}
+	if (hw->clk)
+		clk_disable(hw->clk);
+
+	spi_master_put(master);
+}
+
+static int spi_s3c24xx_dma_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct spi_s3c24xx_dma_hw *hw;
+	struct spi_master *master;
+	struct resource *res;
+	int ret;
+#if 0
+	struct spi_board_info *board;
+	int i;
+#endif
+
+	if (!dev->platform_data) {
+		dev_err(dev, "no platform data available\n");
+		return -EINVAL;
+	}
+
+	master = spi_alloc_master(dev, sizeof(*hw));
+	if (!master) {
+		dev_err(dev, "failed to allocate spi_master\n");
+		return -ENOMEM;
+	}
+	platform_set_drvdata(pdev, master);
+
+	hw = spi_master_get_devdata(master);
+	memset(hw, 0, sizeof(*hw));
+
+	master->bus_num = pdev->id;
+	master->setup = spi_s3c24xx_dma_setup;
+	master->transfer = spi_s3c24xx_dma_transfer;
+	master->num_chipselect = ~0; /* all possible GPIOs */
+
+	hw->spcon = S3C2410_SPCON_ENSCK | S3C2410_SPCON_MSTR;
+	hw->cs = -1;
+	hw->pdata = dev->platform_data;
+	hw->set_cs = hw->pdata->set_cs;
+	if (!hw->set_cs)
+		hw->set_cs = spi_s3c24xx_dma_default_set_cs;
+	INIT_LIST_HEAD(&hw->queue);
+	spin_lock_init(&hw->queue_lock);
+
+	hw->dev = dev;
+
+	hw->clk = clk_get(dev, "spi");
+	if (IS_ERR(hw->clk)) {
+		dev_err(dev, "clk_get failed\n");
+		ret = -ENOENT;
+		goto out;
+	}
+
+	dev_dbg(dev, "clk %p\n", hw->clk);
+
+	clk_enable(hw->clk);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "unable to find registers\n");
+		ret = -ENOENT;
+		goto out;
+	}
+
+	hw->iomem = request_mem_region(res->start,
+				       (res->end-res->start)+1,
+				       pdev->name);
+	if (!hw->iomem) {
+		dev_err(dev, "request_mem_region failed\n");
+		ret = -ENXIO;
+		goto out;
+	}
+
+	hw->regs = ioremap(res->start, (res->end-res->start)+1);
+	if (!hw->regs) {
+		dev_err(dev, "ioremap failed\n");
+		ret = -ENXIO;
+		goto out;
+	}
+
+	writel(255, hw->regs + S3C2410_SPPRE);
+	writel(hw->spcon | S3C2410_SPCON_SMOD_POLL,
+	       hw->regs + S3C2410_SPCON);
+	writel(S3C2410_SPPIN_KEEP, hw->regs + S3C2410_SPPIN);
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "failed to get IORESOURCE_IRQ\n");
+		ret = -ENOENT;
+		goto out;
+	}
+
+	ret = request_irq(res->start, spi_s3c24xx_dma_irq, 0, pdev->name, hw);
+	if (ret) {
+		dev_err(&pdev->dev, "request_irq failed\n");
+		goto out;
+	}
+	hw->irq = res;
+
+	if (use_dma) {
+#ifndef NODMA
+		res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+		if (!res) {
+			dev_err(dev, "unable to find dma\n");
+			ret = -ENOENT;
+			goto out;
+		}
+		hw->dmach = res->start;
+
+		if (s3c2410_dma_request(hw->dmach, &spi_s3c24xx_dma_client,
+					NULL)) {
+			dev_err(dev, "unable to allocate dma channel\n");
+			ret = -ENOENT;
+			goto out;
+		}
+		hw->have_dma = 1;
+
+		s3c2410_dma_config(hw->dmach, 1, 0);
+		s3c2410_dma_set_buffdone_fn(hw->dmach,
+					    spi_s3c24xx_dma_callback);
+		s3c2410_dma_setflags(hw->dmach, S3C2410_DMAF_AUTOSTART);
+#else
+		printk("no dma support!\n");
+		ret = -ENOENT;
+		goto out;
+#endif
+	}
+
+	hw->active = 1;
+
+	ret = spi_register_master(master);
+	if (ret) {
+		dev_err(dev, "unable to register spi master\n");
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	if (ret)
+		spi_s3c24xx_dma_free(master);
+
+	return ret;
+}
+
+static int spi_s3c24xx_dma_remove(struct platform_device *pdev)
+{
+	struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+
+	spi_s3c24xx_dma_deactivate(hw);
+
+	spi_unregister_master(master);
+	spi_s3c24xx_dma_free(master);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+	struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+
+	hw->save_SPPRE = readl(hw->regs + S3C2410_SPPRE);
+	hw->save_SPPIN = readl(hw->regs + S3C2410_SPPIN);
+	hw->save_SPCON = readl(hw->regs + S3C2410_SPCON);
+	clk_disable(hw->clk);
+	return 0;
+}
+
+static int s3c24xx_spi_resume(struct platform_device *pdev)
+{
+	struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
+	struct spi_s3c24xx_dma_hw *hw = spi_master_get_devdata(master);
+
+	clk_enable(hw->clk);
+
+	writeb(hw->save_SPPRE, hw->regs + S3C2410_SPPRE);
+	writeb(hw->save_SPPIN, hw->regs + S3C2410_SPPIN);
+	writeb(hw->save_SPCON, hw->regs + S3C2410_SPCON);
+	/* needed since otherwise we can stall forever after a suspend */
+	if (hw->transfer) {
+		dev_dbg(hw->dev, "spi losing chars after resume\n");
+		spi_s3c24xx_dma_transfer_done(hw);
+	}
+	return 0;
+}
+
+#else
+#define s3c24xx_spi_suspend NULL
+#define s3c24xx_spi_resume  NULL
+#endif
+
+static struct platform_driver spi_s3c24xx_dma_driver = {
+	.suspend	= s3c24xx_spi_suspend,
+	.resume		= s3c24xx_spi_resume,
+	.probe          = spi_s3c24xx_dma_probe,
+	.remove         = spi_s3c24xx_dma_remove,
+	.driver         = {
+		.name   = "s3c2410-spi",
+		.owner  = THIS_MODULE,
+	},
+};
+
+static int __init spi_s3c24xx_dma_init(void)
+{
+	pr_info("S3C24xx SPI DMA driver (c) 2007 Nordnav Technologies AB\n");
+
+	return platform_driver_probe(&spi_s3c24xx_dma_driver,
+				     spi_s3c24xx_dma_probe);
+}
+
+static void __exit spi_s3c24xx_dma_exit(void)
+{
+	platform_driver_unregister(&spi_s3c24xx_dma_driver);
+}
+
+module_init(spi_s3c24xx_dma_init);
+module_exit(spi_s3c24xx_dma_exit);
+
+MODULE_DESCRIPTION("S3C24xx SPI DMA Driver");
+MODULE_LICENSE("GPL");
-- 
1.5.6.5
