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

From: Peter Pearse
Date: Mon Oct 30 2006 - 07:02:02 EST


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 <peter.pearse@xxxxxxx>

---

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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/