/* writel ensures all descriptors are flushed before activation */
writel(BIT(0), XOR_ACTIVATION(chan));
}
staticchar mv_chan_is_busy(struct mv_xor_chan *chan)
{
u32 state = readl_relaxed(XOR_ACTIVATION(chan));
state = (state >> 4) & 0x3;
return (state == 1) ? 1 : 0;
}
/* * mv_chan_start_new_chain - program the engine to operate on new * chain headed by sw_desc * Caller must hold &mv_chan->lock while calling this function
*/ staticvoid mv_chan_start_new_chain(struct mv_xor_chan *mv_chan, struct mv_xor_desc_slot *sw_desc)
{
dev_dbg(mv_chan_to_devp(mv_chan), "%s %d: sw_desc %p\n",
__func__, __LINE__, sw_desc);
/* set the hardware chain */
mv_chan_set_next_descriptor(mv_chan, sw_desc->async_tx.phys);
if (desc->async_tx.cookie > 0) {
cookie = desc->async_tx.cookie;
dma_descriptor_unmap(&desc->async_tx); /* call the callback (must not sleep or submit new * operations to this channel)
*/
dmaengine_desc_get_callback_invoke(&desc->async_tx, NULL);
}
/* run dependent operations */
dma_run_dependencies(&desc->async_tx);
/* the client is allowed to attach dependent operations * until 'ack' is set
*/ if (!async_tx_test_ack(&desc->async_tx)) { /* move this slot to the completed_slots */
list_move_tail(&desc->node, &mv_chan->completed_slots); if (!list_empty(&desc->sg_tx_list)) {
list_splice_tail_init(&desc->sg_tx_list,
&mv_chan->completed_slots);
}
} else {
list_move_tail(&desc->node, &mv_chan->free_slots); if (!list_empty(&desc->sg_tx_list)) {
list_splice_tail_init(&desc->sg_tx_list,
&mv_chan->free_slots);
}
}
return 0;
}
/* This function must be called with the mv_xor_chan spinlock held */ staticvoid mv_chan_slot_cleanup(struct mv_xor_chan *mv_chan)
{ struct mv_xor_desc_slot *iter, *_iter;
dma_cookie_t cookie = 0; int busy = mv_chan_is_busy(mv_chan);
u32 current_desc = mv_chan_get_current_desc(mv_chan); int current_cleaned = 0; struct mv_xor_desc *hw_desc;
/* break if we did cleaned the current */ if (iter->async_tx.phys == current_desc) {
current_cleaned = 1; break;
}
} else { if (iter->async_tx.phys == current_desc) {
current_cleaned = 0; break;
}
}
}
if ((busy == 0) && !list_empty(&mv_chan->chain)) { if (current_cleaned) { /* * current descriptor cleaned and removed, run * from list head
*/
iter = list_entry(mv_chan->chain.next, struct mv_xor_desc_slot,
node);
mv_chan_start_new_chain(mv_chan, iter);
} else { if (!list_is_last(&iter->node, &mv_chan->chain)) { /* * descriptors are still waiting after * current, trigger them
*/
iter = list_entry(iter->node.next, struct mv_xor_desc_slot,
node);
mv_chan_start_new_chain(mv_chan, iter);
} else { /* * some descriptors are still waiting * to be cleaned
*/
tasklet_schedule(&mv_chan->irq_tasklet);
}
}
}
if (cookie > 0)
mv_chan->dmachan.completed_cookie = cookie;
}
dev_dbg(mv_chan_to_devp(mv_chan), "Append to last desc %pa\n",
&old_chain_tail->async_tx.phys);
/* fix up the hardware chain */
mv_desc_set_next_desc(old_chain_tail, sw_desc->async_tx.phys);
/* if the channel is not busy */ if (!mv_chan_is_busy(mv_chan)) {
u32 current_desc = mv_chan_get_current_desc(mv_chan); /* * and the current desc is the end of the chain before * the append, then we need to start the channel
*/ if (current_desc == old_chain_tail->async_tx.phys)
new_hw_chain = 1;
}
}
if (new_hw_chain)
mv_chan_start_new_chain(mv_chan, sw_desc);
spin_unlock_bh(&mv_chan->lock);
return cookie;
}
/* returns the number of allocated descriptors */ staticint mv_xor_alloc_chan_resources(struct dma_chan *chan)
{ void *virt_desc;
dma_addr_t dma_desc; int idx; struct mv_xor_chan *mv_chan = to_mv_xor_chan(chan); struct mv_xor_desc_slot *slot = NULL; int num_descs_in_pool = MV_XOR_POOL_SIZE/MV_XOR_SLOT_SIZE;
/* * Check if source or destination is an PCIe/IO address (non-SDRAM) and add * a new MBus window if necessary. Use a cache for these check so that * the MMIO mapped registers don't have to be accessed for this check * to speed up this process.
*/ staticint mv_xor_add_io_win(struct mv_xor_chan *mv_chan, u32 addr)
{ struct mv_xor_device *xordev = mv_chan->xordev; void __iomem *base = mv_chan->mmr_high_base;
u32 win_enable;
u32 size;
u8 target, attr; int ret; int i;
/* Nothing needs to get done for the Armada 3700 */ if (xordev->xor_type == XOR_ARMADA_37XX) return 0;
/* * Loop over the cached windows to check, if the requested area * is already mapped. If this the case, nothing needs to be done * and we can return.
*/ for (i = 0; i < WINDOW_COUNT; i++) { if (addr >= xordev->win_start[i] &&
addr <= xordev->win_end[i]) { /* Window is already mapped */ return 0;
}
}
/* * The window is not mapped, so we need to create the new mapping
*/
/* If no IO window is found that addr has to be located in SDRAM */
ret = mvebu_mbus_get_io_win_info(addr, &size, &target, &attr); if (ret < 0) return 0;
/* * Mask the base addr 'addr' according to 'size' read back from the * MBus window. Otherwise we might end up with an address located * somewhere in the middle of this area here.
*/
size -= 1;
addr &= ~size;
/* * Reading one of both enabled register is enough, as they are always * programmed to the identical values
*/
win_enable = readl(base + WINDOW_BAR_ENABLE(0));
/* Set 'i' to the first free window to write the new values to */
i = ffs(~win_enable) - 1; if (i >= WINDOW_COUNT) return -ENOMEM;
writel((addr & 0xffff0000) | (attr << 8) | target,
base + WINDOW_BASE(i));
writel(size & 0xffff0000, base + WINDOW_SIZE(i));
/* Fill the caching variables for later use */
xordev->win_start[i] = addr;
xordev->win_end[i] = addr + size;
/* Check if a new window needs to get added for 'dest' */
ret = mv_xor_add_io_win(mv_chan, dest); if (ret) return NULL;
sw_desc = mv_chan_alloc_slot(mv_chan); if (sw_desc) {
sw_desc->type = DMA_XOR;
sw_desc->async_tx.flags = flags;
mv_desc_init(sw_desc, dest, len, flags); if (mv_chan->op_in_desc == XOR_MODE_IN_DESC)
mv_desc_set_mode(sw_desc); while (src_cnt--) { /* Check if a new window needs to get added for 'src' */
ret = mv_xor_add_io_win(mv_chan, src[src_cnt]); if (ret) return NULL;
mv_desc_set_src_addr(sw_desc, src_cnt, src[src_cnt]);
}
}
src = mv_chan->dummy_src_addr;
dest = mv_chan->dummy_dst_addr;
len = MV_XOR_MIN_BYTE_COUNT;
/* * We implement the DMA_INTERRUPT operation as a minimum sized * XOR operation with a single dummy source address.
*/ return mv_xor_prep_dma_xor(chan, dest, &src, 1, len, flags);
}
/* * These source and destination dummy buffers are used to implement * a DMA_INTERRUPT operation as a minimum-sized XOR operation. * Hence, we only need to map the buffers at initialization-time.
*/
mv_chan->dummy_src_addr = dma_map_single(dma_dev->dev,
mv_chan->dummy_src, MV_XOR_MIN_BYTE_COUNT, DMA_FROM_DEVICE); if (dma_mapping_error(dma_dev->dev, mv_chan->dummy_src_addr)) return ERR_PTR(-ENOMEM);
mv_chan->dummy_dst_addr = dma_map_single(dma_dev->dev,
mv_chan->dummy_dst, MV_XOR_MIN_BYTE_COUNT, DMA_TO_DEVICE); if (dma_mapping_error(dma_dev->dev, mv_chan->dummy_dst_addr)) {
ret = -ENOMEM; goto err_unmap_src;
}
/* allocate coherent memory for hardware descriptors * note: writecombine gives slightly better performance, but * requires that we explicitly flush the writes
*/
mv_chan->dma_desc_pool_virt =
dma_alloc_wc(&pdev->dev, MV_XOR_POOL_SIZE, &mv_chan->dma_desc_pool,
GFP_KERNEL); if (!mv_chan->dma_desc_pool_virt) {
ret = -ENOMEM; goto err_unmap_dst;
}
/* discover transaction capabilities from the platform data */
dma_dev->cap_mask = cap_mask;
INIT_LIST_HEAD(&dma_dev->channels);
/* set base routines */
dma_dev->device_alloc_chan_resources = mv_xor_alloc_chan_resources;
dma_dev->device_free_chan_resources = mv_xor_free_chan_resources;
dma_dev->device_tx_status = mv_xor_status;
dma_dev->device_issue_pending = mv_xor_issue_pending;
/* set prep routines based on capability */ if (dma_has_cap(DMA_INTERRUPT, dma_dev->cap_mask))
dma_dev->device_prep_dma_interrupt = mv_xor_prep_dma_interrupt; if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask))
dma_dev->device_prep_dma_memcpy = mv_xor_prep_dma_memcpy; if (dma_has_cap(DMA_XOR, dma_dev->cap_mask)) {
dma_dev->max_xor = 8;
dma_dev->device_prep_dma_xor = mv_xor_prep_dma_xor;
}
if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) {
ret = mv_chan_memcpy_self_test(mv_chan);
dev_dbg(&pdev->dev, "memcpy self test returned %d\n", ret); if (ret) goto err_free_irq;
}
if (dma_has_cap(DMA_XOR, dma_dev->cap_mask)) {
ret = mv_chan_xor_self_test(mv_chan);
dev_dbg(&pdev->dev, "xor self test returned %d\n", ret); if (ret) goto err_free_irq;
}
writel(win_enable, base + WINDOW_BAR_ENABLE(0));
writel(win_enable, base + WINDOW_BAR_ENABLE(1));
writel(0, base + WINDOW_OVERRIDE_CTRL(0));
writel(0, base + WINDOW_OVERRIDE_CTRL(1));
}
for (i = 0; i < 8; i++) {
writel(0, base + WINDOW_BASE(i));
writel(0, base + WINDOW_SIZE(i)); if (i < 4)
writel(0, base + WINDOW_REMAP_HIGH(i));
} /* * For Armada3700 open default 4GB Mbus window. The dram * related configuration are done at AXIS level.
*/
writel(0xffff0000, base + WINDOW_SIZE(0));
win_enable |= 1;
win_enable |= 3 << 16;
writel(win_enable, base + WINDOW_BAR_ENABLE(0));
writel(win_enable, base + WINDOW_BAR_ENABLE(1));
writel(0, base + WINDOW_OVERRIDE_CTRL(0));
writel(0, base + WINDOW_OVERRIDE_CTRL(1));
}
/* * Since this XOR driver is basically used only for RAID5, we don't * need to care about synchronizing ->suspend with DMA activity, * because the DMA engine will naturally be quiet due to the block * devices being suspended.
*/ staticint mv_xor_suspend(struct platform_device *pdev, pm_message_t state)
{ struct mv_xor_device *xordev = platform_get_drvdata(pdev); int i;
for (i = 0; i < MV_XOR_MAX_CHANNELS; i++) { struct mv_xor_chan *mv_chan = xordev->channels[i];
xordev = devm_kzalloc(&pdev->dev, sizeof(*xordev), GFP_KERNEL); if (!xordev) return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV;
xordev->xor_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res)); if (!xordev->xor_base) return -EBUSY;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) return -ENODEV;
xordev->xor_high_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res)); if (!xordev->xor_high_base) return -EBUSY;
platform_set_drvdata(pdev, xordev);
/* * We need to know which type of XOR device we use before * setting up. In non-dt case it can only be the legacy one.
*/
xordev->xor_type = XOR_ORION; if (pdev->dev.of_node)
xordev->xor_type = (uintptr_t)device_get_match_data(&pdev->dev);
/* * (Re-)program MBUS remapping windows if we are asked to.
*/ if (xordev->xor_type == XOR_ARMADA_37XX) {
mv_xor_conf_mbus_windows_a3700(xordev);
} else {
dram = mv_mbus_dram_info(); if (dram)
mv_xor_conf_mbus_windows(xordev, dram);
}
/* Not all platforms can gate the clock, so it is not * an error if the clock does not exists.
*/
xordev->clk = clk_get(&pdev->dev, NULL); if (!IS_ERR(xordev->clk))
clk_prepare_enable(xordev->clk);
/* * We don't want to have more than one channel per CPU in * order for async_tx to perform well. So we limit the number * of engines and channels so that we take into account this * constraint. Note that we also want to use channels from * separate engines when possible. For dual-CPU Armada 3700 * SoC with single XOR engine allow using its both channels.
*/
max_engines = num_present_cpus(); if (xordev->xor_type == XOR_ARMADA_37XX)
max_channels = num_present_cpus(); else
max_channels = min_t(unsignedint,
MV_XOR_MAX_CHANNELS,
DIV_ROUND_UP(num_present_cpus(), 2));
if (mv_xor_engine_count >= max_engines) return 0;
if (pdev->dev.of_node) { int i = 0;
for_each_child_of_node_scoped(pdev->dev.of_node, np) { struct mv_xor_chan *chan;
dma_cap_mask_t cap_mask; int irq;
irq = irq_of_parse_and_map(np, 0); if (!irq) {
ret = -ENODEV; goto err_channel_add;
}
chan = mv_xor_channel_add(xordev, pdev, i,
cap_mask, irq); if (IS_ERR(chan)) {
ret = PTR_ERR(chan);
irq_dispose_mapping(irq); goto err_channel_add;
}
xordev->channels[i] = chan;
i++;
}
} elseif (pdata && pdata->channels) { for (i = 0; i < max_channels; i++) { struct mv_xor_channel_data *cd; struct mv_xor_chan *chan; int irq;
cd = &pdata->channels[i];
irq = platform_get_irq(pdev, i); if (irq < 0) {
ret = irq; goto err_channel_add;
}
chan = mv_xor_channel_add(xordev, pdev, i,
cd->cap_mask, irq); if (IS_ERR(chan)) {
ret = PTR_ERR(chan); goto err_channel_add;
}
xordev->channels[i] = chan;
}
}
return 0;
err_channel_add: for (i = 0; i < MV_XOR_MAX_CHANNELS; i++) if (xordev->channels[i]) {
mv_xor_channel_remove(xordev->channels[i]); if (pdev->dev.of_node)
irq_dispose_mapping(xordev->channels[i]->irq);
}
if (!IS_ERR(xordev->clk)) {
clk_disable_unprepare(xordev->clk);
clk_put(xordev->clk);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.