Commit bbea0b6e0d214ef1511b9c6ccf3af26b38f0af7d

Authored by Ira Snyder
Committed by Dan Williams
1 parent e6c7ecb64e

fsldma: Add DMA_SLAVE support

Use the DMA_SLAVE capability of the DMAEngine API to copy/from a
scatterlist into an arbitrary list of hardware address/length pairs.

This allows a single DMA transaction to copy data from several different
devices into a scatterlist at the same time.

This also adds support to enable some controller-specific features such as
external start and external pause for a DMA transaction.

[dan.j.williams@intel.com: rebased on tx_list movement]
Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu>
Acked-by: Li Yang <leoli@freescale.com>
Acked-by: Kumar Gala <galak@kernel.crashing.org>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

Showing 2 changed files with 363 additions and 0 deletions Side-by-side Diff

arch/powerpc/include/asm/fsldma.h
  1 +/*
  2 + * Freescale MPC83XX / MPC85XX DMA Controller
  3 + *
  4 + * Copyright (c) 2009 Ira W. Snyder <iws@ovro.caltech.edu>
  5 + *
  6 + * This file is licensed under the terms of the GNU General Public License
  7 + * version 2. This program is licensed "as is" without any warranty of any
  8 + * kind, whether express or implied.
  9 + */
  10 +
  11 +#ifndef __ARCH_POWERPC_ASM_FSLDMA_H__
  12 +#define __ARCH_POWERPC_ASM_FSLDMA_H__
  13 +
  14 +#include <linux/dmaengine.h>
  15 +
  16 +/*
  17 + * Definitions for the Freescale DMA controller's DMA_SLAVE implemention
  18 + *
  19 + * The Freescale DMA_SLAVE implementation was designed to handle many-to-many
  20 + * transfers. An example usage would be an accelerated copy between two
  21 + * scatterlists. Another example use would be an accelerated copy from
  22 + * multiple non-contiguous device buffers into a single scatterlist.
  23 + *
  24 + * A DMA_SLAVE transaction is defined by a struct fsl_dma_slave. This
  25 + * structure contains a list of hardware addresses that should be copied
  26 + * to/from the scatterlist passed into device_prep_slave_sg(). The structure
  27 + * also has some fields to enable hardware-specific features.
  28 + */
  29 +
  30 +/**
  31 + * struct fsl_dma_hw_addr
  32 + * @entry: linked list entry
  33 + * @address: the hardware address
  34 + * @length: length to transfer
  35 + *
  36 + * Holds a single physical hardware address / length pair for use
  37 + * with the DMAEngine DMA_SLAVE API.
  38 + */
  39 +struct fsl_dma_hw_addr {
  40 + struct list_head entry;
  41 +
  42 + dma_addr_t address;
  43 + size_t length;
  44 +};
  45 +
  46 +/**
  47 + * struct fsl_dma_slave
  48 + * @addresses: a linked list of struct fsl_dma_hw_addr structures
  49 + * @request_count: value for DMA request count
  50 + * @src_loop_size: setup and enable constant source-address DMA transfers
  51 + * @dst_loop_size: setup and enable constant destination address DMA transfers
  52 + * @external_start: enable externally started DMA transfers
  53 + * @external_pause: enable externally paused DMA transfers
  54 + *
  55 + * Holds a list of address / length pairs for use with the DMAEngine
  56 + * DMA_SLAVE API implementation for the Freescale DMA controller.
  57 + */
  58 +struct fsl_dma_slave {
  59 +
  60 + /* List of hardware address/length pairs */
  61 + struct list_head addresses;
  62 +
  63 + /* Support for extra controller features */
  64 + unsigned int request_count;
  65 + unsigned int src_loop_size;
  66 + unsigned int dst_loop_size;
  67 + bool external_start;
  68 + bool external_pause;
  69 +};
  70 +
  71 +/**
  72 + * fsl_dma_slave_append - add an address/length pair to a struct fsl_dma_slave
  73 + * @slave: the &struct fsl_dma_slave to add to
  74 + * @address: the hardware address to add
  75 + * @length: the length of bytes to transfer from @address
  76 + *
  77 + * Add a hardware address/length pair to a struct fsl_dma_slave. Returns 0 on
  78 + * success, -ERRNO otherwise.
  79 + */
  80 +static inline int fsl_dma_slave_append(struct fsl_dma_slave *slave,
  81 + dma_addr_t address, size_t length)
  82 +{
  83 + struct fsl_dma_hw_addr *addr;
  84 +
  85 + addr = kzalloc(sizeof(*addr), GFP_ATOMIC);
  86 + if (!addr)
  87 + return -ENOMEM;
  88 +
  89 + INIT_LIST_HEAD(&addr->entry);
  90 + addr->address = address;
  91 + addr->length = length;
  92 +
  93 + list_add_tail(&addr->entry, &slave->addresses);
  94 + return 0;
  95 +}
  96 +
  97 +/**
  98 + * fsl_dma_slave_free - free a struct fsl_dma_slave
  99 + * @slave: the struct fsl_dma_slave to free
  100 + *
  101 + * Free a struct fsl_dma_slave and all associated address/length pairs
  102 + */
  103 +static inline void fsl_dma_slave_free(struct fsl_dma_slave *slave)
  104 +{
  105 + struct fsl_dma_hw_addr *addr, *tmp;
  106 +
  107 + if (slave) {
  108 + list_for_each_entry_safe(addr, tmp, &slave->addresses, entry) {
  109 + list_del(&addr->entry);
  110 + kfree(addr);
  111 + }
  112 +
  113 + kfree(slave);
  114 + }
  115 +}
  116 +
  117 +/**
  118 + * fsl_dma_slave_alloc - allocate a struct fsl_dma_slave
  119 + * @gfp: the flags to pass to kmalloc when allocating this structure
  120 + *
  121 + * Allocate a struct fsl_dma_slave for use by the DMA_SLAVE API. Returns a new
  122 + * struct fsl_dma_slave on success, or NULL on failure.
  123 + */
  124 +static inline struct fsl_dma_slave *fsl_dma_slave_alloc(gfp_t gfp)
  125 +{
  126 + struct fsl_dma_slave *slave;
  127 +
  128 + slave = kzalloc(sizeof(*slave), gfp);
  129 + if (!slave)
  130 + return NULL;
  131 +
  132 + INIT_LIST_HEAD(&slave->addresses);
  133 + return slave;
  134 +}
  135 +
  136 +#endif /* __ARCH_POWERPC_ASM_FSLDMA_H__ */
