/*
*
* BRIEF MODULE DESCRIPTION
* A DMA channel allocator for Au1x00 . API is modeled loosely off of
* linux / kernel / dma . c .
*
* Copyright 2000 , 2008 MontaVista Software Inc .
* Author : MontaVista Software , Inc . < source @ mvista . com >
* Copyright ( C ) 2005 Ralf Baechle ( ralf @ linux - mips . org )
*
* 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 SOFTWARE IS PROVIDED ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF
* USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* 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 . ,
* 675 Mass Ave , Cambridge , MA 02139 , USA .
*
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <asm /mach-au1x00/au1000.h>
#include <asm /mach-au1x00/au1000_dma.h>
/*
* A note on resource allocation :
*
* All drivers needing DMA channels , should allocate and release them
* through the public routines ` request_dma ( ) ' and ` free_dma ( ) ' .
*
* In order to avoid problems , all processes should allocate resources in
* the same sequence and release them in the reverse order .
*
* So , when allocating DMAs and IRQs , first allocate the DMA , then the IRQ .
* When releasing them , first release the IRQ , then release the DMA . The
* main reason for this order is that , if you are requesting the DMA buffer
* done interrupt , you won ' t know the irq number until the DMA channel is
* returned from request_dma .
*/
/* DMA Channel register block spacing */
#define DMA_CHANNEL_LEN 0 x00000100
DEFINE_SPINLOCK(au1000_dma_spin_lock);
struct dma_chan au1000_dma_table[NUM_AU1000_DMA_CHANNELS] = {
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,},
{.dev_id = -1 ,}
};
EXPORT_SYMBOL(au1000_dma_table);
/* Device FIFO addresses and default DMA modes */
static const struct dma_dev {
unsigned int fifo_addr;
unsigned int dma_mode;
} dma_dev_table[DMA_NUM_DEV] = {
{ AU1000_UART0_PHYS_ADDR + 0 x04, DMA_DW8 }, /* UART0_TX */
{ AU1000_UART0_PHYS_ADDR + 0 x00, DMA_DW8 | DMA_DR }, /* UART0_RX */
{ 0 , 0 }, /* DMA_REQ0 */
{ 0 , 0 }, /* DMA_REQ1 */
{ AU1000_AC97_PHYS_ADDR + 0 x08, DMA_DW16 }, /* AC97 TX c */
{ AU1000_AC97_PHYS_ADDR + 0 x08, DMA_DW16 | DMA_DR }, /* AC97 RX c */
{ AU1000_UART3_PHYS_ADDR + 0 x04, DMA_DW8 | DMA_NC }, /* UART3_TX */
{ AU1000_UART3_PHYS_ADDR + 0 x00, DMA_DW8 | DMA_NC | DMA_DR }, /* UART3_RX */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x00, DMA_DW8 | DMA_NC | DMA_DR }, /* EP0RD */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x04, DMA_DW8 | DMA_NC }, /* EP0WR */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x08, DMA_DW8 | DMA_NC }, /* EP2WR */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x0c, DMA_DW8 | DMA_NC }, /* EP3WR */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x10, DMA_DW8 | DMA_NC | DMA_DR }, /* EP4RD */
{ AU1000_USB_UDC_PHYS_ADDR + 0 x14, DMA_DW8 | DMA_NC | DMA_DR }, /* EP5RD */
/* on Au1500, these 2 are DMA_REQ2/3 (GPIO208/209) instead! */
{ AU1000_I2S_PHYS_ADDR + 0 x00, DMA_DW32 | DMA_NC}, /* I2S TX */
{ AU1000_I2S_PHYS_ADDR + 0 x00, DMA_DW32 | DMA_NC | DMA_DR}, /* I2S RX */
};
int au1000_dma_read_proc(char *buf, char **start, off_t fpos,
int length, int *eof, void *data)
{
int i, len = 0 ;
struct dma_chan *chan;
for (i = 0 ; i < NUM_AU1000_DMA_CHANNELS; i++) {
chan = get_dma_chan(i);
if (chan != NULL)
len += sprintf(buf + len, "%2d: %s\n" ,
i, chan->dev_str);
}
if (fpos >= len) {
*start = buf;
*eof = 1 ;
return 0 ;
}
*start = buf + fpos;
len -= fpos;
if (len > length)
return length;
*eof = 1 ;
return len;
}
/* Device FIFO addresses and default DMA modes - 2nd bank */
static const struct dma_dev dma_dev_table_bank2[DMA_NUM_DEV_BANK2] = {
{ AU1100_SD0_PHYS_ADDR + 0 x00, DMA_DS | DMA_DW8 }, /* coherent */
{ AU1100_SD0_PHYS_ADDR + 0 x04, DMA_DS | DMA_DW8 | DMA_DR }, /* coherent */
{ AU1100_SD1_PHYS_ADDR + 0 x00, DMA_DS | DMA_DW8 }, /* coherent */
{ AU1100_SD1_PHYS_ADDR + 0 x04, DMA_DS | DMA_DW8 | DMA_DR } /* coherent */
};
/*
* Finds a free channel , and binds the requested device to it .
* Returns the allocated channel number , or negative on error .
* Requests the DMA done IRQ if irqhandler ! = NULL .
*/
int request_au1000_dma(int dev_id, const char *dev_str,
irq_handler_t irqhandler,
unsigned long irqflags,
void *irq_dev_id)
{
struct dma_chan *chan;
const struct dma_dev *dev;
int i, ret;
if (alchemy_get_cputype() == ALCHEMY_CPU_AU1100) {
if (dev_id < 0 || dev_id >= (DMA_NUM_DEV + DMA_NUM_DEV_BANK2))
return -EINVAL;
} else {
if (dev_id < 0 || dev_id >= DMA_NUM_DEV)
return -EINVAL;
}
for (i = 0 ; i < NUM_AU1000_DMA_CHANNELS; i++)
if (au1000_dma_table[i].dev_id < 0 )
break ;
if (i == NUM_AU1000_DMA_CHANNELS)
return -ENODEV;
chan = &au1000_dma_table[i];
if (dev_id >= DMA_NUM_DEV) {
dev_id -= DMA_NUM_DEV;
dev = &dma_dev_table_bank2[dev_id];
} else
dev = &dma_dev_table[dev_id];
if (irqhandler) {
chan->irq_dev = irq_dev_id;
ret = request_irq(chan->irq, irqhandler, irqflags, dev_str,
chan->irq_dev);
if (ret) {
chan->irq_dev = NULL;
return ret;
}
} else {
chan->irq_dev = NULL;
}
/* fill it in */
chan->io = (void __iomem *)(KSEG1ADDR(AU1000_DMA_PHYS_ADDR) +
i * DMA_CHANNEL_LEN);
chan->dev_id = dev_id;
chan->dev_str = dev_str;
chan->fifo_addr = dev->fifo_addr;
chan->mode = dev->dma_mode;
/* initialize the channel before returning */
init_dma(i);
return i;
}
EXPORT_SYMBOL(request_au1000_dma);
void free_au1000_dma(unsigned int dmanr)
{
struct dma_chan *chan = get_dma_chan(dmanr);
if (!chan) {
printk(KERN_ERR "Error trying to free DMA%d\n" , dmanr);
return ;
}
disable_dma(dmanr);
if (chan->irq_dev)
free_irq(chan->irq, chan->irq_dev);
chan->irq_dev = NULL;
chan->dev_id = -1 ;
}
EXPORT_SYMBOL(free_au1000_dma);
static int __init au1000_dma_init(void )
{
int base, i;
switch (alchemy_get_cputype()) {
case ALCHEMY_CPU_AU1000:
base = AU1000_DMA_INT_BASE;
break ;
case ALCHEMY_CPU_AU1500:
base = AU1500_DMA_INT_BASE;
break ;
case ALCHEMY_CPU_AU1100:
base = AU1100_DMA_INT_BASE;
break ;
default :
goto out;
}
for (i = 0 ; i < NUM_AU1000_DMA_CHANNELS; i++)
au1000_dma_table[i].irq = base + i;
printk(KERN_INFO "Alchemy DMA initialized\n" );
out:
return 0 ;
}
arch_initcall(au1000_dma_init);
Messung V0.5 in Prozent C=93 H=95 G=93
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland