patch-2.4.9 linux/arch/arm/mach-sa1100/dma-sa1100.c

Next file: linux/arch/arm/mach-sa1100/dma-sa1111.c
Previous file: linux/arch/arm/mach-sa1100/cpu-sa1110.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.8/linux/arch/arm/mach-sa1100/dma-sa1100.c linux/arch/arm/mach-sa1100/dma-sa1100.c
@@ -16,7 +16,7 @@
 #include <linux/init.h>
 #include <linux/sched.h>
 #include <linux/spinlock.h>
-#include <linux/malloc.h>
+#include <linux/slab.h>
 #include <linux/errno.h>
 
 #include <asm/system.h>
@@ -36,13 +36,6 @@
 
 
 /*
- * Maximum physical DMA buffer size
- */
-#define MAX_DMA_SIZE		0x1fff
-#define MAX_DMA_ORDER		12
-
-
-/*
  * DMA control register structure
  */
 typedef struct {
@@ -56,24 +49,16 @@
 	volatile u_long DBTB;
 } dma_regs_t;
 
-
-/*
- * DMA buffer structure
- */
-struct dma_buf_s {
-	int size;		/* buffer size */
-	dma_addr_t dma_start;	/* starting DMA address */
-	dma_addr_t dma_ptr;	/* current DMA pointer position */
-	int ref;		/* number of DMA references */
-	void *id;		/* to identify buffer from outside */
-	struct dma_buf_s *next;	/* next buffer to process */
-};
-
-
 #include "dma.h"
 
 sa1100_dma_t dma_chan[MAX_SA1100_DMA_CHANNELS];
 
+/*
+ * Maximum physical DMA buffer size
+ */
+#define MAX_DMA_SIZE		0x1fff
+#define MAX_DMA_ORDER		12
+
 
 /*
  * DMA processing...
@@ -130,7 +115,7 @@
 	for (;;) {
 		buf = dma->tail;
 
-		if (!buf) {
+		if (!buf || dma->stopped) {
 			/* no more data available */
 			DPRINTK("process: no more buf (dma %s)\n",
 				dma->curr ? "active" : "inactive");
@@ -164,7 +149,6 @@
 		DPRINTK("process: b=%#x s=%d\n", (int) buf->id, buf->size);
 		if (start_dma(dma, buf->dma_ptr, chunksize) != 0)
 			break;
-		dma->active = 1;
 		if (!dma->curr)
 			dma->curr = buf;
 		buf->ref++;
@@ -201,9 +185,10 @@
 				dma->spin_ref = -dma->spin_ref;
 			if (dma->head == buf)
 				dma->head = NULL;
-			buf->size = buf->dma_ptr - buf->dma_start;
-			if (dma->callback)
-				dma->callback(buf->id, buf->size);
+			if (dma->callback) {
+				int size = buf->dma_ptr - buf->dma_start;
+				dma->callback(buf->id, size);
+			}
 			kfree(buf);
 		}
 	}
@@ -231,62 +216,59 @@
  * DMA interface functions
  */
 
-/*
- * Get dma list
- * for /proc/dma
- */
-int sa1100_get_dma_list(char *buf)
-{
-	int i, len = 0;
-
-	for (i = 0; i < MAX_SA1100_DMA_CHANNELS; i++) {
-		if (dma_chan[i].lock)
-			len += sprintf(buf + len, "%2d: %s\n",
-				       i, dma_chan[i].device_id);
-	}
-	return len;
-}
+static spinlock_t dma_list_lock;
 
