[linux-sunxi] [PATCH v4] dma: sun4i: Add support for the DMA engine on sun[457]i SoCs
Priit Laes
plaes at plaes.org
Sun Feb 1 02:03:58 PST 2015
On Sat, 2015-01-31 at 19:58 -0300, Emilio López wrote:
> This patch adds support for the DMA engine present on Allwinner A10,
> A13, A10S and A20 SoCs. This engine has two kinds of channels:
> normal and dedicated. The main difference is in the mode of
> operation; while a single normal channel may be operating at any
> given time, dedicated channels may operate simultaneously provided
> there is no overlap of source or destination.
>
> Hardware documentation can be found on A10 User Manual (section 12),
> A13 User Manual (section 14) and A20 User Manual (section 1.12)
>
> Signed-off-by: Emilio López <emilio at elopez.com.ar>
> ---
>
> (Partial?) changes from v3:
> * Drop threaded IRQ to get lower latency
> * Drop chancnt
> * Fix crash on first use when using a DMA-aware bootloader (eg., one
> that supports NAND)
>
> Changes from v2:
> * Faster memcpy
> * Quicker cyclic transfers
> * Address some stylistic and locking comments from Maxime
> * probably some more stuff I'm forgetting
>
> Changes from v1:
> * address comments from Chen-Yu and Maxime
> * fix issue converting bus width
> * switch to using a threaded IRQ instead of a tasklet on
> recommendation from Maxime
> * fix issue setting magic timing parameter for SPI transfers
> * fix an issue with list handling reported by the kbuild 0-DAY robot (thanks!)
> * drop a lot of unused #define
> * probably some more stuff I'm forgetting
>
> .../devicetree/bindings/dma/sun4i-dma.txt | 46 +
> drivers/dma/Kconfig | 11 +
> drivers/dma/Makefile | 1 +
> drivers/dma/sun4i-dma.c | 1264 ++++++++++++++++++++
> 4 files changed, 1322 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dma/sun4i-dma.txt
> create mode 100644 drivers/dma/sun4i-dma.c
>
> diff --git a/Documentation/devicetree/bindings/dma/sun4i-dma.txt b/Documentation/devicetree/bindings/dma/sun4i-dma.txt
> new file mode 100644
> index 0000000..f1634a2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/sun4i-dma.txt
> @@ -0,0 +1,46 @@
> +Allwinner A10 DMA Controller
Don't you want to mention A13, A10S and A20 too?
> +
> +This driver follows the generic DMA bindings defined in dma.txt.
> +
> +Required properties:
> +
> +- compatible: Must be "allwinner,sun4i-a10-dma"
> +- reg: Should contain the registers base address and length
> +- interrupts: Should contain a reference to the interrupt used by this device
> +- clocks: Should contain a reference to the parent AHB clock
> +- #dma-cells : Should be 2, first cell denoting normal or dedicated dma,
> + second cell holding the request line number.
> +
> +Example:
> + dma: dma-controller at 01c02000 {
> + compatible = "allwinner,sun4i-a10-dma";
> + reg = <0x01c02000 0x1000>;
> + interrupts = <27>;
> + clocks = <&ahb_gates 6>;
> + #dma-cells = <2>;
> + };
> +
> +Clients:
> +
> +DMA clients connected to the Allwinner A10 DMA controller must use the
Ditto.
> +format described in the dma.txt file, using a three-cell specifier
> for
> +each channel: a phandle plus two integer cells.
> +The three cells in order are:
> +
> +1. A phandle pointing to the DMA controller.
> +2. Whether it is using normal (0) or dedicated (1) channels
> +3. The port ID as specified in the datasheet
> +
> +Example:
> + spi2: spi at 01c17000 {
> + compatible = "allwinner,sun4i-a10-spi";
> + reg = <0x01c17000 0x1000>;
> + interrupts = <0 12 4>;
> + clocks = <&ahb_gates 22>, <&spi2_clk>;
> + clock-names = "ahb", "mod";
> + dmas = <&dma 1 29>, <&dma 1 28>;
> + dma-names = "rx", "tx";
> + status = "disabled";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index f2b2c4e..a15374e 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -416,6 +416,17 @@ config NBPFAXI_DMA
> help
> Support for "Type-AXI" NBPF DMA IPs from Renesas
>
> +config SUN4I_DMA
> + tristate "Allwinner A10 DMA support"
Ditto.
> + depends on (MACH_SUN4I || MACH_SUN5I || MACH_SUN7I ||
> (COMPILE_TEST && OF && ARM))
> + default (MACH_SUN4I || MACH_SUN5I || MACH_SUN7I)
> + select DMA_ENGINE
> + select DMA_OF
> + select DMA_VIRTUAL_CHANNELS
> + help
> + Enable support for the DMA controller present in the sun4i,
> + sun5i and sun7i Allwinner ARM SoCs.
> +
> config DMA_ENGINE
> bool
>
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index 2022b54..675b98f 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -50,3 +50,4 @@ obj-y += xilinx/
> obj-$(CONFIG_INTEL_MIC_X100_DMA) += mic_x100_dma.o
> obj-$(CONFIG_NBPFAXI_DMA) += nbpfaxi.o
> obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
> +obj-$(CONFIG_SUN4I_DMA) += sun4i-dma.o
> diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c new file mode 100644
> index 0000000..a025405
> --- /dev/null
> +++ b/drivers/dma/sun4i-dma.c
> @@ -0,0 +1,1264 @@
> +/*
> + * Copyright (C) 2014 Emilio López
2014, 2015 ?
> + * Emilio López <emilio at elopez.com.ar>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +
> +#include "virt-dma.h"
> +
[...]
> +static struct sun4i_dma_dev *to_sun4i_dma_dev(struct dma_device
> *dev)
> +{
> + return container_of(dev, struct sun4i_dma_dev, slave);
> +}
> +
> +static struct sun4i_dma_vchan *to_sun4i_dma_vchan(struct dma_chan *chan)
> +{
> + return container_of(chan, struct sun4i_dma_vchan, vc.chan);
> +}
> +
> +static struct sun4i_dma_contract *to_sun4i_dma_contract(struct virt_dma_desc *vd)
> +{
> + return container_of(vd, struct sun4i_dma_contract, vd);
> +}
> +
> +static struct device *chan2dev(struct dma_chan *chan)
> +{
> + return &chan->dev->device;
> +}
> +
> +static int convert_burst(u32 maxburst)
> +{
> + if (maxburst > 8)
> + return -EINVAL;
Would it make sense to add check for maxburst = 0 too?
> +
> + /* 1 -> 0, 4 -> 1, 8 -> 2 */
> + return (maxburst >> 2);
> +}
> +
> +static int convert_buswidth(enum dma_slave_buswidth addr_width)
> +{
> + if (addr_width > DMA_SLAVE_BUSWIDTH_4_BYTES)
> + return -EINVAL;
> +
> + /* 8 (1 byte) -> 0, 16 (2 bytes) -> 1, 32 (4 bytes) -> 2 */
> + return (addr_width >> 1);
> +}
> +
> +static int choose_optimal_buswidth(dma_addr_t addr)
> +{
> + /* On 32 bit aligned addresses, we can use a 32 bit bus width */
> + if (addr % 4 == 0)
Not sure, whether it makes sense to optimize or not, but this can be
calculated like this:
(addr & (4 - 1)) == 0
> + return DMA_SLAVE_BUSWIDTH_4_BYTES;
> + /* On 16 bit aligned addresses, we can use a 16 bit bus width */
> + else if (addr % 2 == 0)
(addr & (2 - 1)) == 0
> + return DMA_SLAVE_BUSWIDTH_2_BYTES;
> +
> + /* Worst-case scenario, we need to do byte aligned reads */
> + return DMA_SLAVE_BUSWIDTH_1_BYTE;
> +}
> +
>
...
>
> +
> +static void configure_pchan(struct sun4i_dma_pchan *pchan,
> + struct sun4i_dma_promise *d)
> +{
> + /*
> + * Configure addresses and misc parameters depending on type
> + * DDMA has an extra field with timing parameters
> + */
> + if (pchan->is_dedicated) {
> + writel_relaxed(d->src, pchan->base + DDMA_SRC_ADDR_REG);
> + writel_relaxed(d->dst, pchan->base + DDMA_DEST_ADDR_REG);
> + writel_relaxed(d->len, pchan->base + DDMA_BYTE_COUNT_REG);
> + writel_relaxed(d->para, pchan->base + DDMA_PARA_REG);
> + writel_relaxed(d->cfg, pchan->base + DDMA_CFG_REG);
> + } else {
> + writel_relaxed(d->src, pchan->base + NDMA_SRC_ADDR_REG);
> + writel_relaxed(d->dst, pchan->base + NDMA_DEST_ADDR_REG);
> + writel_relaxed(d->len, pchan->base + NDMA_BYTE_COUNT_REG);
> + writel_relaxed(d->cfg, pchan->base + NDMA_CFG_REG);
> + }
> +}
> +
> +static void set_pchan_interrupt(struct sun4i_dma_dev *priv,
> + struct sun4i_dma_pchan *pchan,
> + int half, int end)
> +{
> + u32 reg;
> + int pchan_number = pchan - priv->pchans;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + reg = readl_relaxed(priv->base + DMA_IRQ_ENABLE_REG);
> +
> + if (half)
> + reg |= BIT(pchan_number * 2);
> + else
> + reg &= ~BIT(pchan_number * 2);
> +
> + if (end)
> + reg |= BIT(pchan_number * 2 + 1);
> + else
> + reg &= ~BIT(pchan_number * 2 + 1);
> +
> + writel_relaxed(reg, priv->base + DMA_IRQ_ENABLE_REG);
> +
> + spin_unlock_irqrestore(&priv->lock, flags);
> +}
> +
> +/**
> + * Generate a promise, to be used in a dedicated DMA contract.
> + *
> + * A DDMA promise contains all the information required to program the
> + * Dedicated part of the DMA Engine and get data copied. A non-executed
> + * promise will live in the demands list on a contract. Once it has been
> + * completed, it will be moved to the completed demands list for later freeing.
> + * All linked promises will be freed when the corresponding contract is freed
> + */
> +static struct sun4i_dma_promise *
> +generate_ddma_promise(struct dma_chan *chan, dma_addr_t src, dma_addr_t dest,
> + size_t len, struct dma_slave_config *sconfig)
> +{
> + struct sun4i_dma_promise *promise;
> + int ret;
> +
> + promise = kzalloc(sizeof(*promise), GFP_NOWAIT);
> + if (!promise)
> + return NULL;
> +
> + promise->src = src;
> + promise->dst = dest;
> + promise->len = len;
> +
No timing parameters or this is just a quirk required for SPI?
> promise->cfg = DDMA_CFG_LOADING |
> DDMA_CFG_BYTE_COUNT_MODE_REMAIN;
> +
> + /* Source burst */
> + ret = convert_burst(sconfig->src_maxburst);
> + if (IS_ERR_VALUE(ret))
> + goto fail;
> + promise->cfg |= DDMA_CFG_SRC_BURST_LENGTH(ret);
> +
> + /* Destination burst */
> + ret = convert_burst(sconfig->dst_maxburst);
> + if (IS_ERR_VALUE(ret))
> + goto fail;
> + promise->cfg |= DDMA_CFG_DEST_BURST_LENGTH(ret);
> +
> + /* Source bus width */
> + ret = convert_buswidth(sconfig->src_addr_width);
> + if (IS_ERR_VALUE(ret))
> + goto fail;
> + promise->cfg |= DDMA_CFG_SRC_DATA_WIDTH(ret);
> +
> + /* Destination bus width */
> + ret = convert_buswidth(sconfig->dst_addr_width);
> + if (IS_ERR_VALUE(ret))
> + goto fail;
> + promise->cfg |= DDMA_CFG_DEST_DATA_WIDTH(ret);
> +
> + return promise;
> +
> +fail:
> + kfree(promise);
> + return NULL;
> +}
> +
[...]
Päikest,
Priit :)
More information about the linux-arm-kernel
mailing list