/*
* SA11x0 DMAengine support
*
* Copyright (C) 2012 Russell King
* Derived in part from arch/arm/mach-sa1100/dma.c,
* Copyright (C) 2000, 2001 by Nicolas Pitre
*
* 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.
*/
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/dmaengine.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sa11x0-dma.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define NR_PHY_CHAN 6
#define DMA_ALIGN 3
#define DMA_MAX_SIZE 0x1fff
#define DMA_CHUNK_SIZE 0x1000
#define DMA_DDAR 0x00
#define DMA_DCSR_S 0x04
#define DMA_DCSR_C 0x08
#define DMA_DCSR_R 0x0c
#define DMA_DBSA 0x10
#define DMA_DBTA 0x14
#define DMA_DBSB 0x18
#define DMA_DBTB 0x1c
#define DMA_SIZE 0x20
#define DCSR_RUN (1 << 0)
#define DCSR_IE (1 << 1)
#define DCSR_ERROR (1 << 2)
#define DCSR_DONEA (1 << 3)
#define DCSR_STRTA (1 << 4)
#define DCSR_DONEB (1 << 5)
#define DCSR_STRTB (1 << 6)
#define DCSR_BIU (1 << 7)
#define DDAR_RW (1 << 0) /* 0 = W, 1 = R */
#define DDAR_E (1 << 1) /* 0 = LE, 1 = BE */
#define DDAR_BS (1 << 2) /* 0 = BS4, 1 = BS8 */
#define DDAR_DW (1 << 3) /* 0 = 8b, 1 = 16b */
#define DDAR_Ser0UDCTr (0x0 << 4)
#define DDAR_Ser0UDCRc (0x1 << 4)
#define DDAR_Ser1SDLCTr (0x2 << 4)
#define DDAR_Ser1SDLCRc (0x3 << 4)
#define DDAR_Ser1UARTTr (0x4 << 4)
#define DDAR_Ser1UARTRc (0x5 << 4)
#define DDAR_Ser2ICPTr (0x6 << 4)
#define DDAR_Ser2ICPRc (0x7 << 4)
#define DDAR_Ser3UARTTr (0x8 << 4)
#define DDAR_Ser3UARTRc (0x9 << 4)
#define DDAR_Ser4MCP0Tr (0xa << 4)
#define DDAR_Ser4MCP0Rc (0xb << 4)
#define DDAR_Ser4MCP1Tr (0xc << 4)
#define DDAR_Ser4MCP1Rc (0xd << 4)
#define DDAR_Ser4SSPTr (0xe << 4)
#define DDAR_Ser4SSPRc (0xf << 4)
struct sa11x0_dma_sg {
u32 addr;
u32 len;
};
struct sa11x0_dma_desc {
struct dma_async_tx_descriptor tx;
u32 ddar;
size_t size;
/* maybe protected by c->lock */
struct list_head node;
unsigned sglen;
struct sa11x0_dma_sg sg[0];
};
struct sa11x0_dma_phy;
struct sa11x0_dma_chan {
struct dma_chan chan;
spinlock_t lock;
dma_cookie_t lc;
/* protected by c->lock */
struct sa11x0_dma_phy *phy;
enum dma_status status;
struct list_head desc_submitted;
struct list_head desc_issued;
/* protected by d->lock */
struct list_head node;
u32 ddar;
const char *name;
};
struct sa11x0_dma_phy {
void __iomem *base;
struct sa11x0_dma_dev *dev;
unsigned num;
struct sa11x0_dma_chan *vchan;
/* Protected by c->lock */
unsigned sg_load;
struct sa11x0_dma_desc *txd_load;
unsigned sg_done;
struct sa11x0_dma_desc *txd_done;
#ifdef CONFIG_PM_SLEEP
u32 dbs[2];
u32 dbt[2];
u32 dcsr;
#endif
};
struct sa11x0_dma_dev {
struct dma_device slave;
void __iomem *base;
spinlock_t lock;
struct tasklet_struct task;
struct list_head chan_pending;
struct list_head desc_complete;
struct sa11x0_dma_phy phy[NR_PHY_CHAN];
};
static struct sa11x0_dma_chan *to_sa11x0_dma_chan(struct dma_chan *chan)
{
return container_of(chan, struct sa11x0_dma_chan, chan);
}
static struct sa11x0_dma_dev *to_sa11x0_dma(struct dma_device *dmadev)
{
return container_of(dmadev, struct sa11x0_dma_dev, slave);
}
static struct sa11x0_dma_desc *to_sa11x0_dma_tx(struct dma_async_tx_descriptor *tx)
{
return container_of(tx, struct sa11x0_dma_desc, tx);
}
static struct sa11x0_dma_desc *sa11x0_dma_next_desc(struct sa11x0_dma_chan *c)
{
if (list_empty(&c->desc_issued))
return NULL;
return list_first_entry(&c->desc_issued, struct sa11x0_dma_desc, node);
}
static void sa11x0_dma_start_desc(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd)
{
list_del(&txd->node);
p->txd_load = txd;
p->sg_load = 0;
dev_vdbg(p->dev->slave.dev, "pchan %u: txd %p[%x]: starting: DDAR:%x\n",
p->num, txd, txd->tx.cookie, txd->ddar);
}
static void noinline sa11x0_dma_start_sg(struct sa11x0_dma_phy *p,
struct sa11x0_dma_chan *c)
{
struct sa11x0_dma_desc