drivers/dma/fsldma.c
... ... @@ -34,6 +34,7 @@
34 34 #include <linux/dmapool.h>
35 35 #include <linux/of_platform.h>
36 36  
  37 +#include <asm/fsldma.h>
37 38 #include "fsldma.h"
38 39  
39 40 static void dma_init(struct fsl_dma_chan *fsl_chan)
... ... @@ -552,6 +553,229 @@
552 553 }
553 554  
554 555 /**
  556 + * fsl_dma_prep_slave_sg - prepare descriptors for a DMA_SLAVE transaction
  557 + * @chan: DMA channel
  558 + * @sgl: scatterlist to transfer to/from
  559 + * @sg_len: number of entries in @scatterlist
  560 + * @direction: DMA direction
  561 + * @flags: DMAEngine flags
  562 + *
  563 + * Prepare a set of descriptors for a DMA_SLAVE transaction. Following the
  564 + * DMA_SLAVE API, this gets the device-specific information from the
  565 + * chan->private variable.
  566 + */
  567 +static struct dma_async_tx_descriptor *fsl_dma_prep_slave_sg(
  568 + struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len,
  569 + enum dma_data_direction direction, unsigned long flags)
  570 +{
  571 + struct fsl_dma_chan *fsl_chan;
  572 + struct fsl_desc_sw *first = NULL, *prev = NULL, *new = NULL;
  573 + struct fsl_dma_slave *slave;
  574 + struct list_head *tx_list;
  575 + size_t copy;
  576 +
  577 + int i;
  578 + struct scatterlist *sg;
  579 + size_t sg_used;
  580 + size_t hw_used;
  581 + struct fsl_dma_hw_addr *hw;
  582 + dma_addr_t dma_dst, dma_src;
  583 +
  584 + if (!chan)
  585 + return NULL;
  586 +
  587 + if (!chan->private)
  588 + return NULL;
  589 +
  590 + fsl_chan = to_fsl_chan(chan);
  591 + slave = chan->private;
  592 +
  593 + if (list_empty(&slave->addresses))
  594 + return NULL;
  595 +
  596 + hw = list_first_entry(&slave->addresses, struct fsl_dma_hw_addr, entry);
  597 + hw_used = 0;
  598 +
  599 + /*
  600 + * Build the hardware transaction to copy from the scatterlist to
  601 + * the hardware, or from the hardware to the scatterlist
  602 + *
  603 + * If you are copying from the hardware to the scatterlist and it
  604 + * takes two hardware entries to fill an entire page, then both
  605 + * hardware entries will be coalesced into the same page
  606 + *
  607 + * If you are copying from the scatterlist to the hardware and a
  608 + * single page can fill two hardware entries, then the data will
  609 + * be read out of the page into the first hardware entry, and so on
  610 + */
  611 + for_each_sg(sgl, sg, sg_len, i) {
  612 + sg_used = 0;
  613 +
  614 + /* Loop until the entire scatterlist entry is used */
  615 + while (sg_used < sg_dma_len(sg)) {
  616 +
  617 + /*
  618 + * If we've used up the current hardware address/length
  619 + * pair, we need to load a new one
  620 + *
  621 + * This is done in a while loop so that descriptors with
  622 + * length == 0 will be skipped
  623 + */
  624 + while (hw_used >= hw->length) {
  625 +
  626 + /*
  627 + * If the current hardware entry is the last
  628 + * entry in the list, we're finished
  629 + */
  630 + if (list_is_last(&hw->entry, &slave->addresses))
  631 + goto finished;
  632 +
  633 + /* Get the next hardware address/length pair */
  634 + hw = list_entry(hw->entry.next,
  635 + struct fsl_dma_hw_addr, entry);
  636 + hw_used = 0;
  637 + }
  638 +
  639 + /* Allocate the link descriptor from DMA pool */
  640 + new = fsl_dma_alloc_descriptor(fsl_chan);
  641 + if (!new) {
  642 + dev_err(fsl_chan->dev, "No free memory for "
  643 + "link descriptor\n");
  644 + goto fail;
  645 + }
  646 +#ifdef FSL_DMA_LD_DEBUG
  647 + dev_dbg(fsl_chan->dev, "new link desc alloc %p\n", new);
  648 +#endif
  649 +
  650 + /*
  651 + * Calculate the maximum number of bytes to transfer,
  652 + * making sure it is less than the DMA controller limit
  653 + */
  654 + copy = min_t(size_t, sg_dma_len(sg) - sg_used,
  655 + hw->length - hw_used);
  656 + copy = min_t(size_t, copy, FSL_DMA_BCR_MAX_CNT);
  657 +
  658 + /*
  659 + * DMA_FROM_DEVICE
  660 + * from the hardware to the scatterlist
  661 + *
  662 + * DMA_TO_DEVICE
  663 + * from the scatterlist to the hardware
  664 + */
  665 + if (direction == DMA_FROM_DEVICE) {
  666 + dma_src = hw->address + hw_used;
  667 + dma_dst = sg_dma_address(sg) + sg_used;
  668 + } else {
  669 + dma_src = sg_dma_address(sg) + sg_used;
  670 + dma_dst = hw->address + hw_used;
  671 + }
  672 +
  673 + /* Fill in the descriptor */
  674 + set_desc_cnt(fsl_chan, &new->hw, copy);
  675 + set_desc_src(fsl_chan, &new->hw, dma_src);
  676 + set_desc_dest(fsl_chan, &new->hw, dma_dst);
  677 +
  678 + /*
  679 + * If this is not the first descriptor, chain the
  680 + * current descriptor after the previous descriptor
  681 + */
  682 + if (!first) {
  683 + first = new;
  684 + } else {
  685 + set_desc_next(fsl_chan, &prev->hw,
  686 + new->async_tx.phys);
  687 + }
  688 +
  689 + new->async_tx.cookie = 0;
  690 + async_tx_ack(&new->async_tx);
  691 +
  692 + prev = new;
  693 + sg_used += copy;
  694 + hw_used += copy;
  695 +
  696 + /* Insert the link descriptor into the LD ring */
  697 + list_add_tail(&new->node, &first->tx_list);
  698 + }
  699 + }
  700 +
  701 +finished:
  702 +
  703 + /* All of the hardware address/length pairs had length == 0 */
  704 + if (!first || !new)
  705 + return NULL;
  706 +
  707 + new->async_tx.flags = flags;
  708 + new->async_tx.cookie = -EBUSY;
  709 +
  710 + /* Set End-of-link to the last link descriptor of new list */
  711 + set_ld_eol(fsl_chan, new);
  712 +
  713 + /* Enable extra controller features */
  714 + if (fsl_chan->set_src_loop_size)
  715 + fsl_chan->set_src_loop_size(fsl_chan, slave->src_loop_size);
  716 +
  717 + if (fsl_chan->set_dest_loop_size)
  718 + fsl_chan->set_dest_loop_size(fsl_chan, slave->dst_loop_size);
  719 +
  720 + if (fsl_chan->toggle_ext_start)
  721 + fsl_chan->toggle_ext_start(fsl_chan, slave->external_start);
  722 +
  723 + if (fsl_chan->toggle_ext_pause)
  724 + fsl_chan->toggle_ext_pause(fsl_chan, slave->external_pause);
  725 +
  726 + if (fsl_chan->set_request_count)
  727 + fsl_chan->set_request_count(fsl_chan, slave->request_count);
  728 +
  729 + return &first->async_tx;
  730 +
  731 +fail:
  732 + /* If first was not set, then we failed to allocate the very first
  733 + * descriptor, and we're done */
  734 + if (!first)
  735 + return NULL;
  736 +
  737 + /*
  738 + * First is set, so all of the descriptors we allocated have been added
  739 + * to first->tx_list, INCLUDING "first" itself. Therefore we
  740 + * must traverse the list backwards freeing each descriptor in turn
  741 + *
  742 + * We're re-using variables for the loop, oh well
  743 + */
  744 + tx_list = &first->tx_list;
  745 + list_for_each_entry_safe_reverse(new, prev, tx_list, node) {
  746 + list_del_init(&new->node);
  747 + dma_pool_free(fsl_chan->desc_pool, new, new->async_tx.phys);
  748 + }
  749 +
  750 + return NULL;
  751 +}
  752 +
  753 +static void fsl_dma_device_terminate_all(struct dma_chan *chan)
  754 +{
  755 + struct fsl_dma_chan *fsl_chan;
  756 + struct fsl_desc_sw *desc, *tmp;
  757 + unsigned long flags;
  758 +
  759 + if (!chan)
  760 + return;
  761 +
  762 + fsl_chan = to_fsl_chan(chan);
  763 +
  764 + /* Halt the DMA engine */
  765 + dma_halt(fsl_chan);
  766 +
  767 + spin_lock_irqsave(&fsl_chan->desc_lock, flags);
  768 +
  769 + /* Remove and free all of the descriptors in the LD queue */
  770 + list_for_each_entry_safe(desc, tmp, &fsl_chan->ld_queue, node) {
  771 + list_del(&desc->node);
  772 + dma_pool_free(fsl_chan->desc_pool, desc, desc->async_tx.phys);
  773 + }
  774 +
  775 + spin_unlock_irqrestore(&fsl_chan->desc_lock, flags);
  776 +}
  777 +
  778 +/**
555 779 * fsl_dma_update_completed_cookie - Update the completed cookie.
556 780 * @fsl_chan : Freescale DMA channel
557 781 */
558 782  
... ... @@ -977,12 +1201,15 @@
977 1201  
978 1202 dma_cap_set(DMA_MEMCPY, fdev->common.cap_mask);
979 1203 dma_cap_set(DMA_INTERRUPT, fdev->common.cap_mask);
  1204 + dma_cap_set(DMA_SLAVE, fdev->common.cap_mask);
980 1205 fdev->common.device_alloc_chan_resources = fsl_dma_alloc_chan_resources;
981 1206 fdev->common.device_free_chan_resources = fsl_dma_free_chan_resources;
982 1207 fdev->common.device_prep_dma_interrupt = fsl_dma_prep_interrupt;
983 1208 fdev->common.device_prep_dma_memcpy = fsl_dma_prep_memcpy;
984 1209 fdev->common.device_is_tx_complete = fsl_dma_is_complete;
985 1210 fdev->common.device_issue_pending = fsl_dma_memcpy_issue_pending;
  1211 + fdev->common.device_prep_slave_sg = fsl_dma_prep_slave_sg;
  1212 + fdev->common.device_terminate_all = fsl_dma_device_terminate_all;
986 1213 fdev->common.dev = &dev->dev;
987 1214  
988 1215 fdev->irq = irq_of_parse_and_map(dev->node, 0);