blob: 2586635f9e9478e2b9160e1ccf48a62eead8e8e9 [file] [log] [blame]
/*
* Chorus2 specific DMA code.
*
* Copyright (C) 2007,2008 Imagination Technologies Ltd.
*
*/
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/spinlock.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <asm/global_lock.h>
#include <asm/img_dma.h>
#include <asm/soc-chorus2/dma.h>
static DEFINE_SPINLOCK(dma_spin_lock);
static unsigned int dma_channels[MAX_DMA_CHANNELS] = {
#ifdef CONFIG_SOC_CHORUSX_DMA0
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA1
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA2
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA3
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA4
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA5
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA6
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA7
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA8
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA9
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA10
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
#ifdef CONFIG_SOC_CHORUSX_DMA11
IMG_DMA_CHANNEL_AVAILABLE,
#else
IMG_DMA_CHANNEL_RESERVED,
#endif
};
static int get_free_channel(void)
{
int i;
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
if (dma_channels[i] == IMG_DMA_CHANNEL_AVAILABLE)
return i;
}
return -1;
}
static void setup_dma_channel(int dmanr, unsigned int periph)
{
unsigned int shift, val, mask;
int flags;
unsigned int *reg = NULL;
if ((dmanr >= 0) && (dmanr < 4))
reg = (unsigned int *)DMA_CHAN_SEL_0_3;
else if ((dmanr >= 4) && (dmanr < 8))
reg = (unsigned int *)DMA_CHAN_SEL_4_7;
else if ((dmanr >= 8) && (dmanr < 12))
reg = (unsigned int *)DMA_CHAN_SEL_8_11;
BUG_ON(!reg);
/* Peripherals are specified using 6 bits but have an 8 bit stride
in the register. */
shift = (dmanr & 0x3) * 8;
mask = ~(DMAC_PERIPH_MASK << shift);
periph = periph << shift;
__global_lock2(flags);
val = ioread32(reg);
val &= mask;
val |= periph;
iowrite32(val, reg);
__global_unlock2(flags);
}
int img_request_dma(int dmanr, unsigned int periph)
{
unsigned long flags;
int err;
if (dmanr >= MAX_DMA_CHANNELS)
return -EINVAL;
if (periph > MAX_PERIPH_CHANNELS)
return -EINVAL;
spin_lock_irqsave(&dma_spin_lock, flags);
/* If dmanr is -1 we pick the DMA channel to use. */
if (dmanr == -1) {
dmanr = get_free_channel();
if (dmanr == -1) {
err = -EBUSY;
goto out;
}
}
if (dma_channels[dmanr] != IMG_DMA_CHANNEL_AVAILABLE) {
err = -EBUSY;
goto out;
}
dma_channels[dmanr] = IMG_DMA_CHANNEL_INUSE;
setup_dma_channel(dmanr, periph);
err = dmanr;
out:
spin_unlock_irqrestore(&dma_spin_lock, flags);
return err;
}
EXPORT_SYMBOL(img_request_dma);
int img_free_dma(int dmanr)
{
unsigned long flags;
if (dmanr >= MAX_DMA_CHANNELS)
return -EINVAL;
spin_lock_irqsave(&dma_spin_lock, flags);
if (dma_channels[dmanr] == IMG_DMA_CHANNEL_INUSE)
dma_channels[dmanr] = IMG_DMA_CHANNEL_AVAILABLE;
setup_dma_channel(dmanr, 0);
spin_unlock_irqrestore(&dma_spin_lock, flags);
return 0;
}
EXPORT_SYMBOL(img_free_dma);
unsigned int *get_dma_regs(int dmanr)
{
return (unsigned int *)(DMAC_HWBASE + (dmanr * DMAC_CHANNEL_STRIDE));
}
EXPORT_SYMBOL(get_dma_regs);
#ifdef CONFIG_PROC_FS
static const char *periph_names[] = {
"Local Bus",
"Reed Solomon Input",
"Reed Solomon Output",
"Memory Stick",
"4",
"SCP input",
"SCP output",
"7",
"ECP output",
"ATAPI",
"SPI 1 output",
"SPI 1 input",
"SPI 2 output",
"SPI 2 input",
"Noise Shaper",
"LCD",
"I2S out",
"I2S in",
"SPDIF out",
"SPDIF in",
"I2S out",
"Core/SCP",
"Core/Local",
"Core",
"RDI",
"NAND",
};
const static char *state_names[] = {
"Reserved",
"Available",
"In Use",
};
static int proc_dma_show_reg(struct seq_file *m, unsigned int val, int chan)
{
int i;
for (i = 0; i < 4; i++, chan++) {
int periph = (val >> (i * 8)) & DMAC_PERIPH_MASK;
if (periph < sizeof(periph_names))
seq_printf(m, "%2d: %s (%s)\n", chan,
periph_names[periph],
state_names[dma_channels[chan]]);
else
seq_printf(m, "%2d: %d (%s)\n", chan, periph,
state_names[dma_channels[chan]]);
}
return chan;
}
static int proc_dma_show(struct seq_file *m, void *v)
{
int chan = 0;
unsigned int *reg;
reg = (unsigned int *)DMA_CHAN_SEL_0_3;
chan = proc_dma_show_reg(m, *reg, chan);
reg = (unsigned int *)DMA_CHAN_SEL_4_7;
chan = proc_dma_show_reg(m, *reg, chan);
reg = (unsigned int *)DMA_CHAN_SEL_8_11;
chan = proc_dma_show_reg(m, *reg, chan);
return 0;
}
static int proc_dma_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_dma_show, NULL);
}
static const 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)
{
proc_create("dma", 0, NULL, &proc_dma_operations);
return 0;
}
__initcall(proc_dma_init);
#endif