blob: dde086c03ff1ddf89976eb835a019cd977d6e557 [file] [log] [blame]
#include <linux/init.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <mach/c2k_dma.h>
static int mdma_busy = 0;
static int mdma_done;
static spinlock_t mdma_lock;
static void *virtbase;
#define M2IO_CONTROL (virtbase)
#define M2IO_HEAD (virtbase + 0x4)
#define M2IO_BURST (virtbase + 0x8)
#define M2IO_FLEN (virtbase + 0xC)
#define M2IO_IRQ_ENABLE (virtbase + 0x10)
#define M2IO_IRQ_STATUS (virtbase + 0x14)
#define M2IO_RESET (virtbase + 0x20)
#define IO2M_CONTROL (virtbase + 0x80)
#define IO2M_HEAD (virtbase + 0x84)
#define IO2M_BURST (virtbase + 0x88)
#define IO2M_FLEN (virtbase + 0x8C)
#define IO2M_IRQ_ENABLE (virtbase + 0x90)
#define IO2M_IRQ_STATUS (virtbase + 0x94)
#define IO2M_RESET (virtbase + 0xA0)
#define FDONE_MASK 0x80000000
static DECLARE_WAIT_QUEUE_HEAD(mdma_busy_queue);
static DECLARE_WAIT_QUEUE_HEAD(mdma_done_queue);
unsigned long mdma_in_desc_phy;
unsigned long mdma_out_desc_phy;
struct comcerto_xor_inbound_fdesc *mdma_in_desc;
struct comcerto_xor_outbound_fdesc *mdma_out_desc;
EXPORT_SYMBOL(mdma_in_desc);
EXPORT_SYMBOL(mdma_out_desc);
#if defined(CONFIG_COMCERTO_MDMA_PROF)
unsigned int mdma_time_counter[256];
unsigned int mdma_reqtime_counter[256];
unsigned int mdma_data_counter[256];
static struct timeval last_mdma;
unsigned int init_mdma_prof = 0;
unsigned int enable_mdma_prof = 0;
void comcerto_dma_profiling_start(struct comcerto_dma_sg *sg, unsigned int len)
{
long diff_time_us;
if (enable_mdma_prof) {
do_gettimeofday(&sg->start);
if (init_mdma_prof) {
diff_time_us = ((sg->start.tv_sec - last_mdma.tv_sec) * 1000000 + sg->start.tv_usec - last_mdma.tv_usec) >> 4;
if (diff_time_us < 256) {
mdma_time_counter[diff_time_us]++;
}
else {
mdma_time_counter[255]++;
}
}
len >>= 13;
if (len < 256)
mdma_data_counter[len]++;
else
mdma_data_counter[255]++;
}
}
void comcerto_dma_profiling_end(struct comcerto_dma_sg *sg)
{
long diff_time_us;
if (enable_mdma_prof) {
do_gettimeofday(&sg->end);
diff_time_us = ((sg->end.tv_sec - sg->start.tv_sec) * 1000000 + sg->end.tv_usec - sg->start.tv_usec) >> 4;
if (diff_time_us < 256) {
mdma_reqtime_counter[diff_time_us]++;
}
else
mdma_reqtime_counter[255]++;
if (!init_mdma_prof)
init_mdma_prof = 1;
last_mdma = sg->end;
}
}
#else
void comcerto_dma_profiling_start(struct comcerto_dma_sg *sg, unsigned int len) {}
void comcerto_dma_profiling_end(struct comcerto_dma_sg *sg) {}
#endif
static inline dma_addr_t dma_acp_map_page(struct comcerto_dma_sg *sg, struct page *page, unsigned int offset, unsigned int len, int dir, int use_acp)
{
dma_addr_t phys_addr = page_to_phys(page) + offset;
dma_addr_t low, high;
if (!use_acp)
goto map;
if ((phys_addr >= sg->low_phys_addr) && (phys_addr + len) < sg->high_phys_addr)
{
/* In range, skip mapping */
return COMCERTO_AXI_ACP_BASE + phys_addr;
}
/* Try to grow window, if possible */
if (phys_addr < sg->low_phys_addr)
low = phys_addr & ~(COMCERTO_AXI_ACP_SIZE - 1);
else
low = sg->low_phys_addr;
if ((phys_addr + len) > sg->high_phys_addr)
high = (phys_addr + len + COMCERTO_AXI_ACP_SIZE - 1) & ~(COMCERTO_AXI_ACP_SIZE - 1);
else
high = sg->high_phys_addr;
if ((high - low) <= COMCERTO_AXI_ACP_SIZE) {
sg->low_phys_addr = low;
sg->high_phys_addr = high;
return COMCERTO_AXI_ACP_BASE + phys_addr;
}
map:
return dma_map_page(NULL, page, offset, len, dir); //TODO add proper checks
}
int comcerto_dma_sg_add_input(struct comcerto_dma_sg *sg, struct page *page, unsigned int offset, unsigned int len, int use_acp)
{
dma_addr_t phys_addr;
if (unlikely(len > (MDMA_MAX_BUF_SIZE + 1))) {
printk(KERN_ERR "%s: tried to add a page larger than %d kB.\n", __func__, MDMA_MAX_BUF_SIZE + 1);
return -2;
}
if (len <= MDMA_MAX_BUF_SIZE) {
if (sg->input_idx >= MDMA_INBOUND_BUF_DESC)
return -1;
phys_addr = dma_acp_map_page(sg, page, offset, len, DMA_TO_DEVICE, use_acp);
sg->in_bdesc[sg->input_idx].split = 0;
sg->in_bdesc[sg->input_idx].phys_addr = phys_addr;
sg->in_bdesc[sg->input_idx].len = len;
sg->input_idx++;
return 0;
}
else { /* len = MSPD_MDMA_MAX_BUF_SIZE +1, split it in 2 pieces */
if (sg->input_idx >= (MDMA_INBOUND_BUF_DESC - 1))
return -1;
phys_addr = dma_acp_map_page(sg, page, offset, len, DMA_TO_DEVICE, use_acp);
sg->in_bdesc[sg->input_idx].split = 1;
sg->in_bdesc[sg->input_idx].phys_addr = phys_addr;
sg->in_bdesc[sg->input_idx].len = MDMA_SPLIT_BUF_SIZE;
sg->input_idx++;
sg->in_bdesc[sg->input_idx].split = 0;
sg->in_bdesc[sg->input_idx].phys_addr = phys_addr + MDMA_SPLIT_BUF_SIZE;
sg->in_bdesc[sg->input_idx].len = MDMA_SPLIT_BUF_SIZE;
sg->input_idx++;
return 0;
}
}
EXPORT_SYMBOL(comcerto_dma_sg_add_input);
int comcerto_dma_sg_add_output(struct comcerto_dma_sg *sg, struct page *page, unsigned int offset, unsigned int len, int use_acp)
{
dma_addr_t phys_addr;
if (unlikely(len > (MDMA_MAX_BUF_SIZE + 1))) {
printk(KERN_ERR "%s: tried to add a page larger than %d kB.\n", __func__, MDMA_MAX_BUF_SIZE + 1);
return -2;
}
if (len <= MDMA_MAX_BUF_SIZE) {
if (sg->output_idx >= MDMA_OUTBOUND_BUF_DESC)
return -1;
phys_addr = dma_acp_map_page(sg, page, offset, len, DMA_FROM_DEVICE, use_acp);
sg->out_bdesc[sg->output_idx].split = 0;
sg->out_bdesc[sg->output_idx].phys_addr = phys_addr;
sg->out_bdesc[sg->output_idx].len = len;
sg->output_idx++;
return 0;
}
else { /* len = MDMA_MAX_BUF_SIZE +1, split it in 2 pieces */
if (sg->output_idx >= (MDMA_OUTBOUND_BUF_DESC - 1))
return -1;
phys_addr = dma_acp_map_page(sg, page, offset, len, DMA_FROM_DEVICE, use_acp);
sg->out_bdesc[sg->output_idx].split = 1;
sg->out_bdesc[sg->output_idx].phys_addr = phys_addr;
sg->out_bdesc[sg->output_idx].len = MDMA_SPLIT_BUF_SIZE;
sg->output_idx++;
sg->out_bdesc[sg->output_idx].split = 0;
sg->out_bdesc[sg->output_idx].phys_addr = phys_addr + MDMA_SPLIT_BUF_SIZE;
sg->out_bdesc[sg->output_idx].len = MDMA_SPLIT_BUF_SIZE;
sg->output_idx++;
return 0;
}
}
EXPORT_SYMBOL(comcerto_dma_sg_add_output);
void comcerto_dma_sg_setup(struct comcerto_dma_sg *sg, unsigned int len)
{
int i;
unsigned int remaining;
comcerto_dma_profiling_start(sg, len);
writel_relaxed(sg->low_phys_addr |
AWUSER_COHERENT(WRITEBACK) | AWPROT(0x0) | AWCACHE(CACHEABLE | BUFFERABLE) |
ARUSER_COHERENT(WRITEBACK) | ARPROT(0x0) | ARCACHE(CACHEABLE | BUFFERABLE),
COMCERTO_GPIO_A9_ACP_CONF_REG);
remaining = len;
i = 0;
while (remaining > sg->in_bdesc[i].len) {
if (sg->in_bdesc[i].phys_addr >= COMCERTO_AXI_ACP_BASE)
sg->in_bdesc[i].phys_addr -= sg->low_phys_addr;
comcerto_dma_set_in_bdesc(i, sg->in_bdesc[i].phys_addr, sg->in_bdesc[i].len);
remaining -= sg->in_bdesc[i].len;
i++;
}
if (sg->in_bdesc[i].phys_addr >= COMCERTO_AXI_ACP_BASE)
sg->in_bdesc[i].phys_addr -= sg->low_phys_addr;
comcerto_dma_set_in_bdesc(i, sg->in_bdesc[i].phys_addr, remaining | BLAST);
remaining = len;
i = 0;
while (remaining > sg->out_bdesc[i].len) {
if (sg->out_bdesc[i].phys_addr >= COMCERTO_AXI_ACP_BASE)
sg->out_bdesc[i].phys_addr -= sg->low_phys_addr;
comcerto_dma_set_out_bdesc(i, sg->out_bdesc[i].phys_addr, sg->out_bdesc[i].len);
remaining -= sg->out_bdesc[i].len;
i++;
}
if (sg->out_bdesc[i].phys_addr >= COMCERTO_AXI_ACP_BASE)
sg->out_bdesc[i].phys_addr -= sg->low_phys_addr;
comcerto_dma_set_out_bdesc(i, sg->out_bdesc[i].phys_addr, remaining | BLAST);
}
EXPORT_SYMBOL(comcerto_dma_sg_setup);
void comcerto_dma_sg_cleanup(struct comcerto_dma_sg *sg, unsigned int len)
{
int i;
unsigned int remaining;
remaining = len;
i = 0;
while (remaining > sg->in_bdesc[i].len) {
if (sg->in_bdesc[i].split) {
sg->in_bdesc[i+1].phys_addr = sg->in_bdesc[i].phys_addr;
sg->in_bdesc[i+1].len += sg->in_bdesc[i].len;
}
else {
if (sg->in_bdesc[i].phys_addr < COMCERTO_AXI_ACP_BASE)
dma_unmap_page(NULL, sg->in_bdesc[i].phys_addr, sg->in_bdesc[i].len, DMA_TO_DEVICE);
remaining -= sg->in_bdesc[i].len;
}
i++;
}
if (sg->in_bdesc[i].phys_addr < COMCERTO_AXI_ACP_BASE)
dma_unmap_page(NULL, sg->in_bdesc[i].phys_addr, sg->in_bdesc[i].len, DMA_TO_DEVICE);
remaining = len;
i = 0;
while (remaining > sg->out_bdesc[i].len) {
if (sg->out_bdesc[i].split) {
sg->out_bdesc[i+1].phys_addr = sg->out_bdesc[i].phys_addr;
sg->out_bdesc[i+1].len += sg->out_bdesc[i].len;
}
else {
if (sg->out_bdesc[i].phys_addr < COMCERTO_AXI_ACP_BASE)
dma_unmap_page(NULL, sg->out_bdesc[i].phys_addr, sg->out_bdesc[i].len, DMA_FROM_DEVICE);
remaining -= sg->out_bdesc[i].len;
}
i++;
}
if (sg->out_bdesc[i].phys_addr < COMCERTO_AXI_ACP_BASE)
dma_unmap_page(NULL, sg->out_bdesc[i].phys_addr, sg->out_bdesc[i].len, DMA_FROM_DEVICE);
comcerto_dma_profiling_end(sg);
}
EXPORT_SYMBOL(comcerto_dma_sg_cleanup);
void comcerto_dma_get(void)
{
unsigned long flags;
DEFINE_WAIT(wait);
spin_lock_irqsave(&mdma_lock, flags);
if (mdma_busy) {
prepare_to_wait(&mdma_busy_queue, &wait, TASK_UNINTERRUPTIBLE);
while (mdma_busy) {
spin_unlock_irqrestore(&mdma_lock, flags);
schedule();
spin_lock_irqsave(&mdma_lock, flags);
prepare_to_wait(&mdma_busy_queue, &wait, TASK_UNINTERRUPTIBLE);
}
finish_wait(&mdma_busy_queue, &wait);
}
mdma_busy = 1;
spin_unlock_irqrestore(&mdma_lock, flags);
}
EXPORT_SYMBOL(comcerto_dma_get);
void comcerto_dma_put(void)
{
unsigned long flags;
spin_lock_irqsave(&mdma_lock, flags);
mdma_busy = 0;
spin_unlock_irqrestore(&mdma_lock, flags);
wake_up(&mdma_busy_queue);
}
EXPORT_SYMBOL(comcerto_dma_put);
/* Called once to setup common registers */
static void comcerto_dma_setup(void)
{
/* IO2M_IRQ_ENABLE: Enable IRQ_IRQFDON*/
writel_relaxed(IRQ_IRQFDON, IO2M_IRQ_ENABLE);
writel_relaxed(0x0, M2IO_CONTROL);
writel_relaxed(0xf, M2IO_BURST);
writel_relaxed(0x0, IO2M_CONTROL);
writel_relaxed(0xf, IO2M_BURST);
}
void comcerto_dma_start(void)
{
mdma_done = 0;
mdma_in_desc->next_desc = 0;
mdma_in_desc->fcontrol = 0;
mdma_in_desc->fstatus0 = 0;
mdma_in_desc->fstatus1 = 0;
// outbound
mdma_out_desc->next_desc = 0;
mdma_out_desc->fcontrol = 0;
mdma_out_desc->fstatus0 = 0;
mdma_out_desc->fstatus1 = 0;
wmb();
// Initialize the Outbound Head Pointer
writel_relaxed(mdma_out_desc_phy, IO2M_HEAD);
// Initialize the Inbound Head Pointer
writel_relaxed(mdma_in_desc_phy, M2IO_HEAD);
wmb();
}
EXPORT_SYMBOL(comcerto_dma_start);
void comcerto_dma_wait(void)
{
DEFINE_WAIT(wait);
prepare_to_wait(&mdma_done_queue, &wait, TASK_UNINTERRUPTIBLE);
if (!mdma_done)
schedule();
finish_wait(&mdma_done_queue, &wait);
}
EXPORT_SYMBOL(comcerto_dma_wait);
static void comcerto_dump_regs(void)
{
u32 val;
val = readl_relaxed(M2IO_CONTROL);
printk(KERN_ERR"M2IO_CONTROL 0x%8x.\n",val);
val = readl_relaxed(M2IO_HEAD);
printk(KERN_ERR"M2IO_HEAD 0x%8x.\n",val);
val = readl_relaxed(M2IO_BURST);
printk(KERN_ERR"M2IO_BURST 0x%8x.\n",val);
val = readl_relaxed(M2IO_FLEN);
printk(KERN_ERR"M2IO_FLEN 0x%8x.\n",val);
val = readl_relaxed(M2IO_IRQ_ENABLE);
printk(KERN_ERR"M2IO_IRQ_ENABLE 0x%8x.\n",val);
val = readl_relaxed(M2IO_IRQ_STATUS);
printk(KERN_ERR"M2IO_IRQ_STATUS 0x%8x.\n",val);
val = readl_relaxed(M2IO_RESET);
printk(KERN_ERR"M2IO_RESET 0x%8x.\n",val);
val = readl_relaxed(IO2M_CONTROL);
printk(KERN_ERR"IO2M_CONTROL 0x%8x.\n",val);
val = readl_relaxed(IO2M_HEAD);
printk(KERN_ERR"IO2M_HEAD 0x%8x.\n",val);
val = readl_relaxed(IO2M_BURST);
printk(KERN_ERR"IO2M_BURST 0x%8x.\n",val);
val = readl_relaxed(IO2M_FLEN);
printk(KERN_ERR"IO2M_FLEN 0x%8x.\n",val);
val = readl_relaxed(IO2M_IRQ_ENABLE);
printk(KERN_ERR"IO2M_IRQ_ENABLE 0x%8x.\n",val);
val = readl_relaxed(IO2M_IRQ_STATUS);
printk(KERN_ERR"IO2M_IRQ_STATUS 0x%8x.\n",val);
val = readl_relaxed(IO2M_RESET);
printk(KERN_ERR"IO2M_RESET 0x%8x.\n",val);
}
static irqreturn_t c2k_dma_handle_interrupt(int irq, void *data)
{
u32 intr_cause = readl_relaxed(IO2M_IRQ_STATUS);
writel_relaxed(intr_cause, IO2M_IRQ_STATUS);
if (unlikely(intr_cause & ~(IRQ_IRQFDON | IRQ_IRQFLST | IRQ_IRQFLEN))) {
if (intr_cause & IRQ_IRQFRDYN)
printk(KERN_ALERT "IRQFRDYN: A frame is started but the frame is not ready");
if (intr_cause & IRQ_IRQFLSH)
printk(KERN_ALERT "IRQFLSH: IO has more data than the memory buffer");
if (intr_cause & IRQ_IRQFTHLD)
printk(KERN_ALERT "IRQFTHLD: Frame threshold reached. FLEN=FTHLDL");
if (intr_cause & IRQ_IRQFCTRL)
printk(KERN_ALERT "IRQFCTRL: 1 frame is completed or when a frame is started but not ready");
comcerto_dump_regs();
}
if (intr_cause & IRQ_IRQFDON) {
if (unlikely(!(mdma_out_desc->fstatus1 & FDONE_MASK)))
printk(KERN_INFO "Fdesc not done\n");
mdma_done = 1;
wake_up(&mdma_done_queue);
}
return IRQ_HANDLED;
}
static int __devexit comcerto_dma_remove(struct platform_device *pdev)
{
int irq;
irq = platform_get_irq(pdev, 0);
iounmap(virtbase);
free_irq(irq, NULL);
platform_set_drvdata(pdev, NULL);
return 0;
}
static int __devinit comcerto_dma_probe(struct platform_device *pdev)
{
struct resource *io;
int irq;
void *aram_pool = (void *)IRAM_MEMORY_VADDR;
int ret;
/* Retrieve related resources(mem, irq) from platform_device */
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!io)
return -ENODEV;
irq = platform_get_irq(pdev,0);
if (irq < 0)
return irq;
ret = request_irq(irq, c2k_dma_handle_interrupt, 0, "MDMA", NULL);
if (ret < 0)
goto err_irq;
virtbase = ioremap(io->start, resource_size(io));
if (!virtbase)
goto err_ioremap;
spin_lock_init(&mdma_lock);
//initializing
mdma_in_desc = (struct comcerto_xor_inbound_fdesc *) (aram_pool);
aram_pool += sizeof(struct comcerto_xor_inbound_fdesc);
aram_pool = (void *)((unsigned long)(aram_pool + 15) & ~15);
mdma_out_desc = (struct comcerto_xor_outbound_fdesc *) (aram_pool);
mdma_in_desc_phy = virt_to_aram(mdma_in_desc);
mdma_out_desc_phy = virt_to_aram(mdma_out_desc);
comcerto_dma_setup();
return 0;
err_ioremap:
free_irq(irq, NULL);
err_irq:
return -1;
}
static struct platform_driver comcerto_dma_driver = {
.probe = comcerto_dma_probe,
.remove = comcerto_dma_remove,
.driver = {
.owner = THIS_MODULE,
.name = "comcerto_dma",
},
};
static int __init comcerto_dma_init(void)
{
return platform_driver_register(&comcerto_dma_driver);
}
module_init(comcerto_dma_init);
static void __exit comcerto_dma_exit(void)
{
platform_driver_unregister(&comcerto_dma_driver);
}
module_exit(comcerto_dma_exit);
MODULE_DESCRIPTION("DMA engine driver for Mindspeed Comcerto C2000 devices");
MODULE_LICENSE("GPL");