162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * offload engine driver for the Marvell XOR engine 462306a36Sopenharmony_ci * Copyright (C) 2007, 2008, Marvell International Ltd. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1162306a36Sopenharmony_ci#include <linux/spinlock.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/of_device.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/memory.h> 1662306a36Sopenharmony_ci#include <linux/clk.h> 1762306a36Sopenharmony_ci#include <linux/of.h> 1862306a36Sopenharmony_ci#include <linux/of_irq.h> 1962306a36Sopenharmony_ci#include <linux/irqdomain.h> 2062306a36Sopenharmony_ci#include <linux/cpumask.h> 2162306a36Sopenharmony_ci#include <linux/platform_data/dma-mv_xor.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "dmaengine.h" 2462306a36Sopenharmony_ci#include "mv_xor.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cienum mv_xor_type { 2762306a36Sopenharmony_ci XOR_ORION, 2862306a36Sopenharmony_ci XOR_ARMADA_38X, 2962306a36Sopenharmony_ci XOR_ARMADA_37XX, 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cienum mv_xor_mode { 3362306a36Sopenharmony_ci XOR_MODE_IN_REG, 3462306a36Sopenharmony_ci XOR_MODE_IN_DESC, 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic void mv_xor_issue_pending(struct dma_chan *chan); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define to_mv_xor_chan(chan) \ 4062306a36Sopenharmony_ci container_of(chan, struct mv_xor_chan, dmachan) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define to_mv_xor_slot(tx) \ 4362306a36Sopenharmony_ci container_of(tx, struct mv_xor_desc_slot, async_tx) 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define mv_chan_to_devp(chan) \ 4662306a36Sopenharmony_ci ((chan)->dmadev.dev) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic void mv_desc_init(struct mv_xor_desc_slot *desc, 4962306a36Sopenharmony_ci dma_addr_t addr, u32 byte_count, 5062306a36Sopenharmony_ci enum dma_ctrl_flags flags) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci struct mv_xor_desc *hw_desc = desc->hw_desc; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci hw_desc->status = XOR_DESC_DMA_OWNED; 5562306a36Sopenharmony_ci hw_desc->phy_next_desc = 0; 5662306a36Sopenharmony_ci /* Enable end-of-descriptor interrupts only for DMA_PREP_INTERRUPT */ 5762306a36Sopenharmony_ci hw_desc->desc_command = (flags & DMA_PREP_INTERRUPT) ? 5862306a36Sopenharmony_ci XOR_DESC_EOD_INT_EN : 0; 5962306a36Sopenharmony_ci hw_desc->phy_dest_addr = addr; 6062306a36Sopenharmony_ci hw_desc->byte_count = byte_count; 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic void mv_desc_set_mode(struct mv_xor_desc_slot *desc) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct mv_xor_desc *hw_desc = desc->hw_desc; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci switch (desc->type) { 6862306a36Sopenharmony_ci case DMA_XOR: 6962306a36Sopenharmony_ci case DMA_INTERRUPT: 7062306a36Sopenharmony_ci hw_desc->desc_command |= XOR_DESC_OPERATION_XOR; 7162306a36Sopenharmony_ci break; 7262306a36Sopenharmony_ci case DMA_MEMCPY: 7362306a36Sopenharmony_ci hw_desc->desc_command |= XOR_DESC_OPERATION_MEMCPY; 7462306a36Sopenharmony_ci break; 7562306a36Sopenharmony_ci default: 7662306a36Sopenharmony_ci BUG(); 7762306a36Sopenharmony_ci return; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic void mv_desc_set_next_desc(struct mv_xor_desc_slot *desc, 8262306a36Sopenharmony_ci u32 next_desc_addr) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci struct mv_xor_desc *hw_desc = desc->hw_desc; 8562306a36Sopenharmony_ci BUG_ON(hw_desc->phy_next_desc); 8662306a36Sopenharmony_ci hw_desc->phy_next_desc = next_desc_addr; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic void mv_desc_set_src_addr(struct mv_xor_desc_slot *desc, 9062306a36Sopenharmony_ci int index, dma_addr_t addr) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct mv_xor_desc *hw_desc = desc->hw_desc; 9362306a36Sopenharmony_ci hw_desc->phy_src_addr[mv_phy_src_idx(index)] = addr; 9462306a36Sopenharmony_ci if (desc->type == DMA_XOR) 9562306a36Sopenharmony_ci hw_desc->desc_command |= (1 << index); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic u32 mv_chan_get_current_desc(struct mv_xor_chan *chan) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci return readl_relaxed(XOR_CURR_DESC(chan)); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic void mv_chan_set_next_descriptor(struct mv_xor_chan *chan, 10462306a36Sopenharmony_ci u32 next_desc_addr) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci writel_relaxed(next_desc_addr, XOR_NEXT_DESC(chan)); 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic void mv_chan_unmask_interrupts(struct mv_xor_chan *chan) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci u32 val = readl_relaxed(XOR_INTR_MASK(chan)); 11262306a36Sopenharmony_ci val |= XOR_INTR_MASK_VALUE << (chan->idx * 16); 11362306a36Sopenharmony_ci writel_relaxed(val, XOR_INTR_MASK(chan)); 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic u32 mv_chan_get_intr_cause(struct mv_xor_chan *chan) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci u32 intr_cause = readl_relaxed(XOR_INTR_CAUSE(chan)); 11962306a36Sopenharmony_ci intr_cause = (intr_cause >> (chan->idx * 16)) & 0xFFFF; 12062306a36Sopenharmony_ci return intr_cause; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic void mv_chan_clear_eoc_cause(struct mv_xor_chan *chan) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci u32 val; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci val = XOR_INT_END_OF_DESC | XOR_INT_END_OF_CHAIN | XOR_INT_STOPPED; 12862306a36Sopenharmony_ci val = ~(val << (chan->idx * 16)); 12962306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(chan), "%s, val 0x%08x\n", __func__, val); 13062306a36Sopenharmony_ci writel_relaxed(val, XOR_INTR_CAUSE(chan)); 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic void mv_chan_clear_err_status(struct mv_xor_chan *chan) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci u32 val = 0xFFFF0000 >> (chan->idx * 16); 13662306a36Sopenharmony_ci writel_relaxed(val, XOR_INTR_CAUSE(chan)); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic void mv_chan_set_mode(struct mv_xor_chan *chan, 14062306a36Sopenharmony_ci u32 op_mode) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci u32 config = readl_relaxed(XOR_CONFIG(chan)); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci config &= ~0x7; 14562306a36Sopenharmony_ci config |= op_mode; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci#if defined(__BIG_ENDIAN) 14862306a36Sopenharmony_ci config |= XOR_DESCRIPTOR_SWAP; 14962306a36Sopenharmony_ci#else 15062306a36Sopenharmony_ci config &= ~XOR_DESCRIPTOR_SWAP; 15162306a36Sopenharmony_ci#endif 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci writel_relaxed(config, XOR_CONFIG(chan)); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic void mv_chan_activate(struct mv_xor_chan *chan) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(chan), " activate chan.\n"); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* writel ensures all descriptors are flushed before activation */ 16162306a36Sopenharmony_ci writel(BIT(0), XOR_ACTIVATION(chan)); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic char mv_chan_is_busy(struct mv_xor_chan *chan) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci u32 state = readl_relaxed(XOR_ACTIVATION(chan)); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci state = (state >> 4) & 0x3; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return (state == 1) ? 1 : 0; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci/* 17462306a36Sopenharmony_ci * mv_chan_start_new_chain - program the engine to operate on new 17562306a36Sopenharmony_ci * chain headed by sw_desc 17662306a36Sopenharmony_ci * Caller must hold &mv_chan->lock while calling this function 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_cistatic void mv_chan_start_new_chain(struct mv_xor_chan *mv_chan, 17962306a36Sopenharmony_ci struct mv_xor_desc_slot *sw_desc) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "%s %d: sw_desc %p\n", 18262306a36Sopenharmony_ci __func__, __LINE__, sw_desc); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* set the hardware chain */ 18562306a36Sopenharmony_ci mv_chan_set_next_descriptor(mv_chan, sw_desc->async_tx.phys); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci mv_chan->pending++; 18862306a36Sopenharmony_ci mv_xor_issue_pending(&mv_chan->dmachan); 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic dma_cookie_t 19262306a36Sopenharmony_cimv_desc_run_tx_complete_actions(struct mv_xor_desc_slot *desc, 19362306a36Sopenharmony_ci struct mv_xor_chan *mv_chan, 19462306a36Sopenharmony_ci dma_cookie_t cookie) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci BUG_ON(desc->async_tx.cookie < 0); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (desc->async_tx.cookie > 0) { 19962306a36Sopenharmony_ci cookie = desc->async_tx.cookie; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci dma_descriptor_unmap(&desc->async_tx); 20262306a36Sopenharmony_ci /* call the callback (must not sleep or submit new 20362306a36Sopenharmony_ci * operations to this channel) 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_ci dmaengine_desc_get_callback_invoke(&desc->async_tx, NULL); 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* run dependent operations */ 20962306a36Sopenharmony_ci dma_run_dependencies(&desc->async_tx); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci return cookie; 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic int 21562306a36Sopenharmony_cimv_chan_clean_completed_slots(struct mv_xor_chan *mv_chan) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct mv_xor_desc_slot *iter, *_iter; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "%s %d\n", __func__, __LINE__); 22062306a36Sopenharmony_ci list_for_each_entry_safe(iter, _iter, &mv_chan->completed_slots, 22162306a36Sopenharmony_ci node) { 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (async_tx_test_ack(&iter->async_tx)) { 22462306a36Sopenharmony_ci list_move_tail(&iter->node, &mv_chan->free_slots); 22562306a36Sopenharmony_ci if (!list_empty(&iter->sg_tx_list)) { 22662306a36Sopenharmony_ci list_splice_tail_init(&iter->sg_tx_list, 22762306a36Sopenharmony_ci &mv_chan->free_slots); 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci return 0; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic int 23562306a36Sopenharmony_cimv_desc_clean_slot(struct mv_xor_desc_slot *desc, 23662306a36Sopenharmony_ci struct mv_xor_chan *mv_chan) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "%s %d: desc %p flags %d\n", 23962306a36Sopenharmony_ci __func__, __LINE__, desc, desc->async_tx.flags); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci /* the client is allowed to attach dependent operations 24262306a36Sopenharmony_ci * until 'ack' is set 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_ci if (!async_tx_test_ack(&desc->async_tx)) { 24562306a36Sopenharmony_ci /* move this slot to the completed_slots */ 24662306a36Sopenharmony_ci list_move_tail(&desc->node, &mv_chan->completed_slots); 24762306a36Sopenharmony_ci if (!list_empty(&desc->sg_tx_list)) { 24862306a36Sopenharmony_ci list_splice_tail_init(&desc->sg_tx_list, 24962306a36Sopenharmony_ci &mv_chan->completed_slots); 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci } else { 25262306a36Sopenharmony_ci list_move_tail(&desc->node, &mv_chan->free_slots); 25362306a36Sopenharmony_ci if (!list_empty(&desc->sg_tx_list)) { 25462306a36Sopenharmony_ci list_splice_tail_init(&desc->sg_tx_list, 25562306a36Sopenharmony_ci &mv_chan->free_slots); 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci return 0; 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci/* This function must be called with the mv_xor_chan spinlock held */ 26362306a36Sopenharmony_cistatic void mv_chan_slot_cleanup(struct mv_xor_chan *mv_chan) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci struct mv_xor_desc_slot *iter, *_iter; 26662306a36Sopenharmony_ci dma_cookie_t cookie = 0; 26762306a36Sopenharmony_ci int busy = mv_chan_is_busy(mv_chan); 26862306a36Sopenharmony_ci u32 current_desc = mv_chan_get_current_desc(mv_chan); 26962306a36Sopenharmony_ci int current_cleaned = 0; 27062306a36Sopenharmony_ci struct mv_xor_desc *hw_desc; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "%s %d\n", __func__, __LINE__); 27362306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "current_desc %x\n", current_desc); 27462306a36Sopenharmony_ci mv_chan_clean_completed_slots(mv_chan); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* free completed slots from the chain starting with 27762306a36Sopenharmony_ci * the oldest descriptor 27862306a36Sopenharmony_ci */ 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci list_for_each_entry_safe(iter, _iter, &mv_chan->chain, 28162306a36Sopenharmony_ci node) { 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci /* clean finished descriptors */ 28462306a36Sopenharmony_ci hw_desc = iter->hw_desc; 28562306a36Sopenharmony_ci if (hw_desc->status & XOR_DESC_SUCCESS) { 28662306a36Sopenharmony_ci cookie = mv_desc_run_tx_complete_actions(iter, mv_chan, 28762306a36Sopenharmony_ci cookie); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* done processing desc, clean slot */ 29062306a36Sopenharmony_ci mv_desc_clean_slot(iter, mv_chan); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci /* break if we did cleaned the current */ 29362306a36Sopenharmony_ci if (iter->async_tx.phys == current_desc) { 29462306a36Sopenharmony_ci current_cleaned = 1; 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci } else { 29862306a36Sopenharmony_ci if (iter->async_tx.phys == current_desc) { 29962306a36Sopenharmony_ci current_cleaned = 0; 30062306a36Sopenharmony_ci break; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci if ((busy == 0) && !list_empty(&mv_chan->chain)) { 30662306a36Sopenharmony_ci if (current_cleaned) { 30762306a36Sopenharmony_ci /* 30862306a36Sopenharmony_ci * current descriptor cleaned and removed, run 30962306a36Sopenharmony_ci * from list head 31062306a36Sopenharmony_ci */ 31162306a36Sopenharmony_ci iter = list_entry(mv_chan->chain.next, 31262306a36Sopenharmony_ci struct mv_xor_desc_slot, 31362306a36Sopenharmony_ci node); 31462306a36Sopenharmony_ci mv_chan_start_new_chain(mv_chan, iter); 31562306a36Sopenharmony_ci } else { 31662306a36Sopenharmony_ci if (!list_is_last(&iter->node, &mv_chan->chain)) { 31762306a36Sopenharmony_ci /* 31862306a36Sopenharmony_ci * descriptors are still waiting after 31962306a36Sopenharmony_ci * current, trigger them 32062306a36Sopenharmony_ci */ 32162306a36Sopenharmony_ci iter = list_entry(iter->node.next, 32262306a36Sopenharmony_ci struct mv_xor_desc_slot, 32362306a36Sopenharmony_ci node); 32462306a36Sopenharmony_ci mv_chan_start_new_chain(mv_chan, iter); 32562306a36Sopenharmony_ci } else { 32662306a36Sopenharmony_ci /* 32762306a36Sopenharmony_ci * some descriptors are still waiting 32862306a36Sopenharmony_ci * to be cleaned 32962306a36Sopenharmony_ci */ 33062306a36Sopenharmony_ci tasklet_schedule(&mv_chan->irq_tasklet); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci } 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci if (cookie > 0) 33662306a36Sopenharmony_ci mv_chan->dmachan.completed_cookie = cookie; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_cistatic void mv_xor_tasklet(struct tasklet_struct *t) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct mv_xor_chan *chan = from_tasklet(chan, t, irq_tasklet); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci spin_lock(&chan->lock); 34462306a36Sopenharmony_ci mv_chan_slot_cleanup(chan); 34562306a36Sopenharmony_ci spin_unlock(&chan->lock); 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic struct mv_xor_desc_slot * 34962306a36Sopenharmony_cimv_chan_alloc_slot(struct mv_xor_chan *mv_chan) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct mv_xor_desc_slot *iter; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci spin_lock_bh(&mv_chan->lock); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (!list_empty(&mv_chan->free_slots)) { 35662306a36Sopenharmony_ci iter = list_first_entry(&mv_chan->free_slots, 35762306a36Sopenharmony_ci struct mv_xor_desc_slot, 35862306a36Sopenharmony_ci node); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci list_move_tail(&iter->node, &mv_chan->allocated_slots); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* pre-ack descriptor */ 36562306a36Sopenharmony_ci async_tx_ack(&iter->async_tx); 36662306a36Sopenharmony_ci iter->async_tx.cookie = -EBUSY; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci return iter; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci /* try to free some slots if the allocation fails */ 37562306a36Sopenharmony_ci tasklet_schedule(&mv_chan->irq_tasklet); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return NULL; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci/************************ DMA engine API functions ****************************/ 38162306a36Sopenharmony_cistatic dma_cookie_t 38262306a36Sopenharmony_cimv_xor_tx_submit(struct dma_async_tx_descriptor *tx) 38362306a36Sopenharmony_ci{ 38462306a36Sopenharmony_ci struct mv_xor_desc_slot *sw_desc = to_mv_xor_slot(tx); 38562306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(tx->chan); 38662306a36Sopenharmony_ci struct mv_xor_desc_slot *old_chain_tail; 38762306a36Sopenharmony_ci dma_cookie_t cookie; 38862306a36Sopenharmony_ci int new_hw_chain = 1; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), 39162306a36Sopenharmony_ci "%s sw_desc %p: async_tx %p\n", 39262306a36Sopenharmony_ci __func__, sw_desc, &sw_desc->async_tx); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci spin_lock_bh(&mv_chan->lock); 39562306a36Sopenharmony_ci cookie = dma_cookie_assign(tx); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci if (list_empty(&mv_chan->chain)) 39862306a36Sopenharmony_ci list_move_tail(&sw_desc->node, &mv_chan->chain); 39962306a36Sopenharmony_ci else { 40062306a36Sopenharmony_ci new_hw_chain = 0; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci old_chain_tail = list_entry(mv_chan->chain.prev, 40362306a36Sopenharmony_ci struct mv_xor_desc_slot, 40462306a36Sopenharmony_ci node); 40562306a36Sopenharmony_ci list_move_tail(&sw_desc->node, &mv_chan->chain); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "Append to last desc %pa\n", 40862306a36Sopenharmony_ci &old_chain_tail->async_tx.phys); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci /* fix up the hardware chain */ 41162306a36Sopenharmony_ci mv_desc_set_next_desc(old_chain_tail, sw_desc->async_tx.phys); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci /* if the channel is not busy */ 41462306a36Sopenharmony_ci if (!mv_chan_is_busy(mv_chan)) { 41562306a36Sopenharmony_ci u32 current_desc = mv_chan_get_current_desc(mv_chan); 41662306a36Sopenharmony_ci /* 41762306a36Sopenharmony_ci * and the curren desc is the end of the chain before 41862306a36Sopenharmony_ci * the append, then we need to start the channel 41962306a36Sopenharmony_ci */ 42062306a36Sopenharmony_ci if (current_desc == old_chain_tail->async_tx.phys) 42162306a36Sopenharmony_ci new_hw_chain = 1; 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci if (new_hw_chain) 42662306a36Sopenharmony_ci mv_chan_start_new_chain(mv_chan, sw_desc); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return cookie; 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci/* returns the number of allocated descriptors */ 43462306a36Sopenharmony_cistatic int mv_xor_alloc_chan_resources(struct dma_chan *chan) 43562306a36Sopenharmony_ci{ 43662306a36Sopenharmony_ci void *virt_desc; 43762306a36Sopenharmony_ci dma_addr_t dma_desc; 43862306a36Sopenharmony_ci int idx; 43962306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 44062306a36Sopenharmony_ci struct mv_xor_desc_slot *slot = NULL; 44162306a36Sopenharmony_ci int num_descs_in_pool = MV_XOR_POOL_SIZE/MV_XOR_SLOT_SIZE; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci /* Allocate descriptor slots */ 44462306a36Sopenharmony_ci idx = mv_chan->slots_allocated; 44562306a36Sopenharmony_ci while (idx < num_descs_in_pool) { 44662306a36Sopenharmony_ci slot = kzalloc(sizeof(*slot), GFP_KERNEL); 44762306a36Sopenharmony_ci if (!slot) { 44862306a36Sopenharmony_ci dev_info(mv_chan_to_devp(mv_chan), 44962306a36Sopenharmony_ci "channel only initialized %d descriptor slots", 45062306a36Sopenharmony_ci idx); 45162306a36Sopenharmony_ci break; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci virt_desc = mv_chan->dma_desc_pool_virt; 45462306a36Sopenharmony_ci slot->hw_desc = virt_desc + idx * MV_XOR_SLOT_SIZE; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci dma_async_tx_descriptor_init(&slot->async_tx, chan); 45762306a36Sopenharmony_ci slot->async_tx.tx_submit = mv_xor_tx_submit; 45862306a36Sopenharmony_ci INIT_LIST_HEAD(&slot->node); 45962306a36Sopenharmony_ci INIT_LIST_HEAD(&slot->sg_tx_list); 46062306a36Sopenharmony_ci dma_desc = mv_chan->dma_desc_pool; 46162306a36Sopenharmony_ci slot->async_tx.phys = dma_desc + idx * MV_XOR_SLOT_SIZE; 46262306a36Sopenharmony_ci slot->idx = idx++; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci spin_lock_bh(&mv_chan->lock); 46562306a36Sopenharmony_ci mv_chan->slots_allocated = idx; 46662306a36Sopenharmony_ci list_add_tail(&slot->node, &mv_chan->free_slots); 46762306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), 47162306a36Sopenharmony_ci "allocated %d descriptor slots\n", 47262306a36Sopenharmony_ci mv_chan->slots_allocated); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci return mv_chan->slots_allocated ? : -ENOMEM; 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci/* 47862306a36Sopenharmony_ci * Check if source or destination is an PCIe/IO address (non-SDRAM) and add 47962306a36Sopenharmony_ci * a new MBus window if necessary. Use a cache for these check so that 48062306a36Sopenharmony_ci * the MMIO mapped registers don't have to be accessed for this check 48162306a36Sopenharmony_ci * to speed up this process. 48262306a36Sopenharmony_ci */ 48362306a36Sopenharmony_cistatic int mv_xor_add_io_win(struct mv_xor_chan *mv_chan, u32 addr) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct mv_xor_device *xordev = mv_chan->xordev; 48662306a36Sopenharmony_ci void __iomem *base = mv_chan->mmr_high_base; 48762306a36Sopenharmony_ci u32 win_enable; 48862306a36Sopenharmony_ci u32 size; 48962306a36Sopenharmony_ci u8 target, attr; 49062306a36Sopenharmony_ci int ret; 49162306a36Sopenharmony_ci int i; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci /* Nothing needs to get done for the Armada 3700 */ 49462306a36Sopenharmony_ci if (xordev->xor_type == XOR_ARMADA_37XX) 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci /* 49862306a36Sopenharmony_ci * Loop over the cached windows to check, if the requested area 49962306a36Sopenharmony_ci * is already mapped. If this the case, nothing needs to be done 50062306a36Sopenharmony_ci * and we can return. 50162306a36Sopenharmony_ci */ 50262306a36Sopenharmony_ci for (i = 0; i < WINDOW_COUNT; i++) { 50362306a36Sopenharmony_ci if (addr >= xordev->win_start[i] && 50462306a36Sopenharmony_ci addr <= xordev->win_end[i]) { 50562306a36Sopenharmony_ci /* Window is already mapped */ 50662306a36Sopenharmony_ci return 0; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* 51162306a36Sopenharmony_ci * The window is not mapped, so we need to create the new mapping 51262306a36Sopenharmony_ci */ 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci /* If no IO window is found that addr has to be located in SDRAM */ 51562306a36Sopenharmony_ci ret = mvebu_mbus_get_io_win_info(addr, &size, &target, &attr); 51662306a36Sopenharmony_ci if (ret < 0) 51762306a36Sopenharmony_ci return 0; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci /* 52062306a36Sopenharmony_ci * Mask the base addr 'addr' according to 'size' read back from the 52162306a36Sopenharmony_ci * MBus window. Otherwise we might end up with an address located 52262306a36Sopenharmony_ci * somewhere in the middle of this area here. 52362306a36Sopenharmony_ci */ 52462306a36Sopenharmony_ci size -= 1; 52562306a36Sopenharmony_ci addr &= ~size; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci /* 52862306a36Sopenharmony_ci * Reading one of both enabled register is enough, as they are always 52962306a36Sopenharmony_ci * programmed to the identical values 53062306a36Sopenharmony_ci */ 53162306a36Sopenharmony_ci win_enable = readl(base + WINDOW_BAR_ENABLE(0)); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci /* Set 'i' to the first free window to write the new values to */ 53462306a36Sopenharmony_ci i = ffs(~win_enable) - 1; 53562306a36Sopenharmony_ci if (i >= WINDOW_COUNT) 53662306a36Sopenharmony_ci return -ENOMEM; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci writel((addr & 0xffff0000) | (attr << 8) | target, 53962306a36Sopenharmony_ci base + WINDOW_BASE(i)); 54062306a36Sopenharmony_ci writel(size & 0xffff0000, base + WINDOW_SIZE(i)); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* Fill the caching variables for later use */ 54362306a36Sopenharmony_ci xordev->win_start[i] = addr; 54462306a36Sopenharmony_ci xordev->win_end[i] = addr + size; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci win_enable |= (1 << i); 54762306a36Sopenharmony_ci win_enable |= 3 << (16 + (2 * i)); 54862306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(0)); 54962306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(1)); 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci return 0; 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic struct dma_async_tx_descriptor * 55562306a36Sopenharmony_cimv_xor_prep_dma_xor(struct dma_chan *chan, dma_addr_t dest, dma_addr_t *src, 55662306a36Sopenharmony_ci unsigned int src_cnt, size_t len, unsigned long flags) 55762306a36Sopenharmony_ci{ 55862306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 55962306a36Sopenharmony_ci struct mv_xor_desc_slot *sw_desc; 56062306a36Sopenharmony_ci int ret; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci if (unlikely(len < MV_XOR_MIN_BYTE_COUNT)) 56362306a36Sopenharmony_ci return NULL; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci BUG_ON(len > MV_XOR_MAX_BYTE_COUNT); 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), 56862306a36Sopenharmony_ci "%s src_cnt: %d len: %zu dest %pad flags: %ld\n", 56962306a36Sopenharmony_ci __func__, src_cnt, len, &dest, flags); 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci /* Check if a new window needs to get added for 'dest' */ 57262306a36Sopenharmony_ci ret = mv_xor_add_io_win(mv_chan, dest); 57362306a36Sopenharmony_ci if (ret) 57462306a36Sopenharmony_ci return NULL; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci sw_desc = mv_chan_alloc_slot(mv_chan); 57762306a36Sopenharmony_ci if (sw_desc) { 57862306a36Sopenharmony_ci sw_desc->type = DMA_XOR; 57962306a36Sopenharmony_ci sw_desc->async_tx.flags = flags; 58062306a36Sopenharmony_ci mv_desc_init(sw_desc, dest, len, flags); 58162306a36Sopenharmony_ci if (mv_chan->op_in_desc == XOR_MODE_IN_DESC) 58262306a36Sopenharmony_ci mv_desc_set_mode(sw_desc); 58362306a36Sopenharmony_ci while (src_cnt--) { 58462306a36Sopenharmony_ci /* Check if a new window needs to get added for 'src' */ 58562306a36Sopenharmony_ci ret = mv_xor_add_io_win(mv_chan, src[src_cnt]); 58662306a36Sopenharmony_ci if (ret) 58762306a36Sopenharmony_ci return NULL; 58862306a36Sopenharmony_ci mv_desc_set_src_addr(sw_desc, src_cnt, src[src_cnt]); 58962306a36Sopenharmony_ci } 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), 59362306a36Sopenharmony_ci "%s sw_desc %p async_tx %p \n", 59462306a36Sopenharmony_ci __func__, sw_desc, &sw_desc->async_tx); 59562306a36Sopenharmony_ci return sw_desc ? &sw_desc->async_tx : NULL; 59662306a36Sopenharmony_ci} 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_cistatic struct dma_async_tx_descriptor * 59962306a36Sopenharmony_cimv_xor_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, 60062306a36Sopenharmony_ci size_t len, unsigned long flags) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci /* 60362306a36Sopenharmony_ci * A MEMCPY operation is identical to an XOR operation with only 60462306a36Sopenharmony_ci * a single source address. 60562306a36Sopenharmony_ci */ 60662306a36Sopenharmony_ci return mv_xor_prep_dma_xor(chan, dest, &src, 1, len, flags); 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic struct dma_async_tx_descriptor * 61062306a36Sopenharmony_cimv_xor_prep_dma_interrupt(struct dma_chan *chan, unsigned long flags) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 61362306a36Sopenharmony_ci dma_addr_t src, dest; 61462306a36Sopenharmony_ci size_t len; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci src = mv_chan->dummy_src_addr; 61762306a36Sopenharmony_ci dest = mv_chan->dummy_dst_addr; 61862306a36Sopenharmony_ci len = MV_XOR_MIN_BYTE_COUNT; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci /* 62162306a36Sopenharmony_ci * We implement the DMA_INTERRUPT operation as a minimum sized 62262306a36Sopenharmony_ci * XOR operation with a single dummy source address. 62362306a36Sopenharmony_ci */ 62462306a36Sopenharmony_ci return mv_xor_prep_dma_xor(chan, dest, &src, 1, len, flags); 62562306a36Sopenharmony_ci} 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_cistatic void mv_xor_free_chan_resources(struct dma_chan *chan) 62862306a36Sopenharmony_ci{ 62962306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 63062306a36Sopenharmony_ci struct mv_xor_desc_slot *iter, *_iter; 63162306a36Sopenharmony_ci int in_use_descs = 0; 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci spin_lock_bh(&mv_chan->lock); 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci mv_chan_slot_cleanup(mv_chan); 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci list_for_each_entry_safe(iter, _iter, &mv_chan->chain, 63862306a36Sopenharmony_ci node) { 63962306a36Sopenharmony_ci in_use_descs++; 64062306a36Sopenharmony_ci list_move_tail(&iter->node, &mv_chan->free_slots); 64162306a36Sopenharmony_ci } 64262306a36Sopenharmony_ci list_for_each_entry_safe(iter, _iter, &mv_chan->completed_slots, 64362306a36Sopenharmony_ci node) { 64462306a36Sopenharmony_ci in_use_descs++; 64562306a36Sopenharmony_ci list_move_tail(&iter->node, &mv_chan->free_slots); 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci list_for_each_entry_safe(iter, _iter, &mv_chan->allocated_slots, 64862306a36Sopenharmony_ci node) { 64962306a36Sopenharmony_ci in_use_descs++; 65062306a36Sopenharmony_ci list_move_tail(&iter->node, &mv_chan->free_slots); 65162306a36Sopenharmony_ci } 65262306a36Sopenharmony_ci list_for_each_entry_safe_reverse( 65362306a36Sopenharmony_ci iter, _iter, &mv_chan->free_slots, node) { 65462306a36Sopenharmony_ci list_del(&iter->node); 65562306a36Sopenharmony_ci kfree(iter); 65662306a36Sopenharmony_ci mv_chan->slots_allocated--; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(mv_chan), "%s slots_allocated %d\n", 66062306a36Sopenharmony_ci __func__, mv_chan->slots_allocated); 66162306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci if (in_use_descs) 66462306a36Sopenharmony_ci dev_err(mv_chan_to_devp(mv_chan), 66562306a36Sopenharmony_ci "freeing %d in use descriptors!\n", in_use_descs); 66662306a36Sopenharmony_ci} 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci/** 66962306a36Sopenharmony_ci * mv_xor_status - poll the status of an XOR transaction 67062306a36Sopenharmony_ci * @chan: XOR channel handle 67162306a36Sopenharmony_ci * @cookie: XOR transaction identifier 67262306a36Sopenharmony_ci * @txstate: XOR transactions state holder (or NULL) 67362306a36Sopenharmony_ci */ 67462306a36Sopenharmony_cistatic enum dma_status mv_xor_status(struct dma_chan *chan, 67562306a36Sopenharmony_ci dma_cookie_t cookie, 67662306a36Sopenharmony_ci struct dma_tx_state *txstate) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 67962306a36Sopenharmony_ci enum dma_status ret; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci ret = dma_cookie_status(chan, cookie, txstate); 68262306a36Sopenharmony_ci if (ret == DMA_COMPLETE) 68362306a36Sopenharmony_ci return ret; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci spin_lock_bh(&mv_chan->lock); 68662306a36Sopenharmony_ci mv_chan_slot_cleanup(mv_chan); 68762306a36Sopenharmony_ci spin_unlock_bh(&mv_chan->lock); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci return dma_cookie_status(chan, cookie, txstate); 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic void mv_chan_dump_regs(struct mv_xor_chan *chan) 69362306a36Sopenharmony_ci{ 69462306a36Sopenharmony_ci u32 val; 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci val = readl_relaxed(XOR_CONFIG(chan)); 69762306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "config 0x%08x\n", val); 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci val = readl_relaxed(XOR_ACTIVATION(chan)); 70062306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "activation 0x%08x\n", val); 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci val = readl_relaxed(XOR_INTR_CAUSE(chan)); 70362306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "intr cause 0x%08x\n", val); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci val = readl_relaxed(XOR_INTR_MASK(chan)); 70662306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "intr mask 0x%08x\n", val); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci val = readl_relaxed(XOR_ERROR_CAUSE(chan)); 70962306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "error cause 0x%08x\n", val); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci val = readl_relaxed(XOR_ERROR_ADDR(chan)); 71262306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "error addr 0x%08x\n", val); 71362306a36Sopenharmony_ci} 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic void mv_chan_err_interrupt_handler(struct mv_xor_chan *chan, 71662306a36Sopenharmony_ci u32 intr_cause) 71762306a36Sopenharmony_ci{ 71862306a36Sopenharmony_ci if (intr_cause & XOR_INT_ERR_DECODE) { 71962306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(chan), "ignoring address decode error\n"); 72062306a36Sopenharmony_ci return; 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci dev_err(mv_chan_to_devp(chan), "error on chan %d. intr cause 0x%08x\n", 72462306a36Sopenharmony_ci chan->idx, intr_cause); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci mv_chan_dump_regs(chan); 72762306a36Sopenharmony_ci WARN_ON(1); 72862306a36Sopenharmony_ci} 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_cistatic irqreturn_t mv_xor_interrupt_handler(int irq, void *data) 73162306a36Sopenharmony_ci{ 73262306a36Sopenharmony_ci struct mv_xor_chan *chan = data; 73362306a36Sopenharmony_ci u32 intr_cause = mv_chan_get_intr_cause(chan); 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci dev_dbg(mv_chan_to_devp(chan), "intr cause %x\n", intr_cause); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci if (intr_cause & XOR_INTR_ERRORS) 73862306a36Sopenharmony_ci mv_chan_err_interrupt_handler(chan, intr_cause); 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci tasklet_schedule(&chan->irq_tasklet); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci mv_chan_clear_eoc_cause(chan); 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci return IRQ_HANDLED; 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_cistatic void mv_xor_issue_pending(struct dma_chan *chan) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci if (mv_chan->pending >= MV_XOR_THRESHOLD) { 75262306a36Sopenharmony_ci mv_chan->pending = 0; 75362306a36Sopenharmony_ci mv_chan_activate(mv_chan); 75462306a36Sopenharmony_ci } 75562306a36Sopenharmony_ci} 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci/* 75862306a36Sopenharmony_ci * Perform a transaction to verify the HW works. 75962306a36Sopenharmony_ci */ 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_cistatic int mv_chan_memcpy_self_test(struct mv_xor_chan *mv_chan) 76262306a36Sopenharmony_ci{ 76362306a36Sopenharmony_ci int i, ret; 76462306a36Sopenharmony_ci void *src, *dest; 76562306a36Sopenharmony_ci dma_addr_t src_dma, dest_dma; 76662306a36Sopenharmony_ci struct dma_chan *dma_chan; 76762306a36Sopenharmony_ci dma_cookie_t cookie; 76862306a36Sopenharmony_ci struct dma_async_tx_descriptor *tx; 76962306a36Sopenharmony_ci struct dmaengine_unmap_data *unmap; 77062306a36Sopenharmony_ci int err = 0; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci src = kmalloc(PAGE_SIZE, GFP_KERNEL); 77362306a36Sopenharmony_ci if (!src) 77462306a36Sopenharmony_ci return -ENOMEM; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci dest = kzalloc(PAGE_SIZE, GFP_KERNEL); 77762306a36Sopenharmony_ci if (!dest) { 77862306a36Sopenharmony_ci kfree(src); 77962306a36Sopenharmony_ci return -ENOMEM; 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci /* Fill in src buffer */ 78362306a36Sopenharmony_ci for (i = 0; i < PAGE_SIZE; i++) 78462306a36Sopenharmony_ci ((u8 *) src)[i] = (u8)i; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci dma_chan = &mv_chan->dmachan; 78762306a36Sopenharmony_ci if (mv_xor_alloc_chan_resources(dma_chan) < 1) { 78862306a36Sopenharmony_ci err = -ENODEV; 78962306a36Sopenharmony_ci goto out; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci unmap = dmaengine_get_unmap_data(dma_chan->device->dev, 2, GFP_KERNEL); 79362306a36Sopenharmony_ci if (!unmap) { 79462306a36Sopenharmony_ci err = -ENOMEM; 79562306a36Sopenharmony_ci goto free_resources; 79662306a36Sopenharmony_ci } 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci src_dma = dma_map_page(dma_chan->device->dev, virt_to_page(src), 79962306a36Sopenharmony_ci offset_in_page(src), PAGE_SIZE, 80062306a36Sopenharmony_ci DMA_TO_DEVICE); 80162306a36Sopenharmony_ci unmap->addr[0] = src_dma; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci ret = dma_mapping_error(dma_chan->device->dev, src_dma); 80462306a36Sopenharmony_ci if (ret) { 80562306a36Sopenharmony_ci err = -ENOMEM; 80662306a36Sopenharmony_ci goto free_resources; 80762306a36Sopenharmony_ci } 80862306a36Sopenharmony_ci unmap->to_cnt = 1; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci dest_dma = dma_map_page(dma_chan->device->dev, virt_to_page(dest), 81162306a36Sopenharmony_ci offset_in_page(dest), PAGE_SIZE, 81262306a36Sopenharmony_ci DMA_FROM_DEVICE); 81362306a36Sopenharmony_ci unmap->addr[1] = dest_dma; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci ret = dma_mapping_error(dma_chan->device->dev, dest_dma); 81662306a36Sopenharmony_ci if (ret) { 81762306a36Sopenharmony_ci err = -ENOMEM; 81862306a36Sopenharmony_ci goto free_resources; 81962306a36Sopenharmony_ci } 82062306a36Sopenharmony_ci unmap->from_cnt = 1; 82162306a36Sopenharmony_ci unmap->len = PAGE_SIZE; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci tx = mv_xor_prep_dma_memcpy(dma_chan, dest_dma, src_dma, 82462306a36Sopenharmony_ci PAGE_SIZE, 0); 82562306a36Sopenharmony_ci if (!tx) { 82662306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 82762306a36Sopenharmony_ci "Self-test cannot prepare operation, disabling\n"); 82862306a36Sopenharmony_ci err = -ENODEV; 82962306a36Sopenharmony_ci goto free_resources; 83062306a36Sopenharmony_ci } 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci cookie = mv_xor_tx_submit(tx); 83362306a36Sopenharmony_ci if (dma_submit_error(cookie)) { 83462306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 83562306a36Sopenharmony_ci "Self-test submit error, disabling\n"); 83662306a36Sopenharmony_ci err = -ENODEV; 83762306a36Sopenharmony_ci goto free_resources; 83862306a36Sopenharmony_ci } 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci mv_xor_issue_pending(dma_chan); 84162306a36Sopenharmony_ci async_tx_ack(tx); 84262306a36Sopenharmony_ci msleep(1); 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci if (mv_xor_status(dma_chan, cookie, NULL) != 84562306a36Sopenharmony_ci DMA_COMPLETE) { 84662306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 84762306a36Sopenharmony_ci "Self-test copy timed out, disabling\n"); 84862306a36Sopenharmony_ci err = -ENODEV; 84962306a36Sopenharmony_ci goto free_resources; 85062306a36Sopenharmony_ci } 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci dma_sync_single_for_cpu(dma_chan->device->dev, dest_dma, 85362306a36Sopenharmony_ci PAGE_SIZE, DMA_FROM_DEVICE); 85462306a36Sopenharmony_ci if (memcmp(src, dest, PAGE_SIZE)) { 85562306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 85662306a36Sopenharmony_ci "Self-test copy failed compare, disabling\n"); 85762306a36Sopenharmony_ci err = -ENODEV; 85862306a36Sopenharmony_ci goto free_resources; 85962306a36Sopenharmony_ci } 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_cifree_resources: 86262306a36Sopenharmony_ci dmaengine_unmap_put(unmap); 86362306a36Sopenharmony_ci mv_xor_free_chan_resources(dma_chan); 86462306a36Sopenharmony_ciout: 86562306a36Sopenharmony_ci kfree(src); 86662306a36Sopenharmony_ci kfree(dest); 86762306a36Sopenharmony_ci return err; 86862306a36Sopenharmony_ci} 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci#define MV_XOR_NUM_SRC_TEST 4 /* must be <= 15 */ 87162306a36Sopenharmony_cistatic int 87262306a36Sopenharmony_cimv_chan_xor_self_test(struct mv_xor_chan *mv_chan) 87362306a36Sopenharmony_ci{ 87462306a36Sopenharmony_ci int i, src_idx, ret; 87562306a36Sopenharmony_ci struct page *dest; 87662306a36Sopenharmony_ci struct page *xor_srcs[MV_XOR_NUM_SRC_TEST]; 87762306a36Sopenharmony_ci dma_addr_t dma_srcs[MV_XOR_NUM_SRC_TEST]; 87862306a36Sopenharmony_ci dma_addr_t dest_dma; 87962306a36Sopenharmony_ci struct dma_async_tx_descriptor *tx; 88062306a36Sopenharmony_ci struct dmaengine_unmap_data *unmap; 88162306a36Sopenharmony_ci struct dma_chan *dma_chan; 88262306a36Sopenharmony_ci dma_cookie_t cookie; 88362306a36Sopenharmony_ci u8 cmp_byte = 0; 88462306a36Sopenharmony_ci u32 cmp_word; 88562306a36Sopenharmony_ci int err = 0; 88662306a36Sopenharmony_ci int src_count = MV_XOR_NUM_SRC_TEST; 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci for (src_idx = 0; src_idx < src_count; src_idx++) { 88962306a36Sopenharmony_ci xor_srcs[src_idx] = alloc_page(GFP_KERNEL); 89062306a36Sopenharmony_ci if (!xor_srcs[src_idx]) { 89162306a36Sopenharmony_ci while (src_idx--) 89262306a36Sopenharmony_ci __free_page(xor_srcs[src_idx]); 89362306a36Sopenharmony_ci return -ENOMEM; 89462306a36Sopenharmony_ci } 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci dest = alloc_page(GFP_KERNEL); 89862306a36Sopenharmony_ci if (!dest) { 89962306a36Sopenharmony_ci while (src_idx--) 90062306a36Sopenharmony_ci __free_page(xor_srcs[src_idx]); 90162306a36Sopenharmony_ci return -ENOMEM; 90262306a36Sopenharmony_ci } 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci /* Fill in src buffers */ 90562306a36Sopenharmony_ci for (src_idx = 0; src_idx < src_count; src_idx++) { 90662306a36Sopenharmony_ci u8 *ptr = page_address(xor_srcs[src_idx]); 90762306a36Sopenharmony_ci for (i = 0; i < PAGE_SIZE; i++) 90862306a36Sopenharmony_ci ptr[i] = (1 << src_idx); 90962306a36Sopenharmony_ci } 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci for (src_idx = 0; src_idx < src_count; src_idx++) 91262306a36Sopenharmony_ci cmp_byte ^= (u8) (1 << src_idx); 91362306a36Sopenharmony_ci 91462306a36Sopenharmony_ci cmp_word = (cmp_byte << 24) | (cmp_byte << 16) | 91562306a36Sopenharmony_ci (cmp_byte << 8) | cmp_byte; 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci memset(page_address(dest), 0, PAGE_SIZE); 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci dma_chan = &mv_chan->dmachan; 92062306a36Sopenharmony_ci if (mv_xor_alloc_chan_resources(dma_chan) < 1) { 92162306a36Sopenharmony_ci err = -ENODEV; 92262306a36Sopenharmony_ci goto out; 92362306a36Sopenharmony_ci } 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci unmap = dmaengine_get_unmap_data(dma_chan->device->dev, src_count + 1, 92662306a36Sopenharmony_ci GFP_KERNEL); 92762306a36Sopenharmony_ci if (!unmap) { 92862306a36Sopenharmony_ci err = -ENOMEM; 92962306a36Sopenharmony_ci goto free_resources; 93062306a36Sopenharmony_ci } 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci /* test xor */ 93362306a36Sopenharmony_ci for (i = 0; i < src_count; i++) { 93462306a36Sopenharmony_ci unmap->addr[i] = dma_map_page(dma_chan->device->dev, xor_srcs[i], 93562306a36Sopenharmony_ci 0, PAGE_SIZE, DMA_TO_DEVICE); 93662306a36Sopenharmony_ci dma_srcs[i] = unmap->addr[i]; 93762306a36Sopenharmony_ci ret = dma_mapping_error(dma_chan->device->dev, unmap->addr[i]); 93862306a36Sopenharmony_ci if (ret) { 93962306a36Sopenharmony_ci err = -ENOMEM; 94062306a36Sopenharmony_ci goto free_resources; 94162306a36Sopenharmony_ci } 94262306a36Sopenharmony_ci unmap->to_cnt++; 94362306a36Sopenharmony_ci } 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci unmap->addr[src_count] = dma_map_page(dma_chan->device->dev, dest, 0, PAGE_SIZE, 94662306a36Sopenharmony_ci DMA_FROM_DEVICE); 94762306a36Sopenharmony_ci dest_dma = unmap->addr[src_count]; 94862306a36Sopenharmony_ci ret = dma_mapping_error(dma_chan->device->dev, unmap->addr[src_count]); 94962306a36Sopenharmony_ci if (ret) { 95062306a36Sopenharmony_ci err = -ENOMEM; 95162306a36Sopenharmony_ci goto free_resources; 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci unmap->from_cnt = 1; 95462306a36Sopenharmony_ci unmap->len = PAGE_SIZE; 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci tx = mv_xor_prep_dma_xor(dma_chan, dest_dma, dma_srcs, 95762306a36Sopenharmony_ci src_count, PAGE_SIZE, 0); 95862306a36Sopenharmony_ci if (!tx) { 95962306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 96062306a36Sopenharmony_ci "Self-test cannot prepare operation, disabling\n"); 96162306a36Sopenharmony_ci err = -ENODEV; 96262306a36Sopenharmony_ci goto free_resources; 96362306a36Sopenharmony_ci } 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci cookie = mv_xor_tx_submit(tx); 96662306a36Sopenharmony_ci if (dma_submit_error(cookie)) { 96762306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 96862306a36Sopenharmony_ci "Self-test submit error, disabling\n"); 96962306a36Sopenharmony_ci err = -ENODEV; 97062306a36Sopenharmony_ci goto free_resources; 97162306a36Sopenharmony_ci } 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci mv_xor_issue_pending(dma_chan); 97462306a36Sopenharmony_ci async_tx_ack(tx); 97562306a36Sopenharmony_ci msleep(8); 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci if (mv_xor_status(dma_chan, cookie, NULL) != 97862306a36Sopenharmony_ci DMA_COMPLETE) { 97962306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 98062306a36Sopenharmony_ci "Self-test xor timed out, disabling\n"); 98162306a36Sopenharmony_ci err = -ENODEV; 98262306a36Sopenharmony_ci goto free_resources; 98362306a36Sopenharmony_ci } 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci dma_sync_single_for_cpu(dma_chan->device->dev, dest_dma, 98662306a36Sopenharmony_ci PAGE_SIZE, DMA_FROM_DEVICE); 98762306a36Sopenharmony_ci for (i = 0; i < (PAGE_SIZE / sizeof(u32)); i++) { 98862306a36Sopenharmony_ci u32 *ptr = page_address(dest); 98962306a36Sopenharmony_ci if (ptr[i] != cmp_word) { 99062306a36Sopenharmony_ci dev_err(dma_chan->device->dev, 99162306a36Sopenharmony_ci "Self-test xor failed compare, disabling. index %d, data %x, expected %x\n", 99262306a36Sopenharmony_ci i, ptr[i], cmp_word); 99362306a36Sopenharmony_ci err = -ENODEV; 99462306a36Sopenharmony_ci goto free_resources; 99562306a36Sopenharmony_ci } 99662306a36Sopenharmony_ci } 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_cifree_resources: 99962306a36Sopenharmony_ci dmaengine_unmap_put(unmap); 100062306a36Sopenharmony_ci mv_xor_free_chan_resources(dma_chan); 100162306a36Sopenharmony_ciout: 100262306a36Sopenharmony_ci src_idx = src_count; 100362306a36Sopenharmony_ci while (src_idx--) 100462306a36Sopenharmony_ci __free_page(xor_srcs[src_idx]); 100562306a36Sopenharmony_ci __free_page(dest); 100662306a36Sopenharmony_ci return err; 100762306a36Sopenharmony_ci} 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_cistatic int mv_xor_channel_remove(struct mv_xor_chan *mv_chan) 101062306a36Sopenharmony_ci{ 101162306a36Sopenharmony_ci struct dma_chan *chan, *_chan; 101262306a36Sopenharmony_ci struct device *dev = mv_chan->dmadev.dev; 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_ci dma_async_device_unregister(&mv_chan->dmadev); 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci dma_free_coherent(dev, MV_XOR_POOL_SIZE, 101762306a36Sopenharmony_ci mv_chan->dma_desc_pool_virt, mv_chan->dma_desc_pool); 101862306a36Sopenharmony_ci dma_unmap_single(dev, mv_chan->dummy_src_addr, 101962306a36Sopenharmony_ci MV_XOR_MIN_BYTE_COUNT, DMA_FROM_DEVICE); 102062306a36Sopenharmony_ci dma_unmap_single(dev, mv_chan->dummy_dst_addr, 102162306a36Sopenharmony_ci MV_XOR_MIN_BYTE_COUNT, DMA_TO_DEVICE); 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci list_for_each_entry_safe(chan, _chan, &mv_chan->dmadev.channels, 102462306a36Sopenharmony_ci device_node) { 102562306a36Sopenharmony_ci list_del(&chan->device_node); 102662306a36Sopenharmony_ci } 102762306a36Sopenharmony_ci 102862306a36Sopenharmony_ci free_irq(mv_chan->irq, mv_chan); 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci return 0; 103162306a36Sopenharmony_ci} 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_cistatic struct mv_xor_chan * 103462306a36Sopenharmony_cimv_xor_channel_add(struct mv_xor_device *xordev, 103562306a36Sopenharmony_ci struct platform_device *pdev, 103662306a36Sopenharmony_ci int idx, dma_cap_mask_t cap_mask, int irq) 103762306a36Sopenharmony_ci{ 103862306a36Sopenharmony_ci int ret = 0; 103962306a36Sopenharmony_ci struct mv_xor_chan *mv_chan; 104062306a36Sopenharmony_ci struct dma_device *dma_dev; 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci mv_chan = devm_kzalloc(&pdev->dev, sizeof(*mv_chan), GFP_KERNEL); 104362306a36Sopenharmony_ci if (!mv_chan) 104462306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci mv_chan->idx = idx; 104762306a36Sopenharmony_ci mv_chan->irq = irq; 104862306a36Sopenharmony_ci if (xordev->xor_type == XOR_ORION) 104962306a36Sopenharmony_ci mv_chan->op_in_desc = XOR_MODE_IN_REG; 105062306a36Sopenharmony_ci else 105162306a36Sopenharmony_ci mv_chan->op_in_desc = XOR_MODE_IN_DESC; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci dma_dev = &mv_chan->dmadev; 105462306a36Sopenharmony_ci dma_dev->dev = &pdev->dev; 105562306a36Sopenharmony_ci mv_chan->xordev = xordev; 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci /* 105862306a36Sopenharmony_ci * These source and destination dummy buffers are used to implement 105962306a36Sopenharmony_ci * a DMA_INTERRUPT operation as a minimum-sized XOR operation. 106062306a36Sopenharmony_ci * Hence, we only need to map the buffers at initialization-time. 106162306a36Sopenharmony_ci */ 106262306a36Sopenharmony_ci mv_chan->dummy_src_addr = dma_map_single(dma_dev->dev, 106362306a36Sopenharmony_ci mv_chan->dummy_src, MV_XOR_MIN_BYTE_COUNT, DMA_FROM_DEVICE); 106462306a36Sopenharmony_ci mv_chan->dummy_dst_addr = dma_map_single(dma_dev->dev, 106562306a36Sopenharmony_ci mv_chan->dummy_dst, MV_XOR_MIN_BYTE_COUNT, DMA_TO_DEVICE); 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci /* allocate coherent memory for hardware descriptors 106862306a36Sopenharmony_ci * note: writecombine gives slightly better performance, but 106962306a36Sopenharmony_ci * requires that we explicitly flush the writes 107062306a36Sopenharmony_ci */ 107162306a36Sopenharmony_ci mv_chan->dma_desc_pool_virt = 107262306a36Sopenharmony_ci dma_alloc_wc(&pdev->dev, MV_XOR_POOL_SIZE, &mv_chan->dma_desc_pool, 107362306a36Sopenharmony_ci GFP_KERNEL); 107462306a36Sopenharmony_ci if (!mv_chan->dma_desc_pool_virt) 107562306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 107662306a36Sopenharmony_ci 107762306a36Sopenharmony_ci /* discover transaction capabilites from the platform data */ 107862306a36Sopenharmony_ci dma_dev->cap_mask = cap_mask; 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci INIT_LIST_HEAD(&dma_dev->channels); 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci /* set base routines */ 108362306a36Sopenharmony_ci dma_dev->device_alloc_chan_resources = mv_xor_alloc_chan_resources; 108462306a36Sopenharmony_ci dma_dev->device_free_chan_resources = mv_xor_free_chan_resources; 108562306a36Sopenharmony_ci dma_dev->device_tx_status = mv_xor_status; 108662306a36Sopenharmony_ci dma_dev->device_issue_pending = mv_xor_issue_pending; 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci /* set prep routines based on capability */ 108962306a36Sopenharmony_ci if (dma_has_cap(DMA_INTERRUPT, dma_dev->cap_mask)) 109062306a36Sopenharmony_ci dma_dev->device_prep_dma_interrupt = mv_xor_prep_dma_interrupt; 109162306a36Sopenharmony_ci if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) 109262306a36Sopenharmony_ci dma_dev->device_prep_dma_memcpy = mv_xor_prep_dma_memcpy; 109362306a36Sopenharmony_ci if (dma_has_cap(DMA_XOR, dma_dev->cap_mask)) { 109462306a36Sopenharmony_ci dma_dev->max_xor = 8; 109562306a36Sopenharmony_ci dma_dev->device_prep_dma_xor = mv_xor_prep_dma_xor; 109662306a36Sopenharmony_ci } 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci mv_chan->mmr_base = xordev->xor_base; 109962306a36Sopenharmony_ci mv_chan->mmr_high_base = xordev->xor_high_base; 110062306a36Sopenharmony_ci tasklet_setup(&mv_chan->irq_tasklet, mv_xor_tasklet); 110162306a36Sopenharmony_ci 110262306a36Sopenharmony_ci /* clear errors before enabling interrupts */ 110362306a36Sopenharmony_ci mv_chan_clear_err_status(mv_chan); 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci ret = request_irq(mv_chan->irq, mv_xor_interrupt_handler, 110662306a36Sopenharmony_ci 0, dev_name(&pdev->dev), mv_chan); 110762306a36Sopenharmony_ci if (ret) 110862306a36Sopenharmony_ci goto err_free_dma; 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci mv_chan_unmask_interrupts(mv_chan); 111162306a36Sopenharmony_ci 111262306a36Sopenharmony_ci if (mv_chan->op_in_desc == XOR_MODE_IN_DESC) 111362306a36Sopenharmony_ci mv_chan_set_mode(mv_chan, XOR_OPERATION_MODE_IN_DESC); 111462306a36Sopenharmony_ci else 111562306a36Sopenharmony_ci mv_chan_set_mode(mv_chan, XOR_OPERATION_MODE_XOR); 111662306a36Sopenharmony_ci 111762306a36Sopenharmony_ci spin_lock_init(&mv_chan->lock); 111862306a36Sopenharmony_ci INIT_LIST_HEAD(&mv_chan->chain); 111962306a36Sopenharmony_ci INIT_LIST_HEAD(&mv_chan->completed_slots); 112062306a36Sopenharmony_ci INIT_LIST_HEAD(&mv_chan->free_slots); 112162306a36Sopenharmony_ci INIT_LIST_HEAD(&mv_chan->allocated_slots); 112262306a36Sopenharmony_ci mv_chan->dmachan.device = dma_dev; 112362306a36Sopenharmony_ci dma_cookie_init(&mv_chan->dmachan); 112462306a36Sopenharmony_ci 112562306a36Sopenharmony_ci list_add_tail(&mv_chan->dmachan.device_node, &dma_dev->channels); 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) { 112862306a36Sopenharmony_ci ret = mv_chan_memcpy_self_test(mv_chan); 112962306a36Sopenharmony_ci dev_dbg(&pdev->dev, "memcpy self test returned %d\n", ret); 113062306a36Sopenharmony_ci if (ret) 113162306a36Sopenharmony_ci goto err_free_irq; 113262306a36Sopenharmony_ci } 113362306a36Sopenharmony_ci 113462306a36Sopenharmony_ci if (dma_has_cap(DMA_XOR, dma_dev->cap_mask)) { 113562306a36Sopenharmony_ci ret = mv_chan_xor_self_test(mv_chan); 113662306a36Sopenharmony_ci dev_dbg(&pdev->dev, "xor self test returned %d\n", ret); 113762306a36Sopenharmony_ci if (ret) 113862306a36Sopenharmony_ci goto err_free_irq; 113962306a36Sopenharmony_ci } 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci dev_info(&pdev->dev, "Marvell XOR (%s): ( %s%s%s)\n", 114262306a36Sopenharmony_ci mv_chan->op_in_desc ? "Descriptor Mode" : "Registers Mode", 114362306a36Sopenharmony_ci dma_has_cap(DMA_XOR, dma_dev->cap_mask) ? "xor " : "", 114462306a36Sopenharmony_ci dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask) ? "cpy " : "", 114562306a36Sopenharmony_ci dma_has_cap(DMA_INTERRUPT, dma_dev->cap_mask) ? "intr " : ""); 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci ret = dma_async_device_register(dma_dev); 114862306a36Sopenharmony_ci if (ret) 114962306a36Sopenharmony_ci goto err_free_irq; 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci return mv_chan; 115262306a36Sopenharmony_ci 115362306a36Sopenharmony_cierr_free_irq: 115462306a36Sopenharmony_ci free_irq(mv_chan->irq, mv_chan); 115562306a36Sopenharmony_cierr_free_dma: 115662306a36Sopenharmony_ci dma_free_coherent(&pdev->dev, MV_XOR_POOL_SIZE, 115762306a36Sopenharmony_ci mv_chan->dma_desc_pool_virt, mv_chan->dma_desc_pool); 115862306a36Sopenharmony_ci return ERR_PTR(ret); 115962306a36Sopenharmony_ci} 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_cistatic void 116262306a36Sopenharmony_cimv_xor_conf_mbus_windows(struct mv_xor_device *xordev, 116362306a36Sopenharmony_ci const struct mbus_dram_target_info *dram) 116462306a36Sopenharmony_ci{ 116562306a36Sopenharmony_ci void __iomem *base = xordev->xor_high_base; 116662306a36Sopenharmony_ci u32 win_enable = 0; 116762306a36Sopenharmony_ci int i; 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 117062306a36Sopenharmony_ci writel(0, base + WINDOW_BASE(i)); 117162306a36Sopenharmony_ci writel(0, base + WINDOW_SIZE(i)); 117262306a36Sopenharmony_ci if (i < 4) 117362306a36Sopenharmony_ci writel(0, base + WINDOW_REMAP_HIGH(i)); 117462306a36Sopenharmony_ci } 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci for (i = 0; i < dram->num_cs; i++) { 117762306a36Sopenharmony_ci const struct mbus_dram_window *cs = dram->cs + i; 117862306a36Sopenharmony_ci 117962306a36Sopenharmony_ci writel((cs->base & 0xffff0000) | 118062306a36Sopenharmony_ci (cs->mbus_attr << 8) | 118162306a36Sopenharmony_ci dram->mbus_dram_target_id, base + WINDOW_BASE(i)); 118262306a36Sopenharmony_ci writel((cs->size - 1) & 0xffff0000, base + WINDOW_SIZE(i)); 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci /* Fill the caching variables for later use */ 118562306a36Sopenharmony_ci xordev->win_start[i] = cs->base; 118662306a36Sopenharmony_ci xordev->win_end[i] = cs->base + cs->size - 1; 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_ci win_enable |= (1 << i); 118962306a36Sopenharmony_ci win_enable |= 3 << (16 + (2 * i)); 119062306a36Sopenharmony_ci } 119162306a36Sopenharmony_ci 119262306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(0)); 119362306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(1)); 119462306a36Sopenharmony_ci writel(0, base + WINDOW_OVERRIDE_CTRL(0)); 119562306a36Sopenharmony_ci writel(0, base + WINDOW_OVERRIDE_CTRL(1)); 119662306a36Sopenharmony_ci} 119762306a36Sopenharmony_ci 119862306a36Sopenharmony_cistatic void 119962306a36Sopenharmony_cimv_xor_conf_mbus_windows_a3700(struct mv_xor_device *xordev) 120062306a36Sopenharmony_ci{ 120162306a36Sopenharmony_ci void __iomem *base = xordev->xor_high_base; 120262306a36Sopenharmony_ci u32 win_enable = 0; 120362306a36Sopenharmony_ci int i; 120462306a36Sopenharmony_ci 120562306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 120662306a36Sopenharmony_ci writel(0, base + WINDOW_BASE(i)); 120762306a36Sopenharmony_ci writel(0, base + WINDOW_SIZE(i)); 120862306a36Sopenharmony_ci if (i < 4) 120962306a36Sopenharmony_ci writel(0, base + WINDOW_REMAP_HIGH(i)); 121062306a36Sopenharmony_ci } 121162306a36Sopenharmony_ci /* 121262306a36Sopenharmony_ci * For Armada3700 open default 4GB Mbus window. The dram 121362306a36Sopenharmony_ci * related configuration are done at AXIS level. 121462306a36Sopenharmony_ci */ 121562306a36Sopenharmony_ci writel(0xffff0000, base + WINDOW_SIZE(0)); 121662306a36Sopenharmony_ci win_enable |= 1; 121762306a36Sopenharmony_ci win_enable |= 3 << 16; 121862306a36Sopenharmony_ci 121962306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(0)); 122062306a36Sopenharmony_ci writel(win_enable, base + WINDOW_BAR_ENABLE(1)); 122162306a36Sopenharmony_ci writel(0, base + WINDOW_OVERRIDE_CTRL(0)); 122262306a36Sopenharmony_ci writel(0, base + WINDOW_OVERRIDE_CTRL(1)); 122362306a36Sopenharmony_ci} 122462306a36Sopenharmony_ci 122562306a36Sopenharmony_ci/* 122662306a36Sopenharmony_ci * Since this XOR driver is basically used only for RAID5, we don't 122762306a36Sopenharmony_ci * need to care about synchronizing ->suspend with DMA activity, 122862306a36Sopenharmony_ci * because the DMA engine will naturally be quiet due to the block 122962306a36Sopenharmony_ci * devices being suspended. 123062306a36Sopenharmony_ci */ 123162306a36Sopenharmony_cistatic int mv_xor_suspend(struct platform_device *pdev, pm_message_t state) 123262306a36Sopenharmony_ci{ 123362306a36Sopenharmony_ci struct mv_xor_device *xordev = platform_get_drvdata(pdev); 123462306a36Sopenharmony_ci int i; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci for (i = 0; i < MV_XOR_MAX_CHANNELS; i++) { 123762306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = xordev->channels[i]; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci if (!mv_chan) 124062306a36Sopenharmony_ci continue; 124162306a36Sopenharmony_ci 124262306a36Sopenharmony_ci mv_chan->saved_config_reg = 124362306a36Sopenharmony_ci readl_relaxed(XOR_CONFIG(mv_chan)); 124462306a36Sopenharmony_ci mv_chan->saved_int_mask_reg = 124562306a36Sopenharmony_ci readl_relaxed(XOR_INTR_MASK(mv_chan)); 124662306a36Sopenharmony_ci } 124762306a36Sopenharmony_ci 124862306a36Sopenharmony_ci return 0; 124962306a36Sopenharmony_ci} 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_cistatic int mv_xor_resume(struct platform_device *dev) 125262306a36Sopenharmony_ci{ 125362306a36Sopenharmony_ci struct mv_xor_device *xordev = platform_get_drvdata(dev); 125462306a36Sopenharmony_ci const struct mbus_dram_target_info *dram; 125562306a36Sopenharmony_ci int i; 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci for (i = 0; i < MV_XOR_MAX_CHANNELS; i++) { 125862306a36Sopenharmony_ci struct mv_xor_chan *mv_chan = xordev->channels[i]; 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_ci if (!mv_chan) 126162306a36Sopenharmony_ci continue; 126262306a36Sopenharmony_ci 126362306a36Sopenharmony_ci writel_relaxed(mv_chan->saved_config_reg, 126462306a36Sopenharmony_ci XOR_CONFIG(mv_chan)); 126562306a36Sopenharmony_ci writel_relaxed(mv_chan->saved_int_mask_reg, 126662306a36Sopenharmony_ci XOR_INTR_MASK(mv_chan)); 126762306a36Sopenharmony_ci } 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_ci if (xordev->xor_type == XOR_ARMADA_37XX) { 127062306a36Sopenharmony_ci mv_xor_conf_mbus_windows_a3700(xordev); 127162306a36Sopenharmony_ci return 0; 127262306a36Sopenharmony_ci } 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci dram = mv_mbus_dram_info(); 127562306a36Sopenharmony_ci if (dram) 127662306a36Sopenharmony_ci mv_xor_conf_mbus_windows(xordev, dram); 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_ci return 0; 127962306a36Sopenharmony_ci} 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_cistatic const struct of_device_id mv_xor_dt_ids[] = { 128262306a36Sopenharmony_ci { .compatible = "marvell,orion-xor", .data = (void *)XOR_ORION }, 128362306a36Sopenharmony_ci { .compatible = "marvell,armada-380-xor", .data = (void *)XOR_ARMADA_38X }, 128462306a36Sopenharmony_ci { .compatible = "marvell,armada-3700-xor", .data = (void *)XOR_ARMADA_37XX }, 128562306a36Sopenharmony_ci {}, 128662306a36Sopenharmony_ci}; 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_cistatic unsigned int mv_xor_engine_count; 128962306a36Sopenharmony_ci 129062306a36Sopenharmony_cistatic int mv_xor_probe(struct platform_device *pdev) 129162306a36Sopenharmony_ci{ 129262306a36Sopenharmony_ci const struct mbus_dram_target_info *dram; 129362306a36Sopenharmony_ci struct mv_xor_device *xordev; 129462306a36Sopenharmony_ci struct mv_xor_platform_data *pdata = dev_get_platdata(&pdev->dev); 129562306a36Sopenharmony_ci struct resource *res; 129662306a36Sopenharmony_ci unsigned int max_engines, max_channels; 129762306a36Sopenharmony_ci int i, ret; 129862306a36Sopenharmony_ci 129962306a36Sopenharmony_ci dev_notice(&pdev->dev, "Marvell shared XOR driver\n"); 130062306a36Sopenharmony_ci 130162306a36Sopenharmony_ci xordev = devm_kzalloc(&pdev->dev, sizeof(*xordev), GFP_KERNEL); 130262306a36Sopenharmony_ci if (!xordev) 130362306a36Sopenharmony_ci return -ENOMEM; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 130662306a36Sopenharmony_ci if (!res) 130762306a36Sopenharmony_ci return -ENODEV; 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci xordev->xor_base = devm_ioremap(&pdev->dev, res->start, 131062306a36Sopenharmony_ci resource_size(res)); 131162306a36Sopenharmony_ci if (!xordev->xor_base) 131262306a36Sopenharmony_ci return -EBUSY; 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 131562306a36Sopenharmony_ci if (!res) 131662306a36Sopenharmony_ci return -ENODEV; 131762306a36Sopenharmony_ci 131862306a36Sopenharmony_ci xordev->xor_high_base = devm_ioremap(&pdev->dev, res->start, 131962306a36Sopenharmony_ci resource_size(res)); 132062306a36Sopenharmony_ci if (!xordev->xor_high_base) 132162306a36Sopenharmony_ci return -EBUSY; 132262306a36Sopenharmony_ci 132362306a36Sopenharmony_ci platform_set_drvdata(pdev, xordev); 132462306a36Sopenharmony_ci 132562306a36Sopenharmony_ci 132662306a36Sopenharmony_ci /* 132762306a36Sopenharmony_ci * We need to know which type of XOR device we use before 132862306a36Sopenharmony_ci * setting up. In non-dt case it can only be the legacy one. 132962306a36Sopenharmony_ci */ 133062306a36Sopenharmony_ci xordev->xor_type = XOR_ORION; 133162306a36Sopenharmony_ci if (pdev->dev.of_node) { 133262306a36Sopenharmony_ci const struct of_device_id *of_id = 133362306a36Sopenharmony_ci of_match_device(mv_xor_dt_ids, 133462306a36Sopenharmony_ci &pdev->dev); 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci xordev->xor_type = (uintptr_t)of_id->data; 133762306a36Sopenharmony_ci } 133862306a36Sopenharmony_ci 133962306a36Sopenharmony_ci /* 134062306a36Sopenharmony_ci * (Re-)program MBUS remapping windows if we are asked to. 134162306a36Sopenharmony_ci */ 134262306a36Sopenharmony_ci if (xordev->xor_type == XOR_ARMADA_37XX) { 134362306a36Sopenharmony_ci mv_xor_conf_mbus_windows_a3700(xordev); 134462306a36Sopenharmony_ci } else { 134562306a36Sopenharmony_ci dram = mv_mbus_dram_info(); 134662306a36Sopenharmony_ci if (dram) 134762306a36Sopenharmony_ci mv_xor_conf_mbus_windows(xordev, dram); 134862306a36Sopenharmony_ci } 134962306a36Sopenharmony_ci 135062306a36Sopenharmony_ci /* Not all platforms can gate the clock, so it is not 135162306a36Sopenharmony_ci * an error if the clock does not exists. 135262306a36Sopenharmony_ci */ 135362306a36Sopenharmony_ci xordev->clk = clk_get(&pdev->dev, NULL); 135462306a36Sopenharmony_ci if (!IS_ERR(xordev->clk)) 135562306a36Sopenharmony_ci clk_prepare_enable(xordev->clk); 135662306a36Sopenharmony_ci 135762306a36Sopenharmony_ci /* 135862306a36Sopenharmony_ci * We don't want to have more than one channel per CPU in 135962306a36Sopenharmony_ci * order for async_tx to perform well. So we limit the number 136062306a36Sopenharmony_ci * of engines and channels so that we take into account this 136162306a36Sopenharmony_ci * constraint. Note that we also want to use channels from 136262306a36Sopenharmony_ci * separate engines when possible. For dual-CPU Armada 3700 136362306a36Sopenharmony_ci * SoC with single XOR engine allow using its both channels. 136462306a36Sopenharmony_ci */ 136562306a36Sopenharmony_ci max_engines = num_present_cpus(); 136662306a36Sopenharmony_ci if (xordev->xor_type == XOR_ARMADA_37XX) 136762306a36Sopenharmony_ci max_channels = num_present_cpus(); 136862306a36Sopenharmony_ci else 136962306a36Sopenharmony_ci max_channels = min_t(unsigned int, 137062306a36Sopenharmony_ci MV_XOR_MAX_CHANNELS, 137162306a36Sopenharmony_ci DIV_ROUND_UP(num_present_cpus(), 2)); 137262306a36Sopenharmony_ci 137362306a36Sopenharmony_ci if (mv_xor_engine_count >= max_engines) 137462306a36Sopenharmony_ci return 0; 137562306a36Sopenharmony_ci 137662306a36Sopenharmony_ci if (pdev->dev.of_node) { 137762306a36Sopenharmony_ci struct device_node *np; 137862306a36Sopenharmony_ci int i = 0; 137962306a36Sopenharmony_ci 138062306a36Sopenharmony_ci for_each_child_of_node(pdev->dev.of_node, np) { 138162306a36Sopenharmony_ci struct mv_xor_chan *chan; 138262306a36Sopenharmony_ci dma_cap_mask_t cap_mask; 138362306a36Sopenharmony_ci int irq; 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci if (i >= max_channels) 138662306a36Sopenharmony_ci continue; 138762306a36Sopenharmony_ci 138862306a36Sopenharmony_ci dma_cap_zero(cap_mask); 138962306a36Sopenharmony_ci dma_cap_set(DMA_MEMCPY, cap_mask); 139062306a36Sopenharmony_ci dma_cap_set(DMA_XOR, cap_mask); 139162306a36Sopenharmony_ci dma_cap_set(DMA_INTERRUPT, cap_mask); 139262306a36Sopenharmony_ci 139362306a36Sopenharmony_ci irq = irq_of_parse_and_map(np, 0); 139462306a36Sopenharmony_ci if (!irq) { 139562306a36Sopenharmony_ci ret = -ENODEV; 139662306a36Sopenharmony_ci goto err_channel_add; 139762306a36Sopenharmony_ci } 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci chan = mv_xor_channel_add(xordev, pdev, i, 140062306a36Sopenharmony_ci cap_mask, irq); 140162306a36Sopenharmony_ci if (IS_ERR(chan)) { 140262306a36Sopenharmony_ci ret = PTR_ERR(chan); 140362306a36Sopenharmony_ci irq_dispose_mapping(irq); 140462306a36Sopenharmony_ci goto err_channel_add; 140562306a36Sopenharmony_ci } 140662306a36Sopenharmony_ci 140762306a36Sopenharmony_ci xordev->channels[i] = chan; 140862306a36Sopenharmony_ci i++; 140962306a36Sopenharmony_ci } 141062306a36Sopenharmony_ci } else if (pdata && pdata->channels) { 141162306a36Sopenharmony_ci for (i = 0; i < max_channels; i++) { 141262306a36Sopenharmony_ci struct mv_xor_channel_data *cd; 141362306a36Sopenharmony_ci struct mv_xor_chan *chan; 141462306a36Sopenharmony_ci int irq; 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci cd = &pdata->channels[i]; 141762306a36Sopenharmony_ci irq = platform_get_irq(pdev, i); 141862306a36Sopenharmony_ci if (irq < 0) { 141962306a36Sopenharmony_ci ret = irq; 142062306a36Sopenharmony_ci goto err_channel_add; 142162306a36Sopenharmony_ci } 142262306a36Sopenharmony_ci 142362306a36Sopenharmony_ci chan = mv_xor_channel_add(xordev, pdev, i, 142462306a36Sopenharmony_ci cd->cap_mask, irq); 142562306a36Sopenharmony_ci if (IS_ERR(chan)) { 142662306a36Sopenharmony_ci ret = PTR_ERR(chan); 142762306a36Sopenharmony_ci goto err_channel_add; 142862306a36Sopenharmony_ci } 142962306a36Sopenharmony_ci 143062306a36Sopenharmony_ci xordev->channels[i] = chan; 143162306a36Sopenharmony_ci } 143262306a36Sopenharmony_ci } 143362306a36Sopenharmony_ci 143462306a36Sopenharmony_ci return 0; 143562306a36Sopenharmony_ci 143662306a36Sopenharmony_cierr_channel_add: 143762306a36Sopenharmony_ci for (i = 0; i < MV_XOR_MAX_CHANNELS; i++) 143862306a36Sopenharmony_ci if (xordev->channels[i]) { 143962306a36Sopenharmony_ci mv_xor_channel_remove(xordev->channels[i]); 144062306a36Sopenharmony_ci if (pdev->dev.of_node) 144162306a36Sopenharmony_ci irq_dispose_mapping(xordev->channels[i]->irq); 144262306a36Sopenharmony_ci } 144362306a36Sopenharmony_ci 144462306a36Sopenharmony_ci if (!IS_ERR(xordev->clk)) { 144562306a36Sopenharmony_ci clk_disable_unprepare(xordev->clk); 144662306a36Sopenharmony_ci clk_put(xordev->clk); 144762306a36Sopenharmony_ci } 144862306a36Sopenharmony_ci 144962306a36Sopenharmony_ci return ret; 145062306a36Sopenharmony_ci} 145162306a36Sopenharmony_ci 145262306a36Sopenharmony_cistatic struct platform_driver mv_xor_driver = { 145362306a36Sopenharmony_ci .probe = mv_xor_probe, 145462306a36Sopenharmony_ci .suspend = mv_xor_suspend, 145562306a36Sopenharmony_ci .resume = mv_xor_resume, 145662306a36Sopenharmony_ci .driver = { 145762306a36Sopenharmony_ci .name = MV_XOR_NAME, 145862306a36Sopenharmony_ci .of_match_table = mv_xor_dt_ids, 145962306a36Sopenharmony_ci }, 146062306a36Sopenharmony_ci}; 146162306a36Sopenharmony_ci 146262306a36Sopenharmony_cibuiltin_platform_driver(mv_xor_driver); 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci/* 146562306a36Sopenharmony_ciMODULE_AUTHOR("Saeed Bishara <saeed@marvell.com>"); 146662306a36Sopenharmony_ciMODULE_DESCRIPTION("DMA engine driver for Marvell's XOR engine"); 146762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 146862306a36Sopenharmony_ci*/ 1469