Commit 5d20c2cd authored by Peter Pearse's avatar Peter Pearse Committed by Catalin Marinas

Add DMA support for the Versatile AB and PB926 platforms

This patch also enables the DMA use for the AACI driver
Signed-off-by: default avatarPeter Pearse <peter.pearse@arm.com>
parent 6a457c22
......@@ -235,6 +235,8 @@ config ARCH_OMAP
config ARCH_VERSATILE
bool "Versatile"
select ARM_AMBA
select ARM_AMBA_PL080
select ARM_AMBA_PL041
select ARM_VIC
select ICST307
help
......@@ -337,8 +339,7 @@ config FORCE_MAX_ZONEORDER
menu "Bus support"
config ARM_AMBA
bool
source "drivers/amba/Kconfig"
config ISA
bool
......
......@@ -15,6 +15,8 @@
#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>
......@@ -25,23 +27,6 @@ EXPORT_SYMBOL(dma_spin_lock);
static dma_t dma_chan[MAX_DMA_CHANNELS];
/*
* Get dma list for /proc/dma
*/
int get_dma_list(char *buf)
{
dma_t *dma;
char *p = buf;
int i;
for (i = 0, dma = dma_chan; i < MAX_DMA_CHANNELS; i++, dma++)
if (dma->lock)
p += sprintf(p, "%2d: %14s %s\n", i,
dma->d_ops->type, dma->device_id);
return p - buf;
}
/*
* Request DMA channel
*
......@@ -140,8 +125,15 @@ void __set_dma_addr (dmach_t channel, void *addr)
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);
......@@ -264,4 +256,45 @@ static int __init init_dma(void)
return 0;
}
#ifdef CONFIG_PROC_FS
static int proc_dma_show(struct seq_file *m, void *v)
{
int i;
for (i = 0 ; i < MAX_DMA_CHANNELS ; i++) {
if (dma_chan[i].lock) {
seq_printf(m, "%2d: %14s %s\n", i,
dma_chan[i].d_ops->type, dma_chan[i].device_id);
}
}
return 0;
}
static int proc_dma_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_dma_show, NULL);
}
static struct file_operations proc_dma_operations = {
.open = proc_dma_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init proc_dma_init(void)
{
if(MAX_DMA_CHANNELS > 0){
struct proc_dir_entry *e;
e = create_proc_entry("dma", 0, NULL);
if (e)
e->proc_fops = &proc_dma_operations;
}
return 0;
}
__initcall(proc_dma_init);
#endif
core_initcall(init_dma);
......@@ -6,3 +6,5 @@ obj-y := core.o clock.o
obj-$(CONFIG_ARCH_VERSATILE_PB) += versatile_pb.o
obj-$(CONFIG_MACH_VERSATILE_AB) += versatile_ab.o
obj-$(CONFIG_PCI) += pci.o
obj-$(CONFIG_ARM_AMBA_DMA) += dma.o
......@@ -47,13 +47,16 @@
#include "core.h"
#include "clock.h"
#ifdef CONFIG_ARM_AMBA_DMA
# include "dma.h"
#endif
/*
* All IO addresses are mapped onto VA 0xFFFx.xxxx, where x.xxxx
* is the (PA >> 12).
*
* Setup a VA for the Versatile Vectored Interrupt Controller.
*/
#define __io_address(n) __io(IO_ADDRESS(n))
#define VA_VIC_BASE __io_address(VERSATILE_VIC_BASE)
#define VA_SIC_BASE __io_address(VERSATILE_SIC_BASE)
......@@ -707,7 +710,13 @@ AMBA_DEVICE(uart1, "dev:f2", UART1, NULL);
AMBA_DEVICE(uart2, "dev:f3", UART2, NULL);
AMBA_DEVICE(ssp0, "dev:f4", SSP, NULL);
static struct amba_device *amba_devs[] __initdata = {
static struct amba_device *amba_devs[]
#ifdef CONFIG_ARM_AMBA_DMA
// Do not discard - used by AMBA DMA code
// These devices are common to Versatile AB && PB
__initdata
#endif
= {
&dmac_device,
&uart0_device,
&uart1_device,
......
......@@ -30,6 +30,12 @@ extern void __init versatile_map_io(void);
extern struct sys_timer versatile_timer;
extern unsigned int mmc_status(struct device *dev);
/*
* All IO addresses are mapped onto VA 0xFFFx.xxxx, where x.xxxx
* is the (PA >> 12).
*/
#define __io_address(n) __io(IO_ADDRESS(n))
#define AMBA_DEVICE(name,busid,base,plat) \
static struct amba_device name##_device = { \
.dev = { \
......@@ -44,7 +50,6 @@ static struct amba_device name##_device = { \
}, \
.dma_mask = ~0, \
.irq = base##_IRQ, \
/* .dma = base##_DMA,*/ \
}
#endif
/*
* linux/arch/arm/mach-versatile/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 as published by
* the Free Software Foundation; either version 2 of the License, 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Board specific dma code.
* This board knows
* - it has an AMBA bus with
* AMBA pl080 bus DMA controller
* as device dev:30
*
*/
#include <linux/init.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl080.h>
#include <asm/arch/hardware.h>
#include <asm/mach-types.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "core.h"
#include "dma.h"
/* The pl080 ops to be called by the versatile */
static struct dma_ops dmac_ops;
variant_ops vops;
/*
* Entries must be in the same order as the amba_devs array
* - only aaci is implemented at present
* TODO:: Some parts of the values should be constructed
* from the parts which might vary.....
*/
setting settings[] = {
{0x80,0,0}, /* dmac_device */
{0,0}, /* uart0_devic */
{0,0}, /* uart1_devic */
{0,0}, /* uart2_devic */
{0,0}, /* smc_device, */
{0,0}, /* mpmc_device */
{0,0}, /* clcd_device */
{0,0}, /* sctl_device */
{0,0}, /* wdog_device */
{0,0}, /* gpio0_devic */
{0,0}, /* gpio1_devic */
{0,0}, /* rtc_device, */
{0,0}, /* sci0_device */
{0,0}, /* ssp0_device */
{0x41,VDMA_AACI_CCTL, VDMA_AACI_CCFG}, /* aaci_device */
{0,0}, /* mmc0_device */
{0,0}, /* kmi0_device */
{0,0} /* kmi1_device */
};
/*
* Return 0 for success
*/
static int versatile_request(dmach_t chan_num, dma_t * chan_data)
{
int status = -EINVAL;
struct amba_device * client;
/*
* Find the driver data for the named device
*/
client = amba_get_device_with_name((char*)chan_data->device_id);
/* Configure the DMA mechanism e.g. the AMBA DMA controller */
if(!vops.versatile_dma_configure(chan_num, chan_data, client))
status = -EINVAL;
else {
/*
* Check pl080 is happy before doing anything complicated
*/
if(!dmac_ops.request(chan_num, chan_data)) {
if(!vops.versatile_dma_transfer_setup(chan_num, chan_data, client))
status = -EINVAL;
else
status = 0;
}
}
return status;
}
static void versatile_free(dmach_t chan_num, dma_t * chan_data)
{
struct amba_device * client;
/*
* Find the driver data for the named device
*/
client = amba_get_device_with_name((char*)chan_data->device_id);
/*
*Deconfigure the DMA mechanism e.g. the AMBA DMA controller
* and any machine settings e.g. DMA mapping register
*/
switch(amba_part(client)) {
case AMBA_PERIPHID_AACI_97:
switch(chan_num){
case 0:
case 1:
case 2:
{
#ifdef CONFIG_ARCH_VERSATILE_PB
if(machine_is_versatile_pb()){
void __iomem **map_base = __io_address(VERSATILE_SYS_BASE) + VERSATILE_SYS_DMAPSR0_OFFSET + (chan_num * 4);
/*
* Clear down the V/PB DMA mapping register connection
*/
writel(
// [31:8] Reserved
(0 << 7) | // 0 = Disable this mapping
// [6:5] Reserved
(0 << 0) // 0b00000 = AACI Tx
, &map_base[chan_num]);
}
#endif
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_free() Invalid DMA channel 0x%02x for AACI \n", chan_num);
break;
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_free() Unexpected device %p, periphid part number 0x%03x - AACI is 0x%03x\n", client, amba_part(client), AMBA_PERIPHID_AACI_97);
break;
}
dmac_ops.free(chan_num, chan_data);
}
void versatile_enable(dmach_t chan_num, dma_t * chan_data)
{
pl080_reset_cycle(chan_num);
dmac_ops.enable(chan_num, chan_data);
}
void versatile_disable(dmach_t chan_num, dma_t * chan_data)
{
dmac_ops.disable(chan_num, chan_data);
}
/* ASSUME returns bytes */
int versatile_residue(dmach_t chan_num, dma_t * chan_data)
{
int res = 0;
return res;
}
int versatile_setspeed(dmach_t chan_num, dma_t * chan_data, int speed)
{
int new_speed = 0;
return new_speed;
}
/*
* Board ops to be called by AMBA functions
*/
static struct dma_ops versatile_dma_ops = {
versatile_request, /* optional */
versatile_free, /* optional */
versatile_enable, /* mandatory */
versatile_disable, /* mandatory */
versatile_residue, /* optional */
versatile_setspeed, /* optional */
"VERSATILE DMA"
};
void __init arch_dma_init(dma_t *dma)
{
#ifdef CONFIG_ARM_AMBA_DMA
/* Adjust the DMA channel configuration values for the peripherals */
if(machine_is_versatile_ab()){
/*
* AACI is peripheral 1 on the VAB, 0 on the VPB
* VDMA_AACI_CCFG is VPB (peripheral 0) value
*/
settings[0x0E].cfg |= VDMA_CCFG_PERIPHAL_NUM_1;
}
amba_init_dma(dma);
pl080_init_dma(dma, &dmac_ops);
amba_set_ops(&versatile_dma_ops);
#endif
}
/*
* linux/include/asm-arm/hardware/amba.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.
*
* Pass dma symbols to the core.c code
*/
extern int versatile_init_dma_channels(void __iomem *base);
/*
* Board variant specific functions
*/
typedef struct _variant_ops{
const int (*versatile_dma_configure )(dmach_t chan_num, dma_t * chan_data, struct amba_device * client);
const int (*versatile_dma_transfer_setup)(dmach_t chan_num, dma_t * chan_data, struct amba_device * client);
} variant_ops;
extern variant_ops vops;
#define VA_SYS_BASE (__io(IO_ADDRESS(VERSATILE_SYS_BASE)))
......@@ -44,3 +44,131 @@ MACHINE_START(VERSATILE_AB, "ARM-Versatile AB")
.timer = &versatile_timer,
.init_machine = versatile_init,
MACHINE_END
#ifdef CONFIG_ARM_AMBA_DMA
#include "dma.h"
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/amba/pl080.h>
extern setting settings[];
/*
* Configure the board && the dma controller channel for the requesting peripheral
* as far as possible when the actual transfer
* (source address, size, dest address, etc.) is not known
*
* Versatile AB :
*
* DMA Peripheral
*
* 15 UART0 Tx
* 14 UART0 Rx
* 13 UART1 Tx
* 12 UART1 Rx
* 11 UART2 Tx
* 10 UART2 Rx
* 9 SSP Tx
* 8 SSP Rx
* 7 SCI Tx
* 6 SCI Rx
*
* 5-3 Unused
*
* FPGA peripherals using DMA channels 0,1,2
*
* 2 MMCI
* 1 AACI Tx
* 0 AACI Rx
*
* Return 1 for success
*
*/
const int versatileab_dma_configure(dmach_t chan_num, dma_t * chan_data, struct amba_device * client){
int retval = 0;
/* Switch by client AMBA device part number*/
switch(amba_part(client)) {
case AMBA_PERIPHID_AACI_97:
/*
* Only DMA channel 1 can be used for AACI DMA TX
*/
switch(chan_num){
case 1:
pl080_configure_chan(chan_num, &client->dma_data);
retval = 1;
break;
default:
break;
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_configure() Unexpected device %p, periphid part number 0x%03x\n", client, amba_part(client));
}
return retval;
}
const int versatileab_dma_transfer_setup(dmach_t chan_num, dma_t * chan_data, struct amba_device * client){
pl080_lli setting;
unsigned int ccfg = 0;
dma_addr_t cllis = 0;
int retval = 0;
unsigned int periph_index = 0;
switch(amba_part(client)) {
case AMBA_PERIPHID_AACI_97:
{
// TODO:: RX DMA
while(amba_part(client) != settings[periph_index].periphid){
periph_index++;
}
ccfg = settings[periph_index].cfg;
/* Destination is the FIFO read/write register */
setting.dest = VERSATILE_AACI_BASE + (unsigned int)client->dma_data.dmac_data;
setting.cword = settings[periph_index].ctl;
/*
* Construct the LLIs
*/
// TODO:: determine whether the bus address needs the bus distinguishing bit set
// - hard code the 1 for now
setting.next = 1;
cllis = pl080_make_llis(chan_num, chan_data->buf.dma_address, chan_data->count ,client->dma_data.packet_size, &setting);
if(cllis) {
retval = 1;
} else {
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_transfer_setup() No memory for LLIs\n");
}
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_transfer_setup() - Unexpected device %p, periphid part number 0x%03x\n", client, amba_part(client));
return 0;
break;
}
if(retval)
pl080_transfer_configure(chan_num, &setting, ccfg);
return retval;
}
static int __init versatile_ab_init(void)
{
if (machine_is_versatile_ab()) {
vops.versatile_dma_configure = versatileab_dma_configure ;
vops.versatile_dma_transfer_setup = versatileab_dma_transfer_setup;
}
return 0;
}
arch_initcall(versatile_ab_init);
#endif
......@@ -82,6 +82,155 @@ static struct amba_device *amba_devs[] __initdata = {
&mmc1_device,
};
#ifdef CONFIG_ARM_AMBA_DMA
#include "dma.h"
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/amba/pl080.h>
extern setting settings[];
/*
* Configure the board && the dma controller channel for the requesting peripheral
* as far as possible when the actual transfer
* (source address, size, dest address, etc.) is not known
*
* Versatile PB :
*
* Other possible assignments:
* DMA Peripheral
*
* 15 UART0 Tx
* 14 UART0 Rx
* 13 UART1 Tx
* 12 UART1 Rx
* 11 UART2 Tx
* 10 UART2 Rx
* 9 SSP Tx
* 8 SSP Rx
* 7 SCI Tx
* 6 SCI Rx
*
* 5-3 I/O device in RealView Logic Tile
*
* 2-0 I/O device in RealView Logic Tile or FPGA
*
* FPGA peripherals using DMA channels 0,1,2
*
* AACI Tx
* AACI Rx
* USB A
* USB B
* MCI 0
* MCI 1
* UART3 Tx
* UART3 Rx
* SCI0 int A
* SCI0 int B
*
* Return 1 for success
*
*/
const int versatilepb_dma_configure(dmach_t chan_num, dma_t * chan_data, struct amba_device * client){
int retval = 0;
/*
* Versatile DMA mapping register for assigned DMA channel
*/
void __iomem **map_base = __io_address(VERSATILE_SYS_BASE) + VERSATILE_SYS_DMAPSR0_OFFSET + (chan_num * 4);
struct amba_dma_data * data = &client->dma_data;
/* Switch by client AMBA device part number*/
switch(amba_part(client)) {
case AMBA_PERIPHID_AACI_97:
/*
* Only DMA channels 0,1,2 can be used for AACI DMA
*/
switch(chan_num){
case 0:
case 1:
case 2:
/*
* Set V/PB DMA mapping register to connect
* AACI Tx DMAC request signals to DMAC peripheral #0 request lines
*
* ASSUMES Tx
* TODO:: Distinguish Tx/Rx
*/
writel(
// [31:8] Reserved
(1 << 7) | // 1 = Enable this mapping
// [6:5] Reserved
(0 << 0) // 0b00000 = AACI Tx
, &map_base[chan_num]);
pl080_configure_chan(chan_num, data);
retval = 1;
break;
default:
break;
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_configure() Unexpected device %p, periphid part number 0x%03x\n", client, amba_part(client));
}
return retval;
}
const int versatilepb_dma_transfer_setup(dmach_t chan_num, dma_t * chan_data, struct amba_device * client){
pl080_lli setting;
unsigned int ccfg = 0;
dma_addr_t cllis = 0;
int retval = 0;
unsigned int periph_index = 0;
switch(amba_part(client)) {
case AMBA_PERIPHID_AACI_97:
{
while(amba_part(client) != settings[periph_index].periphid){
periph_index++;
}
ccfg = settings[periph_index].cfg;
/* Destination is the FIFO read/write register */
setting.dest = VERSATILE_AACI_BASE + (unsigned int)client->dma_data.dmac_data;
setting.cword = settings[periph_index].ctl;
/*
* Construct the LLIs
*/
// TODO:: determine whether the bus address needs the bus distinguishing bit set
// - hard code the 1 for now
setting.next = 1;
cllis = pl080_make_llis(chan_num, chan_data->buf.dma_address, chan_data->count ,client->dma_data.packet_size, &setting);
if(cllis) {
retval = 1;
} else {
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_transfer_setup() No memory for LLIs\n");
}
}
break;
default:
printk(KERN_ERR "mach-versatile/dma.c::versatile_dma_transfer_setup() - Unexpected device %p, periphid part number 0x%03x\n", client, amba_part(client));
return 0;
break;
}
if(retval)
pl080_transfer_configure(chan_num, &setting, ccfg);
return retval;
}
#endif
static int __init versatile_pb_init(void)
{
int i;
......@@ -91,8 +240,30 @@ static int __init versatile_pb_init(void)
struct amba_device *d = amba_devs[i];
amba_device_register(d, &iomem_resource);
}
}
#ifdef CONFIG_ARM_AMBA_DMA
{
volatile unsigned int r;
/*
* Initial disposition of the DMA select signals
* - later a contention mechanism must be implemented to allow
* apps/drivers of the 10 FPGA sources to contend for the 3 lines
*/
/* AACI TX is line 0 */
r = readl(VA_SYS_BASE + VERSATILE_SYS_DMAPSR0_OFFSET);
mb();
r &= VSYSMASK_DMAPSR;
r |= VSYS_VAL_DMAPSR_AACI_TX;
r |= VSYS_VAL_DMAPSR_ENABLE;
writel(r, VA_SYS_BASE + VERSATILE_SYS_DMAPSR0_OFFSET);
mb();
vops.versatile_dma_configure = versatilepb_dma_configure ;
vops.versatile_dma_transfer_setup = versatilepb_dma_transfer_setup;
}
#endif
}
return 0;
}
......
config ARM_AMBA
bool "ARM AMBA bus"
---help---
This motherboard has AMBA bus support.
config ARM_AMBA_DMA
bool "AMBA DMA"
depends on ARM_AMBA && (ARCH_VERSATILE_PB || MACH_VERSATILE_AB)
select ISA_DMA_API
default y
---help---
This motherboard has AMBA DMA support.
If you wish DMA capable drivers to use it,
say Y, otherwise N.
config ARM_AMBA_PL080
bool "AMBA PrimeCell DMAC PL080" if (ARCH_VERSATILE_PB || MACH_VERSATILE_AB)
depends on ARM_AMBA_DMA
---help---
This board has an AMBA PrimeCell PL080 DMA Controller.
It will be used if AMBA DMA is selected.
config ARM_AMBA_PL041
bool "AMBA PrimeCell AACI AC'97" if (ARCH_VERSATILE_PB || MACH_VERSATILE_AB)
depends on ARM_AMBA
---help---
This board has an AMBA PrimeCell Advanced Audio Codec Interface.
It can be selected under sound/Advanced Linux Sound Architecture/ALSA ARM devices.
obj-y += bus.o
obj-$(CONFIG_ARM_AMBA_DMA) += dma.o
obj-$(CONFIG_ARM_AMBA_PL080) += pl080.o
......@@ -349,6 +349,57 @@ void amba_release_regions(struct amba_device *dev)
release_mem_region(dev->res.start, SZ_4K);
}
#ifdef CONFIG_ARCH_VERSATILE
/*
* 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);
#endif
EXPORT_SYMBOL(amba_driver_register);
EXPORT_SYMBOL(amba_driver_unregister);
EXPORT_SYMBOL(amba_device_register);
......
/*
* 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.
*
* Wrapper 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;
void amba_set_ops(struct dma_ops * ops){
board_ops = ops;
}
/*
* Successful int functions return 0
* - unless otherwise detailed
*/
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;
}
void amba_free(dmach_t chan_num, dma_t * chan_data)
{
if((board_ops) && (board_ops->free)){
board_ops->free(chan_num, chan_data);
}
}
void amba_enable(dmach_t chan_num, dma_t * chan_data)
{
if((board_ops) && (board_ops->enable)){
board_ops->enable(chan_num, chan_data);
}
}
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 */
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;
}
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 __init 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;
}
}
/*
* drivers/amba/pl080.c - ARM PrimeCell DMA Controller PL080 driver
*
* 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.
*
* Documentation: ARM DDI 0196F
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/amba/bus.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/sizes.h>
#include <asm/dma.h>
#include <asm/mach/dma.h>
#include <linux/amba/dma.h>
#include <linux/amba/pl080.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/ac97_codec.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#define DRIVER_NAME "dmac-pl080"
/*
* PM support is not complete. Turn it off.
*/
#undef CONFIG_PM
static int pl080_request(dmach_t cnum, dma_t * cdata);
static void pl080_free(dmach_t cnum, dma_t * cdata);
static void pl080_enable(dmach_t cnum, dma_t * cdata);
static void pl080_disable(dmach_t cnum, dma_t * cdata);
static int pl080_residue(dmach_t cnum, dma_t * cdata);
static int pl080_setspeed(dmach_t cnum, dma_t * cdata, int newspeed);
struct pl080_tag {
/* pl080 register base */
void __iomem * base;
/*
* - dma_pool for the LLIs
* (can't use dma_alloc_coherent/free because of the requirement for
* free() to have interrupts enabled when it is called)
*/
struct dma_pool * pool;
/* LLI details for each DMA channel */
chanlli * chanllis;
} pl080;
static struct amba_driver pl080_driver;
/* All int functions return 0 for success unless detailed */
static int pl080_request(dmach_t cnum, dma_t * cdata){
return 0;
}
static void pl080_free(dmach_t chan_num, dma_t * cdata){
unsigned int i, active_count = 0;
/*
* Free any DMA memory in use for the LLIs
*/
if((pl080.chanllis[chan_num].va_list) && (pl080.chanllis[chan_num].bus_list)){
dma_pool_free(pl080.pool, pl080.chanllis[chan_num].va_list, pl080.chanllis[chan_num].bus_list);
pl080.chanllis[chan_num].num_entries = 0;
pl080.chanllis[chan_num].va_list = NULL;
pl080.chanllis[chan_num].bus_list = (dma_addr_t)NULL;
}
/*
* If no DMA channels active then deconfigure the DMAC entirely
*/
for(i = 0; i < MAX_DMA_CHANNELS; i++)
active_count += dma_channel_active((dmach_t)i);
if(!active_count)
{
unsigned int r;
r = readl(pl080.base + PL080_OS_CFG);
r &= PL080_MASK_CFG;
r &= ~PL080_MASK_EN;
writel(r, pl080.base + PL080_OS_CFG);
}
}
/*
* Enable the DMA channel & terminal count interrupts on it
* TODO:: Currently hard coded for memory to peripheral transfer (DMA in control)
* - flow control setting should be stored in cdata
*/
inline void pl080_enable(dmach_t cnum, dma_t * cdata){
void __iomem * cbase = pl080.base + PL080_OS_CHAN_BASE + (cnum * PL080_OS_CHAN);
volatile unsigned int r = 0;
/*
* Do not access config register until channel shows as disabled
*/
while((readl(pl080.base + PL080_OS_ENCHNS) & (1<< cnum)) & PL080_MASK_ENCHNS){
;
}
mb();
r = readl(cbase + PL080_OS_CCFG );
mb();
writel((r | PL080_MASK_CEN | PL080_MASK_INTTC | PL080_MASK_INTERR | PL080_FCMASK_M2P_DMA) &~(PL080_MASK_HALT), cbase + PL080_OS_CCFG );
}
/*
* Disable without losing data in the channel's FIFO:
* - Set the Halt bit so that subsequent DMA requests are ignored.
* - Poll the Active bit until it reaches 0, indicating that there is no data
* remaining in the channel's FIFO.
* - Clear the Channel Enable bit.
*
* Currently not implemented correctly in the hardware
*
*/
/*
* TODO:: Currently hard coded for memory to peripheral transfer (DMA in control)
* - flow control setting should be stored in cdata
*/
inline void pl080_disable(dmach_t cnum, dma_t * cdata){
void __iomem * cbase = pl080.base + PL080_OS_CHAN_BASE + (cnum * PL080_OS_CHAN);
volatile unsigned int r = readl(cbase + PL080_OS_CCFG );
#ifdef ERRATUM_FIXED
mb();
writel(r | PL080_MASK_HALT, cbase + PL080_OS_CCFG );
mb();
while(readl(cbase + PL080_OS_CCFG ) & PL080_MASK_ACTIVE){
;
}
#else
// printk("pl080_disable() - check if we can use the active flag\n");
// printk(" - this code doesn't\n");
#endif
r = readl(cbase + PL080_OS_CCFG );
mb();
writel(r & ~(PL080_MASK_CEN) & ~(PL080_MASK_INTTC) & ~(PL080_MASK_INTERR & ~(PL080_FCMASK_M2P_DMA)), cbase + PL080_OS_CCFG );
mb();
while(readl(cbase + PL080_OS_CCFG ) & PL080_MASK_CEN){
;
}
}
/* Disable the channel, read the control register, re-enable the channel */
/* May not be an accurate value - see TRM */
/* ASSUME returns bytes */
static int pl080_residue(dmach_t cnum, dma_t * cdata){
void __iomem * cbase = pl080.base + PL080_OS_CHAN_BASE + (cnum * PL080_OS_CHAN) + PL080_OS_CCTL;
volatile unsigned int r = 0;
pl080_disable(cnum, cdata);
mb();
/* The number of source width transfers (AACI == 32 bits) completed */
r = readl(cbase + PL080_OS_CCTL ) & PL080_MASK_TSFR_SIZE;
mb();
pl080_enable(cnum, cdata);
return r * 4;
}
/* Not implemented - should return the new speed */
static int pl080_setspeed(dmach_t cnum, dma_t * cdata, int newspeed){
return 0;
}
#ifdef PL080_IRQ_REQUIRED
/* Each device requesting DMA chains its interrupt handler to the DMA interrupt,
* rather than this handler attaching this interrupt....
*/
static irqreturn_t pl080_irq(int irq, void *devid, struct pt_regs *regs)
{
u32 mask = 0;
return mask ? IRQ_HANDLED : IRQ_NONE;
}
#endif
/*
* Power Management.
*/
#ifdef CONFIG_PM
#else
#define pl080_do_suspend NULL
#define pl080_do_resume NULL
#define pl080_suspend NULL
#define pl080_resume NULL
#endif
/*
* Complete the DMA channel initialization
* started by the AMBA DMA code
* - Set the ops for the board to call
*/
char pl080_type[0x10] = "PL080 DMAC";
int pl080_init_dma(dma_t * dma_chan, struct dma_ops * ops){
int i;
for(i = 0; i < MAX_DMA_CHANNELS; i++){
dma_chan[i].dma_base = (unsigned int)pl080.base;
}
ops->request = pl080_request;
ops->free = pl080_free;
ops->enable = pl080_enable ;
ops->disable = pl080_disable;
ops->residue = pl080_residue;
ops->setspeed = pl080_setspeed;
ops->type = pl080_type;
return 0;
}
static int __devinit pl080_probe(struct amba_device *dev, void *id)
{
int ret,i;
ret = amba_request_regions(dev, NULL);
if (ret)
return ret;
pl080.base = ioremap(dev->res.start, SZ_4K);
if (!pl080.base) {
ret = -ENOMEM;
goto out;
}
/*
* Make one struct for each DMA channel
* to hold details of its LLIs
*/
pl080.chanllis = kmalloc(sizeof(chanlli) * MAX_DMA_CHANNELS, GFP_KERNEL);
if (!pl080.chanllis) {
ret = -ENOMEM;
goto out;
}
for(i = 0; i < MAX_DMA_CHANNELS; i++){
pl080.chanllis[i].num_entries = 0;
pl080.chanllis[i].va_list = NULL;
pl080.chanllis[i].bus_list = (dma_addr_t)NULL;
}
if(!(pl080.pool = dma_pool_create(pl080_driver.drv.name, (struct device *)dev,
PL080_MAX_LLIS_SIZE, PL080_ALIGN, PL080_ALLOC))){
kfree(pl080.chanllis);
ret = -ENOMEM;
goto out;
}
out:
amba_release_regions(dev);
return ret;
}
static int __devexit pl080_remove(struct amba_device *dev)
{
if(pl080.pool){
dma_pool_destroy(pl080.pool);
pl080.pool = NULL;
}
return 0;
}
static struct amba_id pl080_ids[] = {
{
.id = 0x00041080,
.mask = 0x000fffff,
},
{ 0, 0 },
};
static struct amba_driver pl080_driver = {
.drv = {
.name = DRIVER_NAME,
},
.probe = pl080_probe,
.remove = __devexit_p(pl080_remove),
.suspend = pl080_suspend,
.resume = pl080_resume,
.id_table = pl080_ids,
};
/*
* Module initialization
*/
static int __init pl080_init(void)
{
pl080.base = NULL;
pl080.chanllis = NULL; /* LLI details for each DMA channel */
pl080.pool = NULL;
return amba_driver_register(&pl080_driver);
}
/*
* Module destruction
*/
static void __exit pl080_exit(void)
{
amba_driver_unregister(&pl080_driver);
}
/*
* Set up a circular linked list of period sized packets
* We loop until stopped by another entity
*
* TODO Abstract this function for use by any device.
* It has only been tested with VersatilePB/AACI
*
* - CAUTION ASSUMES FIXED dest, cword ?? other??
*
* All addresses stored in the LLI must be bus addresses
* Set the lower bit of the bus address to ensure the correct bus is used
* dma_chan[chan_num] holds the DMA buffer info
*
* Return bus address of first LLI
*/
dma_addr_t pl080_make_llis(dmach_t chan_num, unsigned int address, unsigned int length, unsigned int packet_size, pl080_lli * setting){
int i;
unsigned int num_llis = 0;
/*
* Whether we increment the lli address to indicate the bus
* for this transfer
*/
unsigned int bus_increment = setting->next;
pl080_lli * llis_bus = NULL;
pl080_lli * llis_va = NULL;
if(NULL != pl080.chanllis[chan_num].va_list){
/*
* Repeated call - destroy previous LLIs
*/
dma_pool_free(pl080.pool, pl080.chanllis[chan_num].va_list, pl080.chanllis[chan_num].bus_list);
pl080.chanllis[chan_num].num_entries = 0;
pl080.chanllis[chan_num].va_list = NULL;
pl080.chanllis[chan_num].bus_list = (dma_addr_t)NULL;
}
/*
* dma_chan[chan_num] holds the DMA buffer info
*/
setting->start = address;
num_llis = length / packet_size;
/* Get memory for the LLIs */
if(PL080_MAX_LLIS_SIZE < num_llis * sizeof(pl080_lli)){
printk(KERN_ERR "pl080.c::make_lli_aaci() - 0x%08x bytesNeed for the LLIs, consider rebuilding with PL080_MAX_LLIS_SIZE increased\n", num_llis * sizeof(pl080_lli));
} else {
pl080.chanllis[chan_num].va_list = dma_pool_alloc(pl080.pool, GFP_KERNEL, &pl080.chanllis[chan_num].bus_list);
}
if(NULL == pl080.chanllis[chan_num].va_list){
printk(KERN_ERR "pl080.c::make_lli_aaci() - Failed to get DMA memory for the LLIs\n");
return (dma_addr_t)NULL;
}
pl080.chanllis[chan_num].num_entries = num_llis;
llis_va = (pl080_lli *) pl080.chanllis[chan_num].va_list;
llis_bus = (pl080_lli *) pl080.chanllis[chan_num].bus_list;
for(i = 0; i < num_llis - 1; i++){
llis_va[i].start = (dma_addr_t)((unsigned int)setting->start + (i * packet_size));
llis_va[i].dest = setting->dest;
llis_va[i].cword = setting->cword;
/*
* Adjust to access the memory on the correct DMA bus
*/
llis_va[i].next = (dma_addr_t)((unsigned int) &llis_bus[i + 1] + bus_increment);
}
llis_va[i].start = (dma_addr_t)((unsigned int)setting->start + (i * packet_size));
llis_va[i].dest = setting->dest;
llis_va[i].cword = setting->cword;
llis_va[i].next = (dma_addr_t)((unsigned int) &llis_bus[0] + bus_increment);
/*
* Initial register setting
*/
setting->next = llis_va[0].next;
// printk("\nPMP llis at %p\n", pl080.chanllis[chan_num].va_list);
return pl080.chanllis[chan_num].bus_list;
}
/*
* Generalized interrupt handling
* i.e. not the terminal count part which will be device specific
*/
/* Handle only interrupts on the correct channel */
static int pl080_ignore_this_irq(dmach_t dma_chan){
unsigned int r = readl(pl080.base + PL080_OS_ISR);
return !(r & 1 << dma_chan);
}
/*
* TODO:: Report the errors, rather than just clearing them
*/
static unsigned int errCtr = 0;
static void pl080_pre_irq(dmach_t dma_chan){
unsigned int isr_err = readl(pl080.base + PL080_OS_ISR_ERR);
if((1 << dma_chan) & isr_err){
errCtr++;
}
}
/* Clear any interrupts on the correct channel */
static void pl080_post_irq(dmach_t dma_chan){
/* Finally clear the interrupt of both kinds */
writel((1 << dma_chan), pl080.base + PL080_OS_ICLR_TC);
writel((1 << dma_chan), pl080.base + PL080_OS_ICLR_ERR);
}
/*
* Configure the DMAC
*
* LittleEndian, LittleEndian, disabled
*/
void pl080_configure(void){
unsigned int reg;
reg = readl(pl080.base + PL080_OS_CFG);
reg &= PL080_MASK_CFG;
reg |= PL080_MASK_EN;
writel(reg, pl080.base + PL080_OS_CFG);
}
/*
* Configure the DMA channel
*
* Set up interrupt calls for devices to use
* TODO:: Find a neater way.....
*/
void pl080_configure_chan(dmach_t chan_num, struct amba_dma_data * data){
/*
* Set address of DMA channel registers
*/
unsigned int chan_base = (unsigned int)pl080.base + PL080_OS_CHAN_BASE;
chan_base += chan_num * PL080_OS_CHAN;
/*
* The interrupt handlers & destination are known
*/
/*
* Interrupt data
* - DMAC interrupt status register address
* - mask to use
* - DMAC interrupt clear address
* - mask(s) to use
*/
data->irq_ignore = pl080_ignore_this_irq;
data->irq_pre = pl080_pre_irq;
data->irq_post = pl080_post_irq;
/*
* Always configure the pl080 itself,
* in case this is the first channel configuration
*/
pl080_configure();
}
/*
* Restart the LLIs by setting the channel LLI
* register to point to the first entry
*
* Useful where e.g. the data supplier
* restarts due to perceived underrun
*/
void pl080_reset_cycle(dmach_t cnum){
void __iomem * cbase = (void __iomem * )((unsigned int)pl080.base + (unsigned int)PL080_OS_CHAN_BASE + (unsigned int)(cnum * PL080_OS_CHAN));
writel((unsigned int)(((unsigned int)(pl080.chanllis[cnum].bus_list)) + 1), cbase + PL080_OS_CLLI);
}
/*
* Set up the DMA channel registers for a transfer
* whose LLIs are ready
*/
void pl080_transfer_configure(dmach_t chan_num, pl080_lli *setting, unsigned int ccfg){
unsigned int chan_base = (unsigned int)pl080.base + PL080_OS_CHAN_BASE;
chan_base += chan_num * PL080_OS_CHAN;
writel(setting->start, chan_base + PL080_OS_CSRC);
writel(setting->dest , chan_base + PL080_OS_CDST);
writel(setting->next , chan_base + PL080_OS_CLLI);
writel(setting->cword, chan_base + PL080_OS_CCTL);
writel(ccfg , chan_base + PL080_OS_CCFG);
}
module_init(pl080_init);
module_exit(pl080_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ARM PrimeCell PL080 DMA Controller driver");
......@@ -18,3 +18,15 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __ASM_ARCH_DMA_H
#define __ASM_ARCH_DMA_H
#define MAX_DMA_ADDRESS 0xffffffff
#ifdef CONFIG_ARM_AMBA_DMA
# define MAX_DMA_CHANNELS 8
#else
# define MAX_DMA_CHANNELS 0
#endif
#endif /* _ASM_ARCH_DMA_H */
......@@ -49,4 +49,75 @@
/* macro to get at IO space when running virtually */
#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
#ifdef CONFIG_ARM_AMBA_DMA
/* DMA defines for AACI/DMAC transfers */
// Packet is the data transferred during the run of one LLI
// Memory boundary size means the byte size of the data packet transferred by one LLI
// Burst sizes are in bytes
// Transfer size is destination burst sizes in a source burst == run of one LLI
/* Register settings - see PrimeCell DMA Controller (PL080) TRM && AACI PL041 TRM */
#define DMABlockSize 0x40 // 64 transfers @ 32 bit width == 256(0x100) bytes
#define DWidth 2 // 32 bits 0/1/2
#define SWidth 2 // 32 bits 0/1/2
#define DBSize 3 // 16 transfers @ 16 bit width == 32(0x20) bytes AACI FIFO width /0 == 1/1 == 4/2 == 8/3 == 16/4 == 32/5 == 64/6 == 128/7 == 256/
#define SBSize 5 // Set to half of the AACI 512 byte FIFO depth == 64 transfers @ 32 bit width == 256(0x100) bytes Memory
// - later check with FIFO status whether we could use full depth
/*
* TODO::
* - some from DMAC == same for all device DMA
* - some AACI device specific
*/
#define VDMA_AACI_CCTL \
((1 << 31) | /* ALL 'I' - Terminal count interrupt enable */ \
(0 << 28) | /* ALL 'Prot' - AHB HPROT information to destination slave. (Set to Not Cacheable, Not bufferable, User mode). */ \
(0 << 27) | /* AACI 'DI' - Destination address NOT incremented after each transfer */ \
(1 << 26) | /* AACI 'SI' - Source address incremented after each transfer */ \
(0 << 25) | /* AACI 'D' - Destination AHB master select; 0=AHB1, 1=AHB2. (Transferring to AACI on V/PB M2 bus) */ \
(1 << 24) | /* AACI 'S' - Source AHB master select; 0=AHB1, 1=AHB2. (Transferring from memory on V/PB DMA1 bus) */ \
(DWidth << 21) | /* AACI 'DWidth' - Destination transfer width. */ \
(SWidth << 18) | /* ALL 'SWidth' - Source transfer width. */ \
(DBSize << 15) | /* AACI 'DBSize' - Destination burst size. */ \
/* The number of bytes transferred when the peripheral requests a burst of data. */ \
/* AACI requests a burst when the FIFO has four or less words. */ \
(SBSize << 12) | /* AACI 'SBSize' - Source burst size. (Memory boundary size when transferring from memory). (Set to 256 bytes) */ \
DMABlockSize) /* AACI Number of 'destination width' transfers in one DMA 'packet'. (2^12)-1 is the maximum. */
#define VDMA_AACI_CCFG \
/* [31:19] ALL 'Reserved' - Write as zero. */ \
((0 << 18) | /* 'Halt' ALL - 0 = Allow DMA requests, 1 = Finish current request and ignore others */ \
(0 << 17) | /* [17] ALL 'Active' - Read only bit. 0 = No data in FIFO. */ \
(0 << 16) | /* 'L' ALL - Lock. Generates locked tranfers on the AHB. */ \
(1 << 15) | /* 'ITC' ALL - Terminal Count Interrupt mask. Masks TC interrupt when cleared. */ \
(1 << 14) | /* 'IE' ALL - Interrupt error mask. Masks error interrupt when cleared. */ \
(1 << 11) | /* 'FlowCntrl' AACI - Flow control method. We're using Mem -> Peripheral, with the DMAC as the flow controller. */ \
(0 << 10) | /* 'Reserved' ALL - Write as zero */ \
(0 << 6) | /* 'DestPeripheral' AACI - Destination peripheral number to associate with this channel. */ \
/* Set zero here, OR in the correct value in the machine initialisation */ \
/* VPB uses #0, which is shared with a logic tile DMA line on the V/PB. */ \
/* AACI Must be selected in V/PB SYS_DMAPSR0 reg. */ \
/* VAB uses #1 AACI TX which is fixed to DMA channel 1 */ \
(0 << 5) | /* 'Reserved' ALL - Write as zero */ \
(0 << 1) | /* 'SrcPeripheral' AACI - Source peripheral number to associate with this channel. Ignored for 'from memory' transfers.*/ \
(0 << 0)) /* 'E' ALL - Enables this DMA channel. (Channels automatically disabled when a transfer completes) */
#endif /* CONFIG_ARM_AMBA_DMA */
#define VDMA_CCFG_PERIPHAL_NUM_0 0x00000000
#define VDMA_CCFG_PERIPHAL_NUM_1 0x00000040
#define VDMA_CCFG_PERIPHAL_NUM_2 0x00000080
#define VDMA_CCFG_PERIPHAL_NUM_3 0x000000C0
#define VDMA_CCFG_PERIPHAL_NUM_4 0x00000100
#define VDMA_CCFG_PERIPHAL_NUM_5 0x00000140
#define VDMA_CCFG_PERIPHAL_NUM_6 0x00000180
#define VDMA_CCFG_PERIPHAL_NUM_7 0x000001C0
#define VDMA_CCFG_PERIPHAL_NUM_8 0x00000200
#define VDMA_CCFG_PERIPHAL_NUM_9 0x00000240
#define VDMA_CCFG_PERIPHAL_NUM_A 0x00000280
#define VDMA_CCFG_PERIPHAL_NUM_B 0x000002C0
#define VDMA_CCFG_PERIPHAL_NUM_C 0x00000300
#define VDMA_CCFG_PERIPHAL_NUM_D 0x00000340
#define VDMA_CCFG_PERIPHAL_NUM_E 0x00000380
#define VDMA_CCFG_PERIPHAL_NUM_F 0x000003C0
#endif
......@@ -84,6 +84,19 @@
#define VERSATILE_SYS_BOOTCS_OFFSET 0x58
#define VERSATILE_SYS_24MHz_OFFSET 0x5C
#define VERSATILE_SYS_MISC_OFFSET 0x60
#if defined(CONFIG_ARCH_VERSATILE_PB)
# define VERSATILE_SYS_DMAPSR0_OFFSET 0x64
# define VERSATILE_SYS_DMAPSR1_OFFSET 0x68
# define VERSATILE_SYS_DMAPSR2_OFFSET 0x6C
# define VSYSMASK_DMAPSR 0xFFFFFF60
# define VSYS_VAL_DMAPSR_ENABLE (1 << 7)
# define VSYS_VAL_DMAPSR_AACI_TX 0x00000000
# define VSYS_VAL_DMAPSR_AACI_RX 0x00000001
# define VSYS_VAL_DMAPSR_USB_A 0x00000002
# define VSYS_VAL_DMAPSR_USB_B 0x00000003
#endif
#define VERSATILE_SYS_TEST_OSC0_OFFSET 0x80
#define VERSATILE_SYS_TEST_OSC1_OFFSET 0x84
#define VERSATILE_SYS_TEST_OSC2_OFFSET 0x88
......
......@@ -11,18 +11,8 @@
* (dma.c) and the architecture-specific DMA backends (dma-*.c)
*/
struct dma_struct;
typedef struct dma_struct dma_t;
struct dma_ops {
int (*request)(dmach_t, dma_t *); /* optional */
void (*free)(dmach_t, dma_t *); /* optional */
void (*enable)(dmach_t, dma_t *); /* mandatory */
void (*disable)(dmach_t, dma_t *); /* mandatory */
int (*residue)(dmach_t, dma_t *); /* optional */
int (*setspeed)(dmach_t, dma_t *, int); /* optional */
char *type;
};
#ifndef _ASMARM_MACH_DMA_H_
#define _ASMARM_MACH_DMA_H_
struct dma_struct {
void *addr; /* single DMA address */
......@@ -48,6 +38,20 @@ struct dma_struct {
struct dma_ops *d_ops;
};
typedef struct dma_struct dma_t;
struct dma_ops {
int (*request)(dmach_t, dma_t *); /* optional */
void (*free)(dmach_t, dma_t *); /* optional */
void (*enable)(dmach_t, dma_t *); /* mandatory */
void (*disable)(dmach_t, dma_t *); /* mandatory */
int (*residue)(dmach_t, dma_t *); /* optional */
int (*setspeed)(dmach_t, dma_t *, int); /* optional */
char *type;
};
/* Prototype: void arch_dma_init(dma)
* Purpose : Initialise architecture specific DMA
* Params : dma - pointer to array of DMA structures
......@@ -55,3 +59,10 @@ struct dma_struct {
extern void arch_dma_init(dma_t *dma);
extern void isa_init_dma(dma_t *dma);
#ifdef CONFIG_ARM_AMBA_DMA
extern void amba_init_dma(dma_t *dma);
#endif
#endif
......@@ -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,9 @@ 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;
#endif
};
struct amba_id {
......@@ -47,9 +56,32 @@ struct amba_device *amba_find_device(const char *, struct device *, unsigned int
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 PrimeCell peripheral ids */
/*
* Note that the hex number is that from the prime cell 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
/*
* 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 void amba_set_ops(struct dma_ops * board_ops);
#endif
/*
* linux/amba/pl080.h - ARM PrimeCell AACI DMA Controller driver
*
* Copyright (C) 2005 ARM Ltd
*
* 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.
*
* Please credit ARM.com
* Documentation: ARM DDI 0196D
*/
#ifndef AMBA_PL080_H
#define AMBA_PL080_H
/*
* Memory to peripheral transfer may be visualized as
* Source burst data from memory to DMAC
* Until no data left
* On burst request from peripheral
* Destination burst from DMAC to peripheral
* Clear burst request
* Raise terminal count interrupt
*
* For peripherals with a FIFO:
* Source burst size == half the depth of the peripheral FIFO
* Destination burst size == width of the peripheral FIFO
*
*
*/
/*
* Header for pl080 drivers
*/
/*
* dma_pool defines
*/
#define PL080_MAX_LLIS_SIZE 0x2000 /* Size (bytes) of each buffer allocated */
#define PL080_ALIGN 8 /* Alignment each buffer allocated */
#define PL080_ALLOC 0 /* Boundary not to cross 0 == N/A */
#define PL080_OS_ISR 0x00
#define PL080_OS_ISR_TC 0x04
#define PL080_OS_ICLR_TC 0x08
#define PL080_OS_ISR_ERR 0x0C
#define PL080_OS_ICLR_ERR 0x10
#define PL080_OS_ENCHNS 0x1C
#define PL080_MASK_ENCHNS 0x000000FF
#define PL080_OS_CFG 0x30
#define PL080_MASK_CFG 0xFFFFFFF1
#define PL080_MASK_EN 0x00000001
#define PL080_OS_CHAN_BASE 0x100
#define PL080_OS_CHAN 0x20
#define PL080_OS_CSRC 0x00
#define PL080_OS_CDST 0x04
#define PL080_OS_CLLI 0x08
#define PL080_MASK_CLLI 0x00000002
#define PL080_OS_CCTL 0x0C
/*
* The DMA channel control register entries (see pl080 TRM) have the following units
* Width - bits
* Burst size - number of transfers
* Transfer (packet) size - number of (destination width) transfers
* See e.g include/asm-arm/arch-versatile/hardware.h
*/
#define PL080_MASK_TSFR_SIZE 0x00000FFF
#define PL080_OS_CCFG 0x10
#define PL080_MASK_CEN 0x00000001
#define PL080_MASK_INTTC 0x00008000
#define PL080_MASK_INTERR 0x00004000
#define PL080_MASK_HALT 0x00040000
#define PL080_MASK_ACTIVE 0x00020000
#define PL080_MASK_CCFG 0x00000000
/*
* Flow control bit masks
*/
/*
* Bit values for Transfer type Controller
* [13 - 11]
*/
#define PL080_FCMASK_M2M_DMA 0x00000000 /* Memory-to-memory DMA */
#define PL080_FCMASK_M2P_DMA 0x00000800 /* Memory-to-peripheral DMA */
#define PL080_FCMASK_P2M_DMA 0x00001000 /* Peripheral-to-memory DMA */
#define PL080_FCMASK_P2P_DMA 0x00001800 /* Source peripheral-to-destination peripheral DMA */
#define PL080_FCMASK_P2P_DST 0x00002000 /* Source peripheral-to-destination peripheral Destination peripheral */
#define PL080_FCMASK_M2P_PER 0x00002800 /* Memory-to-peripheral Peripheral */
#define PL080_FCMASK_P2P_PER 0x00003000 /* Peripheral-to-memory Peripheral */
#define PL080_FCMASK_P2P_SRC 0x00003800 /* Source peripheral-to-destination peripheral Source peripheral */
typedef struct _chan_lli {
dma_addr_t bus_list; /* the linked lli list bus address for use in the LLI */
void * va_list; /* the linked lli list virtual address for use by the driver */
unsigned int num_entries; /* number of entries - might not be circular */
unsigned int index_next; /* Index of next lli to load */
} chanlli;
/*
* An LLI struct - see pl080 TRM
*/
typedef struct _lli{
dma_addr_t start;
dma_addr_t dest;
dma_addr_t next;
unsigned int cword;
} pl080_lli;
/*
* Channel register settings
*/
typedef struct _periph_id_dma_channel_settings {
unsigned int periphid;
unsigned int ctl;
unsigned int cfg;
} setting;
/*
* One structure for each DMA channel
*/
extern chanlli * chanllis;
int pl080_init_dma (dma_t * dma_chan, struct dma_ops * ops);
void pl080_reset_cycle (dmach_t chan_num);
dma_addr_t pl080_make_llis (dmach_t chan_num, unsigned int address, unsigned int length, unsigned int packet_size, pl080_lli * setting);
void pl080_configure_chan (dmach_t chan_num, struct amba_dma_data * data);
void pl080_transfer_configure(dmach_t chan_num, pl080_lli * setting, unsigned int ccfg);
#endif /* AMBA_PL080_H */
......@@ -18,11 +18,19 @@
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/amba/bus.h>
#include <linux/dma-mapping.h> /* DMA memory is used for the buffer even if no DMA for this architecture */
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/sizes.h>
#ifdef CONFIG_ARM_AMBA_DMA
/*
* DMA code is for Versatile PB only
*/
# include <asm/mach-types.h>
#endif
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
......@@ -31,7 +39,7 @@
#include <sound/pcm_params.h>
#include "aaci.h"
#include "devdma.h"
#include "devdma.h" /* DMA memory is used for the buffer even if no DMA for this architecture */
#define DRIVER_NAME "aaci-pl041"
......@@ -122,10 +130,9 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
} while (v & SLFR_1TXB);
/*
* Give the AC'97 codec more than enough time
* to respond. (42us = ~2 frames at 48kHz.)
* Allow time for AC'97 codec response
*/
udelay(42);
udelay(TIME_TO_RESPOND);
/*
* Wait for slot 2 to indicate data.
......@@ -164,7 +171,59 @@ static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun)
/*
* Interrupt support.
*/
static void aaci_fifo_irq(struct aaci *aaci, u32 mask)
#if defined(CONFIG_ARM_AMBA_DMA) && defined(CONFIG_ARCH_VERSATILE)
/*
* Simple handler for chaining to the DMAC interrupt
* - called after each DMA packet (half a fifo depth)
*/
static irqreturn_t simpleDMA(int irq, void *devid, struct pt_regs *regs)
{
struct amba_device * client = devid;
struct amba_dma_data * data = &(client->dma_data);
snd_card_t * card = amba_get_drvdata(client);
struct aaci * aaci = card->private_data;
struct aaci_runtime * tsfr = &aaci->playback;
unsigned long flags;
/* e.g. check for errors */
data->irq_pre(0);
/* Update pointer by one packet */
spin_lock_irqsave(&aaci->lock, flags);
{
tsfr->ptr += aaci->fifosize/2;
if (tsfr->ptr >= tsfr->end) {
tsfr->ptr = tsfr->start;
}
tsfr->bytes -= aaci->fifosize;
if(tsfr->bytes <= 0){
/* Starting another period */
tsfr->bytes += tsfr->period;
spin_unlock_irqrestore(&aaci->lock, flags);
snd_pcm_period_elapsed(tsfr->substream);
spin_lock_irqsave(&aaci->lock, flags);
}
}
spin_unlock_irqrestore(&aaci->lock, flags);
/*
* Should we examine the FIFO status
* to ensure we don't re-trigger on same DMABREQ
*/
/* e.g clear interrupt on the DMAC */
data->irq_post(tsfr->dma_chan);
return IRQ_HANDLED;
}
#endif
static void aaci_fifo_irq(struct aaci *aaci, u32 mask, unsigned long * flags)
{
if (mask & ISR_URINTR) {
writel(ICLR_TXUEC1, aaci->base + AACI_INTCLR);
......@@ -188,9 +247,9 @@ static void aaci_fifo_irq(struct aaci *aaci, u32 mask)
if (aacirun->bytes <= 0) {
aacirun->bytes += aacirun->period;
aacirun->ptr = ptr;
spin_unlock(&aaci->lock);
spin_unlock_irqrestore(&aaci->lock, *flags);
snd_pcm_period_elapsed(aacirun->substream);
spin_lock(&aaci->lock);
spin_lock_irqsave(&aaci->lock, *flags);
}
if (!(aacirun->cr & TXCR_TXEN))
break;
......@@ -220,24 +279,27 @@ static void aaci_fifo_irq(struct aaci *aaci, u32 mask)
aacirun->ptr = ptr;
}
}
/*
* Interrupt handler for AACI FIFO operation
*/
static irqreturn_t aaci_irq(int irq, void *devid, struct pt_regs *regs)
{
struct aaci *aaci = devid;
u32 mask;
int i;
unsigned long flags;
spin_lock(&aaci->lock);
spin_lock_irqsave(&aaci->lock, flags);
mask = readl(aaci->base + AACI_ALLINTS);
if (mask) {
u32 m = mask;
for (i = 0; i < 4; i++, m >>= 7) {
if (m & 0x7f) {
aaci_fifo_irq(aaci, m);
aaci_fifo_irq(aaci, m, &flags);
}
}
}
spin_unlock(&aaci->lock);
spin_unlock_irqrestore(&aaci->lock, flags);
return mask ? IRQ_HANDLED : IRQ_NONE;
}
......@@ -334,7 +396,15 @@ static int aaci_pcm_open(struct aaci *aaci, struct snd_pcm_substream *substream,
struct aaci_runtime *aacirun)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
int ret = 0;
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab())
/*
* Indicate no channel yet assigned
*/
aacirun->dma_chan = MAX_DMA_CHANNELS;
#endif
aacirun->substream = substream;
runtime->private_data = aacirun;
......@@ -357,17 +427,6 @@ static int aaci_pcm_open(struct aaci *aaci, struct snd_pcm_substream *substream,
aaci_rule_rate_by_channels, aaci,
SNDRV_PCM_HW_PARAM_CHANNELS,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (ret)
goto out;
ret = request_irq(aaci->dev->irq[0], aaci_irq, SA_SHIRQ|SA_INTERRUPT,
DRIVER_NAME, aaci);
if (ret)
goto out;
return 0;
out:
return ret;
}
......@@ -383,14 +442,40 @@ static int aaci_pcm_close(struct snd_pcm_substream *substream)
WARN_ON(aacirun->cr & TXCR_TXEN);
aacirun->substream = NULL;
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab()){
/* If DMA was in use, free it */
if(MAX_DMA_CHANNELS == aacirun->dma_chan){
free_irq(aaci->dev->irq[0], aaci);
} else {
unsigned long flags;
flags = claim_dma_lock();
free_dma(aacirun->dma_chan);
aacirun->dma_chan = MAX_DMA_CHANNELS;
free_irq(INT_DMAINT, aaci->dev);
release_dma_lock(flags);
}
} else {
free_irq(aaci->dev->irq[0], aaci);
}
#else /* No DMA in this architecture */
free_irq(aaci->dev->irq[0], aaci);
#endif
// TODO:: Should only be done when all transfers complete, not just the playback...
writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
return 0;
}
/* There may be multiple calls during one transfer */
static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct aaci_runtime *aacirun = substream->runtime->private_data;
struct aaci *aaci = substream->private_data;
/*
* This must not be called with the device enabled.
......@@ -399,25 +484,29 @@ static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
if (aacirun->pcm_open)
snd_ac97_pcm_close(aacirun->pcm);
aacirun->pcm_open = 0;
/*
* Clear out the DMA and any allocated buffers.
*/
devdma_hw_free(NULL, substream);
devdma_hw_free((struct device *)aaci->dev, substream);
aacirun->start = NULL;
return 0;
}
/* There may be multiple calls during one transfer */
static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
struct aaci_runtime *aacirun,
struct snd_pcm_hw_params *params)
{
int err;
struct aaci *aaci = substream->private_data;
aaci_pcm_hw_free(substream);
err = devdma_hw_alloc(NULL, substream,
err = devdma_hw_alloc((struct device *)aaci->dev, substream,
params_buffer_bytes(params));
if (err < 0)
goto out;
......@@ -434,8 +523,23 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
return err;
}
/* There may be multiple calls during one transfer */
/*
* Setup the AACI for the transfer
*/
/*
* For DMA :-
* Finalize the linked list
* - we dont know the period size until here
*
* If using the pl080 and a different DMA buffer has been allocated
* we must set up the LLIs again
*/
static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
{
int ret = -EINVAL;
struct aaci *aaci = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct aaci_runtime *aacirun = runtime->private_data;
......@@ -445,9 +549,89 @@ static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
aacirun->period =
aacirun->bytes = frames_to_bytes(runtime, runtime->period_size);
return 0;
}
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab()){
/*
* Try for DMA, if fails fall back to fifo interrrupts
* - first, attempt to attach our handler to the DMA interrupt
*/
/*
* May be a multiple call
* - if so release any assigned channel & re-acquire
*/
if(MAX_DMA_CHANNELS != aacirun->dma_chan){
unsigned long flags;
flags = claim_dma_lock();
free_dma(aacirun->dma_chan);
aacirun->dma_chan = MAX_DMA_CHANNELS;
free_irq(INT_DMAINT, aaci->dev);
release_dma_lock(flags);
}
if(!request_irq(IRQ_DMAINT, simpleDMA, SA_SHIRQ|SA_INTERRUPT,
DRIVER_NAME, aaci->dev)){
/* Acquired the DMA interrupt, now request a channel for playback */
int i;
/*
* May need to change when capture implemented
*/
/*
* Transfer half a FIFO's worth of data in each DMA transfer
* - ensures no overrun
*/
aaci->dev->dma_data.packet_size = aaci->fifosize/2;
/*
* FIFO register offset - DMA only available on channel 1
*/
aaci->dev->dma_data.dmac_data = (void *)AACI_DR1;
for(i = 0; i < MAX_DMA_CHANNELS; i++){
if(!dma_channel_active(i)){
set_dma_mode (i, DMA_TO_DEVICE);
set_dma_addr (i, runtime->dma_buffer_p->addr);
set_dma_count(i, runtime->dma_bytes);
if(!(ret = request_dma(i, DRIVER_NAME))){
aacirun->dma_chan = i;
break;
}
}
}
if(MAX_DMA_CHANNELS == aacirun->dma_chan)
free_irq(IRQ_DMAINT, aaci->dev);
} else {
printk(KERN_ERR "aaci.c::aaci_pcm_prepare() Couldn't attach the interrupt handler\n");
}
if(MAX_DMA_CHANNELS == aacirun->dma_chan){
/* No DMA available - use fifo interrupts */
printk(KERN_ERR "aaci.c::aaci_pcm_prepare() USING FIFO\n");
ret = request_irq(aaci->dev->irq[0], aaci_irq, SA_SHIRQ|SA_INTERRUPT,
DRIVER_NAME, aaci);
}
} else {
ret = request_irq(aaci->dev->irq[0], aaci_irq, SA_SHIRQ|SA_INTERRUPT,
DRIVER_NAME, aaci);
}
#else /* No DMA in this architecture */
ret = request_irq(aaci->dev->irq[0], aaci_irq, SA_SHIRQ|SA_INTERRUPT,
DRIVER_NAME, aaci);
#endif
return ret;
}
/*
* Must be atomic
*/
static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
......@@ -554,9 +738,36 @@ static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
{
u32 ie;
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab()){
if(MAX_DMA_CHANNELS != aacirun->dma_chan){
struct aaci* aaci = aacirun->substream->private_data;
disable_dma(aacirun->dma_chan);
/*
* Disable DMA on the AACI by clearing the 'DMAEnable' bit.
*/
writel(aaci->maincr & ~MAINCR_DMAEN, aaci->base + AACI_MAINCR);
} else {
ie = readl(aacirun->base + AACI_IE);
ie &= ~(IE_URIE|IE_TXIE);
writel(ie, aacirun->base + AACI_IE);
}
} else {
ie = readl(aacirun->base + AACI_IE);
ie &= ~(IE_URIE|IE_TXIE);
writel(ie, aacirun->base + AACI_IE);
}
#else /* No DMA in this architecture */
ie = readl(aacirun->base + AACI_IE);
ie &= ~(IE_URIE|IE_TXIE);
writel(ie, aacirun->base + AACI_IE);
#endif
aacirun->cr &= ~TXCR_TXEN;
aaci_chan_wait_ready(aacirun);
writel(aacirun->cr, aacirun->base + AACI_TXCR);
......@@ -566,15 +777,55 @@ static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
{
u32 ie;
/*
* Get ready
*/
aaci_chan_wait_ready(aacirun);
aacirun->cr |= TXCR_TXEN;
/*
* Go
*/
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab()){
/*
* Ensure the DMAC finds a BREQ from the AACI
* as soon as the DMAC is enabled
*/
writel(aacirun->cr, aacirun->base + AACI_TXCR);
if(MAX_DMA_CHANNELS != aacirun->dma_chan){
struct aaci* aaci = aacirun->substream->private_data;
/*
* Set the 'DMAEnable' bit in the AACI PrimeCell.
*/
writel(aaci->maincr | MAINCR_DMAEN, aaci->base + AACI_MAINCR);
enable_dma(aacirun->dma_chan);
} else {
ie = readl(aacirun->base + AACI_IE);
ie |= IE_URIE | IE_TXIE;
writel(ie, aacirun->base + AACI_IE);
}
} else {
ie = readl(aacirun->base + AACI_IE);
ie |= IE_URIE | IE_TXIE;
writel(ie, aacirun->base + AACI_IE);
writel(aacirun->cr, aacirun->base + AACI_TXCR);
}
#else /* No DMA in this architecture */
ie = readl(aacirun->base + AACI_IE);
ie |= IE_URIE | IE_TXIE;
writel(ie, aacirun->base + AACI_IE);
writel(aacirun->cr, aacirun->base + AACI_TXCR);
#endif
}
/*
* Must be atomic
*/
static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct aaci *aaci = substream->private_data;
......@@ -720,10 +971,9 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci)
writel(RESET_NRST, aaci->base + AACI_RESET);
/*
* Give the AC'97 codec more than enough time
* to wake up. (42us = ~2 frames at 48kHz.)
* Allow time for AC'97 codec response
*/
udelay(42);
udelay(TIME_TO_RESPOND);
ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
if (ret)
......@@ -788,6 +1038,14 @@ static struct aaci * __devinit aaci_init_card(struct amba_device *dev)
aaci->card = card;
aaci->dev = dev;
#ifdef CONFIG_ARM_AMBA_DMA
if(machine_is_versatile_pb() || machine_is_versatile_ab()){
aaci->playback.dma_chan = MAX_DMA_CHANNELS;
aaci->capture.dma_chan = MAX_DMA_CHANNELS;
}
#endif
/* Set MAINCR to allow slot 1 and 2 data IO */
aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN |
MAINCR_SL2RXEN | MAINCR_SL2TXEN;
......@@ -830,6 +1088,8 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
* Re-initialise the AACI after the FIFO depth test, to
* ensure that the FIFOs are empty. Unfortunately, merely
* disabling the channel doesn't clear the FIFO.
* TRM:: "The FIFOs are flushed when the AACIfEN bit
* in the AACIMAINCR is deasserted"
*/
writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
writel(aaci->maincr, aaci->base + AACI_MAINCR);
......@@ -867,6 +1127,7 @@ static int __devinit aaci_probe(struct amba_device *dev, void *id)
/*
* Playback uses AACI channel 0
* - known in the TRM as channel 1
*/
aaci->playback.base = aaci->base + AACI_CSCH1;
aaci->playback.fifo = aaci->base + AACI_DR1;
......
......@@ -12,7 +12,7 @@
/*
* Control and status register offsets
* P39.
* - see Programmer's Model in TRM
*/
#define AACI_CSCH1 0x000
#define AACI_CSCH2 0x014
......@@ -209,6 +209,10 @@ struct aaci_runtime {
u32 cr;
struct snd_pcm_substream *substream;
#ifdef CONFIG_ARM_AMBA_DMA
dmach_t dma_chan;
#endif
/*
* PIO support
*/
......@@ -223,7 +227,7 @@ struct aaci_runtime {
struct aaci {
struct amba_device *dev;
struct snd_card *card;
void __iomem *base;
void __iomem *base; /* AACI register base */
unsigned int fifosize;
/* AC'97 */
......@@ -243,4 +247,17 @@ struct aaci {
#define ACSTREAM_SURROUND 1
#define ACSTREAM_LFE 2
#define TIME_TO_RESPOND 42 /* 42us = ~2 frames at 48kHz. */
/* This should give the AC'97 codec */
/* sufficient time to respond */
#ifdef CONFIG_ARM_AMBA_DMA
/*
* DMA only possible on audio channel 1
* - the first of four
*/
# define AACI_INDEX_DMACHAN 0
# define AACI_DMACHAN 1
#endif
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment