blob: c5b7d91b18bbd560bf836986142bff814f019b39 [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>
#include <linux/dmaengine.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#define XOR_MAX_SRC_CNT 6
#define virt_to_xor_dma(pbase, vbase, vaddr) ((pbase + ((unsigned long)vaddr - (unsigned long)vbase)))
struct comcerto_xor_device {
struct dma_device device;
};
struct comcerto_xor_chan {
struct comcerto_xor_device *device;
struct dma_chan chan;
struct tasklet_struct irq_tasklet;
spinlock_t lock;
dma_cookie_t completed_cookie;
};
struct comcerto_sw_xor_desc {
struct dma_async_tx_descriptor async_tx;
int src_cnt;
size_t len;
dma_addr_t dma_dest;
dma_addr_t dma_src[XOR_MAX_SRC_CNT];
};
void *comcerto_xor_pool_virt;
dma_addr_t comcerto_xor_pool_phy;
spinlock_t comcerto_xor_cleanup_lock;
static DECLARE_WAIT_QUEUE_HEAD(comcerto_xor_wait_queue);
static int comcerto_xor_sleeping = 0;
static int xor_rd_idx = 0;
static int xor_wr_idx = 0;
static int xor_dma_idx = 0;
static int xor_sw_rd_idx = 0;
static int xor_sw_wr_idx = 0;
static int xor_sw_wr_prev_idx = 0;
static int xor_current_batch_count = 0;
#define XOR_FDESC_COUNT 512
#if defined(CONFIG_COMCERTO_64K_PAGES)
#define XOR_SW_FDESC_COUNT 32
#else
#define XOR_SW_FDESC_COUNT XOR_FDESC_COUNT
#endif
struct timer_list comcerto_xor_timer;
#define COMPLETION_TIMEOUT msecs_to_jiffies(100)
static int comerto_xor_timer_first = 1;
struct comcerto_xor_inbound_fdesc *xor_in_fdesc[XOR_FDESC_COUNT];
struct comcerto_xor_outbound_fdesc *xor_out_fdesc[XOR_FDESC_COUNT];
struct comcerto_sw_xor_desc sw_xor_desc[XOR_SW_FDESC_COUNT];
struct comcerto_xor_device *comcerto_xor_dev;
struct comcerto_xor_chan comcerto_xor_ch;
#define OWNER_XOR_FREE 1
#define OWNER_MEMCPY_FREE 2
#define OWNER_XOR_BUSY 3
#define OWNER_MEMCPY_BUSY 4
static int dma_owned = 0;
static int memcpy_processed_ongoing = 0;
static int memcpy_pending_count = 0;
//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
#define FLENEN 0x2
static DECLARE_WAIT_QUEUE_HEAD(mdma_memcpy_busy_queue);
static DECLARE_WAIT_QUEUE_HEAD(mdma_done_queue);
unsigned long mdma_in_desc_phy;
unsigned long mdma_out_desc_phy;
struct comcerto_memcpy_inbound_fdesc *mdma_in_desc;
struct comcerto_memcpy_outbound_fdesc *mdma_out_desc;
EXPORT_SYMBOL(mdma_in_desc);
EXPORT_SYMBOL(mdma_out_desc);
static inline void comcerto_xor_set_in_bdesc(u32 buf_idx, u32 bdesc_idx, u32 addr, u32 ctrl)
{
xor_in_fdesc[buf_idx]->bdesc[bdesc_idx].bpointer = addr;
xor_in_fdesc[buf_idx]->bdesc[bdesc_idx].bcontrol = ctrl;
}
static inline void comcerto_xor_set_out_bdesc(u32 buf_idx, u32 bdesc_idx, u32 addr, u32 ctrl)
{
xor_out_fdesc[buf_idx]->bdesc[bdesc_idx].bpointer = addr;
xor_out_fdesc[buf_idx]->bdesc[bdesc_idx].bcontrol = ctrl;
}
static void comcerto_xor_set_desc(sw_idx, hw_idx)
{
int i,split_no;
u32 fstatus0 = 0;
u32 addr;
int split_size;
dma_addr_t dest;
dma_addr_t *srcs;
u32 block_size;
int src_cnt;
block_size = sw_xor_desc[sw_idx].len;
src_cnt = sw_xor_desc[sw_idx].src_cnt;
srcs = sw_xor_desc[sw_idx].dma_src;
dest = sw_xor_desc[sw_idx].dma_dest;
if(block_size != PAGE_SIZE)
printk("%s: input buffers not %d len\n",__func__, (unsigned int)PAGE_SIZE);
#if defined(CONFIG_COMCERTO_64K_PAGES)
block_size = block_size/16; //to get 4K
split_size = 16;
#else
split_size = 1;
#endif
for(split_no = 0 ; split_no < split_size; split_no++)
{
for(i = 0; i < src_cnt - 1; i++) {
addr = (u32)sw_xor_desc[sw_idx].dma_src[i] + 4096 * split_no;
comcerto_xor_set_in_bdesc(hw_idx, i, addr, block_size);
}
addr = (u32)sw_xor_desc[sw_idx].dma_src[src_cnt - 1] + 4096 * split_no;
comcerto_xor_set_in_bdesc(hw_idx, src_cnt - 1, addr, block_size | BLAST);
fstatus0 = 1; // New Req, reset block counter, block offset, clear scratchpad (overwrite existing data)
fstatus0 |= (1 << 1); // Read SP, return content of scratch pad after processing input data
fstatus0 |= (0 << 2); // Mode, Encode
fstatus0 |= (src_cnt << 4); // Number of blocks to be processed
fstatus0 |= (1 << 9); // Type, XOR
fstatus0 |= (XOR_BLOCK_SIZE_4096 << 11);
xor_in_fdesc[hw_idx]->fcontrol = 0;
xor_in_fdesc[hw_idx]->fstatus0 = fstatus0;
xor_in_fdesc[hw_idx]->fstatus1 = 0;
addr = (u32)sw_xor_desc[sw_idx].dma_dest + 4096 * split_no;
comcerto_xor_set_out_bdesc(hw_idx, 0, addr, block_size | BLAST);
xor_out_fdesc[hw_idx]->fcontrol = 0;
xor_out_fdesc[hw_idx]->fstatus0 = 0;
xor_out_fdesc[hw_idx]->fstatus1 = 0;
hw_idx = (hw_idx + 1) % XOR_FDESC_COUNT;
}
xor_wr_idx = hw_idx;
}
static inline int comcerto_dma_busy(void)
{
return (readl_relaxed(IO2M_CONTROL) & 0x1);
}
static void comcerto_xor_update_dma_head(int idx)
{
u32 out_desc_head, in_desc_head;
out_desc_head = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_out_fdesc[idx]);
in_desc_head = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_in_fdesc[idx]);
wmb();
writel_relaxed(out_desc_head, IO2M_HEAD);
writel_relaxed(in_desc_head, M2IO_HEAD);
}
static void comcerto_xor_update_dma_flen(int flen)
{
wmb();
writel_relaxed(flen, M2IO_FLEN);
writel_relaxed(flen, IO2M_FLEN);
}
static int comcerto_xor_rb_full(void)
{
if(CIRC_SPACE(xor_sw_wr_idx, xor_sw_rd_idx, XOR_SW_FDESC_COUNT) > 0)
return 0;
else
return 1;
}
void comcerto_xor_request_wait(void)
{
DEFINE_WAIT(wait);
prepare_to_wait(&comcerto_xor_wait_queue, &wait, TASK_UNINTERRUPTIBLE);
comcerto_xor_sleeping++;
while (comcerto_xor_rb_full()) {
spin_unlock_bh(&comcerto_xor_ch.lock);
schedule();
spin_lock_bh(&comcerto_xor_ch.lock);
prepare_to_wait(&comcerto_xor_wait_queue, &wait, TASK_UNINTERRUPTIBLE);
}
finish_wait(&comcerto_xor_wait_queue, &wait);
comcerto_xor_sleeping--;
}
static void comcerto_dma_process(void)
{
unsigned long pending_count;
if(!dma_owned || dma_owned==OWNER_XOR_FREE)
{
pending_count = CIRC_CNT(xor_wr_idx, xor_dma_idx, XOR_FDESC_COUNT);
if(pending_count)
{
dma_owned = OWNER_XOR_BUSY;
xor_current_batch_count = pending_count;
comcerto_xor_update_dma_flen(pending_count);
comcerto_xor_update_dma_head(xor_dma_idx);
}
}
}
static void comcerto_xor_cleanup(void)
{
int i,j,k;
int idx;
int cleanup_count;
unsigned long flags;
int split_size;
struct comcerto_sw_xor_desc *sw_desc;
spin_lock_irqsave(&mdma_lock, flags);
cleanup_count = CIRC_CNT(xor_dma_idx, xor_rd_idx, XOR_FDESC_COUNT);
spin_unlock_irqrestore(&mdma_lock, flags);
#if defined(CONFIG_COMCERTO_64K_PAGES)
split_size = 16;
#else
split_size = 1;
#endif
if(cleanup_count && (cleanup_count % split_size == 0))
{
for(i = 0 ; i < cleanup_count/split_size; i++)
{
struct dma_async_tx_descriptor *tx;
#if defined(CONFIG_COMCERTO_64K_PAGES)
if(xor_rd_idx%16)
printk("%s: xor_rd_idx %d not multiple of 16\n",__func__, xor_rd_idx);
#endif
idx = xor_sw_rd_idx;
tx = &sw_xor_desc[idx].async_tx;
sw_desc = &sw_xor_desc[idx];
for (j = 0; j < sw_desc->src_cnt; j++) {
dma_unmap_page(NULL, sw_desc->dma_src[j], sw_desc->len, DMA_TO_DEVICE);
}
dma_unmap_page(NULL, sw_desc->dma_dest, sw_desc->len, DMA_BIDIRECTIONAL);
comcerto_xor_ch.completed_cookie = tx->cookie;
if (tx->callback) {
tx->callback(tx->callback_param);
tx->callback = NULL;
}
else
printk("No Callback\n");
smp_mb();
spin_lock_irqsave(&mdma_lock, flags);
for(k = 0 ; k < split_size; k++)
{
xor_rd_idx = (xor_rd_idx + 1) % XOR_FDESC_COUNT;
}
spin_unlock_irqrestore(&mdma_lock, flags);
xor_sw_rd_idx = (xor_sw_rd_idx + 1) % XOR_SW_FDESC_COUNT;
}
}
else
{
if(cleanup_count)
printk("%s: cleanup_count %d not multiple of 16\n",__func__, cleanup_count);
}
}
static void comcerto_xor_tasklet(unsigned long data)
{
spin_lock_bh(&comcerto_xor_ch.lock);
comcerto_xor_cleanup();
spin_unlock_bh(&comcerto_xor_ch.lock);
}
static void comcerto_xor_timer_fnc(unsigned long data)
{
unsigned long flags;
spin_lock_irqsave(&mdma_lock, flags);
if(comcerto_xor_sleeping)
wake_up(&comcerto_xor_wait_queue);
spin_unlock_irqrestore(&mdma_lock, flags);
comcerto_xor_tasklet(0);
mod_timer(&comcerto_xor_timer, jiffies + COMPLETION_TIMEOUT);
}
static void comcerto_xor_issue_pending(struct dma_chan *chan)
{
unsigned long flags;
spin_lock_irqsave(&mdma_lock, flags);
comcerto_dma_process();
if(comcerto_xor_sleeping)
wake_up(&comcerto_xor_wait_queue);
spin_unlock_irqrestore(&mdma_lock, flags);
comcerto_xor_tasklet(0);
}
static inline struct comcerto_sw_xor_desc *txd_to_comcerto_desc(struct dma_async_tx_descriptor *txd)
{
return container_of(txd, struct comcerto_sw_xor_desc, async_tx);
}
static dma_cookie_t comcerto_xor_tx_submit(struct dma_async_tx_descriptor *tx)
{
unsigned long flags;
struct dma_chan *c = tx->chan;
dma_cookie_t cookie;
spin_lock_bh(&comcerto_xor_ch.lock);
cookie = c->cookie;
cookie++;
if (cookie < 0)
cookie = 1;
tx->cookie = cookie;
c->cookie = cookie;
spin_lock_irqsave(&mdma_lock, flags);
comcerto_xor_set_desc(xor_sw_wr_prev_idx, xor_wr_idx);
xor_sw_wr_prev_idx = (xor_sw_wr_prev_idx + 1) % XOR_SW_FDESC_COUNT ;
comcerto_dma_process();
spin_unlock_irqrestore(&mdma_lock, flags);
spin_unlock_bh(&comcerto_xor_ch.lock);
if(comerto_xor_timer_first)
{
mod_timer(&comcerto_xor_timer, jiffies + COMPLETION_TIMEOUT);
comerto_xor_timer_first = 0;
}
return cookie;
}
struct dma_async_tx_descriptor *
comcerto_xor_prep_dma_xor(struct dma_chan *chan, dma_addr_t dest, dma_addr_t *srcs,
unsigned int src_cnt, size_t len, unsigned long xor_flags)
{
int desc_idx;
int i;
spin_lock_bh(&comcerto_xor_ch.lock);
if(comcerto_xor_rb_full())
{
comcerto_xor_request_wait();
}
desc_idx = xor_sw_wr_idx;
xor_sw_wr_idx = (xor_sw_wr_idx + 1) % XOR_SW_FDESC_COUNT ;
sw_xor_desc[desc_idx].async_tx.flags = xor_flags;
sw_xor_desc[desc_idx].src_cnt = src_cnt;
sw_xor_desc[desc_idx].len = len;
sw_xor_desc[desc_idx].dma_dest = dest;
for(i = 0; i < src_cnt; i++)
sw_xor_desc[desc_idx].dma_src[i] = srcs[i];
spin_unlock_bh(&comcerto_xor_ch.lock);
return &sw_xor_desc[desc_idx].async_tx;
}
static int comcerto_xor_alloc_chan_resources(struct dma_chan *chan)
{
int i;
for(i = 0; i < XOR_SW_FDESC_COUNT; i++) {
memset(&sw_xor_desc[i], 0, sizeof(struct comcerto_sw_xor_desc));
sw_xor_desc[i].async_tx.tx_submit = comcerto_xor_tx_submit;
sw_xor_desc[i].async_tx.cookie = 0;
dma_async_tx_descriptor_init(&sw_xor_desc[i].async_tx, chan);
}
return XOR_SW_FDESC_COUNT;
}
static void comcerto_xor_free_chan_resources(struct dma_chan *chan)
{
//TODO
printk("*** %s ***\n",__func__);
return;
}
static enum dma_status comcerto_xor_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
dma_cookie_t last_used;
dma_cookie_t last_complete;
last_used = chan->cookie;
last_complete = comcerto_xor_ch.completed_cookie;
dma_set_tx_state(txstate, last_complete, last_used, 0);
return dma_async_is_complete(cookie, last_complete, last_used);
}
#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, void *p, unsigned int len, int dir, int use_acp)
{
dma_addr_t phys_addr = virt_to_phys(p);
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_single(NULL, p, len, dir); //TODO add proper checks
}
int comcerto_dma_sg_add_input(struct comcerto_dma_sg *sg, void *p, 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, p, 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, p, 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 = 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, void *p, 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, p, 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, p, 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 = 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 (dma_owned && (dma_owned != OWNER_MEMCPY_FREE)) {
prepare_to_wait(&mdma_memcpy_busy_queue, &wait, TASK_UNINTERRUPTIBLE);
memcpy_pending_count++;
while (!dma_owned || !(dma_owned == OWNER_MEMCPY_FREE)) {
spin_unlock_irqrestore(&mdma_lock, flags);
schedule();
spin_lock_irqsave(&mdma_lock, flags);
prepare_to_wait(&mdma_memcpy_busy_queue, &wait, TASK_UNINTERRUPTIBLE);
}
memcpy_pending_count--;
finish_wait(&mdma_memcpy_busy_queue, &wait);
}
dma_owned = OWNER_MEMCPY_BUSY;
spin_unlock_irqrestore(&mdma_lock, flags);
}
EXPORT_SYMBOL(comcerto_dma_get);
void comcerto_dma_put(void)
{
#if 0
unsigned long flags;
spin_lock_irqsave(&mdma_lock, flags);
mdma_busy = 0;
spin_unlock_irqrestore(&mdma_lock, flags);
wake_up(&mdma_memcpy_busy_queue);
#endif
}
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|IRQ_IRQFLEN|IRQ_IRQFTHLD|IRQ_IRQFLST, IO2M_IRQ_ENABLE);
writel_relaxed(IRQ_IRQFDON|IRQ_IRQFLEN|IRQ_IRQFTHLD|IRQ_IRQFLST, M2IO_IRQ_ENABLE);
writel_relaxed(FLENEN, M2IO_CONTROL);
writel_relaxed(0xf | (0x3ff << 8), M2IO_BURST);
writel_relaxed(FLENEN, IO2M_CONTROL);
writel_relaxed(0xf | (0x3ff << 8), 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);
writel_relaxed(1, M2IO_FLEN);
writel_relaxed(1, IO2M_FLEN);
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)
{
int i;
int pending_count;
unsigned long flags;
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 (intr_cause & IRQ_IRQFLEN) {
spin_lock_irqsave(&mdma_lock, flags);
if(!dma_owned)
printk(KERN_ALERT " NULL MDMA Ownership !!!\n");
if(dma_owned==OWNER_XOR_BUSY)
{
for(i = 0 ; i < xor_current_batch_count; i++)
xor_dma_idx = (xor_dma_idx + 1) % XOR_FDESC_COUNT;
xor_current_batch_count = 0;
if(memcpy_pending_count)
{
dma_owned = OWNER_MEMCPY_FREE;
wake_up(&mdma_memcpy_busy_queue);
}
else
{
pending_count = CIRC_CNT(xor_wr_idx, xor_dma_idx, XOR_FDESC_COUNT);
if(pending_count)
{
dma_owned = OWNER_XOR_BUSY;
xor_current_batch_count = pending_count;
comcerto_xor_update_dma_flen(xor_current_batch_count);
comcerto_xor_update_dma_head(xor_dma_idx);
}
else
{
dma_owned = 0;
}
}
if(comcerto_xor_sleeping)
{
wake_up(&comcerto_xor_wait_queue);
}
tasklet_schedule(&comcerto_xor_ch.irq_tasklet);
}
else //memcpy
{
mdma_done = 1;
wake_up(&mdma_done_queue);
memcpy_processed_ongoing++;
pending_count = CIRC_CNT(xor_wr_idx, xor_dma_idx, XOR_FDESC_COUNT);
if(pending_count)
{
memcpy_processed_ongoing = 0;
dma_owned = OWNER_XOR_BUSY;
xor_current_batch_count = pending_count;
comcerto_xor_update_dma_flen(xor_current_batch_count);
comcerto_xor_update_dma_head(xor_dma_idx);
if(comcerto_xor_sleeping)
{
wake_up(&comcerto_xor_wait_queue);
}
}
else
{
if(memcpy_pending_count)
{
dma_owned = OWNER_MEMCPY_FREE;
wake_up(&mdma_memcpy_busy_queue);
}
else
{
dma_owned = 0;
}
}
}
spin_unlock_irqrestore(&mdma_lock, flags);
}
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 *memcpy_pool = (void *)IRAM_MEMORY_VADDR;
void *xor_pool;
int i;
struct dma_device *dma_dev;
int ret = 0;
/* 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)
return ret;
virtbase = ioremap(io->start, resource_size(io));
if (!virtbase)
goto err_free_irq;
/* Initialize comcerto_xor_device */
comcerto_xor_dev = devm_kzalloc(&pdev->dev, sizeof(struct comcerto_xor_device), GFP_KERNEL);
if(!comcerto_xor_dev)
{
ret = -ENOMEM;
goto err_free_remap;
}
dma_dev = &comcerto_xor_dev->device;
INIT_LIST_HEAD(&dma_dev->channels);
dma_cap_set(DMA_XOR,dma_dev->cap_mask);
dma_dev->dev = &pdev->dev;
dma_dev->device_alloc_chan_resources = comcerto_xor_alloc_chan_resources;
dma_dev->device_free_chan_resources = comcerto_xor_free_chan_resources;
dma_dev->device_tx_status = comcerto_xor_status;
dma_dev->device_issue_pending = comcerto_xor_issue_pending;
dma_dev->device_prep_dma_xor = comcerto_xor_prep_dma_xor;
dma_dev->max_xor = XOR_MAX_SRC_CNT;
platform_set_drvdata(pdev,comcerto_xor_dev);
/* Initialize comcerto_xor_chan */
comcerto_xor_ch.chan.device = dma_dev;
list_add_tail(&comcerto_xor_ch.chan.device_node,&dma_dev->channels);
ret = dma_async_device_register(dma_dev);
if (unlikely(ret)) {
printk(KERN_ERR "%s: Failed to register XOR DMA channel %d\n",__func__,ret);
goto err_free_dma;
}
else
printk(KERN_INFO "%s: XOR DMA channel registered\n",__func__);
spin_lock_init(&comcerto_xor_ch.lock);
spin_lock_init(&comcerto_xor_cleanup_lock);
spin_lock_init(&mdma_lock);
//Memcpy descriptor initializing
mdma_in_desc = (struct comcerto_memcpy_inbound_fdesc *) (memcpy_pool);
memcpy_pool += sizeof(struct comcerto_memcpy_inbound_fdesc);
memcpy_pool = (void *)((unsigned long)(memcpy_pool + 15) & ~15);
mdma_out_desc = (struct comcerto_memcpy_outbound_fdesc *) (memcpy_pool);
memcpy_pool += sizeof(struct comcerto_memcpy_outbound_fdesc);
memcpy_pool = (void *)((unsigned long)(memcpy_pool + 15) & ~15);
mdma_in_desc_phy = virt_to_aram(mdma_in_desc);
mdma_out_desc_phy = virt_to_aram(mdma_out_desc);
//XOR descriptor initializing
comcerto_xor_pool_virt = dma_alloc_coherent(NULL, XOR_FDESC_COUNT * (sizeof(struct comcerto_xor_inbound_fdesc)
+ sizeof(struct comcerto_xor_outbound_fdesc)), &comcerto_xor_pool_phy, GFP_KERNEL);
xor_pool = comcerto_xor_pool_virt;
for (i = 0; i < XOR_FDESC_COUNT; i++) {
xor_in_fdesc[i] = (struct comcerto_xor_inbound_fdesc *) (xor_pool);
xor_pool += sizeof(struct comcerto_xor_inbound_fdesc);
xor_pool = (void *)((unsigned long)(xor_pool + 15) & ~15);
xor_out_fdesc[i] = (struct comcerto_xor_outbound_fdesc *) (xor_pool);
xor_pool += sizeof(struct comcerto_xor_outbound_fdesc);
xor_pool = (void *)((unsigned long)(xor_pool + 15) & ~15);
memset(xor_in_fdesc[i], 0 , sizeof(struct comcerto_xor_inbound_fdesc));
memset(xor_out_fdesc[i], 0 , sizeof(struct comcerto_xor_outbound_fdesc));
}
for(i = 0; i < XOR_FDESC_COUNT - 1; i++) {
xor_in_fdesc[i]->next_desc = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_in_fdesc[i+1]);
xor_out_fdesc[i]->next_desc = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_out_fdesc[i+1]);
}
xor_in_fdesc[XOR_FDESC_COUNT-1]->next_desc = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_in_fdesc[0]);
xor_out_fdesc[XOR_FDESC_COUNT-1]->next_desc = virt_to_xor_dma(comcerto_xor_pool_phy, comcerto_xor_pool_virt, xor_out_fdesc[0]);
init_timer(&comcerto_xor_timer);
comcerto_xor_timer.function = comcerto_xor_timer_fnc;
comcerto_xor_timer.data = 0;
tasklet_init(&comcerto_xor_ch.irq_tasklet, comcerto_xor_tasklet, 0);
comcerto_dma_setup();
goto out;
err_free_dma:
platform_set_drvdata(pdev,NULL);
kfree(comcerto_xor_dev);
err_free_remap:
iounmap(virtbase);
err_free_irq:
free_irq(irq, NULL);
out:
return ret;
}
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");