-int sa1100_request_dma(dmach_t * channel, const char *device_id)
+int sa1100_request_dma (dmach_t * channel, const char *device_id,
+			dma_device_t device)
 {
 	sa1100_dma_t *dma = NULL;
 	dma_regs_t *regs;
-	int ch, err;
+	int i, err;
 
 	*channel = -1;		/* to be sure we catch the freeing of a misregistered channel */
 
-	for (ch = 0; ch < SA1100_DMA_CHANNELS; ch++) {
-		dma = &dma_chan[ch];
-		if (xchg(&dma->lock, 1) == 0)
-			break;
+	err = 0;
+	spin_lock(&dma_list_lock);
+	for (i = 0; i < SA1100_DMA_CHANNELS; i++) {
+		if (dma_chan[i].in_use) {
+			if (dma_chan[i].device == device) {
+				err = -EBUSY;
+				break;
+			}
+		} else if (!dma) {
+			dma = &dma_chan[i];
+		}
 	}
-	if (ch >= SA1100_DMA_CHANNELS) {
-		printk(KERN_ERR "%s: no free DMA channel available\n",
-		       device_id);
-		return -EBUSY;
+	if (!err) {
+	       if (dma)
+		       dma->in_use = 1;
+	       else
+		       err = -ENOSR;
 	}
+	spin_unlock(&dma_list_lock);
+	if (err)
+		return err;
 
 	err = request_irq(dma->irq, dma_irq_handler, SA_INTERRUPT,
 			  device_id, (void *) dma);
 	if (err) {
 		printk(KERN_ERR
-		       "%s: unable to request IRQ %d for DMA channel %d\n",
-		       device_id, dma->irq, ch);
-		dma->lock = 0;
+		       "%s: unable to request IRQ %d for DMA channel\n",
+		       device_id, dma->irq);
 		return err;
 	}
 
-	*channel = ch;
+	*channel = dma - dma_chan;
 	dma->device_id = device_id;
+	dma->device = device;
 	dma->callback = NULL;
 	dma->spin_size = 0;
 
 	regs = dma->regs;
 	regs->ClrDCSR =
-	    (DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB |
-	     DCSR_IE | DCSR_ERROR | DCSR_RUN);
-	regs->DDAR = 0;
-
+		(DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB |
+		 DCSR_IE | DCSR_ERROR | DCSR_RUN);
+	regs->DDAR = device;
 	DPRINTK("requested\n");
 	return 0;
 }
@@ -296,24 +278,11 @@
 {
 	sa1100_dma_t *dma = &dma_chan[channel];
 
-	dma->callback = cb;
-	DPRINTK("cb = %p\n", cb);
-	return 0;
-}
-
-
-int sa1100_dma_set_device(dmach_t channel, dma_device_t device)
-{
-	sa1100_dma_t *dma = &dma_chan[channel];
-	dma_regs_t *regs = dma->regs;
-
-	if (dma->ready)
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
 		return -EINVAL;
 
-	regs->ClrDCSR = DCSR_STRTA | DCSR_STRTB | DCSR_IE | DCSR_RUN;
-	regs->DDAR = device;
-	DPRINTK("DDAR = %#x\n", device);
-	dma->ready = 1;
+	dma->callback = cb;
+	DPRINTK("cb = %p\n", cb);
 	return 0;
 }
 
@@ -323,7 +292,7 @@
 	sa1100_dma_t *dma = &dma_chan[channel];
 	int flags;
 
-	if (channel >= SA1100_DMA_CHANNELS)
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
 		return -EINVAL;
 
 	DPRINTK("set spin %d at %#x\n", size, addr);
@@ -345,7 +314,7 @@
 	int flags;
 
 	dma = &dma_chan[channel];
-	if (!dma->ready)
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
 		return -EINVAL;
 
 	buf = kmalloc(sizeof(*buf), GFP_ATOMIC);
@@ -375,12 +344,16 @@
 int sa1100_dma_get_current(dmach_t channel, void **buf_id, dma_addr_t *addr)
 {
 	sa1100_dma_t *dma = &dma_chan[channel];
-	dma_regs_t *regs = dma->regs;
+	dma_regs_t *regs;
 	int flags, ret;
 
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
+		return -EINVAL;
+
 	if (channel_is_sa1111_sac(channel))
 		return sa1111_dma_get_current(channel, buf_id, addr);
 
+	regs = dma->regs;
 	local_irq_save(flags);
 	if (dma->curr && dma->spin_ref <= 0) {
 		dma_buf_t *buf = dma->curr;
@@ -415,11 +388,16 @@
 			*addr = buf->dma_ptr;
 		DPRINTK("curr_pos: b=%#x a=%#x\n", (int)dma->curr->id, *addr);
 		ret = 0;
+	} else if (dma->tail && dma->stopped) {
+		dma_buf_t *buf = dma->tail;
+		if (buf_id)
+			*buf_id = buf->id;
+		*addr = buf->dma_ptr;
+		ret = 0;
 	} else {
 		if (buf_id)
 			*buf_id = NULL;
 		*addr = 0;
-		DPRINTK("curr_pos: spinning\n");
 		ret = -ENXIO;
 	}
 	local_irq_restore(flags);
@@ -430,12 +408,36 @@
 int sa1100_dma_stop(dmach_t channel)
 {
 	sa1100_dma_t *dma = &dma_chan[channel];
-	dma_regs_t *regs = dma->regs;
+	int flags;
 
 	if (channel_is_sa1111_sac(channel))
 		return sa1111_dma_stop(channel);
 
-	regs->ClrDCSR = DCSR_RUN | DCSR_IE;
+	if (dma->stopped)
+		return 0;
+	local_irq_save(flags);
+	dma->stopped = 1;
+	/*
+	 * Stop DMA and tweak state variables so everything could restart
+	 * from there when resume/wakeup occurs.
+	 */
+	dma->regs->ClrDCSR = DCSR_RUN | DCSR_IE;
+	if (dma->curr) {
+		dma_buf_t *buf = dma->curr;
+		if (dma->spin_ref <= 0) {
+			dma_addr_t curpos;
+			sa1100_dma_get_current(channel, NULL, &curpos);
+			buf->size += buf->dma_ptr - curpos;
+			buf->dma_ptr = curpos;
+		}
+		buf->ref = 0;
+		dma->tail = buf;
+		dma->curr = NULL;
+	}
+	dma->spin_ref = 0;
+	dma->regs->ClrDCSR = DCSR_STRTA|DCSR_STRTB;
+	process_dma(dma);
+	local_irq_restore(flags);
 	return 0;
 }
 
@@ -443,23 +445,35 @@
 int sa1100_dma_resume(dmach_t channel)
 {
 	sa1100_dma_t *dma = &dma_chan[channel];
-	dma_regs_t *regs = dma->regs;
+
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
+		return -EINVAL;
 
 	if (channel_is_sa1111_sac(channel))
 		return sa1111_dma_resume(channel);
 
-	regs->SetDCSR = DCSR_RUN | DCSR_IE;
+	if (dma->stopped) {
+		int flags;
+		save_flags_cli(flags);
+		dma->regs->ClrDCSR = DCSR_STRTA|DCSR_STRTB|DCSR_RUN|DCSR_IE;
+		dma->stopped = 0;
+		dma->spin_ref = 0;
+		process_dma(dma);
+		restore_flags(flags);
+	}
 	return 0;
 }
 
 
 int sa1100_dma_flush_all(dmach_t channel)
 {
-	sa1100_dma_t *dma;
+	sa1100_dma_t *dma = &dma_chan[channel];
 	dma_buf_t *buf, *next_buf;
 	int flags;
 
-	dma = &dma_chan[channel];
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
+		return -EINVAL;
+
 	local_irq_save(flags);
 	if (channel_is_sa1111_sac(channel))
 		sa1111_reset_sac_dma(channel);
@@ -469,10 +483,9 @@
 	if (!buf)
 		buf = dma->tail;
 	dma->head = dma->tail = dma->curr = NULL;
-	dma->active = 0;
+	dma->stopped = 0;
 	dma->spin_ref = 0;
-	if (dma->spin_size)
-		process_dma(dma);
+	process_dma(dma);
 	local_irq_restore(flags);
 	while (buf) {
 		next_buf = buf->next;
@@ -488,25 +501,24 @@
 {
 	sa1100_dma_t *dma;
 
-	if ((unsigned) channel >= MAX_SA1100_DMA_CHANNELS)
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS)
 		return;
 
 	dma = &dma_chan[channel];
-	if (!dma->lock) {
+	if (!dma->in_use) {
 		printk(KERN_ERR "Trying to free free DMA%d\n", channel);
 		return;
 	}
 
 	sa1100_dma_set_spin(channel, 0, 0);
 	sa1100_dma_flush_all(channel);
-	dma->ready = 0;
 
 	if (channel_is_sa1111_sac(channel)) {
 		sa1111_cleanup_sac_dma(channel);
 	} else {
 		free_irq(IRQ_DMA0 + channel, (void *) dma);
 	}
-	dma->lock = 0;
+	dma->in_use = 0;
 
 	DPRINTK("freed\n");
 }
@@ -514,7 +526,6 @@
 
 EXPORT_SYMBOL(sa1100_request_dma);
 EXPORT_SYMBOL(sa1100_dma_set_callback);
-EXPORT_SYMBOL(sa1100_dma_set_device);
 EXPORT_SYMBOL(sa1100_dma_set_spin);
 EXPORT_SYMBOL(sa1100_dma_queue_buffer);
 EXPORT_SYMBOL(sa1100_dma_get_current);
@@ -522,6 +533,66 @@
 EXPORT_SYMBOL(sa1100_dma_resume);
 EXPORT_SYMBOL(sa1100_dma_flush_all);
 EXPORT_SYMBOL(sa1100_free_dma);
+
+
+#ifdef CONFIG_PM
+/* Drivers should call this from their PM callback function */
+
+int sa1100_dma_sleep(dmach_t channel)
+{
+        sa1100_dma_t *dma = &dma_chan[channel];
+	int orig_state;
+
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
+		return -EINVAL;
+
+	if (channel_is_sa1111_sac(channel)) {
+		/* We'll cheat a little until someone actually
+		 * write the real thing.
+		 */
+		sa1111_reset_sac_dma(channel);
+		return 0;
+	}
+
+	orig_state = dma->stopped;
+	sa1100_dma_stop(channel);
+	dma->regs->ClrDCSR = DCSR_RUN | DCSR_IE | DCSR_STRTA | DCSR_STRTB;
+	dma->stopped = orig_state;
+	dma->spin_ref = 0;
+	return 0;
+}
+
+int sa1100_dma_wakeup(dmach_t channel)
+{
+        sa1100_dma_t *dma = &dma_chan[channel];
+	dma_regs_t *regs;
+	int flags;
+
+	if ((unsigned)channel >= MAX_SA1100_DMA_CHANNELS || !dma->in_use)
+		return -EINVAL;
+
+	if (channel_is_sa1111_sac(channel)) {
+		/* We'll cheat a little until someone actually
+		 * write the real thing.
+		 */
+		return 0;
+	}
+
+	regs = dma->regs;
+	regs->ClrDCSR =
+		(DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB |
+		 DCSR_IE | DCSR_ERROR | DCSR_RUN);
+	regs->DDAR = dma->device;
+	local_irq_save(flags);
+	process_dma(dma);
+	local_irq_restore(flags);
+	return 0;
+}
+
+EXPORT_SYMBOL(sa1100_dma_sleep);
+EXPORT_SYMBOL(sa1100_dma_wakeup);
+
+#endif
 
 
 static int __init sa1100_init_dma(void)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)