[RFC 3/7][PATCH] AMBA DMA: Add in functionality to provide AMBA DMA

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The PCI DMA API is implemented for the AMBA bus.
The board registers callback functions with the AMBA DMA.
A platform level example is provided for the ARM Versatile development
board.

Signed-off-by: Peter M Pearse <[email protected]> 

---

diff -purN arm_amba_proc_dma/arch/arm/kernel/dma.c
arm_amba_dma/arch/arm/kernel/dma.c
--- arm_amba_proc_dma/arch/arm/kernel/dma.c	2006-10-17
17:05:21.000000000 +0100
+++ arm_amba_dma/arch/arm/kernel/dma.c	2006-10-17 17:10:15.000000000 +0100
@@ -15,6 +15,7 @@
 #include <linux/init.h>
 #include <linux/spinlock.h>
 #include <linux/errno.h>
+#include <linux/seq_file.h>
 #include <linux/proc_fs.h>
 
 #include <asm/dma.h>
@@ -73,7 +74,10 @@ int request_dma(dmach_t channel, const c
 	return ret;
 
 bad_dma:
-	printk(KERN_ERR "dma: trying to allocate DMA%d\n", channel);
+	/* 
+	 * Don't report an error - channels may be in use by other devices
+	 * printk(KERN_ERR "dma: trying to allocate DMA%d\n", channel);
+	 */
 	return -EINVAL;
 
 busy:
@@ -141,8 +145,14 @@ void __set_dma_addr (dmach_t channel, vo
 		printk(KERN_ERR "dma%d: altering DMA address while "
 		       "DMA active\n", channel);
 
+#ifdef CONFIG_ARM_AMBA_DMA
+ 	dma->sg = &dma->buf;
+ 	dma->sgcount = 1;
+	dma->buf.dma_address = virt_to_bus(addr);
+#else
 	dma->sg = NULL;
 	dma->addr = addr;
+#endif
 	dma->invalid = 1;
 }
 EXPORT_SYMBOL(__set_dma_addr);
@@ -229,6 +239,7 @@ int dma_channel_active(dmach_t channel)
 {
 	return dma_chan[channel].active;
 }
+EXPORT_SYMBOL(dma_channel_active);
 
 void set_dma_page(dmach_t channel, char pagenr)
 {
diff -purN arm_amba_proc_dma/drivers/amba/Kconfig
arm_amba_dma/drivers/amba/Kconfig
--- arm_amba_proc_dma/drivers/amba/Kconfig	2006-10-17
17:01:20.000000000 +0100
+++ arm_amba_dma/drivers/amba/Kconfig	2006-10-17 17:10:15.000000000 +0100
@@ -30,3 +30,10 @@ config HAS_ARM_AMBA_PL080
 
 endmenu
 
+config ARM_AMBA_DMA
+	bool "AMBA DMA support" if(ARCH_VERSATILE_PB || MACH_VERSATILE_AB)
+	depends on ARM_AMBA
+	select ISA_DMA_API
+	---help---
+	 Select to build ARM AMBA DMA support into the kernel.
+
diff -purN arm_amba_proc_dma/drivers/amba/Makefile
arm_amba_dma/drivers/amba/Makefile
--- arm_amba_proc_dma/drivers/amba/Makefile	2006-10-17
13:28:57.000000000 +0100
+++ arm_amba_dma/drivers/amba/Makefile	2006-10-17 17:10:15.000000000 +0100
@@ -1,2 +1,4 @@
-obj-y		+= bus.o
+obj-y				+= bus.o
+obj-$(CONFIG_ARM_AMBA_DMA)	+= dma.o
+
 
diff -purN arm_amba_proc_dma/drivers/amba/bus.c
arm_amba_dma/drivers/amba/bus.c
--- arm_amba_proc_dma/drivers/amba/bus.c	2006-10-17
13:28:57.000000000 +0100
+++ arm_amba_dma/drivers/amba/bus.c	2006-10-17 17:10:15.000000000 +0100
@@ -13,6 +13,9 @@
 #include <linux/string.h>
 #include <linux/slab.h>
 #include <linux/amba/bus.h>
+#ifdef CONFIG_ARM_AMBA_DMA
+# include <linux/amba/dma.h>
+#endif
 
 #include <asm/io.h>
 #include <asm/sizes.h>
@@ -241,6 +244,18 @@ int amba_device_register(struct amba_dev
  out:
 			release_resource(&dev->res);
 		}
+#ifdef CONFIG_ARM_AMBA_DMA
+		/*
+		 *  Inform AMBA DMA ontrol code of the board ops to be
called
+		 */
+		if(dev->board_ops){
+			amba_set_ops(dev->board_ops);
+		} 
+		/*
+		 *  Provide the AMBA device with access to the DMA channel
data
+		 */
+		dev->chans = amba_get_chans();
+#endif
 	}
 	return ret;
 }
@@ -321,6 +336,54 @@ amba_find_device(const char *busid, stru
 	return data.dev;
 }
 
+/*
+ * In the request & free we want to call versatile routines with the
amba_device *
+ * We need this so we can pass the driver the interrupt etc DMAC driver
routines
+ * & to identify the amba_device for e.g setting the DMA mapping register
+ */
+typedef struct _query_data {
+	struct amba_device * ad;
+	char * name;
+} query_data;
+
+static int match_name(struct device * dev, void * data){
+	query_data * d = (query_data*)data;
+	int ret = 0;
+
+	if(dev && data){
+		if(dev->driver){
+			if(dev->driver->name){
+				if(0 == strncmp(dev->driver->name, (const
char *)d->name, strlen(dev->driver->name))){
+					get_device(dev);
+					d->ad = to_amba_device(dev);
+					ret = 1;
+				}
+			} else {
+				printk(KERN_ERR "amba.c::match_name() - no
driver name for device %p, driver %p\n", dev, dev->driver);
+			}
+		} /* else no driver attached */
+	} else  {
+		printk(KERN_ERR "amba.c::match_name() - void pointer(s) dev
%p, data %p\n", dev, data);
+	}
+	return ret;
+}
+
+/*
+ * Attempt to match the passed name to the driver, if any, of each AMBA
device on the bus
+ */
+struct amba_device * amba_get_device_with_name(char * name){
+
+	query_data d;
+
+	d.ad = NULL;
+	d.name = name;
+
+	bus_for_each_dev(&amba_bustype, NULL, &d, match_name);
+
+	return d.ad;
+}
+EXPORT_SYMBOL(amba_get_device_with_name);
+
 /**
  *	amba_request_regions - request all mem regions associated with
device
  *	@dev: amba_device structure for device
diff -purN arm_amba_proc_dma/drivers/amba/dma.c
arm_amba_dma/drivers/amba/dma.c
--- arm_amba_proc_dma/drivers/amba/dma.c	1970-01-01
01:00:00.000000000 +0100
+++ arm_amba_dma/drivers/amba/dma.c	2006-10-17 17:10:15.000000000 +0100
@@ -0,0 +1,141 @@
+/*
+ * drivers/amba/dma.c
+ *
+ * Copyright (C) 2006 ARM Ltd, All Rights Reserved.
+ *
+ * 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.
+ *
+ * Shim for the AMBA DMA API
+ * Calls the board functions which may also call the controller, if any.
+ *
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/amba/bus.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/sizes.h>
+
+DEFINE_SPINLOCK(amba_dma_spin_lock);
+
+/* DMA operations of the board */
+struct dma_ops * board_ops = NULL;
+/* The DMA channel data */
+static dma_t * chans;
+
+void amba_set_ops(struct dma_ops * ops){
+	board_ops = ops;
+}
+
+/* Allow access to the DMA channel data held in arch/arm/kernel/dma.c */
+void __init arch_dma_init(dma_t *dma){
+	chans = dma;
+}
+
+dma_t * amba_get_chans(void){
+	return chans;
+}
+
+/*
+ * Successful int functions return 0
+ * - unless otherwise detailed
+ */
+
+static int	amba_request(dmach_t chan_num, dma_t * chan_data)
+{
+	int status = 0;
+
+	if((board_ops) && (board_ops->request)){
+		status = board_ops->request(chan_num, chan_data);
+	}
+
+	return status;
+}
+
+static void	amba_free(dmach_t chan_num, dma_t * chan_data)
+{
+	if((board_ops) && (board_ops->free)){
+		board_ops->free(chan_num, chan_data);
+	}
+}
+
+static void	amba_enable(dmach_t chan_num, dma_t * chan_data)
+{
+	if((board_ops) && (board_ops->enable)){
+		board_ops->enable(chan_num, chan_data);
+	}
+}
+
+static void 	amba_disable(dmach_t chan_num, dma_t * chan_data)
+{
+	if((board_ops) && (board_ops->disable)){
+		board_ops->disable(chan_num, chan_data);
+	}
+}
+
+/* ASSUME returns number of bytes */
+static int	amba_residue(dmach_t chan_num, dma_t * chan_data)
+{
+	int res = 0;
+	if((board_ops) && (board_ops->residue)){
+		res = board_ops->residue(chan_num, chan_data);
+	}
+	return res;
+}
+
+static int	amba_setspeed(dmach_t chan_num, dma_t * chan_data, int
speed)
+{
+	int new_speed = 0;
+	if((board_ops) && (board_ops->setspeed)){
+		new_speed = board_ops->setspeed(chan_num, chan_data, speed);
+	}
+	return new_speed;
+}
+
+/*
+ * AMBA ops to be called by kernel DMA functions
+ */
+static struct dma_ops amba_dma_ops = {
+	amba_request,	/* optional */
+	amba_free, 	/* optional */
+	amba_enable, 	/* mandatory */
+	amba_disable,	/* mandatory */
+	amba_residue,	/* optional */
+	amba_setspeed,	/* optional */
+	"AMBA DMA"
+};
+
+/*
+ * Initialize the dma channels, as far as we can
+ */
+void amba_init_dma(dma_t *channels)
+{
+	int i;
+
+	for(i = 0; i < MAX_DMA_CHANNELS; i++){
+		channels[i].sgcount = 0;
+		channels[i].sg = NULL;
+		channels[i].active = 0;
+		channels[i].invalid = 1;
+		// Disappeared channels[i].using_sg = 0;
+		channels[i].dma_mode = DMA_NONE;
+		channels[i].speed = 0;
+		channels[i].lock = 0;
+		channels[i].device_id = "UNASSIGNED";
+		channels[i].dma_base = (unsigned int)NULL;
+		channels[i].dma_irq = IRQ_DMAINT;
+		channels[i].state = 0;
+		channels[i].d_ops = &amba_dma_ops;
+	}
+}
+EXPORT_SYMBOL(amba_init_dma);
+
+
diff -purN arm_amba_proc_dma/include/asm-arm/arch-versatile/dma.h
arm_amba_dma/include/asm-arm/arch-versatile/dma.h
--- arm_amba_proc_dma/include/asm-arm/arch-versatile/dma.h	2006-10-17
13:29:29.000000000 +0100
+++ arm_amba_dma/include/asm-arm/arch-versatile/dma.h	2006-10-17
17:10:15.000000000 +0100
@@ -18,3 +18,4 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
USA
  */
+#define MAX_DMA_CHANNELS 0
diff -purN arm_amba_proc_dma/include/asm-arm/mach/dma.h
arm_amba_dma/include/asm-arm/mach/dma.h
--- arm_amba_proc_dma/include/asm-arm/mach/dma.h	2006-10-17
13:29:29.000000000 +0100
+++ arm_amba_dma/include/asm-arm/mach/dma.h	2006-10-17
17:10:15.000000000 +0100
@@ -10,6 +10,8 @@
  *  This header file describes the interface between the generic DMA
handler
  *  (dma.c) and the architecture-specific DMA backends (dma-*.c)
  */
+#ifndef _ASMARM_MACH_DMA_H_
+#define _ASMARM_MACH_DMA_H_
 
 struct dma_struct;
 typedef struct dma_struct dma_t;
@@ -22,6 +24,13 @@ struct dma_ops {
 	int	(*residue)(dmach_t, dma_t *);		/* optional */
 	int	(*setspeed)(dmach_t, dma_t *, int);	/* optional */
 	char	*type;
+#ifdef CONFIG_ARM_AMBA_DMA
+	/* 
+	 * Extra operations required by some particular AMBA DMA controller 
+	 * e.g. PL080
+	 */
+	void    * extra_ops;
+#endif
 };
 
 struct dma_struct {
@@ -55,3 +64,5 @@ struct dma_struct {
 extern void arch_dma_init(dma_t *dma);
 
 extern void isa_init_dma(dma_t *dma);
+
+#endif
diff -purN arm_amba_proc_dma/include/linux/amba/bus.h
arm_amba_dma/include/linux/amba/bus.h
--- arm_amba_proc_dma/include/linux/amba/bus.h	2006-10-17
13:29:40.000000000 +0100
+++ arm_amba_dma/include/linux/amba/bus.h	2006-10-17
17:10:16.000000000 +0100
@@ -10,6 +10,12 @@
 #ifndef ASMARM_AMBA_H
 #define ASMARM_AMBA_H
 
+#ifdef CONFIG_ARM_AMBA_DMA
+# include <asm/dma.h>
+# include <asm/mach/dma.h>
+# include <linux/amba/dma.h>
+#endif
+
 #define AMBA_NR_IRQS	2
 
 struct amba_device {
@@ -18,6 +24,12 @@ struct amba_device {
 	u64			dma_mask;
 	unsigned int		periphid;
 	unsigned int		irq[AMBA_NR_IRQS];
+#ifdef CONFIG_ARM_AMBA_DMA
+	struct amba_dma_data	dma_data;
+	struct dma_ops * 	board_ops;
+	struct dma_ops * 	dmac_ops;
+	dma_t *			chans;
+#endif
 };
 
 struct amba_id {
@@ -34,6 +46,9 @@ struct amba_driver {
 	int			(*suspend)(struct amba_device *,
pm_message_t);
 	int			(*resume)(struct amba_device *);
 	struct amba_id		*id_table;
+#ifdef CONFIG_ARM_AMBA_DMA
+	struct dma_ops * 	dmac_ops;
+#endif
 };
 
 #define amba_get_drvdata(d)	dev_get_drvdata(&d->dev)
@@ -47,9 +62,33 @@ struct amba_device *amba_find_device(con
 int amba_request_regions(struct amba_device *, const char *);
 void amba_release_regions(struct amba_device *);
 
+#ifdef CONFIG_ARM_AMBA_DMA
+struct amba_device * amba_get_device_with_name(char * name);
+#endif
+
 #define amba_config(d)	(((d)->periphid >> 24) & 0xff)
 #define amba_rev(d)	(((d)->periphid >> 20) & 0x0f)
 #define amba_manf(d)	(((d)->periphid >> 12) & 0xff)
 #define amba_part(d)	((d)->periphid & 0xfff)
 
+/* 
+ * AMBA ARM PrimeCell peripheral ids 
+ *
+ * Note that the hex number is that from the PrimeCell id
+ * e.g. pl041 has id 0x41 NOT 0x29
+ * hence OEM peripherals may use 0x<n>[a - f]
+ */
+#define AMBA_PERIPHID_UART	0x11
+#define AMBA_PERIPHID_SSI	0x22
+#define AMBA_PERIPHID_RTC	0x31
+#define AMBA_PERIPHID_AACI	0x40
+#define AMBA_PERIPHID_AACI_97	0x41
+#define AMBA_PERIPHID_KMI	0x50
+#define AMBA_PERIPHID_GPIO	0x61
+#define AMBA_PERIPHID_DMAC2	0x80
+#define AMBA_PERIPHID_DMAC1	0x81
+#define AMBA_PERIPHID_CLCD	0x110
+#define AMBA_PERIPHID_VIC	0x190
+#define AMBA_PERIPHID_CIM	0x321
+
 #endif
diff -purN arm_amba_proc_dma/include/linux/amba/dma.h
arm_amba_dma/include/linux/amba/dma.h
--- arm_amba_proc_dma/include/linux/amba/dma.h	1970-01-01
01:00:00.000000000 +0100
+++ arm_amba_dma/include/linux/amba/dma.h	2006-10-17
17:10:16.000000000 +0100
@@ -0,0 +1,35 @@
+/*
+ *  linux/include/linux/amba/dma.h
+ *
+ * Copyright (C) 2006 ARM Ltd, All Rights Reserved.
+ *
+ * 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.
+ *
+ *	DMA API for any AMBA system supporting DMA
+ *      i.e. there may or may not be an AMBA DMA controller in the system
+ */
+#ifndef LINUX_AMBA_DMA_H
+#define LINUX_AMBA_DMA_H
+
+/*
+ *  Data from the peripheral using DMA, required by the AMBA DMA manager
+ */
+struct amba_dma_data {
+	/* DMAC interrupt data as known by the board */
+	int  (*irq_ignore)(dmach_t chan);	/* Requesting device should
ignore this interrupt */
+	void (*irq_pre)   (dmach_t chan);	/* Any necessary DMA
operations e.g error checking */
+	void (*irq_post)  (dmach_t chan);	/* Clear the interrupt and
carry out any necessary DMA operations e.g re-enable DMAC channel */
+	unsigned int packet_size;		/* Size, in bytes, of each
DMA transfer packet */
+	void * dmac_data; 			/* DMA controller specific
data e.g. for pl080/pl041 TX transfers, the pl040 TX FIFO offset */ 
+};
+
+extern dma_t *		amba_get_chans	(void);
+extern void		amba_init_dma	(dma_t *channels);
+struct amba_device *	amba_get_device_with_name(char * name);
+void			amba_set_ops	(struct dma_ops * ops);
+
+#endif
+
+



-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux