blob: eb0b3a26f6f11d8021f8ea5811bc11a1fbc18926 [file] [log] [blame]
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/memory.h>
#include "comcerto_xor.h"
#define to_comcerto_xor_chan(dma_chan) \
container_of(dma_chan,struct comcerto_xor_chan, chan)
#define to_comcerto_xor_device(dev) \
container_of(dev,struct comcerto_xor_device, device)
#define to_comcerto_xor_slot(tx) \
container_of(tx, struct comcerto_xor_desc_slot, async_tx)
/* ---------------------- Functions to set/read frame descriptor --------------------------- */
/* ------- Set inbound frame descriptor ------- */
static void comcerto_set_next_desc_inbound(struct comcerto_xor_desc_slot *slot, u32 next_desc_addr)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
hw_desc->next_desc = next_desc_addr;
}
static void comcerto_set_fcontrol_inbound(struct comcerto_xor_desc_slot *slot)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
switch (slot->type) {
case DMA_XOR:
case DMA_XOR_VAL:
case DMA_MEMCPY:
hw_desc->fcontrol = 0;
break;
default:
dev_printk(KERN_ERR, slot->async_tx.chan->device->dev,
"error: unsupported operation %d.\n",slot->type);
}
}
static void comcerto_set_fstatus0_inbound(struct comcerto_xor_desc_slot *slot)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
switch (slot->type) {
case DMA_XOR:
hw_desc->fstatus0 = 1; // New Req, reset block counter, block offset, clear scratchpad (overwrite existing data)
hw_desc->fstatus0 = hw_desc->fstatus0 | (1 << 1); // Read SP, return content of scratch pad after processing input data
hw_desc->fstatus0 = hw_desc->fstatus0 | (0 << 2); // Mode, Encode
hw_desc->fstatus0 = hw_desc->fstatus0 | (slot->src_cnt << 4); // Number of blocks to be processed
hw_desc->fstatus0 = hw_desc->fstatus0 | (1 << 9); // Type, XOR
if(slot->len == 256)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_256 << 11);
if(slot->len == 512)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_512 << 11);
if(slot->len == 1024)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_1024 << 11);
if(slot->len == 2048)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_2048 << 11);
if(slot->len == 4096)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_4096 << 11);
break;
case DMA_XOR_VAL:
hw_desc->fstatus0 = 1; // New Req, reset block counter, block offset, clear scratchpad (overwrite existing data)
hw_desc->fstatus0 = hw_desc->fstatus0 | (0 << 1); // Read SP, no output generated, only status
hw_desc->fstatus0 = hw_desc->fstatus0 | (1 << 2); // Mode, Validate
hw_desc->fstatus0 = hw_desc->fstatus0 | (slot->src_cnt << 4); // Number of blocks to be processed
hw_desc->fstatus0 = hw_desc->fstatus0 | (1 << 9); // Type, XOR
if(slot->len == 256)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_256 << 11);
if(slot->len == 512)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_512 << 11);
if(slot->len == 1024)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_1024 << 11);
if(slot->len == 2048)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_2048 << 11);
if(slot->len == 4096)
hw_desc->fstatus0 = hw_desc->fstatus0 | (XOR_BLOCK_SIZE_4096 << 11);
break;
case DMA_MEMCPY:
break;
default:
dev_printk(KERN_ERR, slot->async_tx.chan->device->dev,
"error: unsupported operation %d.\n",slot->type);
return;
}
}
static void comcerto_set_buff_info_inbound(struct comcerto_xor_desc_slot *slot, dma_addr_t *src)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
int i;
if(slot->src_cnt > COMCERTO_XOR_MAX_SRC){
dev_printk(KERN_ERR, slot->async_tx.chan->device->dev,
"error: max source available is %d.\n",COMCERTO_XOR_MAX_SRC);
return;
}
for(i=0; i<slot->src_cnt; i++) {
hw_desc->buff_info[i*2] = src[i];
if(i == slot->src_cnt - 1)
hw_desc->buff_info[i*2 + 1] = slot->len | BLAST;
else
hw_desc->buff_info[i*2 + 1] = slot->len;
}
}
/* ------- Set outbound frame descriptor ------- */
static void comcerto_set_next_desc_outbound(struct comcerto_xor_desc_slot *slot, u32 next_desc_addr)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
hw_desc->next_desc = next_desc_addr;
}
static void comcerto_set_fcontrol_outbound(struct comcerto_xor_desc_slot *slot)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
switch (slot->type) {
case DMA_XOR:
case DMA_XOR_VAL:
case DMA_MEMCPY:
hw_desc->fcontrol = 0;
break;
default:
dev_printk(KERN_ERR, slot->async_tx.chan->device->dev,
"error: unsupported operation %d.\n",slot->type);
return;
}
}
static void comcerto_set_buff_info_outbound(struct comcerto_xor_desc_slot *slot, dma_addr_t dest)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
hw_desc->buff_info[0] = dest;
hw_desc->buff_info[1] = slot->len | BLAST;
}
/* ------- Read inbound frame descriptor ------- */
static u32 comcerto_get_buff_info_inbound(struct comcerto_xor_desc_slot *slot, u16 i)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
return hw_desc->buff_info[i*2];
}
/* ------- Read outbound frame descriptor ------- */
static u32 comcerto_get_fstatus0_outbound(struct comcerto_xor_desc_slot *slot)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
return hw_desc->fstatus0;
}
static u32 comcerto_get_buff_info_outbound(struct comcerto_xor_desc_slot *slot, u16 i)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
return hw_desc->buff_info[i*2];
}
static void comcerto_xor_inbound_desc_init(struct comcerto_xor_desc_slot *slot,
dma_addr_t *src)
{
struct comcerto_xor_inbound_desc *hw_desc = slot->hw_desc_inbound;
memset(hw_desc, 0, sizeof(struct comcerto_xor_inbound_desc));
comcerto_set_fcontrol_inbound(slot);
comcerto_set_fstatus0_inbound(slot);
comcerto_set_next_desc_inbound(slot, 0);
comcerto_set_buff_info_inbound(slot, src);
}
static void comcerto_xor_outbound_desc_init(struct comcerto_xor_desc_slot *slot,
dma_addr_t dest)
{
struct comcerto_xor_outbound_desc *hw_desc = slot->hw_desc_outbound;
memset(hw_desc, 0, sizeof(struct comcerto_xor_outbound_desc));
comcerto_set_fcontrol_outbound(slot);
comcerto_set_next_desc_outbound(slot, 0);
if(dest)
comcerto_set_buff_info_outbound(slot, dest);
}
/* --------------------- Functions of register configuration --------------------------*/
/* ------- Set inbound register ------- */
static void comcerto_xor_set_register_m2io_control(struct comcerto_xor_chan *comcerto_xor_ch, u32 value)
{
__raw_writel(value, M2IO_CONTROL(comcerto_xor_ch));
}
static void comcerto_xor_set_register_m2io_head(struct comcerto_xor_chan *comcerto_xor_ch, u32 value)
{
__raw_writel(value, M2IO_HEAD(comcerto_xor_ch));
}
/* ------- Set outbound register ------- */
static void comcerto_xor_set_register_io2m_head(struct comcerto_xor_chan *comcerto_xor_ch, u32 value)
{
__raw_writel(value, IO2M_HEAD(comcerto_xor_ch));
}
static void comcerto_xor_set_register_io2m_irq_enable(struct comcerto_xor_chan *comcerto_xor_ch, u32 value)
{
__raw_writel(value, IO2M_IRQ_ENABLE(comcerto_xor_ch));
}
static void comcerto_xor_set_register_io2m_irq_status(struct comcerto_xor_chan *comcerto_xor_ch, u32 value)
{
__raw_writel(value, IO2M_IRQ_STATUS(comcerto_xor_ch));
}
/* ------- Read inbound register ------- */
static u32 comcerto_xor_get_register_m2io_control(struct comcerto_xor_chan *comcerto_xor_ch)
{
return __raw_readl(M2IO_CONTROL(comcerto_xor_ch));
}
/* ------- Read outbound register ------- */
static u32 comcerto_xor_get_register_io2m_control(struct comcerto_xor_chan *comcerto_xor_ch)
{
return __raw_readl(IO2M_CONTROL(comcerto_xor_ch));
}
static u32 comcerto_xor_get_register_io2m_head(struct comcerto_xor_chan *comcerto_xor_ch)
{
return __raw_readl(IO2M_HEAD(comcerto_xor_ch));
}
static u32 comcerto_xor_get_register_io2m_irq_status(struct comcerto_xor_chan *comcerto_xor_ch)
{
return __raw_readl(IO2M_IRQ_STATUS(comcerto_xor_ch));
}
static void comcerto_dump_xor_regs(struct comcerto_xor_chan *comcerto_xor_ch)
{
u32 val;
val = __raw_readl(M2IO_CONTROL(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_CONTROL 0x%8x.\n",val);
val = __raw_readl(M2IO_HEAD(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_HEAD 0x%8x.\n",val);
val = __raw_readl(M2IO_BURST(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_BURST 0x%8x.\n",val);
val = __raw_readl(M2IO_FLEN(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_FLEN 0x%8x.\n",val);
val = __raw_readl(M2IO_IRQ_ENABLE(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_IRQ_ENABLE 0x%8x.\n",val);
val = __raw_readl(M2IO_IRQ_STATUS(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_IRQ_STATUS 0x%8x.\n",val);
val = __raw_readl(M2IO_RESET(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"M2IO_RESET 0x%8x.\n",val);
val = __raw_readl(IO2M_CONTROL(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_CONTROL 0x%8x.\n",val);
val = __raw_readl(IO2M_HEAD(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_HEAD 0x%8x.\n",val);
val = __raw_readl(IO2M_BURST(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_BURST 0x%8x.\n",val);
val = __raw_readl(IO2M_FLEN(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_FLEN 0x%8x.\n",val);
val = __raw_readl(IO2M_IRQ_ENABLE(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_IRQ_ENABLE 0x%8x.\n",val);
val = __raw_readl(IO2M_IRQ_STATUS(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_IRQ_STATUS 0x%8x.\n",val);
val = __raw_readl(IO2M_RESET(comcerto_xor_ch));
dev_printk(KERN_ERR, comcerto_xor_ch->device->device.dev,
"IO2M_RESET 0x%8x.\n",val);
}
static void comcerto_xor_register_init(struct comcerto_xor_chan *comcerto_xor_ch)
{
u32 value;
/* M2IO_CONTROL: Enable DONOSTA */
value = M2IO_DONOSTA;
comcerto_xor_set_register_m2io_control(comcerto_xor_ch,value);
/* IO2M_IRQ_ENABLE: Enable IRQFRDYN, IRQFLST and IRQFLSH*/
value = IO2M_IRQFRDYN | IO2M_IRQFLST | IO2M_IRQFLSH;
comcerto_xor_set_register_io2m_irq_enable(comcerto_xor_ch,value);
}
/* --------------------------- Miscellaneous functions --------------------------------*/
static dma_cookie_t comcerto_xor_run_tx_complete_actions(struct comcerto_xor_desc_slot *slot,
struct comcerto_xor_chan *comcerto_xor_ch,
dma_cookie_t cookie)
{
u16 src_cnt;
if(slot->async_tx.cookie < 0)
printk(KERN_ERR "Invalid cookie.");
if(slot->async_tx.cookie >0 ) {
cookie = slot->async_tx.cookie;
src_cnt = slot->src_cnt;
if(slot->async_tx.callback)
slot->async_tx.callback(slot->async_tx.callback_param);
if(slot->len) {
struct device *dev = comcerto_xor_ch->device->device.dev;
enum dma_ctrl_flags flags = slot->async_tx.flags;
dma_addr_t src, dest;
dest = comcerto_get_buff_info_outbound(slot,0);
if(!(flags & DMA_COMPL_SKIP_DEST_UNMAP)) {
enum dma_data_direction dir;
if(src_cnt > 1)
dir = DMA_BIDIRECTIONAL;
else
dir = DMA_FROM_DEVICE;
dma_unmap_page(dev, dest, slot->len, dir);
}
if(!(flags & DMA_COMPL_SKIP_SRC_UNMAP)) {
while(src_cnt--){
src = comcerto_get_buff_info_inbound(slot, src_cnt);
if(src == dest)
continue;
dma_unmap_page(dev, src, slot->len, DMA_TO_DEVICE);
}
}
}
}
dma_run_dependencies(&slot->async_tx);
return cookie;
}
static int comcerto_xor_clean_completed_slots(struct comcerto_xor_chan *comcerto_xor_ch)
{
struct comcerto_xor_desc_slot *iter, *__iter;
list_for_each_entry_safe(iter, __iter, &comcerto_xor_ch->completed_slots, completed_node) {
if(async_tx_test_ack(&iter->async_tx)) {
list_del(&iter->completed_node);
iter->busy = 0;
}
}
return 0;
}
static void __comcerto_xor_slot_cleanup(struct comcerto_xor_chan *comcerto_xor_ch)
{
struct comcerto_xor_desc_slot *iter, *__iter;
dma_cookie_t cookie = 0;
u32 current_desc = comcerto_xor_get_register_io2m_head(comcerto_xor_ch);
int seen_current = 0;
comcerto_xor_clean_completed_slots(comcerto_xor_ch);
list_for_each_entry_safe(iter,__iter,&comcerto_xor_ch->chain,chain_node) {
prefetch(__iter);
prefetch(&__iter->async_tx);
if(seen_current)
break;
if(iter->hw_desc_outbound_dma == current_desc) {
seen_current = 1;
/* When we arrived here, the hardware may in two
* possible situations:
*
* 1. Not working, stopped
* 2. Working on current_desc
*
* For case 1, following if statement will be false and we'll
* continue to clean this slot.
* For case 2, we just break and leave the last slot
* to be cleaned at next execution.
*/
if(!(comcerto_xor_get_register_m2io_control(comcerto_xor_ch)&(0x1)) &&
!(comcerto_xor_get_register_io2m_control(comcerto_xor_ch)&(0x1)))
break;
}
if(iter->xor_check_result && iter->async_tx.cookie)
*iter->xor_check_result = comcerto_get_fstatus0_outbound(iter)&(0x1);
cookie = comcerto_xor_run_tx_complete_actions(iter,comcerto_xor_ch,cookie);
list_del(&iter->chain_node);
if(!async_tx_test_ack(&iter->async_tx))
list_add_tail(&iter->completed_node, &comcerto_xor_ch->completed_slots);
else
iter->busy = 0;
}
if(cookie > 0)
comcerto_xor_ch->completed_cookie = cookie;
}
static void comcerto_xor_slot_cleanup(struct comcerto_xor_chan *comcerto_xor_ch)
{
spin_lock_bh(&comcerto_xor_ch->lock);
__comcerto_xor_slot_cleanup(comcerto_xor_ch);
spin_unlock_bh(&comcerto_xor_ch->lock);
}
static void comcerto_xor_tasklet(unsigned long data)
{
struct comcerto_xor_chan *comcerto_xor_ch = (struct comcerto_xor_chan *) data;
comcerto_xor_slot_cleanup(comcerto_xor_ch);
}
static struct comcerto_xor_desc_slot *
comcerto_xor_alloc_slot(struct comcerto_xor_chan *comcerto_xor_ch)
{
struct comcerto_xor_desc_slot *iter, *__iter = NULL;
int slot_found, retry = 0;
retry:
slot_found = 0;
if(retry == 0 )
iter = comcerto_xor_ch->last_used;
else
iter = list_entry(&comcerto_xor_ch->all_slots,struct comcerto_xor_desc_slot, slot_node);
list_for_each_entry_safe_continue(
iter, __iter, &comcerto_xor_ch->all_slots, slot_node) {
prefetch(__iter);
prefetch(&__iter->async_tx);
if(iter->busy) {
if (retry)
break;
slot_found=0;
continue;
}
iter->async_tx.cookie = -EBUSY;
iter->xor_check_result = NULL;
iter->busy = 1;
comcerto_xor_ch->last_used = iter;
return iter;
}
if(!retry++)
goto retry;
tasklet_schedule(&comcerto_xor_ch->irq_tasklet);
return NULL;
}
static dma_cookie_t comcerto_desc_assign_cookie(struct comcerto_xor_chan *comcerto_xor_ch,
struct comcerto_xor_desc_slot * slot)
{
dma_cookie_t cookie = comcerto_xor_ch->chan.cookie;
if(++cookie < 0)
cookie = 1;
comcerto_xor_ch->chan.cookie = slot->async_tx.cookie = cookie;
return cookie;
}
/*------------------------------- XOR API Function-------------------------------------*/
static enum dma_status comcerto_xor_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
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);
}
static void comcerto_xor_issue_pending(struct dma_chan *chan)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
if(comcerto_xor_ch->pending >= COMCERTO_XOR_THRESHOLD && comcerto_xor_ch->to_be_started) {
comcerto_xor_ch->pending = 0;
comcerto_xor_set_register_m2io_head(comcerto_xor_ch, comcerto_xor_ch->to_be_started->async_tx.phys);
comcerto_xor_set_register_io2m_head(comcerto_xor_ch, comcerto_xor_ch->to_be_started->hw_desc_outbound_dma);
comcerto_xor_ch->to_be_started = NULL;
}
}
static dma_cookie_t comcerto_xor_tx_submit(struct dma_async_tx_descriptor *tx)
{
struct comcerto_xor_desc_slot *slot = to_comcerto_xor_slot(tx);
struct comcerto_xor_desc_slot *old_chain_tail;
dma_cookie_t cookie;
struct comcerto_xor_chan *comcerto_xor_ch;
struct dma_chan *chan;
chan = tx->chan;
comcerto_xor_ch = to_comcerto_xor_chan(chan);
spin_lock_bh(&comcerto_xor_ch->lock);
cookie = comcerto_desc_assign_cookie(comcerto_xor_ch, slot);
/* If chain list is empty, the hardware is absolutely stopped.
* Thus, we need only reset the head register in order to start
* the DMA
*/
if(list_empty(&comcerto_xor_ch->chain)){
list_add_tail(&slot->chain_node, &comcerto_xor_ch->chain);
comcerto_xor_ch->to_be_started = slot;
comcerto_xor_ch->pending++;
comcerto_xor_issue_pending(&comcerto_xor_ch->chan);
}
else {
/* If chain list is not empty, the hardware may or may not stopped.
* - It may not stopped because the chain is not empty. That is to
* say, the hardware still have request to process
* - It may stopped because the hardware has already finished the
* processing. But just haven't cleared the chain. (The chain can
* be only cleared in the tasklet.)
* For whatever case, we just:
* - update the FNext field of last element in chain.
* - set head register to restart the XOR processing if the hardware
* is not working
*/
old_chain_tail = list_entry(comcerto_xor_ch->chain.prev,
struct comcerto_xor_desc_slot,
chain_node);
list_add_tail(&slot->chain_node,&comcerto_xor_ch->chain);
comcerto_set_next_desc_outbound(old_chain_tail,slot->hw_desc_outbound_dma);
comcerto_set_next_desc_inbound(old_chain_tail,slot->async_tx.phys);
if(!(comcerto_xor_get_register_m2io_control(comcerto_xor_ch)&(0x1)) &&
!(comcerto_xor_get_register_io2m_control(comcerto_xor_ch)&(0x1))){
if(!comcerto_xor_ch->to_be_started)
comcerto_xor_ch->to_be_started = slot;
comcerto_xor_ch->pending++;
comcerto_xor_issue_pending(&comcerto_xor_ch->chan);
}
}
spin_unlock_bh(&comcerto_xor_ch->lock);
return cookie;
}
static void comcerto_xor_free_chan_resources(struct dma_chan *chan)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
struct comcerto_xor_desc_slot *iter, *__iter;
int in_use_descs = 0;
comcerto_xor_slot_cleanup(comcerto_xor_ch);
spin_lock_bh(&comcerto_xor_ch->lock);
list_for_each_entry_safe(iter, __iter, &comcerto_xor_ch->chain, chain_node) {
in_use_descs++;
list_del(&iter->chain_node);
}
list_for_each_entry_safe(iter, __iter, &comcerto_xor_ch->completed_slots, completed_node) {
in_use_descs++;
list_del(&iter->completed_node);
}
list_for_each_entry_safe_reverse(iter, __iter, &comcerto_xor_ch->all_slots, slot_node) {
list_del(&iter->slot_node);
kfree(iter);
comcerto_xor_ch->slot_allocated--;
}
comcerto_xor_ch->last_used = NULL;
spin_unlock_bh(&comcerto_xor_ch->lock);
if(in_use_descs)
dev_err(comcerto_xor_ch->device->device.dev,
"freeing %d in use descriptors!\n", in_use_descs);
}
static int comcerto_xor_alloc_chan_resources(struct dma_chan *chan)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
struct comcerto_xor_desc_slot *slot = NULL;
int num_slots_per_pool = PAGE_SIZE / (COMCERTO_XOR_INBOUND_DESC_SIZE + COMCERTO_XOR_OUTBOUND_DESC_SIZE);
int total_slots = POOL_NUMBER * num_slots_per_pool;
int gap = PAGE_SIZE - num_slots_per_pool * (COMCERTO_XOR_INBOUND_DESC_SIZE + COMCERTO_XOR_OUTBOUND_DESC_SIZE);
int i,j;
char *dma_desc_pool_virt_addr;
char *dma_desc_pool_addr;
i = j = comcerto_xor_ch->slot_allocated;
j %= num_slots_per_pool;
while( i < total_slots) {
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
if(!slot) {
printk(KERN_INFO "comcerto XOR Channel only initialized %d slot descriptors.\n", i);
break;
}
dma_desc_pool_virt_addr = (char *) comcerto_xor_ch->device->dma_desc_pool_virt[i/num_slots_per_pool];
slot->hw_desc_inbound = (void *) &dma_desc_pool_virt_addr[j*COMCERTO_XOR_INBOUND_DESC_SIZE];
slot->hw_desc_outbound = (void *) &dma_desc_pool_virt_addr[num_slots_per_pool*COMCERTO_XOR_INBOUND_DESC_SIZE + gap + j*COMCERTO_XOR_OUTBOUND_DESC_SIZE];
dma_async_tx_descriptor_init(&slot->async_tx, chan);
slot->async_tx.tx_submit = comcerto_xor_tx_submit;
INIT_LIST_HEAD(&slot->slot_node);
INIT_LIST_HEAD(&slot->chain_node);
INIT_LIST_HEAD(&slot->completed_node);
dma_desc_pool_addr = (char *) comcerto_xor_ch->device->dma_desc_pool[i/num_slots_per_pool];
slot->async_tx.phys = (dma_addr_t) &dma_desc_pool_addr[j*COMCERTO_XOR_INBOUND_DESC_SIZE];
slot->hw_desc_outbound_dma = (dma_addr_t) &dma_desc_pool_addr[num_slots_per_pool*COMCERTO_XOR_INBOUND_DESC_SIZE + gap + j*COMCERTO_XOR_OUTBOUND_DESC_SIZE];
slot->busy=0;
i++;
if(++j == num_slots_per_pool)
j=0;
spin_lock_bh(&comcerto_xor_ch->lock);
comcerto_xor_ch->slot_allocated = i;
list_add_tail(&slot->slot_node, &comcerto_xor_ch->all_slots);
spin_unlock_bh(&comcerto_xor_ch->lock);
}
if(comcerto_xor_ch->slot_allocated && !comcerto_xor_ch->last_used)
comcerto_xor_ch->last_used = list_entry(comcerto_xor_ch->all_slots.next, struct comcerto_xor_desc_slot, slot_node);
return comcerto_xor_ch->slot_allocated ? comcerto_xor_ch->slot_allocated : -ENOMEM;
}
static struct dma_async_tx_descriptor *
comcerto_xor_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
size_t len, unsigned long flags)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
struct comcerto_xor_desc_slot *slot;
spin_lock_bh(&comcerto_xor_ch->lock);
slot = comcerto_xor_alloc_slot(comcerto_xor_ch);
if(slot) {
slot->type = DMA_MEMCPY;
slot->len = len;
slot->src_cnt = 1;
slot->async_tx.flags = flags;
comcerto_xor_inbound_desc_init(slot,&src);
comcerto_xor_outbound_desc_init(slot,dest);
}
spin_unlock_bh(&comcerto_xor_ch->lock);
return slot ? &slot->async_tx : NULL;
}
static struct dma_async_tx_descriptor *
comcerto_xor_prep_dma_xor(struct dma_chan *chan, dma_addr_t dest, dma_addr_t *src,
unsigned int src_cnt, size_t len, unsigned long flags)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
struct comcerto_xor_desc_slot *slot;
if(unlikely(len!=256&&len!=512&&len!=1024&&len!=2048&&len!=4096)) {
printk(KERN_ERR "Length %d is not supported for XOR.\n", len);
return NULL;
}
spin_lock_bh(&comcerto_xor_ch->lock);
slot = comcerto_xor_alloc_slot(comcerto_xor_ch);
if(slot) {
slot->type = DMA_XOR;
slot->len = len;
slot->src_cnt = src_cnt;
slot->async_tx.flags = flags;
comcerto_xor_inbound_desc_init(slot,src);
comcerto_xor_outbound_desc_init(slot,dest);
}
spin_unlock_bh(&comcerto_xor_ch->lock);
return slot ? &slot->async_tx : NULL;
}
static struct dma_async_tx_descriptor *
comcerto_xor_prep_dma_xor_val(struct dma_chan *chan, dma_addr_t *src,
unsigned int src_cnt, size_t len,
u32 *result, unsigned long flags)
{
struct comcerto_xor_chan *comcerto_xor_ch = to_comcerto_xor_chan(chan);
struct comcerto_xor_desc_slot *slot;
if(unlikely(len!=256&&len!=512&&len!=1024&&len!=2048&&len!=4096)) {
printk(KERN_ERR "Length %d is not supported for XOR.\n", len);
return NULL;
}
spin_lock_bh(&comcerto_xor_ch->lock);
slot = comcerto_xor_alloc_slot(comcerto_xor_ch);
if(slot) {
slot->type = DMA_XOR_VAL;
slot->len = len;
slot->src_cnt = src_cnt;
slot->async_tx.flags = flags;
slot->xor_check_result = result;
comcerto_xor_inbound_desc_init(slot,src);
comcerto_xor_outbound_desc_init(slot,0);
}
spin_unlock_bh(&comcerto_xor_ch->lock);
return slot ? &slot->async_tx : NULL;
}
static irqreturn_t comcerto_xor_interrupt_handler(int irq, void *data)
{
struct comcerto_xor_chan *comcerto_xor_ch = data;
int do_taskset=0;
u32 intr_cause = comcerto_xor_get_register_io2m_irq_status(comcerto_xor_ch);
if(intr_cause & IRQ_IRQFRDYN) {
printk(KERN_ALERT "IRQFRDYN: A frame is started but the frame is not ready");
comcerto_dump_xor_regs(comcerto_xor_ch);
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<0);
do_taskset=1;
}
if(intr_cause & IRQ_IRQFLST) {
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<1);
do_taskset=1;
}
if(intr_cause & IRQ_IRQFDON)
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<2);
if(intr_cause & IRQ_IRQFLSH) {
printk(KERN_ALERT "IRQFLSH: IO has more data than the memory buffer");
comcerto_dump_xor_regs(comcerto_xor_ch);
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<3);
do_taskset=1;
}
if(intr_cause & IRQ_IRQFLEN) {
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<4);
do_taskset=1;
}
if(intr_cause & IRQ_IRQFTHLD) {
printk(KERN_ALERT "IRQFTHLD: Frame threshold reached. FLEN=FTHLDL");
comcerto_dump_xor_regs(comcerto_xor_ch);
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<5);
do_taskset=1;
}
if(intr_cause & IRQ_IRQFCTRL) {
printk(KERN_ALERT "IRQFCTRL: 1 frame is completed or when a frame is started but not ready");
comcerto_dump_xor_regs(comcerto_xor_ch);
comcerto_xor_set_register_io2m_irq_status(comcerto_xor_ch,1<<6);
do_taskset=1;
}
if(do_taskset)
tasklet_schedule(&comcerto_xor_ch->irq_tasklet);
return IRQ_HANDLED;
}
#define COMCERTO_XOR_NUM_SRC_TEST 4
static int __devinit comcerto_xor_xor_self_test(struct comcerto_xor_device *device)
{
int i, src_idx;
struct page *dest;
struct page *xor_srcs[COMCERTO_XOR_NUM_SRC_TEST];
struct page *zero_sum_srcs[COMCERTO_XOR_NUM_SRC_TEST+1];
dma_addr_t dma_srcs[COMCERTO_XOR_NUM_SRC_TEST];
dma_addr_t dma_zero_srcs[COMCERTO_XOR_NUM_SRC_TEST+1];
dma_addr_t dest_dma;
struct dma_async_tx_descriptor *tx;
struct dma_chan *dma_chan;
dma_cookie_t cookie;
u8 cmp_byte = 0;
u32 cmp_word;
u32 zero_sum_result;
int err = 0;
struct comcerto_xor_chan *comcerto_chan;
for (src_idx = 0; src_idx < COMCERTO_XOR_NUM_SRC_TEST; src_idx++) {
xor_srcs[src_idx] = alloc_page(GFP_KERNEL);
if (!xor_srcs[src_idx]) {
while (src_idx--)
__free_page(xor_srcs[src_idx]);
return -ENOMEM;
}
}
dest = alloc_page(GFP_KERNEL);
if (!dest) {
while (src_idx--)
__free_page(xor_srcs[src_idx]);
return -ENOMEM;
}
for (src_idx = 0; src_idx < COMCERTO_XOR_NUM_SRC_TEST; src_idx++) {
u8 *ptr = page_address(xor_srcs[src_idx]);
for (i = 0; i < PAGE_SIZE; i++)
ptr[i] = (1 << src_idx);
}
for (src_idx = 0; src_idx < COMCERTO_XOR_NUM_SRC_TEST; src_idx++)
cmp_byte ^= (u8) (1 << src_idx);
cmp_word = (cmp_byte << 24) | (cmp_byte << 16) |
(cmp_byte << 8) | cmp_byte;
memset(page_address(dest), 0, PAGE_SIZE);
dma_chan = container_of(device->device.channels.next,
struct dma_chan,
device_node);
if (comcerto_xor_alloc_chan_resources(dma_chan) < 1) {
err = -ENOMEM;
goto out;
}
dest_dma = dma_map_page(dma_chan->device->dev, dest, 0,
PAGE_SIZE, DMA_FROM_DEVICE);
comcerto_chan = to_comcerto_xor_chan(dma_chan);
for (i = 0; i < COMCERTO_XOR_NUM_SRC_TEST; i++)
dma_srcs[i] = dma_map_page(dma_chan->device->dev, xor_srcs[i],
0, PAGE_SIZE, DMA_TO_DEVICE);
tx = comcerto_xor_prep_dma_xor(dma_chan, dest_dma, dma_srcs,
COMCERTO_XOR_NUM_SRC_TEST, PAGE_SIZE, 0);
cookie = comcerto_xor_tx_submit(tx);
comcerto_xor_issue_pending(dma_chan);
async_tx_ack(tx);
msleep(8);
if (comcerto_xor_status(dma_chan, cookie, NULL) != DMA_SUCCESS) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test xor timed out, disabling\n");
err = -ENODEV;
goto free_resources;
}
dma_sync_single_for_cpu(device->device.dev, dest_dma,
PAGE_SIZE, DMA_FROM_DEVICE);
for (i = 0; i < (PAGE_SIZE / sizeof(u32)); i++) {
u32 *ptr = page_address(dest);
if (ptr[i] != cmp_word) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test xor failed compare, disabling."
" index %d, data %x, expected %x\n", i,
ptr[i], cmp_word);
err = -ENODEV;
goto free_resources;
}
}
dma_sync_single_for_device(device->device.dev,dest_dma,
PAGE_SIZE, DMA_TO_DEVICE);
if(!dma_has_cap(DMA_XOR_VAL,dma_chan->device->cap_mask))
goto free_resources;
for(i = 0; i < COMCERTO_XOR_NUM_SRC_TEST; i++)
zero_sum_srcs[i]=xor_srcs[i];
zero_sum_srcs[i] = alloc_page(GFP_KERNEL);
for (src_idx = 0; src_idx < 1; src_idx++) {
u8 *ptr = page_address(zero_sum_srcs[i]);
for (i = 0; i < PAGE_SIZE; i++)
ptr[i] = 1 | 1<<1 | 1<<2 | 1<<3;
}
zero_sum_result=1;
for(i=0;i<COMCERTO_XOR_NUM_SRC_TEST+1;i++)
dma_zero_srcs[i]=dma_map_page(dma_chan->device->dev,
zero_sum_srcs[i],0,PAGE_SIZE,
DMA_TO_DEVICE);
tx = comcerto_xor_prep_dma_xor_val(dma_chan,dma_zero_srcs,
COMCERTO_XOR_NUM_SRC_TEST+1, PAGE_SIZE,
&zero_sum_result,
DMA_CTRL_ACK);
cookie = comcerto_xor_tx_submit(tx);
comcerto_xor_issue_pending(dma_chan);
msleep(8);
if (comcerto_xor_status(dma_chan, cookie, NULL) != DMA_SUCCESS) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test zero sum timed out, disabling\n");
err = -ENODEV;
goto free_resources;
}
if(zero_sum_result != 0) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test zero sum failed compare, disabling\n");
err = -ENODEV;
goto free_resources;
}
__free_page(zero_sum_srcs[COMCERTO_XOR_NUM_SRC_TEST]);
free_resources:
comcerto_xor_free_chan_resources(dma_chan);
out:
src_idx = COMCERTO_XOR_NUM_SRC_TEST;
while (src_idx--)
__free_page(xor_srcs[src_idx]);
__free_page(dest);
return err;
}
static int __devinit comcerto_xor_memcpy_self_test(struct comcerto_xor_device *device)
{
int i;
struct page *dest;
struct page *src;
dma_addr_t src_dma;
dma_addr_t dest_dma;
struct dma_async_tx_descriptor *tx;
struct dma_chan *dma_chan;
dma_cookie_t cookie;
int err = 0;
struct comcerto_xor_chan *comcerto_chan;
src = alloc_page(GFP_KERNEL);
if (!src)
return -ENOMEM;
dest = alloc_page(GFP_KERNEL);
if (!dest) {
__free_page(src);
return -ENOMEM;
}
/* Fill in src buffer */
for (i = 0; i < PAGE_SIZE; i++) {
u8 *ptr = page_address(src);
((u8 *) ptr)[i] = (u8)i;
}
memset(page_address(dest), 0, PAGE_SIZE);
dma_chan = container_of(device->device.channels.next,
struct dma_chan,
device_node);
if (comcerto_xor_alloc_chan_resources(dma_chan) < 1) {
err = -ENOMEM;
goto out;
}
dest_dma = dma_map_page(dma_chan->device->dev, dest, 0,
PAGE_SIZE, DMA_FROM_DEVICE);
comcerto_chan = to_comcerto_xor_chan(dma_chan);
src_dma = dma_map_page(dma_chan->device->dev, src,
0, PAGE_SIZE, DMA_TO_DEVICE);
tx = comcerto_xor_prep_dma_memcpy(dma_chan, dest_dma, src_dma,
PAGE_SIZE, 0);
cookie = comcerto_xor_tx_submit(tx);
comcerto_xor_issue_pending(dma_chan);
async_tx_ack(tx);
msleep(8);
if (comcerto_xor_status(dma_chan, cookie, NULL) != DMA_SUCCESS) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test xor timed out, disabling\n");
err = -ENODEV;
goto free_resources;
}
dma_sync_single_for_cpu(device->device.dev, dest_dma,
PAGE_SIZE, DMA_FROM_DEVICE);
if (memcmp(page_address(src), page_address(dest), PAGE_SIZE)) {
dev_printk(KERN_ERR, dma_chan->device->dev,
"Self-test copy failed compare, disabling\n");
err = -ENODEV;
goto free_resources;
}
free_resources:
comcerto_xor_free_chan_resources(dma_chan);
out:
__free_page(src);
__free_page(dest);
return err;
}
static int __devexit comcerto_xor_remove(struct platform_device *pdev)
{
struct comcerto_xor_device *comcerto_xor_dev = platform_get_drvdata(pdev);
struct dma_device *dma_dev = &comcerto_xor_dev->device;
struct comcerto_xor_chan *comcerto_xor_ch;
struct dma_chan *chan, *__chan;
int i;
int irq;
irq = platform_get_irq(pdev,0);
dma_async_device_unregister(dma_dev);
for(i=0;i<POOL_NUMBER;i++)
dma_free_coherent(&pdev->dev, PAGE_SIZE,
comcerto_xor_dev->dma_desc_pool_virt[i],
comcerto_xor_dev->dma_desc_pool[i]);
list_for_each_entry_safe(chan, __chan, &dma_dev->channels,
device_node) {
comcerto_xor_ch = to_comcerto_xor_chan(chan);
devm_free_irq(&pdev->dev, irq, comcerto_xor_ch);
list_del(&chan->device_node);
devm_iounmap(&pdev->dev, comcerto_xor_ch->mmr_base);
kfree(comcerto_xor_ch);
}
platform_set_drvdata(pdev,NULL);
kfree(comcerto_xor_dev);
return 0;
}
static int __devinit comcerto_xor_probe(struct platform_device *pdev)
{
struct resource *io;
struct dma_device *dma_dev;
struct comcerto_xor_device *comcerto_xor_dev;
struct comcerto_xor_chan *comcerto_xor_ch;
int irq;
int ret = 0;
int i;
/* 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;
/* Initialize comcerto_xor_device */
comcerto_xor_dev = devm_kzalloc(&pdev->dev, sizeof(*comcerto_xor_dev), GFP_KERNEL);
if(!comcerto_xor_dev)
return -ENOMEM;
dma_dev = &comcerto_xor_dev->device;
INIT_LIST_HEAD(&dma_dev->channels);
for(i = 0 ; i < POOL_NUMBER; i++)
{
comcerto_xor_dev->dma_desc_pool_virt[i] = dma_alloc_writecombine(&pdev->dev,
PAGE_SIZE,
&comcerto_xor_dev->dma_desc_pool[i],
GFP_KERNEL);
if(!comcerto_xor_dev->dma_desc_pool_virt[i])
{
ret = -ENOMEM;
goto err_free_dma;
}
}
dma_cap_set(DMA_XOR,dma_dev->cap_mask);
dma_cap_set(DMA_XOR_VAL,dma_dev->cap_mask);
// dma_cap_set(DMA_MEMCPY,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->device_prep_dma_xor_val = comcerto_xor_prep_dma_xor_val;
// dma_dev->device_prep_dma_memcpy = comcerto_xor_prep_dma_memcpy;
dma_dev->max_xor = COMCERTO_XOR_MAX_SRC;
platform_set_drvdata(pdev,comcerto_xor_dev);
/* Initialize comcerto_xor_chan */
comcerto_xor_ch = devm_kzalloc(&pdev->dev,sizeof(*comcerto_xor_ch),GFP_KERNEL);
if(!comcerto_xor_ch) {
ret = -ENOMEM;
goto err_free_dma;
}
comcerto_xor_ch->device = comcerto_xor_dev;
comcerto_xor_ch->pending = 0;
comcerto_xor_ch->slot_allocated = 0;
comcerto_xor_ch->completed_cookie = 0;
comcerto_xor_ch->mmr_base = devm_ioremap(&pdev->dev, io->start,resource_size(io));
if(!comcerto_xor_ch->mmr_base) {
ret = -ENOMEM;
goto err_free_ch;
}
tasklet_init(&comcerto_xor_ch->irq_tasklet,comcerto_xor_tasklet,(unsigned long)comcerto_xor_ch);
ret = devm_request_irq(&pdev->dev, irq, comcerto_xor_interrupt_handler,
0, dev_name(&pdev->dev), comcerto_xor_ch);
if(ret)
goto err_free_remap;
spin_lock_init(&comcerto_xor_ch->lock);
INIT_LIST_HEAD(&comcerto_xor_ch->all_slots);
INIT_LIST_HEAD(&comcerto_xor_ch->chain);
INIT_LIST_HEAD(&comcerto_xor_ch->completed_slots);
comcerto_xor_ch->chan.device = dma_dev;
list_add_tail(&comcerto_xor_ch->chan.device_node,&dma_dev->channels);
comcerto_xor_register_init(comcerto_xor_ch);
if (dma_has_cap(DMA_XOR, dma_dev->cap_mask)) {
ret = comcerto_xor_xor_self_test(comcerto_xor_dev);
dev_dbg(&pdev->dev, "xor self test returned %d\n", ret);
if(ret)
goto err_free_irq;
}
if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) {
ret = comcerto_xor_memcpy_self_test(comcerto_xor_dev);
dev_dbg(&pdev->dev, "memcpy self test returned %d\n", ret);
if(ret)
goto err_free_irq;
}
ret = dma_async_device_register(dma_dev);
if (ret) {
dev_dbg(&pdev->dev, "Failed to register channel %d\n",ret);
goto err_free_irq;
}
goto out;
err_free_irq:
devm_free_irq(&pdev->dev, irq, comcerto_xor_ch);
err_free_remap:
devm_iounmap(&pdev->dev, comcerto_xor_ch->mmr_base);
err_free_ch:
devm_kfree(&pdev->dev, comcerto_xor_ch);
err_free_dma:
platform_set_drvdata(pdev,NULL);
while(i--)
dma_free_coherent(&pdev->dev,PAGE_SIZE,comcerto_xor_dev->dma_desc_pool_virt[i],
comcerto_xor_dev->dma_desc_pool[i]);
devm_kfree(&pdev->dev, comcerto_xor_dev);
out:
return ret;
}
static struct platform_driver comcerto_xor_driver = {
.probe = comcerto_xor_probe,
.remove = comcerto_xor_remove,
.driver = {
.owner = THIS_MODULE,
.name = "comcerto_xor",
},
};
static int __init comcerto_xor_init(void)
{
int ret = platform_driver_register(&comcerto_xor_driver);
return ret;
}
module_init(comcerto_xor_init);
static void __exit comcerto_xor_exit(void)
{
platform_driver_unregister(&comcerto_xor_driver);
return;
}
module_exit(comcerto_xor_exit);
MODULE_DESCRIPTION("XOR engine driver for Mindspeed Comcerto C2000 devices");
MODULE_LICENSE("GPL");