[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <7F38996F7185A24AB9071ED4950AD8C101C4D314@swsmsx413.ger.corp.intel.com>
Date: Fri, 4 Jul 2008 16:33:53 +0100
From: "Sosnowski, Maciej" <maciej.sosnowski@...el.com>
To: <haavard.skinnemoen@...el.com>
Cc: "Williams, Dan J" <dan.j.williams@...el.com>,
<drzeus-list@...eus.cx>, "lkml" <linux-kernel@...r.kernel.org>,
<linux-embedded@...r.kernel.org>, <kernel@...32linux.org>,
"Nelson, Shannon" <shannon.nelson@...el.com>, <david-b@...bell.net>
Subject: RE: [PATCH v4 5/6] dmaengine: Driver for the Synopsys DesignWare DMA controller
> ---------- Original message ----------
> From: Haavard Skinnemoen <haavard.skinnemoen@...el.com>
> Date: Jun 26, 2008 3:23 PM
> Subject: [PATCH v4 5/6] dmaengine: Driver for the Synopsys DesignWare
> DMA controller
> To: Dan Williams <dan.j.williams@...el.com>, Pierre Ossman
> <drzeus-list@...eus.cx>
> Cc: linux-kernel@...r.kernel.org, linux-embedded@...r.kernel.org,
> kernel@...32linux.org, shannon.nelson@...el.com, David Brownell
> <david-b@...bell.net>, Haavard Skinnemoen
> <haavard.skinnemoen@...el.com>
>
>
> This adds a driver for the Synopsys DesignWare DMA controller (aka
> DMACA on AVR32 systems.) This DMA controller can be found integrated
> on the AT32AP7000 chip and is primarily meant for peripheral DMA
> transfer, but can also be used for memory-to-memory transfers.
>
> This patch is based on a driver from David Brownell which was based on
> an older version of the DMA Engine framework. It also implements the
> proposed extensions to the DMA Engine API for slave DMA operations.
>
> The dmatest client shows no problems, but there may still be room for
> improvement performance-wise. DMA slave transfer performance is
> definitely "good enough"; reading 100 MiB from an SD card running at
~20
> MHz yields ~7.2 MiB/s average transfer rate.
>
> Full documentation for this controller can be found in the Synopsys
> DW AHB DMAC Databook:
>
>
http://www.synopsys.com/designware/docs/iip/DW_ahb_dmac/latest/doc/dw_ah
b_dmac_db.pdf
>
> The controller has lots of implementation options, so it's usually a
> good idea to check the data sheet of the chip it's intergrated on as
> well. The AT32AP7000 data sheet can be found here:
>
> http://www.atmel.com/dyn/products/datasheets.asp?family_id=682
>
> Signed-off-by: Haavard Skinnemoen <haavard.skinnemoen@...el.com>
>
> Changes since v3:
> * Update to latest DMA engine and DMA slave APIs
> * Embed the hw descriptor into the sw descriptor
> * Clean up and update MODULE_DESCRIPTION, copyright date, etc.
>
> Changes since v2:
> * Dequeue all pending transfers in terminate_all()
> * Rename dw_dmac.h -> dw_dmac_regs.h
> * Define and use controller-specific dma_slave data
> * Fix up a few outdated comments
> * Define hardware registers as structs (doesn't generate better
> code, unfortunately, but it looks nicer.)
> * Get number of channels from platform_data instead of hardcoding it
> based on CONFIG_WHATEVER_CPU.
> * Give slave clients exclusive access to the channel
Coulpe of questions and comments from my side below.
Apart from that the code looks fine to me.
Acked-by: Maciej Sosnowski <maciej.sosnowski@...el.com>
> ---
> arch/avr32/mach-at32ap/at32ap700x.c | 26 +-
> drivers/dma/Kconfig | 9 +
> drivers/dma/Makefile | 1 +
> drivers/dma/dw_dmac.c | 1105
> ++++++++++++++++++++++++++++ drivers/dma/dw_dmac_regs.h
|
> 224 ++++++ include/asm-avr32/arch-at32ap/at32ap700x.h | 16 +
> include/linux/dw_dmac.h | 62 ++
> 7 files changed, 1430 insertions(+), 13 deletions(-)
> create mode 100644 drivers/dma/dw_dmac.c
> create mode 100644 drivers/dma/dw_dmac_regs.h
> create mode 100644 include/linux/dw_dmac.h
>
> diff --git a/arch/avr32/mach-at32ap/at32ap700x.c
> b/arch/avr32/mach-at32ap/at32ap700x.c
> index 0f24b4f..2b92047 100644
> --- a/arch/avr32/mach-at32ap/at32ap700x.c
> +++ b/arch/avr32/mach-at32ap/at32ap700x.c
> @@ -599,6 +599,17 @@ static void __init genclk_init_parent(struct clk
*clk)
> clk->parent = parent;
> }
>
> +static struct dw_dma_platform_data dw_dmac0_data = {
> + .nr_channels = 3,
> +};
> +
> +static struct resource dw_dmac0_resource[] = {
> + PBMEM(0xff200000),
> + IRQ(2),
> +};
> +DEFINE_DEV_DATA(dw_dmac, 0);
> +DEV_CLK(hclk, dw_dmac0, hsb, 10);
> +
> /*
--------------------------------------------------------------------
> * System peripherals
> *
-------------------------------------------------------------------- */
> @@ -705,17 +716,6 @@ static struct clk pico_clk = {
> .users = 1,
> };
>
> -static struct resource dmaca0_resource[] = {
> - {
> - .start = 0xff200000,
> - .end = 0xff20ffff,
> - .flags = IORESOURCE_MEM,
> - },
> - IRQ(2),
> -};
> -DEFINE_DEV(dmaca, 0);
> -DEV_CLK(hclk, dmaca0, hsb, 10);
> -
> /*
--------------------------------------------------------------------
> * HMATRIX
> *
-------------------------------------------------------------------- */
> @@ -828,7 +828,7 @@ void __init at32_add_system_devices(void)
> platform_device_register(&at32_eic0_device);
> platform_device_register(&smc0_device);
> platform_device_register(&pdc_device);
> - platform_device_register(&dmaca0_device);
> + platform_device_register(&dw_dmac0_device);
>
> platform_device_register(&at32_tcb0_device);
> platform_device_register(&at32_tcb1_device);
> @@ -1891,7 +1891,7 @@ struct clk *at32_clock_list[] = {
> &smc0_mck,
> &pdc_hclk,
> &pdc_pclk,
> - &dmaca0_hclk,
> + &dw_dmac0_hclk,
> &pico_clk,
> &pio0_mck,
> &pio1_mck,
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index 2ac09be..4fac4e3 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -37,6 +37,15 @@ config INTEL_IOP_ADMA
> help
> Enable support for the Intel(R) IOP Series RAID engines.
>
> +config DW_DMAC
> + tristate "Synopsys DesignWare AHB DMA support"
> + depends on AVR32
> + select DMA_ENGINE
> + default y if CPU_AT32AP7000
> + help
> + Support the Synopsys DesignWare AHB DMA controller. This
> + can be integrated in chips such as the Atmel AT32ap7000.
> +
> config FSL_DMA
> bool "Freescale MPC85xx/MPC83xx DMA support"
> depends on PPC
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index 2ff6d7f..beebae4 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -1,6 +1,7 @@
> obj-$(CONFIG_DMA_ENGINE) += dmaengine.o
> obj-$(CONFIG_NET_DMA) += iovlock.o
> obj-$(CONFIG_INTEL_IOATDMA) += ioatdma.o
> +obj-$(CONFIG_DW_DMAC) += dw_dmac.o
> ioatdma-objs := ioat.o ioat_dma.o ioat_dca.o
> obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o
> obj-$(CONFIG_FSL_DMA) += fsldma.o
> diff --git a/drivers/dma/dw_dmac.c b/drivers/dma/dw_dmac.c
> new file mode 100644
> index 0000000..e5389e1
> --- /dev/null
> +++ b/drivers/dma/dw_dmac.c
> @@ -0,0 +1,1105 @@
> +/*
> + * Driver for the Synopsys DesignWare DMA Controller (aka DMACA on
> + * AVR32 systems.)
> + *
> + * Copyright (C) 2007-2008 Atmel Corporation
> + *
> + * This program is free software; you can redistribute it and/or
modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include "dw_dmac_regs.h"
> +
> +/*
> + * This supports the Synopsys "DesignWare AHB Central DMA
Controller",
> + * (DW_ahb_dmac) which is used with various AMBA 2.0 systems (not all
> + * of which use ARM any more). See the "Databook" from Synopsys for
> + * information beyond what licensees probably provide.
> + *
> + * The driver has currently been tested only with the Atmel
AT32AP7000,
> + * which does not support descriptor writeback.
> + */
> +
> +/* NOTE: DMS+SMS is system-specific. We should get this information
> + * from the platform code somehow.
> + */
> +#define DWC_DEFAULT_CTLLO (DWC_CTLL_DST_MSIZE(0) \
> + | DWC_CTLL_SRC_MSIZE(0) \
> + | DWC_CTLL_DMS(0) \
> + | DWC_CTLL_SMS(1) \
> + | DWC_CTLL_LLP_D_EN \
> + | DWC_CTLL_LLP_S_EN)
> +
> +/*
> + * This is configuration-dependent and usually a funny size like
4095.
> + * Let's round it down to the nearest power of two.
> + *
> + * Note that this is a transfer count, i.e. if we transfer 32-bit
> + * words, we can do 8192 bytes per descriptor.
> + *
> + * This parameter is also system-specific.
> + */
> +#define DWC_MAX_COUNT 2048U
> +
> +/*
> + * Number of descriptors to allocate for each channel. This should be
> + * made configurable somehow; preferably, the clients (at least the
> + * ones using slave transfers) should be able to give us a hint.
> + */
> +#define NR_DESCS_PER_CHANNEL 64
> +
>
+/*---------------------------------------------------------------------
-*/
> +
> +/*
> + * Because we're not relying on writeback from the controller (it may
not
> + * even be configured into the core!) we don't need to use dma_pool.
These
> + * descriptors -- and associated data -- are cacheable. We do need
to make
> + * sure their dcache entries are written back before handing them off
to
> + * the controller, though.
> + */
> +
> +static struct dw_desc *dwc_first_active(struct dw_dma_chan *dwc)
> +{
> + return list_entry(dwc->active_list.next, struct dw_desc,
desc_node);
> +}
> +
> +static struct dw_desc *dwc_first_queued(struct dw_dma_chan *dwc)
> +{
> + return list_entry(dwc->queue.next, struct dw_desc, desc_node);
> +}
> +
> +static struct dw_desc *dwc_desc_get(struct dw_dma_chan *dwc)
> +{
> + struct dw_desc *desc, *_desc;
> + struct dw_desc *ret = NULL;
> + unsigned int i = 0;
> +
> + spin_lock_bh(&dwc->lock);
> + list_for_each_entry_safe(desc, _desc, &dwc->free_list,
desc_node) {
> + if (async_tx_test_ack(&desc->txd)) {
> + list_del(&desc->desc_node);
> + ret = desc;
> + break;
> + }
> + dev_dbg(&dwc->chan.dev, "desc %p not ACKed\n", desc);
> + i++;
> + }
> + spin_unlock_bh(&dwc->lock);
> +
> + dev_vdbg(&dwc->chan.dev, "scanned %u descriptors on
freelist\n", i);
> +
> + return ret;
> +}
> +
> +static void dwc_sync_desc_for_cpu(struct dw_dma_chan *dwc, struct
> dw_desc *desc)
> +{
> + struct dw_desc *child;
> +
> + list_for_each_entry(child, &desc->txd.tx_list, desc_node)
> + dma_sync_single_for_cpu(dwc->chan.dev.parent,
> + child->txd.phys, sizeof(child->lli),
> + DMA_TO_DEVICE);
> + dma_sync_single_for_cpu(dwc->chan.dev.parent,
> + desc->txd.phys, sizeof(desc->lli),
> + DMA_TO_DEVICE);
> +}
> +
> +/*
> + * Move a descriptor, including any children, to the free list.
> + * `desc' must not be on any lists.
> + */
> +static void dwc_desc_put(struct dw_dma_chan *dwc, struct dw_desc
*desc)
> +{
> + if (desc) {
> + struct dw_desc *child;
> +
> + dwc_sync_desc_for_cpu(dwc, desc);
> +
> + spin_lock_bh(&dwc->lock);
> + list_for_each_entry(child, &desc->txd.tx_list,
desc_node)
> + dev_vdbg(&dwc->chan.dev,
> + "moving child desc %p to
freelist\n",
> + child);
> + list_splice_init(&desc->txd.tx_list, &dwc->free_list);
> + dev_vdbg(&dwc->chan.dev, "moving desc %p to
freelist\n",
> desc); + list_add(&desc->desc_node, &dwc->free_list);
> + spin_unlock_bh(&dwc->lock);
> + }
> +}
> +
> +/* Called with dwc->lock held and bh disabled */
> +static dma_cookie_t
> +dwc_assign_cookie(struct dw_dma_chan *dwc, struct dw_desc *desc)
> +{
> + dma_cookie_t cookie = dwc->chan.cookie;
> +
> + if (++cookie < 0)
> + cookie = 1;
> +
> + dwc->chan.cookie = cookie;
> + desc->txd.cookie = cookie;
> +
> + return cookie;
> +}
> +
>
+/*---------------------------------------------------------------------
-*/
> +
> +/* Called with dwc->lock held and bh disabled */
> +static void dwc_dostart(struct dw_dma_chan *dwc, struct dw_desc
*first)
> +{
> + struct dw_dma *dw = to_dw_dma(dwc->chan.device);
> +
> + /* ASSERT: channel is idle */
> + if (dma_readl(dw, CH_EN) & dwc->mask) {
> + dev_err(&dwc->chan.dev,
> + "BUG: Attempted to start non-idle channel\n");
> + dev_err(&dwc->chan.dev,
> + " SAR: 0x%x DAR: 0x%x LLP: 0x%x CTL:
0x%x:%08x\n",
> + channel_readl(dwc, SAR),
> + channel_readl(dwc, DAR),
> + channel_readl(dwc, LLP),
> + channel_readl(dwc, CTL_HI),
> + channel_readl(dwc, CTL_LO));
> +
> + /* The tasklet will hopefully advance the queue... */
> + return;
Should not at this point an error status be returned
so that it can be handled accordingly by dwc_dostart() caller?
> + }
> +
> + channel_writel(dwc, LLP, first->txd.phys);
> + channel_writel(dwc, CTL_LO,
> + DWC_CTLL_LLP_D_EN | DWC_CTLL_LLP_S_EN);
> + channel_writel(dwc, CTL_HI, 0);
> + channel_set_bit(dw, CH_EN, dwc->mask);
> +}
> +
>
+/*---------------------------------------------------------------------
-*/
> +
> +static void
> +dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc
*desc)
> +{
> + dma_async_tx_callback callback;
> + void *param;
> + struct dma_async_tx_descriptor *txd = &desc->txd;
> +
> + dev_vdbg(&dwc->chan.dev, "descriptor %u complete\n",
txd->cookie);
> +
> + dwc->completed = txd->cookie;
> + callback = txd->callback;
> + param = txd->callback_param;
> +
> + dwc_sync_desc_for_cpu(dwc, desc);
> + list_splice_init(&txd->tx_list, &dwc->free_list);
> + list_move(&desc->desc_node, &dwc->free_list);
> +
> + /*
> + * The API requires that no submissions are done from a
> + * callback, so we don't need to drop the lock here
> + */
> + if (callback)
> + callback(param);
> +}
> +
> +static void dwc_complete_all(struct dw_dma *dw, struct dw_dma_chan
*dwc)
> +{
> + struct dw_desc *desc, *_desc;
> + LIST_HEAD(list);
> +
> + if (dma_readl(dw, CH_EN) & dwc->mask) {
> + dev_err(&dwc->chan.dev,
> + "BUG: XFER bit set, but channel not idle!\n");
> +
> + /* Try to continue after resetting the channel... */
> + channel_clear_bit(dw, CH_EN, dwc->mask);
> + while (dma_readl(dw, CH_EN) & dwc->mask)
> + cpu_relax();
> + }
> +
> + /*
> + * Submit queued descriptors ASAP, i.e. before we go through
> + * the completed ones.
> + */
> + if (!list_empty(&dwc->queue))
> + dwc_dostart(dwc, dwc_first_queued(dwc));
> + list_splice_init(&dwc->active_list, &list);
> + list_splice_init(&dwc->queue, &dwc->active_list);
> +
> + list_for_each_entry_safe(desc, _desc, &list, desc_node)
> + dwc_descriptor_complete(dwc, desc);
> +}
> +
> +static void dwc_scan_descriptors(struct dw_dma *dw, struct
dw_dma_chan *dwc)
> +{
> + dma_addr_t llp;
> + struct dw_desc *desc, *_desc;
> + struct dw_desc *child;
> + u32 status_xfer;
> +
> + /*
> + * Clear block interrupt flag before scanning so that we don't
> + * miss any, and read LLP before RAW_XFER to ensure it is
> + * valid if we decide to scan the list.
> + */
> + dma_writel(dw, CLEAR.BLOCK, dwc->mask);
> + llp = channel_readl(dwc, LLP);
> + status_xfer = dma_readl(dw, RAW.XFER);
> +
> + if (status_xfer & dwc->mask) {
> + /* Everything we've submitted is done */
> + dma_writel(dw, CLEAR.XFER, dwc->mask);
> + dwc_complete_all(dw, dwc);
> + return;
> + }
> +
> + dev_vdbg(&dwc->chan.dev, "scan_descriptors: llp=0x%x\n", llp);
> +
> + list_for_each_entry_safe(desc, _desc, &dwc->active_list,
desc_node) {
> + if (desc->lli.llp == llp)
> + /* This one is currently in progress */
> + return;
> +
> + list_for_each_entry(child, &desc->txd.tx_list,
desc_node)
> + if (child->lli.llp == llp)
> + /* Currently in progress */
> + return;
> +
> + /*
> + * No descriptors so far seem to be in progress, i.e.
> + * this one must be done.
> + */
> + dwc_descriptor_complete(dwc, desc);
> + }
> +
> + dev_err(&dwc->chan.dev,
> + "BUG: All descriptors done, but channel not idle!\n");
> +
> + /* Try to continue after resetting the channel... */
> + channel_clear_bit(dw, CH_EN, dwc->mask);
> + while (dma_readl(dw, CH_EN) & dwc->mask)
> + cpu_relax();
> +
> + if (!list_empty(&dwc->queue)) {
> + dwc_dostart(dwc, dwc_first_queued(dwc));
> + list_splice_init(&dwc->queue, &dwc->active_list);
> + }
> +}
> +
> +static void dwc_dump_lli(struct dw_dma_chan *dwc, struct dw_lli *lli)
> +{
> + dev_printk(KERN_CRIT, &dwc->chan.dev,
> + " desc: s0x%x d0x%x l0x%x c0x%x:%x\n",
> + lli->sar, lli->dar, lli->llp,
> + lli->ctlhi, lli->ctllo);
> +}
> +
> +static void dwc_handle_error(struct dw_dma *dw, struct dw_dma_chan
*dwc)
> +{
> + struct dw_desc *bad_desc;
> + struct dw_desc *child;
> +
> + dwc_scan_descriptors(dw, dwc);
> +
> + /*
> + * The descriptor currently at the head of the active list is
> + * borked. Since we don't have any way to report errors, we'll
> + * just have to scream loudly and try to carry on.
> + */
> + bad_desc = dwc_first_active(dwc);
> + list_del_init(&bad_desc->desc_node);
> + list_splice_init(&dwc->queue, dwc->active_list.prev);
> +
> + /* Clear the error flag and try to restart the controller */
> + dma_writel(dw, CLEAR.ERROR, dwc->mask);
> + if (!list_empty(&dwc->active_list))
> + dwc_dostart(dwc, dwc_first_active(dwc));
> +
> + /*
> + * KERN_CRITICAL may seem harsh, but since this only happens
> + * when someone submits a bad physical address in a
> + * descriptor, we should consider ourselves lucky that the
> + * controller flagged an error instead of scribbling over
> + * random memory locations.
> + */
> + dev_printk(KERN_CRIT, &dwc->chan.dev,
> + "Bad descriptor submitted for DMA!\n");
> + dev_printk(KERN_CRIT, &dwc->chan.dev,
> + " cookie: %d\n", bad_desc->txd.cookie);
> + dwc_dump_lli(dwc, &bad_desc->lli);
> + list_for_each_entry(child, &bad_desc->txd.tx_list, desc_node)
> + dwc_dump_lli(dwc, &child->lli);
> +
> + /* Pretend the descriptor completed successfully */
> + dwc_descriptor_complete(dwc, bad_desc);
> +}
> +
> +static void dw_dma_tasklet(unsigned long data)
> +{
> + struct dw_dma *dw = (struct dw_dma *)data;
> + struct dw_dma_chan *dwc;
> + u32 status_block;
> + u32 status_xfer;
> + u32 status_err;
> + int i;
> +
> + status_block = dma_readl(dw, RAW.BLOCK);
> + status_xfer = dma_readl(dw, RAW.BLOCK);
> + status_err = dma_readl(dw, RAW.ERROR);
> +
> + dev_vdbg(dw->dma.dev, "tasklet: status_block=%x
status_err=%x\n",
> + status_block, status_err);
> +
> + for (i = 0; i < dw->dma.chancnt; i++) {
> + dwc = &dw->chan[i];
> + spin_lock(&dwc->lock);
> + if (status_err & (1 << i))
> + dwc_handle_error(dw, dwc);
> + else if ((status_block | status_xfer) & (1 << i))
> + dwc_scan_descriptors(dw, dwc);
> + spin_unlock(&dwc->lock);
> + }
> +
> + /*
> + * Re-enable interrupts. Block Complete interrupts are only
> + * enabled if the INT_EN bit in the descriptor is set. This
> + * will trigger a scan before the whole list is done.
> + */
> + channel_set_bit(dw, MASK.XFER, dw->all_chan_mask);
> + channel_set_bit(dw, MASK.BLOCK, dw->all_chan_mask);
> + channel_set_bit(dw, MASK.ERROR, dw->all_chan_mask);
> +}
> +
> +static irqreturn_t dw_dma_interrupt(int irq, void *dev_id)
> +{
> + struct dw_dma *dw = dev_id;
> + u32 status;
> +
> + dev_vdbg(dw->dma.dev, "interrupt: status=0x%x\n",
> + dma_readl(dw, STATUS_INT));
> +
> + /*
> + * Just disable the interrupts. We'll turn them back on in the
> + * softirq handler.
> + */
> + channel_clear_bit(dw, MASK.XFER, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.BLOCK, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.ERROR, dw->all_chan_mask);
> +
> + status = dma_readl(dw, STATUS_INT);
> + if (status) {
> + dev_err(dw->dma.dev,
> + "BUG: Unexpected interrupts pending: 0x%x\n",
> + status);
> +
> + /* Try to recover */
> + channel_clear_bit(dw, MASK.XFER, (1 << 8) - 1);
> + channel_clear_bit(dw, MASK.BLOCK, (1 << 8) - 1);
> + channel_clear_bit(dw, MASK.SRC_TRAN, (1 << 8) - 1);
> + channel_clear_bit(dw, MASK.DST_TRAN, (1 << 8) - 1);
> + channel_clear_bit(dw, MASK.ERROR, (1 << 8) - 1);
> + }
> +
> + tasklet_schedule(&dw->tasklet);
> +
> + return IRQ_HANDLED;
> +}
> +
>
+/*---------------------------------------------------------------------
-*/
> +
> +static dma_cookie_t dwc_tx_submit(struct dma_async_tx_descriptor *tx)
> +{
> + struct dw_desc *desc = txd_to_dw_desc(tx);
> + struct dw_dma_chan *dwc = to_dw_dma_chan(tx->chan);
> + dma_cookie_t cookie;
> +
> + spin_lock_bh(&dwc->lock);
> + cookie = dwc_assign_cookie(dwc, desc);
> +
> + /*
> + * REVISIT: We should attempt to chain as many descriptors as
> + * possible, perhaps even appending to those already submitted
> + * for DMA. But this is hard to do in a race-free manner.
> + */
> + if (list_empty(&dwc->active_list)) {
> + dev_vdbg(&tx->chan->dev, "tx_submit: started %u\n",
> + desc->txd.cookie);
> + dwc_dostart(dwc, desc);
> + list_add_tail(&desc->desc_node, &dwc->active_list);
> + } else {
> + dev_vdbg(&tx->chan->dev, "tx_submit: queued %u\n",
> + desc->txd.cookie);
> +
> + list_add_tail(&desc->desc_node, &dwc->queue);
> + }
> +
> + spin_unlock_bh(&dwc->lock);
> +
> + return cookie;
> +}
> +
> +static struct dma_async_tx_descriptor *
> +dwc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest,
dma_addr_t src,
> + size_t len, unsigned long flags)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + struct dw_desc *desc;
> + struct dw_desc *first;
> + struct dw_desc *prev;
> + size_t xfer_count;
> + size_t offset;
> + unsigned int src_width;
> + unsigned int dst_width;
> + u32 ctllo;
> +
> + dev_vdbg(&chan->dev, "prep_dma_memcpy d0x%x s0x%x l0x%zx
f0x%lx\n",
> + dest, src, len, flags);
> +
> + if (unlikely(!len)) {
> + dev_dbg(&chan->dev, "prep_dma_memcpy: length is
zero!\n");
> + return NULL;
> + }
> +
> + /*
> + * We can be a lot more clever here, but this should take care
> + * of the most common optimization.
> + */
> + if (!((src | dest | len) & 3))
> + src_width = dst_width = 2;
> + else if (!((src | dest | len) & 1))
> + src_width = dst_width = 1;
> + else
> + src_width = dst_width = 0;
> +
> + ctllo = DWC_DEFAULT_CTLLO
> + | DWC_CTLL_DST_WIDTH(dst_width)
> + | DWC_CTLL_SRC_WIDTH(src_width)
> + | DWC_CTLL_DST_INC
> + | DWC_CTLL_SRC_INC
> + | DWC_CTLL_FC_M2M;
> + prev = first = NULL;
> +
> + for (offset = 0; offset < len; offset += xfer_count <<
src_width) {
> + xfer_count = min_t(size_t, (len - offset) >>
src_width,
> + DWC_MAX_COUNT);
Here it looks like the maximum xfer_count value can change - it depends
on src_width,
so it may be different for different transactions.
Is that ok?
> +
> + desc = dwc_desc_get(dwc);
> + if (!desc)
> + goto err_desc_get;
> +
> + desc->lli.sar = src + offset;
> + desc->lli.dar = dest + offset;
> + desc->lli.ctllo = ctllo;
> + desc->lli.ctlhi = xfer_count;
> +
> + if (!first) {
> + first = desc;
> + } else {
> + prev->lli.llp = desc->txd.phys;
> + dma_sync_single_for_device(chan->dev.parent,
> + prev->txd.phys,
sizeof(prev->lli),
> + DMA_TO_DEVICE);
> + list_add_tail(&desc->desc_node,
> + &first->txd.tx_list);
> + }
> + prev = desc;
> + }
> +
> +
> + if (flags & DMA_PREP_INTERRUPT)
> + /* Trigger interrupt after last block */
> + prev->lli.ctllo |= DWC_CTLL_INT_EN;
> +
> + prev->lli.llp = 0;
> + dma_sync_single_for_device(chan->dev.parent,
> + prev->txd.phys, sizeof(prev->lli),
> + DMA_TO_DEVICE);
> +
> + first->txd.flags = flags;
> +
> + return &first->txd;
> +
> +err_desc_get:
> + dwc_desc_put(dwc, first);
> + return NULL;
> +}
> +
> +static struct dma_async_tx_descriptor *
> +dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> + unsigned int sg_len, enum dma_data_direction
direction,
> + unsigned long flags)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + struct dw_dma_slave *dws = dwc->dws;
> + struct dw_desc *prev;
> + struct dw_desc *first;
> + u32 ctllo;
> + dma_addr_t reg;
> + unsigned int reg_width;
> + unsigned int mem_width;
> + unsigned int i;
> + struct scatterlist *sg;
> +
> + dev_vdbg(&chan->dev, "prep_dma_slave\n");
> +
> + if (unlikely(!dws || !sg_len))
> + return NULL;
> +
> + reg_width = dws->slave.reg_width;
> + prev = first = NULL;
> +
> + sg_len = dma_map_sg(chan->dev.parent, sgl, sg_len, direction);
> +
> + switch (direction) {
> + case DMA_TO_DEVICE:
> + ctllo = (DWC_DEFAULT_CTLLO
> + | DWC_CTLL_DST_WIDTH(reg_width)
> + | DWC_CTLL_DST_FIX
> + | DWC_CTLL_SRC_INC
> + | DWC_CTLL_FC_M2P);
> + reg = dws->slave.tx_reg;
> + for_each_sg(sgl, sg, sg_len, i) {
> + struct dw_desc *desc;
> + u32 len;
> + u32 mem;
> +
> + desc = dwc_desc_get(dwc);
> + if (!desc) {
> + dev_err(&chan->dev,
> + "not enough descriptors
available\n");
> + goto err_desc_get;
> + }
> +
> + mem = sg_phys(sg);
> + len = sg_dma_len(sg);
> + mem_width = 2;
> + if (unlikely(mem & 3 || len & 3))
> + mem_width = 0;
> +
> + desc->lli.sar = mem;
> + desc->lli.dar = reg;
> + desc->lli.ctllo = ctllo |
> DWC_CTLL_SRC_WIDTH(mem_width); + desc->lli.ctlhi
= len
> >> mem_width; +
> + if (!first) {
> + first = desc;
> + } else {
> + prev->lli.llp = desc->txd.phys;
> +
dma_sync_single_for_device(chan->dev.parent,
> + prev->txd.phys,
> + sizeof(prev->lli),
> + DMA_TO_DEVICE);
> + list_add_tail(&desc->desc_node,
> + &first->txd.tx_list);
> + }
> + prev = desc;
> + }
> + break;
> + case DMA_FROM_DEVICE:
> + ctllo = (DWC_DEFAULT_CTLLO
> + | DWC_CTLL_SRC_WIDTH(reg_width)
> + | DWC_CTLL_DST_INC
> + | DWC_CTLL_SRC_FIX
> + | DWC_CTLL_FC_P2M);
> +
> + reg = dws->slave.rx_reg;
> + for_each_sg(sgl, sg, sg_len, i) {
> + struct dw_desc *desc;
> + u32 len;
> + u32 mem;
> +
> + desc = dwc_desc_get(dwc);
> + if (!desc) {
> + dev_err(&chan->dev,
> + "not enough descriptors
available\n");
> + goto err_desc_get;
> + }
> +
> + mem = sg_phys(sg);
> + len = sg_dma_len(sg);
> + mem_width = 2;
> + if (unlikely(mem & 3 || len & 3))
> + mem_width = 0;
> +
> + desc->lli.sar = reg;
> + desc->lli.dar = mem;
> + desc->lli.ctllo = ctllo |
> DWC_CTLL_DST_WIDTH(mem_width); + desc->lli.ctlhi
= len
> >> reg_width; +
> + if (!first) {
> + first = desc;
> + } else {
> + prev->lli.llp = desc->txd.phys;
> +
dma_sync_single_for_device(chan->dev.parent,
> + prev->txd.phys,
> + sizeof(prev->lli),
> + DMA_TO_DEVICE);
> + list_add_tail(&desc->desc_node,
> + &first->txd.tx_list);
> + }
> + prev = desc;
> + }
> + break;
> + default:
> + return NULL;
> + }
> +
> + if (flags & DMA_PREP_INTERRUPT)
> + /* Trigger interrupt after last block */
> + prev->lli.ctllo |= DWC_CTLL_INT_EN;
> +
> + prev->lli.llp = 0;
> + dma_sync_single_for_device(chan->dev.parent,
> + prev->txd.phys, sizeof(prev->lli),
> + DMA_TO_DEVICE);
> +
> + return &first->txd;
> +
> +err_desc_get:
> + dwc_desc_put(dwc, first);
> + return NULL;
> +}
> +
> +static void dwc_terminate_all(struct dma_chan *chan)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + struct dw_dma *dw = to_dw_dma(chan->device);
> + struct dw_desc *desc, *_desc;
> + LIST_HEAD(list);
> +
> + /*
> + * This is only called when something went wrong elsewhere, so
> + * we don't really care about the data. Just disable the
> + * channel. We still have to poll the channel enable bit due
> + * to AHB/HSB limitations.
> + */
> + spin_lock_bh(&dwc->lock);
> +
> + channel_clear_bit(dw, CH_EN, dwc->mask);
> +
> + while (dma_readl(dw, CH_EN) & dwc->mask)
> + cpu_relax();
> +
> + /* active_list entries will end up before queued entries */
> + list_splice_init(&dwc->queue, &list);
> + list_splice_init(&dwc->active_list, &list);
> +
> + spin_unlock_bh(&dwc->lock);
> +
> + /* Flush all pending and queued descriptors */
> + list_for_each_entry_safe(desc, _desc, &list, desc_node)
> + dwc_descriptor_complete(dwc, desc);
> +}
> +
> +static enum dma_status
> +dwc_is_tx_complete(struct dma_chan *chan,
> + dma_cookie_t cookie,
> + dma_cookie_t *done, dma_cookie_t *used)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + dma_cookie_t last_used;
> + dma_cookie_t last_complete;
> + int ret;
> +
> + last_complete = dwc->completed;
> + last_used = chan->cookie;
> +
> + ret = dma_async_is_complete(cookie, last_complete, last_used);
> + if (ret != DMA_SUCCESS) {
> + dwc_scan_descriptors(to_dw_dma(chan->device), dwc);
> +
> + last_complete = dwc->completed;
> + last_used = chan->cookie;
> +
> + ret = dma_async_is_complete(cookie, last_complete,
last_used);
> + }
> +
> + if (done)
> + *done = last_complete;
> + if (used)
> + *used = last_used;
> +
> + return ret;
> +}
> +
> +static void dwc_issue_pending(struct dma_chan *chan)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> +
> + spin_lock_bh(&dwc->lock);
> + if (!list_empty(&dwc->queue))
> + dwc_scan_descriptors(to_dw_dma(chan->device), dwc);
> + spin_unlock_bh(&dwc->lock);
> +}
> +
> +static int dwc_alloc_chan_resources(struct dma_chan *chan,
> + struct dma_client *client)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + struct dw_dma *dw = to_dw_dma(chan->device);
> + struct dw_desc *desc;
> + struct dma_slave *slave;
> + struct dw_dma_slave *dws;
> + int i;
> + u32 cfghi;
> + u32 cfglo;
> +
> + dev_vdbg(&chan->dev, "alloc_chan_resources\n");
> +
> + /* Channels doing slave DMA can only handle one client. */
> + if (dwc->dws || client->slave) {
> + if (dma_chan_is_in_use(chan))
> + return -EBUSY;
> + }
> +
> + /* ASSERT: channel is idle */
> + if (dma_readl(dw, CH_EN) & dwc->mask) {
> + dev_dbg(&chan->dev, "DMA channel not idle?\n");
> + return -EIO;
> + }
> +
> + dwc->completed = chan->cookie = 1;
> +
> + cfghi = DWC_CFGH_FIFO_MODE;
> + cfglo = 0;
> +
> + slave = client->slave;
> + if (slave) {
> + /*
> + * We need controller-specific data to set up slave
> + * transfers.
> + */
> + BUG_ON(!slave->dma_dev || slave->dma_dev !=
dw->dma.dev);
> +
> + dws = container_of(slave, struct dw_dma_slave, slave);
> +
> + dwc->dws = dws;
> + cfghi = dws->cfg_hi;
> + cfglo = dws->cfg_lo;
> + } else {
> + dwc->dws = NULL;
> + }
> +
> + channel_writel(dwc, CFG_LO, cfglo);
> + channel_writel(dwc, CFG_HI, cfghi);
> +
> + /*
> + * NOTE: some controllers may have additional features that we
> + * need to initialize here, like "scatter-gather" (which
> + * doesn't mean what you think it means), and status
writeback.
> + */
> +
> + spin_lock_bh(&dwc->lock);
> + i = dwc->descs_allocated;
> + while (dwc->descs_allocated < NR_DESCS_PER_CHANNEL) {
> + spin_unlock_bh(&dwc->lock);
> +
> + desc = kzalloc(sizeof(struct dw_desc), GFP_KERNEL);
> + if (!desc) {
> + dev_info(&chan->dev,
> + "only allocated %d descriptors\n", i);
> + spin_lock_bh(&dwc->lock);
> + break;
> + }
> +
> + dma_async_tx_descriptor_init(&desc->txd, chan);
> + desc->txd.tx_submit = dwc_tx_submit;
> + desc->txd.flags = DMA_CTRL_ACK;
> + INIT_LIST_HEAD(&desc->txd.tx_list);
> + desc->txd.phys = dma_map_single(chan->dev.parent,
&desc->lli,
> + sizeof(desc->lli), DMA_TO_DEVICE);
> + dwc_desc_put(dwc, desc);
> +
> + spin_lock_bh(&dwc->lock);
> + i = ++dwc->descs_allocated;
> + }
> +
> + /* Enable interrupts */
> + channel_set_bit(dw, MASK.XFER, dwc->mask);
> + channel_set_bit(dw, MASK.BLOCK, dwc->mask);
> + channel_set_bit(dw, MASK.ERROR, dwc->mask);
> +
> + spin_unlock_bh(&dwc->lock);
> +
> + dev_dbg(&chan->dev,
> + "alloc_chan_resources allocated %d descriptors\n", i);
> +
> + return i;
> +}
> +
> +static void dwc_free_chan_resources(struct dma_chan *chan)
> +{
> + struct dw_dma_chan *dwc = to_dw_dma_chan(chan);
> + struct dw_dma *dw = to_dw_dma(chan->device);
> + struct dw_desc *desc, *_desc;
> + LIST_HEAD(list);
> +
> + dev_dbg(&chan->dev, "free_chan_resources (descs
allocated=%u)\n",
> + dwc->descs_allocated);
> +
> + /* ASSERT: channel is idle */
> + BUG_ON(!list_empty(&dwc->active_list));
> + BUG_ON(!list_empty(&dwc->queue));
> + BUG_ON(dma_readl(to_dw_dma(chan->device), CH_EN) & dwc->mask);
> +
> + spin_lock_bh(&dwc->lock);
> + list_splice_init(&dwc->free_list, &list);
> + dwc->descs_allocated = 0;
> + dwc->dws = NULL;
> +
> + /* Disable interrupts */
> + channel_clear_bit(dw, MASK.XFER, dwc->mask);
> + channel_clear_bit(dw, MASK.BLOCK, dwc->mask);
> + channel_clear_bit(dw, MASK.ERROR, dwc->mask);
> +
> + spin_unlock_bh(&dwc->lock);
> +
> + list_for_each_entry_safe(desc, _desc, &list, desc_node) {
> + dev_vdbg(&chan->dev, " freeing descriptor %p\n",
desc);
> + dma_unmap_single(chan->dev.parent, desc->txd.phys,
> + sizeof(desc->lli), DMA_TO_DEVICE);
> + kfree(desc);
> + }
> +
> + dev_vdbg(&chan->dev, "free_chan_resources done\n");
> +}
> +
>
+/*---------------------------------------------------------------------
-*/
> +
> +static void dw_dma_off(struct dw_dma *dw)
> +{
> + dma_writel(dw, CFG, 0);
> +
> + channel_clear_bit(dw, MASK.XFER, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.BLOCK, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.SRC_TRAN, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.DST_TRAN, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.ERROR, dw->all_chan_mask);
> +
> + while (dma_readl(dw, CFG) & DW_CFG_DMA_EN)
> + cpu_relax();
> +}
> +
> +static int __init dw_probe(struct platform_device *pdev)
> +{
> + struct dw_dma_platform_data *pdata;
> + struct resource *io;
> + struct dw_dma *dw;
> + size_t size;
> + int irq;
> + int err;
> + int i;
> +
> + pdata = pdev->dev.platform_data;
> + if (!pdata || pdata->nr_channels > DW_DMA_MAX_NR_CHANNELS)
> + return -EINVAL;
> +
> + io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!io)
> + return -EINVAL;
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + size = sizeof(struct dw_dma);
> + size += pdata->nr_channels * sizeof(struct dw_dma_chan);
> + dw = kzalloc(size, GFP_KERNEL);
> + if (!dw)
> + return -ENOMEM;
> +
> + if (!request_mem_region(io->start, DW_REGLEN,
> pdev->dev.driver->name)) { + err = -EBUSY;
> + goto err_kfree;
> + }
> +
> + memset(dw, 0, sizeof *dw);
> +
> + dw->regs = ioremap(io->start, DW_REGLEN);
> + if (!dw->regs) {
> + err = -ENOMEM;
> + goto err_release_r;
> + }
> +
> + dw->clk = clk_get(&pdev->dev, "hclk");
> + if (IS_ERR(dw->clk)) {
> + err = PTR_ERR(dw->clk);
> + goto err_clk;
> + }
> + clk_enable(dw->clk);
> +
> + /* force dma off, just in case */
> + dw_dma_off(dw);
> +
> + err = request_irq(irq, dw_dma_interrupt, 0, "dw_dmac", dw);
> + if (err)
> + goto err_irq;
> +
> + platform_set_drvdata(pdev, dw);
> +
> + tasklet_init(&dw->tasklet, dw_dma_tasklet, (unsigned long)dw);
> +
> + dw->all_chan_mask = (1 << pdata->nr_channels) - 1;
> +
> + INIT_LIST_HEAD(&dw->dma.channels);
> + for (i = 0; i < pdata->nr_channels; i++, dw->dma.chancnt++) {
> + struct dw_dma_chan *dwc = &dw->chan[i];
> +
> + dwc->chan.device = &dw->dma;
> + dwc->chan.cookie = dwc->completed = 1;
> + dwc->chan.chan_id = i;
> + list_add_tail(&dwc->chan.device_node,
&dw->dma.channels);
> +
> + dwc->ch_regs = &__dw_regs(dw)->CHAN[i];
> + spin_lock_init(&dwc->lock);
> + dwc->mask = 1 << i;
> +
> + INIT_LIST_HEAD(&dwc->active_list);
> + INIT_LIST_HEAD(&dwc->queue);
> + INIT_LIST_HEAD(&dwc->free_list);
> +
> + channel_clear_bit(dw, CH_EN, dwc->mask);
> + }
> +
> + /* Clear/disable all interrupts on all channels. */
> + dma_writel(dw, CLEAR.XFER, dw->all_chan_mask);
> + dma_writel(dw, CLEAR.BLOCK, dw->all_chan_mask);
> + dma_writel(dw, CLEAR.SRC_TRAN, dw->all_chan_mask);
> + dma_writel(dw, CLEAR.DST_TRAN, dw->all_chan_mask);
> + dma_writel(dw, CLEAR.ERROR, dw->all_chan_mask);
> +
> + channel_clear_bit(dw, MASK.XFER, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.BLOCK, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.SRC_TRAN, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.DST_TRAN, dw->all_chan_mask);
> + channel_clear_bit(dw, MASK.ERROR, dw->all_chan_mask);
> +
> + dma_cap_set(DMA_MEMCPY, dw->dma.cap_mask);
> + dma_cap_set(DMA_SLAVE, dw->dma.cap_mask);
> + dw->dma.dev = &pdev->dev;
> + dw->dma.device_alloc_chan_resources =
dwc_alloc_chan_resources;
> + dw->dma.device_free_chan_resources = dwc_free_chan_resources;
> +
> + dw->dma.device_prep_dma_memcpy = dwc_prep_dma_memcpy;
> +
> + dw->dma.device_prep_slave_sg = dwc_prep_slave_sg;
> + dw->dma.device_terminate_all = dwc_terminate_all;
> +
> + dw->dma.device_is_tx_complete = dwc_is_tx_complete;
> + dw->dma.device_issue_pending = dwc_issue_pending;
> +
> + dma_writel(dw, CFG, DW_CFG_DMA_EN);
> +
> + printk(KERN_INFO "%s: DesignWare DMA Controller, %d
channels\n",
> + pdev->dev.bus_id, dw->dma.chancnt);
> +
> + dma_async_device_register(&dw->dma);
> +
> + return 0;
> +
> +err_irq:
> + clk_disable(dw->clk);
> + clk_put(dw->clk);
> +err_clk:
> + iounmap(dw->regs);
> + dw->regs = NULL;
> +err_release_r:
> + release_resource(io);
> +err_kfree:
> + kfree(dw);
> + return err;
> +}
This driver does not perform any self-test during initialization.
What about adding some initial HW checking?
> +
> +static int __exit dw_remove(struct platform_device *pdev)
> +{
> + struct dw_dma *dw = platform_get_drvdata(pdev);
> + struct dw_dma_chan *dwc, *_dwc;
> + struct resource *io;
> +
> + dw_dma_off(dw);
> + dma_async_device_unregister(&dw->dma);
> +
> + free_irq(platform_get_irq(pdev, 0), dw);
> + tasklet_kill(&dw->tasklet);
> +
> + list_for_each_entry_safe(dwc, _dwc, &dw->dma.channels,
> + chan.device_node) {
> + list_del(&dwc->chan.device_node);
> + channel_clear_bit(dw, CH_EN, dwc->mask);
> + }
> +
> + clk_disable(dw->clk);
> + clk_put(dw->clk);
> +
> + iounmap(dw->regs);
> + dw->regs = NULL;
> +
> + io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + release_mem_region(io->start, DW_REGLEN);
> +
> + kfree(dw);
> +
> + return 0;
> +}
> +
> +static void dw_shutdown(struct platform_device *pdev)
> +{
> + struct dw_dma *dw = platform_get_drvdata(pdev);
> +
> + dw_dma_off(platform_get_drvdata(pdev));
> + clk_disable(dw->clk);
> +}
> +
> +static int dw_suspend_late(struct platform_device *pdev, pm_message_t
mesg)
> +{
> + struct dw_dma *dw = platform_get_drvdata(pdev);
> +
> + dw_dma_off(platform_get_drvdata(pdev));
> + clk_disable(dw->clk);
> + return 0;
> +}
> +
> +static int dw_resume_early(struct platform_device *pdev)
> +{
> + struct dw_dma *dw = platform_get_drvdata(pdev);
> +
> + clk_enable(dw->clk);
> + dma_writel(dw, CFG, DW_CFG_DMA_EN);
> + return 0;
> +
> +}
> +
> +static struct platform_driver dw_driver = {
> + .remove = __exit_p(dw_remove),
> + .shutdown = dw_shutdown,
> + .suspend_late = dw_suspend_late,
> + .resume_early = dw_resume_early,
> + .driver = {
> + .name = "dw_dmac",
> + },
> +};
> +
> +static int __init dw_init(void)
> +{
> + return platform_driver_probe(&dw_driver, dw_probe);
> +}
> +module_init(dw_init);
> +
> +static void __exit dw_exit(void)
> +{
> + platform_driver_unregister(&dw_driver);
> +}
> +module_exit(dw_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Synopsys DesignWare DMA Controller driver");
> +MODULE_AUTHOR("Haavard Skinnemoen <haavard.skinnemoen@...el.com>");
> diff --git a/drivers/dma/dw_dmac_regs.h b/drivers/dma/dw_dmac_regs.h
> new file mode 100644
> index 0000000..119e65b
> --- /dev/null
> +++ b/drivers/dma/dw_dmac_regs.h
> @@ -0,0 +1,224 @@
> +/*
> + * Driver for the Synopsys DesignWare AHB DMA Controller
> + *
> + * Copyright (C) 2005-2007 Atmel Corporation
> + *
> + * This program is free software; you can redistribute it and/or
modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/dw_dmac.h>
> +
> +#define DW_DMA_MAX_NR_CHANNELS 8
> +
> +/*
> + * Redefine this macro to handle differences between 32- and 64-bit
> + * addressing, big vs. little endian, etc.
> + */
> +#define DW_REG(name) u32 name; u32 __pad_##name
> +
> +/* Hardware register definitions. */
> +struct dw_dma_chan_regs {
> + DW_REG(SAR); /* Source Address Register */
> + DW_REG(DAR); /* Destination Address Register */
> + DW_REG(LLP); /* Linked List Pointer */
> + u32 CTL_LO; /* Control Register Low */
> + u32 CTL_HI; /* Control Register High */
> + DW_REG(SSTAT);
> + DW_REG(DSTAT);
> + DW_REG(SSTATAR);
> + DW_REG(DSTATAR);
> + u32 CFG_LO; /* Configuration Register Low */
> + u32 CFG_HI; /* Configuration Register High */
> + DW_REG(SGR);
> + DW_REG(DSR);
> +};
> +
> +struct dw_dma_irq_regs {
> + DW_REG(XFER);
> + DW_REG(BLOCK);
> + DW_REG(SRC_TRAN);
> + DW_REG(DST_TRAN);
> + DW_REG(ERROR);
> +};
> +
> +struct dw_dma_regs {
> + /* per-channel registers */
> + struct dw_dma_chan_regs CHAN[DW_DMA_MAX_NR_CHANNELS];
> +
> + /* irq handling */
> + struct dw_dma_irq_regs RAW; /* r */
> + struct dw_dma_irq_regs STATUS; /* r (raw & mask) */
> + struct dw_dma_irq_regs MASK; /* rw (set = irq
enabled) */
> + struct dw_dma_irq_regs CLEAR; /* w (ack, affects
"raw") */
> +
> + DW_REG(STATUS_INT); /* r */
> +
> + /* software handshaking */
> + DW_REG(REQ_SRC);
> + DW_REG(REQ_DST);
> + DW_REG(SGL_REQ_SRC);
> + DW_REG(SGL_REQ_DST);
> + DW_REG(LAST_SRC);
> + DW_REG(LAST_DST);
> +
> + /* miscellaneous */
> + DW_REG(CFG);
> + DW_REG(CH_EN);
> + DW_REG(ID);
> + DW_REG(TEST);
> +
> + /* optional encoded params, 0x3c8..0x3 */
> +};
> +
> +/* Bitfields in CTL_LO */
> +#define DWC_CTLL_INT_EN (1 << 0) /* irqs
enabled? */
> +#define DWC_CTLL_DST_WIDTH(n) ((n)<<1) /* bytes per element
*/
> +#define DWC_CTLL_SRC_WIDTH(n) ((n)<<4)
> +#define DWC_CTLL_DST_INC (0<<7) /* DAR update/not */
> +#define DWC_CTLL_DST_DEC (1<<7)
> +#define DWC_CTLL_DST_FIX (2<<7)
> +#define DWC_CTLL_SRC_INC (0<<7) /* SAR update/not */
> +#define DWC_CTLL_SRC_DEC (1<<9)
> +#define DWC_CTLL_SRC_FIX (2<<9)
> +#define DWC_CTLL_DST_MSIZE(n) ((n)<<11) /* burst, #elements */
> +#define DWC_CTLL_SRC_MSIZE(n) ((n)<<14)
> +#define DWC_CTLL_S_GATH_EN (1 << 17) /* src gather, !FIX */
> +#define DWC_CTLL_D_SCAT_EN (1 << 18) /* dst scatter, !FIX
*/
> +#define DWC_CTLL_FC_M2M (0 << 20) /* mem-to-mem
*/
> +#define DWC_CTLL_FC_M2P (1 << 20) /*
mem-to-periph */
> +#define DWC_CTLL_FC_P2M (2 << 20) /*
periph-to-mem */
> +#define DWC_CTLL_FC_P2P (3 << 20) /*
periph-to-periph */
> +/* plus 4 transfer types for peripheral-as-flow-controller */
> +#define DWC_CTLL_DMS(n) ((n)<<23) /* dst master
select
> */ +#define DWC_CTLL_SMS(n) ((n)<<25) /* src
master
> select */ +#define DWC_CTLL_LLP_D_EN (1 << 27) /* dest
block chain
> */ +#define DWC_CTLL_LLP_S_EN (1 << 28) /* src block chain
*/
> +
> +/* Bitfields in CTL_HI */
> +#define DWC_CTLH_DONE 0x00001000
> +#define DWC_CTLH_BLOCK_TS_MASK 0x00000fff
> +
> +/* Bitfields in CFG_LO. Platform-configurable bits are in
<linux/dw_dmac.h>
> */ +#define DWC_CFGL_CH_SUSP (1 << 8) /* pause xfer */
> +#define DWC_CFGL_FIFO_EMPTY (1 << 9) /* pause xfer */
> +#define DWC_CFGL_HS_DST (1 << 10) /* handshake
w/dst */
> +#define DWC_CFGL_HS_SRC (1 << 11) /* handshake
w/src */
> +#define DWC_CFGL_MAX_BURST(x) ((x) << 20)
> +#define DWC_CFGL_RELOAD_SAR (1 << 30)
> +#define DWC_CFGL_RELOAD_DAR (1 << 31)
> +
> +/* Bitfields in CFG_HI. Platform-configurable bits are in
<linux/dw_dmac.h>
> */ +#define DWC_CFGH_DS_UPD_EN (1 << 5)
> +#define DWC_CFGH_SS_UPD_EN (1 << 6)
> +
> +/* Bitfields in SGR */
> +#define DWC_SGR_SGI(x) ((x) << 0)
> +#define DWC_SGR_SGC(x) ((x) << 20)
> +
> +/* Bitfields in DSR */
> +#define DWC_DSR_DSI(x) ((x) << 0)
> +#define DWC_DSR_DSC(x) ((x) << 20)
> +
> +/* Bitfields in CFG */
> +#define DW_CFG_DMA_EN (1 << 0)
> +
> +#define DW_REGLEN 0x400
> +
> +struct dw_dma_chan {
> + struct dma_chan chan;
> + void __iomem *ch_regs;
> + u8 mask;
> +
> + spinlock_t lock;
> +
> + /* these other elements are all protected by lock */
> + dma_cookie_t completed;
> + struct list_head active_list;
> + struct list_head queue;
> + struct list_head free_list;
> +
> + struct dw_dma_slave *dws;
> +
> + unsigned int descs_allocated;
> +};
> +
> +static inline struct dw_dma_chan_regs __iomem *
> +__dwc_regs(struct dw_dma_chan *dwc)
> +{
> + return dwc->ch_regs;
> +}
> +
> +#define channel_readl(dwc, name) \
> + __raw_readl(&(__dwc_regs(dwc)->name))
> +#define channel_writel(dwc, name, val) \
> + __raw_writel((val), &(__dwc_regs(dwc)->name))
> +
> +static inline struct dw_dma_chan *to_dw_dma_chan(struct dma_chan
*chan)
> +{
> + return container_of(chan, struct dw_dma_chan, chan);
> +}
> +
> +
> +struct dw_dma {
> + struct dma_device dma;
> + void __iomem *regs;
> + struct tasklet_struct tasklet;
> + struct clk *clk;
> +
> + u8 all_chan_mask;
> +
> + struct dw_dma_chan chan[0];
> +};
> +
> +static inline struct dw_dma_regs __iomem *__dw_regs(struct dw_dma
*dw)
> +{
> + return dw->regs;
> +}
> +
> +#define dma_readl(dw, name) \
> + __raw_readl(&(__dw_regs(dw)->name))
> +#define dma_writel(dw, name, val) \
> + __raw_writel((val), &(__dw_regs(dw)->name))
> +
> +#define channel_set_bit(dw, reg, mask) \
> + dma_writel(dw, reg, ((mask) << 8) | (mask))
> +#define channel_clear_bit(dw, reg, mask) \
> + dma_writel(dw, reg, ((mask) << 8) | 0)
> +
> +static inline struct dw_dma *to_dw_dma(struct dma_device *ddev)
> +{
> + return container_of(ddev, struct dw_dma, dma);
> +}
> +
> +/* LLI == Linked List Item; a.k.a. DMA block descriptor */
> +struct dw_lli {
> + /* values that are not changed by hardware */
> + dma_addr_t sar;
> + dma_addr_t dar;
> + dma_addr_t llp; /* chain to next lli */
> + u32 ctllo;
> + /* values that may get written back: */
> + u32 ctlhi;
> + /* sstat and dstat can snapshot peripheral register state.
> + * silicon config may discard either or both...
> + */
> + u32 sstat;
> + u32 dstat;
> +};
> +
> +struct dw_desc {
> + /* FIRST values the hardware uses */
> + struct dw_lli lli;
> +
> + /* THEN values for driver housekeeping */
> + struct list_head desc_node;
> + struct dma_async_tx_descriptor txd;
> +};
> +
> +static inline struct dw_desc *
> +txd_to_dw_desc(struct dma_async_tx_descriptor *txd)
> +{
> + return container_of(txd, struct dw_desc, txd);
> +}
> diff --git a/include/asm-avr32/arch-at32ap/at32ap700x.h
> b/include/asm-avr32/arch-at32ap/at32ap700x.h
> index 31e48b0..d18a305 100644
> --- a/include/asm-avr32/arch-at32ap/at32ap700x.h
> +++ b/include/asm-avr32/arch-at32ap/at32ap700x.h
> @@ -30,4 +30,20 @@
> #define GPIO_PIN_PD(N) (GPIO_PIOD_BASE + (N))
> #define GPIO_PIN_PE(N) (GPIO_PIOE_BASE + (N))
>
> +
> +/*
> + * DMAC peripheral hardware handshaking interfaces, used with dw_dmac
> + */
> +#define DMAC_MCI_RX 0
> +#define DMAC_MCI_TX 1
> +#define DMAC_DAC_TX 2
> +#define DMAC_AC97_A_RX 3
> +#define DMAC_AC97_A_TX 4
> +#define DMAC_AC97_B_RX 5
> +#define DMAC_AC97_B_TX 6
> +#define DMAC_DMAREQ_0 7
> +#define DMAC_DMAREQ_1 8
> +#define DMAC_DMAREQ_2 9
> +#define DMAC_DMAREQ_3 10
> +
> #endif /* __ASM_ARCH_AT32AP700X_H__ */
> diff --git a/include/linux/dw_dmac.h b/include/linux/dw_dmac.h
> new file mode 100644
> index 0000000..04d217b
> --- /dev/null
> +++ b/include/linux/dw_dmac.h
> @@ -0,0 +1,62 @@
> +/*
> + * Driver for the Synopsys DesignWare DMA Controller (aka DMACA on
> + * AVR32 systems.)
> + *
> + * Copyright (C) 2007 Atmel Corporation
> + *
> + * This program is free software; you can redistribute it and/or
modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#ifndef DW_DMAC_H
> +#define DW_DMAC_H
> +
> +#include <linux/dmaengine.h>
> +
> +/**
> + * struct dw_dma_platform_data - Controller configuration parameters
> + * @nr_channels: Number of channels supported by hardware (max 8)
> + */
> +struct dw_dma_platform_data {
> + unsigned int nr_channels;
> +};
> +
> +/**
> + * struct dw_dma_slave - Controller-specific information about a
slave
> + * @slave: Generic information about the slave
> + * @ctl_lo: Platform-specific initializer for the CTL_LO register
> + * @cfg_hi: Platform-specific initializer for the CFG_HI register
> + * @cfg_lo: Platform-specific initializer for the CFG_LO register
> + */
> +struct dw_dma_slave {
> + struct dma_slave slave;
> + u32 cfg_hi;
> + u32 cfg_lo;
> +};
> +
> +/* Platform-configurable bits in CFG_HI */
> +#define DWC_CFGH_FCMODE (1 << 0)
> +#define DWC_CFGH_FIFO_MODE (1 << 1)
> +#define DWC_CFGH_PROTCTL(x) ((x) << 2)
> +#define DWC_CFGH_SRC_PER(x) ((x) << 7)
> +#define DWC_CFGH_DST_PER(x) ((x) << 11)
> +
> +/* Platform-configurable bits in CFG_LO */
> +#define DWC_CFGL_PRIO(x) ((x) << 5) /* priority */
> +#define DWC_CFGL_LOCK_CH_XFER (0 << 12) /* scope of LOCK_CH */
> +#define DWC_CFGL_LOCK_CH_BLOCK (1 << 12)
> +#define DWC_CFGL_LOCK_CH_XACT (2 << 12)
> +#define DWC_CFGL_LOCK_BUS_XFER (0 << 14) /* scope of LOCK_BUS
*/
> +#define DWC_CFGL_LOCK_BUS_BLOCK (1 << 14)
> +#define DWC_CFGL_LOCK_BUS_XACT (2 << 14)
> +#define DWC_CFGL_LOCK_CH (1 << 15) /* channel lockout */
> +#define DWC_CFGL_LOCK_BUS (1 << 16) /* busmaster lockout
*/
> +#define DWC_CFGL_HS_DST_POL (1 << 18) /* dst handshake
active low */
> +#define DWC_CFGL_HS_SRC_POL (1 << 19) /* src handshake
active low */
> +
> +static inline struct dw_dma_slave *to_dw_dma_slave(struct dma_slave
*slave)
> +{
> + return container_of(slave, struct dw_dma_slave, slave);
> +}
> +
> +#endif /* DW_DMAC_H */
> --
> 1.5.5.4
Regards,
Maciej
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists