This series add NVIDIA Tegra's APB dma controller driver based on dmaengine.
There is already old driver in mach-tegra/dma.c and we want to get rid
of this old style driver which exposes private apis.
Once this driver get through, there will be series of patches to move all
existing driver to use the dmaengine based driver and old mach-tegra/dma.c
will get deleted. This driver has following feature than old one:
- better queue managment.
- Cyclic transfer supports.
- Platform driver.
- Full support for device tree.
- Multiple bug fixes over old driver.
Changes from V1:
- Incorportaed review comments from Vinod.
- get rid of the tegra_dma header.
- Having clock control through runtime pm.
- Remove regmap support.
Laxman Dewangan (2):
dma: dmaengine: add slave req id in slave_config
dmaengine: tegra: add dma driver
drivers/dma/Kconfig | 13 +
drivers/dma/Makefile | 1 +
drivers/dma/tegra_dma.c | 1714 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dmaengine.h | 4 +
4 files changed, 1732 insertions(+), 0 deletions(-)
create mode 100644 drivers/dma/tegra_dma.c
Adding dmaengine based NVIDIA's Tegra APB dma driver.
This driver support the slave mode of data transfer from
peripheral to memory and vice versa.
The driver supports for the cyclic and non-cyclic mode
of data transfer.
Signed-off-by: Laxman Dewangan <[email protected]>
---
Changes from V1:
- Incorportaed review comments from Vinod.
- get rid of the tegra_dma header.
- Having clock control through runtime pm.
- Remove regmap support.
drivers/dma/Kconfig | 13 +
drivers/dma/Makefile | 1 +
drivers/dma/tegra_dma.c | 1714 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1728 insertions(+), 0 deletions(-)
create mode 100644 drivers/dma/tegra_dma.c
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index ef378b5..26d9a23 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -148,6 +148,19 @@ config TXX9_DMAC
Support the TXx9 SoC internal DMA controller. This can be
integrated in chips such as the Toshiba TX4927/38/39.
+config TEGRA_DMA
+ bool "NVIDIA Tegra DMA support"
+ depends on ARCH_TEGRA
+ select DMA_ENGINE
+ help
+ Support for the NVIDIA Tegra DMA controller driver. The DMA
+ controller is having multiple DMA channel which can be configured
+ for different peripherals like audio, UART, SPI, I2C etc which is
+ in APB bus.
+ This DMA controller transfers data from memory to peripheral fifo
+ address or vice versa. It does not support memory to memory data
+ transfer.
+
config SH_DMAE
tristate "Renesas SuperH DMAC support"
depends on (SUPERH && SH_DMA) || (ARM && ARCH_SHMOBILE)
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 86b795b..3aaa63a 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_MXS_DMA) += mxs-dma.o
obj-$(CONFIG_TIMB_DMA) += timb_dma.o
obj-$(CONFIG_SIRF_DMA) += sirf-dma.o
obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
+obj-$(CONFIG_TEGRA_DMA) += tegra_dma.o
obj-$(CONFIG_PL330_DMA) += pl330.o
obj-$(CONFIG_PCH_DMA) += pch_dma.o
obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
diff --git a/drivers/dma/tegra_dma.c b/drivers/dma/tegra_dma.c
new file mode 100644
index 0000000..3c599ca
--- /dev/null
+++ b/drivers/dma/tegra_dma.c
@@ -0,0 +1,1714 @@
+/*
+ * DMA driver for Nvidia's Tegra APB DMA controller.
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/bitops.h>
+#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/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <mach/clk.h>
+#include "dmaengine.h"
+
+#define APB_DMA_GEN 0x0
+#define GEN_ENABLE BIT(31)
+
+#define APB_DMA_CNTRL 0x010
+#define APB_DMA_IRQ_MASK 0x01c
+#define APB_DMA_IRQ_MASK_SET 0x020
+
+/* CSR register */
+#define APB_DMA_CHAN_CSR 0x00
+#define CSR_ENB BIT(31)
+#define CSR_IE_EOC BIT(30)
+#define CSR_HOLD BIT(29)
+#define CSR_DIR BIT(28)
+#define CSR_ONCE BIT(27)
+#define CSR_FLOW BIT(21)
+#define CSR_REQ_SEL_SHIFT 16
+#define CSR_WCOUNT_MASK 0xFFFC
+
+/* STATUS register */
+#define APB_DMA_CHAN_STA 0x004
+#define STA_BUSY BIT(31)
+#define STA_ISE_EOC BIT(30)
+#define STA_HALT BIT(29)
+#define STA_PING_PONG BIT(28)
+#define STA_COUNT_SHIFT 2
+#define STA_COUNT_MASK 0xFFFC
+
+/* AHB memory address */
+#define APB_DMA_CHAN_AHB_PTR 0x010
+
+/* AHB sequence register */
+#define APB_DMA_CHAN_AHB_SEQ 0x14
+#define AHB_SEQ_INTR_ENB BIT(31)
+#define AHB_SEQ_BUS_WIDTH_SHIFT 28
+#define AHB_SEQ_BUS_WIDTH_8 (0 << AHB_SEQ_BUS_WIDTH_SHIFT)
+#define AHB_SEQ_BUS_WIDTH_16 (1 << AHB_SEQ_BUS_WIDTH_SHIFT)
+#define AHB_SEQ_BUS_WIDTH_32 (2 << AHB_SEQ_BUS_WIDTH_SHIFT)
+#define AHB_SEQ_BUS_WIDTH_64 (3 << AHB_SEQ_BUS_WIDTH_SHIFT)
+#define AHB_SEQ_BUS_WIDTH_128 (4 << AHB_SEQ_BUS_WIDTH_SHIFT)
+#define AHB_SEQ_DATA_SWAP BIT(27)
+#define AHB_SEQ_BURST_1 (4 << 24)
+#define AHB_SEQ_BURST_4 (5 << 24)
+#define AHB_SEQ_BURST_8 (6 << 24)
+#define AHB_SEQ_DBL_BUF BIT(19)
+#define AHB_SEQ_WRAP_SHIFT 16
+#define AHB_SEQ_WRAP_NONE 0
+
+/* APB address */
+#define APB_DMA_CHAN_APB_PTR 0x018
+
+/* APB sequence register */
+#define APB_DMA_CHAN_APB_SEQ 0x01c
+#define APB_SEQ_BUS_WIDTH_SHIFT 28
+#define APB_SEQ_BUS_WIDTH_8 (0 << APB_SEQ_BUS_WIDTH_SHIFT)
+#define APB_SEQ_BUS_WIDTH_16 (1 << APB_SEQ_BUS_WIDTH_SHIFT)
+#define APB_SEQ_BUS_WIDTH_32 (2 << APB_SEQ_BUS_WIDTH_SHIFT)
+#define APB_SEQ_BUS_WIDTH_64 (3 << APB_SEQ_BUS_WIDTH_SHIFT)
+#define APB_SEQ_BUS_WIDTH_128 (4 << APB_SEQ_BUS_WIDTH_SHIFT)
+#define APB_SEQ_DATA_SWAP BIT(27)
+#define APB_SEQ_WRAP_SHIFT 16
+#define APB_SEQ_WRAP_WORD_1 (1 << APB_SEQ_WRAP_SHIFT)
+
+/*
+ * If any burst is in flight and DMA paused then this is the time to complete
+ * on-flight burst and update DMA status register.
+ */
+#define DMA_BURST_COMPLETE_TIME 20
+
+/* Channel base address offset from APBDMA base address */
+#define DMA_CHANNEL_BASE_ADDRESS_OFFSET 0x1000
+
+/* DMA channel register space size */
+#define DMA_CHANNEL_REGISTER_SIZE 0x20
+
+/*
+ * Initial number of descriptors to allocate for each channel during
+ * allocation. More descriptors will be allocated dynamically if
+ * client needs it.
+ */
+#define DMA_NR_DESCS_PER_CHANNEL 4
+#define DMA_NR_REQ_PER_DESC 8
+
+struct tegra_dma;
+
+/*
+ * tegra_dma_chip_data Tegra chip specific DMA data
+ * @nr_channels: Number of channels available in the controller.
+ * @max_dma_count: Maximum DMA transfer count supported by DMA controller.
+ */
+struct tegra_dma_chip_data {
+ int nr_channels;
+ int max_dma_count;
+};
+
+/*
+ * dma_transfer_mode: Different DMA transfer mode.
+ * DMA_MODE_ONCE: DMA transfer the configured buffer once and at the end of
+ * transfer, DMA stops automatically and generates interrupt
+ * if enabled. SW need to reprogram DMA for next transfer.
+ * DMA_MODE_CYCLE: DMA keeps transferring the same buffer again and again
+ * until DMA stopped explicitly by SW or another buffer
+ * configured. After transfer completes, DMA again starts
+ * transfer from beginning of buffer without SW intervention.
+ * If any new address/size is configured during buffer transfer
+ * then DMA start transfer with new configuration when the
+ * current transfer has completed otherwise it will keep
+ * transferring with old configuration. It also generates
+ * the interrupt each time the buffer transfer completes.
+ * DMA_MODE_CYCLE_HALF_NOTIFY: This mode is identical to DMA_MODE_CYCLE,
+ * except that an additional interrupt is generated half-way
+ * through the buffer. This is kind of ping-pong buffer mechanism.
+ * If SW wants to change the address/size of the buffer then
+ * it must only change when DMA is transferring the second
+ * half of buffer. In DMA configuration, it only need to
+ * configure starting of first buffer and size of the half buffer.
+ */
+enum dma_transfer_mode {
+ DMA_MODE_NONE,
+ DMA_MODE_ONCE,
+ DMA_MODE_CYCLE,
+ DMA_MODE_CYCLE_HALF_NOTIFY,
+};
+
+/* Dma channel registers */
+struct tegra_dma_channel_regs {
+ unsigned long csr;
+ unsigned long ahb_ptr;
+ unsigned long apb_ptr;
+ unsigned long ahb_seq;
+ unsigned long apb_seq;
+};
+
+/*
+ * tegra_dma_sg_req: Dma request details to configure hardware. This
+ * contains the details for one transfer to configure DMA hw.
+ * The client's request for data transfer can be broken into multiple
+ * sub-transfer as per requester details and hw support.
+ * This sub transfer get added in the list of transfer and point to Tegra
+ * DMA descriptor which manages the transfer details.
+ */
+struct tegra_dma_sg_req {
+ struct tegra_dma_channel_regs ch_regs;
+ int req_len;
+ bool configured;
+ bool last_sg;
+ bool half_done;
+ struct list_head node;
+ struct tegra_dma_desc *dma_desc;
+};
+
+/*
+ * tegra_dma_desc: Tegra DMA descriptors which manages the client requests.
+ * This descriptor keep track of transfer status, callbacks and request
+ * counts etc.
+ */
+struct tegra_dma_desc {
+ struct dma_async_tx_descriptor txd;
+ int bytes_requested;
+ int bytes_transferred;
+ enum dma_status dma_status;
+ struct list_head node;
+ struct list_head tx_list;
+ struct list_head cb_node;
+ bool ack_reqd;
+ bool cb_due;
+ dma_cookie_t cookie;
+};
+
+struct tegra_dma_channel;
+
+typedef void (*dma_isr_handler)(struct tegra_dma_channel *tdc,
+ bool to_terminate);
+
+/* tegra_dma_channel: Channel specific information */
+struct tegra_dma_channel {
+ struct dma_chan dma_chan;
+ bool config_init;
+ int id;
+ int irq;
+ unsigned long chan_base_offset;
+ spinlock_t lock;
+ bool busy;
+ enum dma_transfer_mode dma_mode;
+ int descs_allocated;
+ struct tegra_dma *tdma;
+
+ /* Different lists for managing the requests */
+ struct list_head free_sg_req;
+ struct list_head pending_sg_req;
+ struct list_head free_dma_desc;
+ struct list_head wait_ack_dma_desc;
+ struct list_head cb_desc;
+
+ /* isr handler and tasklet for bottom half of isr handling */
+ dma_isr_handler isr_handler;
+ struct tasklet_struct tasklet;
+ dma_async_tx_callback callback;
+ void *callback_param;
+
+ /* Channel-slave specific configuration */
+ struct dma_slave_config dma_sconfig;
+};
+
+/* tegra_dma: Tegra DMA specific information */
+struct tegra_dma {
+ struct dma_device dma_dev;
+ struct device *dev;
+ struct clk *dma_clk;
+ spinlock_t global_lock;
+ void __iomem *base_addr;
+ struct tegra_dma_chip_data *chip_data;
+
+ /* Some register need to be cache before suspend */
+ u32 reg_gen;
+
+ /* Last member of the structure */
+ struct tegra_dma_channel channels[0];
+};
+
+static inline void tdma_write(struct tegra_dma *tdma, u32 reg, u32 val)
+{
+ writel(val, tdma->base_addr + reg);
+}
+
+static inline u32 tdma_read(struct tegra_dma *tdma, u32 reg)
+{
+ return readl(tdma->base_addr + reg);
+}
+
+static inline void tdc_write(struct tegra_dma_channel *tdc,
+ u32 reg, u32 val)
+{
+ writel(val, tdc->tdma->base_addr + tdc->chan_base_offset + reg);
+}
+
+static inline u32 tdc_read(struct tegra_dma_channel *tdc, u32 reg)
+{
+ return readl(tdc->tdma->base_addr + tdc->chan_base_offset + reg);
+}
+
+static inline struct tegra_dma_channel *to_tegra_dma_chan(struct dma_chan *dc)
+{
+ return container_of(dc, struct tegra_dma_channel, dma_chan);
+}
+
+static inline struct tegra_dma_desc *txd_to_tegra_dma_desc(
+ struct dma_async_tx_descriptor *td)
+{
+ return container_of(td, struct tegra_dma_desc, txd);
+}
+
+static inline struct device *tdc2dev(struct tegra_dma_channel *tdc)
+{
+ return &tdc->dma_chan.dev->device;
+}
+
+static dma_cookie_t tegra_dma_tx_submit(struct dma_async_tx_descriptor *tx);
+static int tegra_dma_runtime_suspend(struct device *dev);
+static int tegra_dma_runtime_resume(struct device *dev);
+
+static int allocate_tegra_desc(struct tegra_dma_channel *tdc,
+ int ndma_desc, int nsg_req)
+{
+ int i;
+ struct tegra_dma_desc *dma_desc;
+ struct tegra_dma_sg_req *sg_req;
+ struct dma_chan *dc = &tdc->dma_chan;
+ struct list_head dma_desc_list;
+ struct list_head sg_req_list;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&dma_desc_list);
+ INIT_LIST_HEAD(&sg_req_list);
+
+ /* Initialize DMA descriptors */
+ for (i = 0; i < ndma_desc; ++i) {
+ dma_desc = kzalloc(sizeof(struct tegra_dma_desc), GFP_ATOMIC);
+ if (!dma_desc) {
+ dev_err(tdc2dev(tdc),
+ "%s(): Memory allocation fails\n", __func__);
+ goto fail_alloc;
+ }
+
+ dma_async_tx_descriptor_init(&dma_desc->txd, dc);
+ dma_desc->txd.tx_submit = tegra_dma_tx_submit;
+ dma_desc->txd.flags = DMA_CTRL_ACK;
+ list_add_tail(&dma_desc->node, &dma_desc_list);
+ }
+
+ /* Initialize req descriptors */
+ for (i = 0; i < nsg_req; ++i) {
+ sg_req = kzalloc(sizeof(struct tegra_dma_sg_req), GFP_ATOMIC);
+ if (!sg_req) {
+ dev_err(tdc2dev(tdc),
+ "%s(): Memory allocation fails\n", __func__);
+ goto fail_alloc;
+ }
+ list_add_tail(&sg_req->node, &sg_req_list);
+ }
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (ndma_desc) {
+ tdc->descs_allocated += ndma_desc;
+ list_splice(&dma_desc_list, &tdc->free_dma_desc);
+ }
+
+ if (nsg_req)
+ list_splice(&sg_req_list, &tdc->free_sg_req);
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return tdc->descs_allocated;
+
+fail_alloc:
+ while (!list_empty(&dma_desc_list)) {
+ dma_desc = list_first_entry(&dma_desc_list,
+ typeof(*dma_desc), node);
+ list_del(&dma_desc->node);
+ }
+
+ while (!list_empty(&sg_req_list)) {
+ sg_req = list_first_entry(&sg_req_list, typeof(*sg_req), node);
+ list_del(&sg_req->node);
+ }
+ return 0;
+}
+
+/* Get DMA desc from free list, if not there then allocate it */
+static struct tegra_dma_desc *tegra_dma_desc_get(struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma_desc *dma_desc = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+
+ /* Check from free list desc */
+ if (!list_empty(&tdc->free_dma_desc)) {
+ dma_desc = list_first_entry(&tdc->free_dma_desc,
+ typeof(*dma_desc), node);
+ list_del(&dma_desc->node);
+ goto end;
+ }
+
+ /*
+ * Check list with desc which are waiting for ack, may be it
+ * got acked from client.
+ */
+ if (!list_empty(&tdc->wait_ack_dma_desc)) {
+ list_for_each_entry(dma_desc, &tdc->wait_ack_dma_desc, node) {
+ if (async_tx_test_ack(&dma_desc->txd)) {
+ list_del(&dma_desc->node);
+ goto end;
+ }
+ }
+ }
+
+ /* There is no free desc, allocate it */
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ dev_dbg(tdc2dev(tdc),
+ "Allocating more descriptors for channel %d\n", tdc->id);
+ allocate_tegra_desc(tdc, DMA_NR_DESCS_PER_CHANNEL,
+ DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC);
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->free_dma_desc))
+ goto end;
+
+ dma_desc = list_first_entry(&tdc->free_dma_desc,
+ typeof(*dma_desc), node);
+ list_del(&dma_desc->node);
+end:
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return dma_desc;
+}
+
+static void tegra_dma_desc_put(struct tegra_dma_channel *tdc,
+ struct tegra_dma_desc *dma_desc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (!list_empty(&dma_desc->tx_list))
+ list_splice_init(&dma_desc->tx_list, &tdc->free_sg_req);
+ list_add_tail(&dma_desc->node, &tdc->free_dma_desc);
+ spin_unlock_irqrestore(&tdc->lock, flags);
+}
+
+static void tegra_dma_desc_done_locked(struct tegra_dma_channel *tdc,
+ struct tegra_dma_desc *dma_desc)
+{
+ if (dma_desc->ack_reqd)
+ list_add_tail(&dma_desc->node, &tdc->wait_ack_dma_desc);
+ else
+ list_add_tail(&dma_desc->node, &tdc->free_dma_desc);
+}
+
+static struct tegra_dma_sg_req *tegra_dma_sg_req_get(
+ struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma_sg_req *sg_req = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->free_sg_req)) {
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ dev_dbg(tdc2dev(tdc),
+ "Reallocating sg_req for channel %d\n", tdc->id);
+ allocate_tegra_desc(tdc, 0,
+ DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC);
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->free_sg_req)) {
+ dev_dbg(tdc2dev(tdc),
+ "Not found free sg_req for channel %d\n", tdc->id);
+ goto end;
+ }
+ }
+
+ sg_req = list_first_entry(&tdc->free_sg_req, typeof(*sg_req), node);
+ list_del(&sg_req->node);
+end:
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return sg_req;
+}
+
+static int tegra_dma_slave_config(struct dma_chan *dc,
+ struct dma_slave_config *sconfig)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+
+ if (!list_empty(&tdc->pending_sg_req)) {
+ dev_err(tdc2dev(tdc),
+ "dma requests are pending, cannot take new configuration");
+ return -EBUSY;
+ }
+
+ memcpy(&tdc->dma_sconfig, sconfig, sizeof(*sconfig));
+ tdc->config_init = true;
+ return 0;
+}
+
+static void tegra_dma_pause(struct tegra_dma_channel *tdc,
+ bool wait_for_burst_complete)
+{
+ struct tegra_dma *tdma = tdc->tdma;
+ spin_lock(&tdma->global_lock);
+ tdma_write(tdma, APB_DMA_GEN, 0);
+ if (wait_for_burst_complete)
+ udelay(DMA_BURST_COMPLETE_TIME);
+}
+
+static void tegra_dma_resume(struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma *tdma = tdc->tdma;
+ tdma_write(tdma, APB_DMA_GEN, GEN_ENABLE);
+ spin_unlock(&tdma->global_lock);
+}
+
+static void tegra_dma_stop(struct tegra_dma_channel *tdc)
+{
+ u32 csr;
+ u32 status;
+
+ /* Disable interrupts */
+ csr = tdc_read(tdc, APB_DMA_CHAN_CSR);
+ csr &= ~CSR_IE_EOC;
+ tdc_write(tdc, APB_DMA_CHAN_CSR, csr);
+
+ /* Disable DMA */
+ csr &= ~CSR_ENB;
+ tdc_write(tdc, APB_DMA_CHAN_CSR, csr);
+
+ /* Clear interrupt status if it is there */
+ status = tdc_read(tdc, APB_DMA_CHAN_STA);
+ if (status & STA_ISE_EOC) {
+ dev_dbg(tdc2dev(tdc), "%s():clearing interrupt\n", __func__);
+ tdc_write(tdc, APB_DMA_CHAN_STA, status);
+ }
+ tdc->busy = false;
+}
+
+static void tegra_dma_start(struct tegra_dma_channel *tdc,
+ struct tegra_dma_sg_req *sg_req)
+{
+ struct tegra_dma_channel_regs *ch_regs = &sg_req->ch_regs;
+ unsigned long csr = ch_regs->csr;
+
+ tdc_write(tdc, APB_DMA_CHAN_CSR, csr);
+ tdc_write(tdc, APB_DMA_CHAN_APB_SEQ, ch_regs->apb_seq);
+ tdc_write(tdc, APB_DMA_CHAN_APB_PTR, ch_regs->apb_ptr);
+ tdc_write(tdc, APB_DMA_CHAN_AHB_SEQ, ch_regs->ahb_seq);
+ tdc_write(tdc, APB_DMA_CHAN_AHB_PTR, ch_regs->ahb_ptr);
+
+ /* Dump the configuration register if verbose mode enabled */
+ dev_vdbg(tdc2dev(tdc),
+ "%s(): csr: 0x%08lx\n", __func__, ch_regs->csr);
+ dev_vdbg(tdc2dev(tdc),
+ "%s(): apbseq: 0x%08lx\n", __func__, ch_regs->apb_seq);
+ dev_vdbg(tdc2dev(tdc),
+ "%s(): apbptr: 0x%08lx\n", __func__, ch_regs->apb_ptr);
+ dev_vdbg(tdc2dev(tdc),
+ "%s(): ahbseq: 0x%08lx\n", __func__, ch_regs->ahb_seq);
+ dev_vdbg(tdc2dev(tdc),
+ "%s(): ahbptr: 0x%08lx\n", __func__, ch_regs->ahb_ptr);
+
+ /* Start DMA */
+ csr |= CSR_ENB;
+ tdc_write(tdc, APB_DMA_CHAN_CSR, csr);
+}
+
+static void tegra_dma_configure_for_next(struct tegra_dma_channel *tdc,
+ struct tegra_dma_sg_req *nsg_req)
+{
+ unsigned long status;
+
+ /*
+ * The DMA controller reloads the new configuration for next transfer
+ * after last burst of current transfer completes.
+ * If there is no IEC status then this makes sure that last burst
+ * has not be completed. There may be case that last burst is on
+ * flight and so it can complete but because DMA is paused, it
+ * will not generates interrupt as well as not reload the new
+ * configuration.
+ * If there is already IEC status then interrupt handler need to
+ * load new configuration.
+ */
+ tegra_dma_pause(tdc, false);
+ status = tdc_read(tdc, APB_DMA_CHAN_STA);
+
+ /*
+ * If interrupt is pending then do nothing as the ISR will handle
+ * the programing for new request.
+ */
+ if (status & STA_ISE_EOC) {
+ dev_err(tdc2dev(tdc),
+ "Skipping new configuration as interrupt is pending\n");
+ goto exit_config;
+ }
+
+ /* Safe to program new configuration */
+ tdc_write(tdc, APB_DMA_CHAN_APB_PTR, nsg_req->ch_regs.apb_ptr);
+ tdc_write(tdc, APB_DMA_CHAN_AHB_PTR, nsg_req->ch_regs.ahb_ptr);
+ tdc_write(tdc, APB_DMA_CHAN_CSR, nsg_req->ch_regs.csr | CSR_ENB);
+ nsg_req->configured = true;
+
+exit_config:
+ tegra_dma_resume(tdc);
+}
+
+static void tdc_start_head_req(struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma_sg_req *sg_req;
+
+ if (list_empty(&tdc->pending_sg_req))
+ return;
+
+ sg_req = list_first_entry(&tdc->pending_sg_req,
+ typeof(*sg_req), node);
+ tegra_dma_start(tdc, sg_req);
+ sg_req->configured = true;
+ tdc->busy = true;
+}
+
+static void tdc_configure_next_head_desc(struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma_sg_req *hsgreq;
+ struct tegra_dma_sg_req *hnsgreq;
+
+ if (list_empty(&tdc->pending_sg_req))
+ return;
+
+ hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node);
+ if (!list_is_last(&hsgreq->node, &tdc->pending_sg_req)) {
+ hnsgreq = list_first_entry(&hsgreq->node,
+ typeof(*hnsgreq), node);
+ tegra_dma_configure_for_next(tdc, hnsgreq);
+ }
+}
+
+static inline int get_current_xferred_count(struct tegra_dma_channel *tdc,
+ struct tegra_dma_sg_req *sg_req, unsigned long status)
+{
+ return sg_req->req_len - ((status & STA_COUNT_MASK) + 4);
+}
+
+static void tegra_dma_abort_all(struct tegra_dma_channel *tdc)
+{
+ struct tegra_dma_sg_req *sgreq;
+ struct tegra_dma_desc *dma_desc;
+ while (!list_empty(&tdc->pending_sg_req)) {
+ sgreq = list_first_entry(&tdc->pending_sg_req,
+ typeof(*sgreq), node);
+ list_del(&sgreq->node);
+ list_add_tail(&sgreq->node, &tdc->free_sg_req);
+ if (sgreq->last_sg) {
+ dma_desc = sgreq->dma_desc;
+ dma_desc->dma_status = DMA_ERROR;
+ tegra_dma_desc_done_locked(tdc, dma_desc);
+
+ /* Add in cb list if it is not there. */
+ if (!dma_desc->cb_due) {
+ list_add_tail(&dma_desc->cb_node,
+ &tdc->cb_desc);
+ dma_desc->cb_due = true;
+ }
+ dma_cookie_complete(&dma_desc->txd);
+ }
+ }
+ tdc->dma_mode = DMA_MODE_NONE;
+}
+
+static bool handle_continuous_head_request(struct tegra_dma_channel *tdc,
+ struct tegra_dma_sg_req *last_sg_req, bool to_terminate)
+{
+ struct tegra_dma_sg_req *hsgreq = NULL;
+
+ if (list_empty(&tdc->pending_sg_req)) {
+ dev_err(tdc2dev(tdc),
+ "%s(): Dma is running without any req list\n",
+ __func__);
+ tegra_dma_stop(tdc);
+ return false;
+ }
+
+ /*
+ * Check that head req on list should be in flight.
+ * If it is not in flight then abort transfer as
+ * transfer looping can not continue.
+ */
+ hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node);
+ if (!hsgreq->configured) {
+ tegra_dma_stop(tdc);
+ dev_err(tdc2dev(tdc),
+ "Error in dma transfer loop, aborting dma\n");
+ tegra_dma_abort_all(tdc);
+ return false;
+ }
+
+ /* Configure next request in single buffer mode */
+ if (!to_terminate && (tdc->dma_mode == DMA_MODE_CYCLE))
+ tdc_configure_next_head_desc(tdc);
+ return true;
+}
+
+static void handle_once_dma_done(struct tegra_dma_channel *tdc,
+ bool to_terminate)
+{
+ struct tegra_dma_sg_req *sgreq;
+ struct tegra_dma_desc *dma_desc;
+
+ tdc->busy = false;
+ sgreq = list_first_entry(&tdc->pending_sg_req, typeof(*sgreq), node);
+ dma_desc = sgreq->dma_desc;
+ dma_desc->bytes_transferred += sgreq->req_len;
+
+ list_del(&sgreq->node);
+ if (sgreq->last_sg) {
+ dma_cookie_complete(&dma_desc->txd);
+ list_add_tail(&dma_desc->cb_node, &tdc->cb_desc);
+ dma_desc->cb_due = true;
+ tegra_dma_desc_done_locked(tdc, dma_desc);
+ }
+ list_add_tail(&sgreq->node, &tdc->free_sg_req);
+
+ /* Do not start DMA if it is going to be terminate */
+ if (to_terminate || list_empty(&tdc->pending_sg_req))
+ return;
+
+ tdc_start_head_req(tdc);
+ return;
+}
+
+static void handle_cont_sngl_cycle_dma_done(struct tegra_dma_channel *tdc,
+ bool to_terminate)
+{
+ struct tegra_dma_sg_req *sgreq;
+ struct tegra_dma_desc *dma_desc;
+ bool st;
+
+ sgreq = list_first_entry(&tdc->pending_sg_req, typeof(*sgreq), node);
+ dma_desc = sgreq->dma_desc;
+ dma_desc->bytes_transferred += sgreq->req_len;
+
+ /* Callback need to be call */
+ list_add_tail(&dma_desc->cb_node, &tdc->cb_desc);
+ dma_desc->cb_due = true;
+
+ /* If not last req then put at end of pending list */
+ if (!list_is_last(&sgreq->node, &tdc->pending_sg_req)) {
+ list_del(&sgreq->node);
+ list_add_tail(&sgreq->node, &tdc->pending_sg_req);
+ sgreq->configured = false;
+ st = handle_continuous_head_request(tdc, sgreq, to_terminate);
+ if (!st)
+ dma_desc->dma_status = DMA_ERROR;
+ }
+ return;
+}
+
+static void handle_cont_dbl_cycle_dma_done(struct tegra_dma_channel *tdc,
+ bool to_terminate)
+{
+ struct tegra_dma_sg_req *hsgreq;
+ struct tegra_dma_sg_req *hnsgreq;
+ struct tegra_dma_desc *dma_desc;
+
+ hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node);
+ dma_desc = hsgreq->dma_desc;
+ dma_desc->bytes_transferred += hsgreq->req_len;
+
+ if (!hsgreq->half_done) {
+ if (!list_is_last(hsgreq->node.next, &tdc->pending_sg_req) &&
+ !to_terminate) {
+ hnsgreq = list_first_entry(&hsgreq->node,
+ typeof(*hnsgreq), node);
+ tegra_dma_configure_for_next(tdc, hnsgreq);
+ }
+ hsgreq->half_done = true;
+ list_add_tail(&dma_desc->cb_node, &tdc->cb_desc);
+ dma_desc->cb_due = true;
+ } else {
+ hsgreq->half_done = false;
+ list_add_tail(&dma_desc->cb_node, &tdc->cb_desc);
+ dma_desc->cb_due = true;
+
+ /*
+ * If this is not last entry then put the req in end of
+ * list for next cycle.
+ */
+ if (!list_is_last(hsgreq->node.next, &tdc->pending_sg_req)) {
+ list_del(&hsgreq->node);
+ list_add_tail(&hsgreq->node, &tdc->pending_sg_req);
+ hsgreq->configured = false;
+ }
+ }
+ return;
+}
+
+static void tegra_dma_tasklet(unsigned long data)
+{
+ struct tegra_dma_channel *tdc = (struct tegra_dma_channel *)data;
+ unsigned long flags;
+ dma_async_tx_callback callback = NULL;
+ void *callback_param = NULL;
+ struct tegra_dma_desc *dma_desc;
+ struct list_head cb_dma_desc_list;
+
+ INIT_LIST_HEAD(&cb_dma_desc_list);
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->cb_desc)) {
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return;
+ }
+ list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
+ spin_unlock_irqrestore(&tdc->lock, flags);
+
+ while (!list_empty(&cb_dma_desc_list)) {
+ dma_desc = list_first_entry(&cb_dma_desc_list,
+ typeof(*dma_desc), cb_node);
+ list_del(&dma_desc->cb_node);
+
+ callback = dma_desc->txd.callback;
+ callback_param = dma_desc->txd.callback_param;
+ dma_desc->cb_due = false;
+ if (callback)
+ callback(callback_param);
+ }
+}
+
+static irqreturn_t tegra_dma_isr(int irq, void *dev_id)
+{
+ struct tegra_dma_channel *tdc = dev_id;
+ unsigned long status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+
+ status = tdc_read(tdc, APB_DMA_CHAN_STA);
+ if (status & STA_ISE_EOC) {
+ tdc_write(tdc, APB_DMA_CHAN_STA, status);
+ if (!list_empty(&tdc->cb_desc)) {
+ dev_err(tdc2dev(tdc),
+ "Int before tasklet handled, Stopping DMA %d\n",
+ tdc->id);
+ tegra_dma_stop(tdc);
+ tdc->isr_handler(tdc, true);
+ tegra_dma_abort_all(tdc);
+ /* Schedule tasklet to make callback */
+ tasklet_schedule(&tdc->tasklet);
+ goto end;
+ }
+ tdc->isr_handler(tdc, false);
+ tasklet_schedule(&tdc->tasklet);
+ } else {
+ dev_info(tdc2dev(tdc),
+ "Interrupt is already handled %d status 0x%08lx\n",
+ tdc->id, status);
+ }
+
+end:
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return IRQ_HANDLED;
+}
+
+static dma_cookie_t tegra_dma_tx_submit(struct dma_async_tx_descriptor *txd)
+{
+ struct tegra_dma_desc *dma_desc = txd_to_tegra_dma_desc(txd);
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(txd->chan);
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ dma_desc->dma_status = DMA_IN_PROGRESS;
+ cookie = dma_cookie_assign(&dma_desc->txd);
+ dma_desc->cookie = dma_desc->txd.cookie;
+ list_splice_tail_init(&dma_desc->tx_list, &tdc->pending_sg_req);
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return cookie;
+}
+
+static void tegra_dma_issue_pending(struct dma_chan *dc)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->pending_sg_req)) {
+ dev_err(tdc2dev(tdc),
+ "No requests for channel %d\n", tdc->id);
+ goto end;
+ }
+ if (!tdc->busy) {
+ tdc_start_head_req(tdc);
+
+ /* Continuous single mode: Configure next req */
+ if (DMA_MODE_CYCLE) {
+ /*
+ * Wait for 1 burst time for configure DMA for
+ * next transfer.
+ */
+ udelay(DMA_BURST_COMPLETE_TIME);
+ tdc_configure_next_head_desc(tdc);
+ }
+ }
+end:
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return;
+}
+
+static void tegra_dma_terminate_all(struct dma_chan *dc)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ struct tegra_dma_sg_req *sgreq;
+ struct tegra_dma_desc *dma_desc;
+ unsigned long flags;
+ unsigned long status;
+ struct list_head new_list;
+ dma_async_tx_callback callback = NULL;
+ void *callback_param = NULL;
+ struct list_head cb_dma_desc_list;
+ bool was_busy;
+
+ INIT_LIST_HEAD(&new_list);
+ INIT_LIST_HEAD(&cb_dma_desc_list);
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ if (list_empty(&tdc->pending_sg_req)) {
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return;
+ }
+
+ if (!tdc->busy) {
+ list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
+ goto skip_dma_stop;
+ }
+
+ /* Pause DMA before checking the queue status */
+ tegra_dma_pause(tdc, true);
+
+ status = tdc_read(tdc, APB_DMA_CHAN_STA);
+ if (status & STA_ISE_EOC) {
+ dev_dbg(tdc2dev(tdc), "%s():handling isr\n", __func__);
+ tdc->isr_handler(tdc, true);
+ status = tdc_read(tdc, APB_DMA_CHAN_STA);
+ }
+ list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
+
+ was_busy = tdc->busy;
+ tegra_dma_stop(tdc);
+ if (!list_empty(&tdc->pending_sg_req) && was_busy) {
+ sgreq = list_first_entry(&tdc->pending_sg_req,
+ typeof(*sgreq), node);
+ sgreq->dma_desc->bytes_transferred +=
+ get_current_xferred_count(tdc, sgreq, status);
+ }
+ tegra_dma_resume(tdc);
+
+skip_dma_stop:
+ tegra_dma_abort_all(tdc);
+ /* Ignore callbacks pending list */
+ INIT_LIST_HEAD(&tdc->cb_desc);
+ spin_unlock_irqrestore(&tdc->lock, flags);
+
+ /* Call callbacks if was pending before aborting requests */
+ while (!list_empty(&cb_dma_desc_list)) {
+ dma_desc = list_first_entry(&cb_dma_desc_list,
+ typeof(*dma_desc), cb_node);
+ list_del(&dma_desc->cb_node);
+ callback = dma_desc->txd.callback;
+ callback_param = dma_desc->txd.callback_param;
+ if (callback)
+ callback(callback_param);
+ }
+}
+
+static enum dma_status tegra_dma_tx_status(struct dma_chan *dc,
+ dma_cookie_t cookie, struct dma_tx_state *txstate)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ struct tegra_dma_desc *dma_desc;
+ struct tegra_dma_sg_req *sg_req;
+ enum dma_status ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tdc->lock, flags);
+
+ ret = dma_cookie_status(dc, cookie, txstate);
+ if (ret != DMA_SUCCESS)
+ goto check_pending_q;
+
+ if (list_empty(&tdc->wait_ack_dma_desc))
+ goto check_pending_q;
+
+ /* Check on wait_ack desc status */
+ list_for_each_entry(dma_desc, &tdc->wait_ack_dma_desc, node) {
+ if (dma_desc->cookie == cookie) {
+ dma_set_residue(txstate,
+ dma_desc->bytes_requested -
+ dma_desc->bytes_transferred);
+ ret = dma_desc->dma_status;
+ goto end;
+ }
+ }
+
+check_pending_q:
+ if (list_empty(&tdc->pending_sg_req))
+ goto end;
+
+ /* May be this is in head list of pending list */
+ list_for_each_entry(sg_req, &tdc->pending_sg_req, node) {
+ dma_desc = sg_req->dma_desc;
+ if (dma_desc->txd.cookie == cookie) {
+ dma_set_residue(txstate,
+ dma_desc->bytes_requested -
+ dma_desc->bytes_transferred);
+ ret = dma_desc->dma_status;
+ goto end;
+ }
+ }
+ dev_info(tdc2dev(tdc), "%s(): cookie does not found\n", __func__);
+end:
+ spin_unlock_irqrestore(&tdc->lock, flags);
+ return ret;
+}
+
+static int tegra_dma_device_control(struct dma_chan *dc, enum dma_ctrl_cmd cmd,
+ unsigned long arg)
+{
+ switch (cmd) {
+ case DMA_SLAVE_CONFIG:
+ return tegra_dma_slave_config(dc,
+ (struct dma_slave_config *)arg);
+
+ case DMA_TERMINATE_ALL:
+ tegra_dma_terminate_all(dc);
+ return 0;
+ default:
+ break;
+ }
+
+ return -ENXIO;
+}
+
+static inline int get_bus_width(enum dma_slave_buswidth slave_bw)
+{
+ BUG_ON(!slave_bw);
+ switch (slave_bw) {
+ case DMA_SLAVE_BUSWIDTH_1_BYTE:
+ return APB_SEQ_BUS_WIDTH_8;
+ case DMA_SLAVE_BUSWIDTH_2_BYTES:
+ return APB_SEQ_BUS_WIDTH_16;
+ case DMA_SLAVE_BUSWIDTH_4_BYTES:
+ return APB_SEQ_BUS_WIDTH_32;
+ case DMA_SLAVE_BUSWIDTH_8_BYTES:
+ return APB_SEQ_BUS_WIDTH_64;
+ default:
+ BUG();
+ }
+}
+
+static inline int get_burst_size(struct tegra_dma_channel *tdc,
+ u32 burst_size, enum dma_slave_buswidth slave_bw, int len)
+{
+ int burst_byte;
+ int burst_ahb_width;
+
+ /*
+ * burst_size from client is in terms of the bus_width.
+ * convert them into ahb mmeory width which is 4 byte.
+ */
+ burst_byte = burst_size * slave_bw;
+ burst_ahb_width = burst_byte / 4;
+
+ /* If burst size is 0 then calculate the burst size based on length */
+ if (!burst_ahb_width) {
+ if (len & 0xF)
+ return AHB_SEQ_BURST_1;
+ else if ((len >> 4) & 0x1)
+ return AHB_SEQ_BURST_4;
+ else
+ return AHB_SEQ_BURST_8;
+ }
+ if (burst_ahb_width < 4)
+ return AHB_SEQ_BURST_1;
+ else if (burst_ahb_width < 8)
+ return AHB_SEQ_BURST_4;
+ else
+ return AHB_SEQ_BURST_8;
+}
+
+static bool init_dma_mode(struct tegra_dma_channel *tdc,
+ enum dma_transfer_mode new_mode)
+{
+ if (tdc->dma_mode == DMA_MODE_NONE) {
+ tdc->dma_mode = new_mode;
+ switch (new_mode) {
+ case DMA_MODE_ONCE:
+ tdc->isr_handler = handle_once_dma_done;
+ break;
+ case DMA_MODE_CYCLE:
+ tdc->isr_handler = handle_cont_sngl_cycle_dma_done;
+ break;
+ case DMA_MODE_CYCLE_HALF_NOTIFY:
+ tdc->isr_handler = handle_cont_dbl_cycle_dma_done;
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (new_mode != tdc->dma_mode)
+ return false;
+ }
+ return true;
+}
+
+static struct dma_async_tx_descriptor *tegra_dma_prep_slave_sg(
+ struct dma_chan *dc, struct scatterlist *sgl, unsigned int sg_len,
+ enum dma_transfer_direction direction, unsigned long flags,
+ void *context)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ struct tegra_dma_desc *dma_desc;
+ unsigned int i;
+ struct scatterlist *sg;
+ unsigned long csr, ahb_seq, apb_ptr, apb_seq;
+ struct list_head req_list;
+ struct tegra_dma_sg_req *sg_req = NULL;
+ u32 burst_size;
+ enum dma_slave_buswidth slave_bw;
+
+ if (!tdc->config_init) {
+ dev_err(tdc2dev(tdc), "dma channel is not configured\n");
+ return NULL;
+ }
+ if (sg_len < 1) {
+ dev_err(tdc2dev(tdc), "Invalid segment length %d\n", sg_len);
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&req_list);
+
+ ahb_seq = AHB_SEQ_INTR_ENB;
+ ahb_seq |= AHB_SEQ_WRAP_NONE << AHB_SEQ_WRAP_SHIFT;
+ ahb_seq |= AHB_SEQ_BUS_WIDTH_32;
+
+ csr = CSR_ONCE | CSR_FLOW;
+ csr |= tdc->dma_sconfig.slave_id << CSR_REQ_SEL_SHIFT;
+ if (flags & DMA_PREP_INTERRUPT)
+ csr |= CSR_IE_EOC;
+
+ apb_seq = APB_SEQ_WRAP_WORD_1;
+
+ switch (direction) {
+ case DMA_MEM_TO_DEV:
+ apb_ptr = tdc->dma_sconfig.dst_addr;
+ apb_seq |= get_bus_width(tdc->dma_sconfig.dst_addr_width);
+ burst_size = tdc->dma_sconfig.dst_maxburst;
+ slave_bw = tdc->dma_sconfig.dst_addr_width;
+ csr |= CSR_DIR;
+ break;
+
+ case DMA_DEV_TO_MEM:
+ apb_ptr = tdc->dma_sconfig.src_addr;
+ apb_seq |= get_bus_width(tdc->dma_sconfig.src_addr_width);
+ burst_size = tdc->dma_sconfig.src_maxburst;
+ slave_bw = tdc->dma_sconfig.src_addr_width;
+ break;
+ default:
+ dev_err(tdc2dev(tdc), "Dma direction is not supported\n");
+ return NULL;
+ }
+
+ dma_desc = tegra_dma_desc_get(tdc);
+ if (!dma_desc) {
+ dev_err(tdc2dev(tdc), "Dma descriptors not available\n");
+ goto fail;
+ }
+ INIT_LIST_HEAD(&dma_desc->tx_list);
+ INIT_LIST_HEAD(&dma_desc->cb_node);
+ dma_desc->bytes_requested = 0;
+ dma_desc->bytes_transferred = 0;
+ dma_desc->dma_status = DMA_IN_PROGRESS;
+
+ /* Make transfer requests */
+ for_each_sg(sgl, sg, sg_len, i) {
+ u32 len, mem;
+
+ mem = sg_phys(sg);
+ len = sg_dma_len(sg);
+
+ if ((len & 3) || (mem & 3) ||
+ (len > tdc->tdma->chip_data->max_dma_count)) {
+ dev_err(tdc2dev(tdc),
+ "Dma length/memory address is not supported\n");
+ goto fail;
+ }
+
+ sg_req = tegra_dma_sg_req_get(tdc);
+ if (!sg_req) {
+ dev_err(tdc2dev(tdc), "Dma sg-req not available\n");
+ goto fail;
+ }
+
+ ahb_seq |= get_burst_size(tdc, burst_size, slave_bw, len);
+ dma_desc->bytes_requested += len;
+
+ sg_req->ch_regs.apb_ptr = apb_ptr;
+ sg_req->ch_regs.ahb_ptr = mem;
+ sg_req->ch_regs.csr = csr | ((len - 4) & 0xFFFC);
+ sg_req->ch_regs.apb_seq = apb_seq;
+ sg_req->ch_regs.ahb_seq = ahb_seq;
+ sg_req->configured = false;
+ sg_req->last_sg = false;
+ sg_req->dma_desc = dma_desc;
+ sg_req->req_len = len;
+
+ list_add_tail(&sg_req->node, &dma_desc->tx_list);
+ }
+ sg_req->last_sg = true;
+ dma_desc->ack_reqd = (flags & DMA_CTRL_ACK) ? false : true;
+ if (dma_desc->ack_reqd)
+ dma_desc->txd.flags = DMA_CTRL_ACK;
+
+ /*
+ * Make sure that mode should not be conflicting with currently
+ * configured mode.
+ */
+ if (!init_dma_mode(tdc, DMA_MODE_ONCE)) {
+ dev_err(tdc2dev(tdc), "Conflict in dma modes\n");
+ goto fail;
+ }
+
+ return &dma_desc->txd;
+
+fail:
+ tegra_dma_desc_put(tdc, dma_desc);
+ return NULL;
+}
+
+struct dma_async_tx_descriptor *tegra_dma_prep_dma_cyclic(
+ struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_len,
+ size_t period_len, enum dma_transfer_direction direction,
+ void *context)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ struct tegra_dma_desc *dma_desc = NULL;
+ struct tegra_dma_sg_req *sg_req = NULL;
+ unsigned long csr, ahb_seq, apb_ptr, apb_seq;
+ int len;
+ bool half_buffer_notify;
+ enum dma_transfer_mode new_mode;
+ size_t remain_len;
+ dma_addr_t mem = buf_addr;
+ u32 burst_size;
+ enum dma_slave_buswidth slave_bw;
+
+ if (!buf_len) {
+ dev_err(tdc2dev(tdc),
+ "Buffer length is invalid len %d\n", buf_len);
+ }
+
+ if (!tdc->config_init) {
+ dev_err(tdc2dev(tdc),
+ "DMA is not configured for slave\n");
+ return NULL;
+ }
+
+ if (tdc->busy) {
+ dev_err(tdc2dev(tdc),
+ "DMA is already started, can not accept any more requests\n");
+ return NULL;
+ }
+
+ /*
+ * We only support cycle transfer when buf_len is multiple of
+ * period_len.
+ * With period of buf_len, it will set DMA mode DMA_MODE_CYCLE
+ * with one request.
+ * With period of buf_len/2, it will set DMA mode
+ * DMA_MODE_CYCLE_HALF_NOTIFY with one requsts.
+ * Othercase, the transfer is broken in smaller requests of size
+ * of period_len and the transfer continues forever in cyclic way
+ * DMA mode of DMA_MODE_CYCLE.
+ * If period_len is zero then assume DMA mode DMA_MODE_CYCLE.
+ * We also allow to take more number of requests till DMA is
+ * not started. The driver will loop over all requests.
+ * Once DMA is started then new requests can be queued only after
+ * terminating the dma.
+ */
+ if (!period_len)
+ period_len = buf_len;
+
+ if (buf_len % period_len) {
+ dev_err(tdc2dev(tdc),
+ "buf_len %d should be multiple of period_len %d\n",
+ buf_len, period_len);
+ return NULL;
+ }
+
+ half_buffer_notify = (buf_len == (2 * period_len)) ? true : false;
+ len = (half_buffer_notify) ? buf_len / 2 : period_len;
+ if ((len & 3) || (buf_addr & 3) ||
+ (len > tdc->tdma->chip_data->max_dma_count)) {
+ dev_err(tdc2dev(tdc),
+ "Dma length/memory address is not correct\n");
+ return NULL;
+ }
+
+ ahb_seq = AHB_SEQ_INTR_ENB;
+ ahb_seq |= AHB_SEQ_WRAP_NONE << AHB_SEQ_WRAP_SHIFT;
+ ahb_seq |= AHB_SEQ_BUS_WIDTH_32;
+ if (half_buffer_notify)
+ ahb_seq |= AHB_SEQ_DBL_BUF;
+
+ csr = CSR_FLOW | CSR_IE_EOC;
+ csr |= tdc->dma_sconfig.slave_id << CSR_REQ_SEL_SHIFT;
+
+ apb_seq = APB_SEQ_WRAP_WORD_1;
+
+ switch (direction) {
+ case DMA_MEM_TO_DEV:
+ apb_ptr = tdc->dma_sconfig.dst_addr;
+ apb_seq |= get_bus_width(tdc->dma_sconfig.dst_addr_width);
+ burst_size = tdc->dma_sconfig.dst_maxburst;
+ slave_bw = tdc->dma_sconfig.dst_addr_width;
+ csr |= CSR_DIR;
+ break;
+
+ case DMA_DEV_TO_MEM:
+ apb_ptr = tdc->dma_sconfig.src_addr;
+ apb_seq |= get_bus_width(tdc->dma_sconfig.src_addr_width);
+ burst_size = tdc->dma_sconfig.src_maxburst;
+ slave_bw = tdc->dma_sconfig.src_addr_width;
+ break;
+ default:
+ dev_err(tdc2dev(tdc), "Dma direction is not supported\n");
+ return NULL;
+ }
+
+ dma_desc = tegra_dma_desc_get(tdc);
+ if (!dma_desc) {
+ dev_err(tdc2dev(tdc), "not enough descriptors available\n");
+ goto fail;
+ }
+ INIT_LIST_HEAD(&dma_desc->tx_list);
+
+ dma_desc->bytes_transferred = 0;
+ dma_desc->bytes_requested = buf_len;
+ remain_len = (half_buffer_notify) ? len : buf_len;
+
+ while (remain_len) {
+ sg_req = tegra_dma_sg_req_get(tdc);
+ if (!sg_req) {
+ dev_err(tdc2dev(tdc), "Dma sg-req not available\n");
+ goto fail;
+ }
+
+ ahb_seq |= get_burst_size(tdc, burst_size, slave_bw, len);
+ sg_req->ch_regs.apb_ptr = apb_ptr;
+ sg_req->ch_regs.ahb_ptr = mem;
+ sg_req->ch_regs.csr = csr | ((len - 4) & 0xFFFC);
+ sg_req->ch_regs.apb_seq = apb_seq;
+ sg_req->ch_regs.ahb_seq = ahb_seq;
+ sg_req->configured = false;
+ sg_req->half_done = false;
+ sg_req->last_sg = false;
+ sg_req->dma_desc = dma_desc;
+ sg_req->req_len = len;
+
+ list_add_tail(&sg_req->node, &dma_desc->tx_list);
+ remain_len -= len;
+ mem += len;
+ }
+ sg_req->last_sg = true;
+ dma_desc->ack_reqd = true;
+ dma_desc->txd.flags = DMA_CTRL_ACK;
+
+ /*
+ * We can not change the DMA mode once it is initialized
+ * until all desc are terminated.
+ */
+ new_mode = (half_buffer_notify) ?
+ DMA_MODE_CYCLE_HALF_NOTIFY : DMA_MODE_CYCLE;
+ if (!init_dma_mode(tdc, new_mode)) {
+ dev_err(tdc2dev(tdc), "Conflict in dma modes\n");
+ goto fail;
+ }
+
+ return &dma_desc->txd;
+
+fail:
+ tegra_dma_desc_put(tdc, dma_desc);
+ return NULL;
+}
+
+static int tegra_dma_alloc_chan_resources(struct dma_chan *dc)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+ int total_desc;
+
+ total_desc = allocate_tegra_desc(tdc, DMA_NR_DESCS_PER_CHANNEL,
+ DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC);
+ dma_cookie_init(&tdc->dma_chan);
+ dev_dbg(tdc2dev(tdc),
+ "%s(): allocated %d descriptors\n", __func__, total_desc);
+ tdc->config_init = false;
+ return total_desc;
+}
+
+static void tegra_dma_free_chan_resources(struct dma_chan *dc)
+{
+ struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
+
+ struct tegra_dma_desc *dma_desc;
+ struct tegra_dma_sg_req *sg_req;
+ struct list_head dma_desc_list;
+ struct list_head sg_req_list;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&dma_desc_list);
+ INIT_LIST_HEAD(&sg_req_list);
+
+ dev_dbg(tdc2dev(tdc),
+ "%s(): channel %d and desc freeing %d\n",
+ __func__, tdc->id, tdc->descs_allocated);
+
+ if (tdc->busy)
+ tegra_dma_terminate_all(dc);
+
+ spin_lock_irqsave(&tdc->lock, flags);
+ list_splice_init(&tdc->pending_sg_req, &sg_req_list);
+ list_splice_init(&tdc->free_sg_req, &sg_req_list);
+ list_splice_init(&tdc->free_dma_desc, &dma_desc_list);
+ list_splice_init(&tdc->wait_ack_dma_desc, &dma_desc_list);
+ INIT_LIST_HEAD(&tdc->cb_desc);
+ tdc->descs_allocated = 0;
+ tdc->config_init = false;
+ spin_unlock_irqrestore(&tdc->lock, flags);
+
+ while (!list_empty(&dma_desc_list)) {
+ dma_desc = list_first_entry(&dma_desc_list,
+ typeof(*dma_desc), node);
+ list_del(&dma_desc->node);
+ }
+
+ while (!list_empty(&sg_req_list)) {
+ sg_req = list_first_entry(&sg_req_list, typeof(*sg_req), node);
+ list_del(&sg_req->node);
+ }
+}
+
+/* Tegra20 specific DMA controller information */
+static struct tegra_dma_chip_data tegra20_dma_chip_data = {
+ .nr_channels = 16,
+ .max_dma_count = 1024UL * 64,
+};
+
+/* Tegra30 specific DMA controller information */
+static struct tegra_dma_chip_data tegra30_dma_chip_data = {
+ .nr_channels = 32,
+ .max_dma_count = 1024UL * 64,
+};
+
+#if defined(CONFIG_OF)
+static const struct of_device_id tegra_dma_of_match[] __devinitconst = {
+ {
+ .compatible = "nvidia,tegra30-apbdma-new",
+ .data = &tegra30_dma_chip_data,
+ }, {
+ .compatible = "nvidia,tegra20-apbdma-new",
+ .data = &tegra20_dma_chip_data,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, tegra_dma_of_match);
+#else
+#define tegra_dma_of_match NULL
+#endif
+
+static int __devinit tegra_dma_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct tegra_dma *tdma;
+ int ret;
+ int i;
+ struct tegra_dma_chip_data *cdata = NULL;
+
+ if (pdev->dev.of_node) {
+ const struct of_device_id *match;
+ match = of_match_device(tegra_dma_of_match, &pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "Error: No device match found\n");
+ return -ENODEV;
+ }
+ cdata = match->data;
+ } else {
+ /* If no device tree then fallback to tegra20 */
+ cdata = &tegra20_dma_chip_data;
+ }
+
+ tdma = devm_kzalloc(&pdev->dev, sizeof(struct tegra_dma) +
+ cdata->nr_channels * sizeof(struct tegra_dma_channel),
+ GFP_KERNEL);
+ if (!tdma) {
+ dev_err(&pdev->dev, "Error: memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ tdma->dev = &pdev->dev;
+ tdma->chip_data = cdata;
+ platform_set_drvdata(pdev, tdma);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no mem resource for DMA\n");
+ return -EINVAL;
+ }
+
+ tdma->base_addr = devm_request_and_ioremap(&pdev->dev, res);
+ if (!tdma->base_addr) {
+ dev_err(&pdev->dev,
+ "Cannot request memregion/iomap dma address\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ tdma->dma_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tdma->dma_clk)) {
+ dev_err(&pdev->dev, "Error: Missing controller clock");
+ return PTR_ERR(tdma->dma_clk);
+ }
+
+ spin_lock_init(&tdma->global_lock);
+
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = tegra_dma_runtime_resume(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "dma_runtime_resume failed %d\n",
+ ret);
+ goto err_pm_disable;
+ }
+ }
+
+ /* Reset DMA controller */
+ tegra_periph_reset_assert(tdma->dma_clk);
+ tegra_periph_reset_deassert(tdma->dma_clk);
+
+ /* Enable global DMA registers */
+ tdma_write(tdma, APB_DMA_GEN, GEN_ENABLE);
+ tdma_write(tdma, APB_DMA_CNTRL, 0);
+ tdma_write(tdma, APB_DMA_IRQ_MASK_SET, 0xFFFFFFFFul);
+
+ INIT_LIST_HEAD(&tdma->dma_dev.channels);
+ for (i = 0; i < cdata->nr_channels; i++) {
+ struct tegra_dma_channel *tdc = &tdma->channels[i];
+ char irq_name[30];
+
+ tdc->chan_base_offset = DMA_CHANNEL_BASE_ADDRESS_OFFSET +
+ i * DMA_CHANNEL_REGISTER_SIZE;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+ if (!res) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev,
+ "Irq resource not found for channel %d\n", i);
+ goto err_irq;
+ }
+ tdc->irq = res->start;
+ snprintf(irq_name, sizeof(irq_name), "apbdma.%d", i);
+ ret = devm_request_irq(&pdev->dev, tdc->irq,
+ tegra_dma_isr, 0, irq_name, tdc);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "request_irq failed for channel %d error %d\n",
+ i, ret);
+ goto err_irq;
+ }
+
+ tdc->dma_chan.device = &tdma->dma_dev;
+ dma_cookie_init(&tdc->dma_chan);
+ list_add_tail(&tdc->dma_chan.device_node,
+ &tdma->dma_dev.channels);
+ tdc->tdma = tdma;
+ tdc->id = i;
+
+ tasklet_init(&tdc->tasklet,
+ tegra_dma_tasklet, (unsigned long)tdc);
+ spin_lock_init(&tdc->lock);
+
+ INIT_LIST_HEAD(&tdc->pending_sg_req);
+ INIT_LIST_HEAD(&tdc->free_sg_req);
+ INIT_LIST_HEAD(&tdc->free_dma_desc);
+ INIT_LIST_HEAD(&tdc->wait_ack_dma_desc);
+ INIT_LIST_HEAD(&tdc->cb_desc);
+ }
+
+ dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask);
+ dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask);
+ tdma->dma_dev.dev = &pdev->dev;
+ tdma->dma_dev.device_alloc_chan_resources =
+ tegra_dma_alloc_chan_resources;
+ tdma->dma_dev.device_free_chan_resources =
+ tegra_dma_free_chan_resources;
+ tdma->dma_dev.device_prep_slave_sg = tegra_dma_prep_slave_sg;
+ tdma->dma_dev.device_prep_dma_cyclic = tegra_dma_prep_dma_cyclic;
+ tdma->dma_dev.device_control = tegra_dma_device_control;
+ tdma->dma_dev.device_tx_status = tegra_dma_tx_status;
+ tdma->dma_dev.device_issue_pending = tegra_dma_issue_pending;
+
+ ret = dma_async_device_register(&tdma->dma_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Error in registering Tegra APB DMA driver %d\n", ret);
+ goto err_irq;
+ }
+ dev_info(&pdev->dev, "Tegra APB DMA Controller, %d channels\n",
+ cdata->nr_channels);
+ return 0;
+
+err_irq:
+ while (--i >= 0) {
+ struct tegra_dma_channel *tdc = &tdma->channels[i];
+ tasklet_kill(&tdc->tasklet);
+ }
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ clk_put(tdma->dma_clk);
+ return ret;
+}
+
+static int __devexit tegra_dma_remove(struct platform_device *pdev)
+{
+ struct tegra_dma *tdma = platform_get_drvdata(pdev);
+ int i;
+ struct tegra_dma_channel *tdc;
+
+ dma_async_device_unregister(&tdma->dma_dev);
+
+ for (i = 0; i < tdma->chip_data->nr_channels; ++i) {
+ tdc = &tdma->channels[i];
+ tasklet_kill(&tdc->tasklet);
+ }
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ tegra_dma_runtime_suspend(&pdev->dev);
+
+ clk_put(tdma->dma_clk);
+
+ return 0;
+}
+
+static int tegra_dma_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_dma *tdma = platform_get_drvdata(pdev);
+
+ clk_disable(tdma->dma_clk);
+ return 0;
+}
+
+static int tegra_dma_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_dma *tdma = platform_get_drvdata(pdev);
+ int ret;
+ ret = clk_enable(tdma->dma_clk);
+ if (ret < 0) {
+ dev_err(dev, "clk_enable failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int tegra_dma_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_dma *tdma = platform_get_drvdata(pdev);
+
+ /* Store the global config register */
+ tdma->reg_gen = tdma_read(tdma, APB_DMA_GEN);
+
+ tegra_dma_runtime_suspend(dev);
+ return 0;
+}
+
+static int tegra_dma_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_dma *tdma = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = tegra_dma_runtime_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "dma runtime_resume failed %d\n", ret);
+ return ret;
+ }
+
+ /* Restore config register */
+ tdma_write(tdma, APB_DMA_GEN, tdma->reg_gen);
+ tdma_write(tdma, APB_DMA_CNTRL, 0);
+ tdma_write(tdma, APB_DMA_IRQ_MASK_SET, 0xFFFFFFFFul);
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_dma_dev_pm_ops __devinitconst = {
+ .suspend_noirq = tegra_dma_suspend_noirq,
+ .resume_noirq = tegra_dma_resume_noirq,
+#ifdef CONFIG_PM_RUNTIME
+ .runtime_suspend = tegra_dma_runtime_suspend,
+ .runtime_resume = tegra_dma_runtime_resume,
+#endif
+};
+
+static struct platform_driver tegra_dmac_driver = {
+ .driver = {
+ .name = "tegra-apbdma",
+ .owner = THIS_MODULE,
+ .pm = &tegra_dma_dev_pm_ops,
+ .of_match_table = tegra_dma_of_match,
+ },
+ .probe = tegra_dma_probe,
+ .remove = __devexit_p(tegra_dma_remove),
+};
+
+static int __init tegra_dmac_init(void)
+{
+ return platform_driver_register(&tegra_dmac_driver);
+}
+arch_initcall_sync(tegra_dmac_init);
+
+static void __exit tegra_dmac_exit(void)
+{
+ platform_driver_unregister(&tegra_dmac_driver);
+}
+module_exit(tegra_dmac_exit);
+
+MODULE_DESCRIPTION("NVIDIA Tegra APB DMA Controller driver");
+MODULE_AUTHOR("Laxman Dewangan <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tegra-apbdma");
--
1.7.1.1
The dma controller like Nvidia's Tegra Dma controller
supports the different slave requestor id from different slave.
This need to be configure in dma controller to handle the request
properly.
Adding the slave-id in the slave configuration so that information
can be passed from client when configuring for slave.
Signed-off-by: Laxman Dewangan <[email protected]>
---
This change is require to get rid of tegra_dma header to pass the
slave requestor id.
include/linux/dmaengine.h | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index 1082698..b0b275f 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -338,6 +338,9 @@ enum dma_slave_buswidth {
* @device_fc: Flow Controller Settings. Only valid for slave channels. Fill
* with 'true' if peripheral should be flow controller. Direction will be
* selected at Runtime.
+ * @slave_id: Slave requester id. Only valid for slave channels. The dma
+ * slave peripheral will have unique id as dma requester which need to be
+ * pass as slave config.
*
* This struct is passed in as configuration data to a DMA engine
* in order to set up a certain channel for DMA transport at runtime.
@@ -365,6 +368,7 @@ struct dma_slave_config {
u32 src_maxburst;
u32 dst_maxburst;
bool device_fc;
+ int slave_id;
};
static inline const char *dma_chan_name(struct dma_chan *chan)
--
1.7.1.1
On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
> The dma controller like Nvidia's Tegra Dma controller
> supports the different slave requestor id from different slave.
> This need to be configure in dma controller to handle the request
> properly.
>
> Adding the slave-id in the slave configuration so that information
> can be passed from client when configuring for slave.
>
> Signed-off-by: Laxman Dewangan <[email protected]>
> ---
>
> This change is require to get rid of tegra_dma header to pass the
> slave requestor id.
>
> include/linux/dmaengine.h | 4 ++++
> 1 files changed, 4 insertions(+), 0 deletions(-)
>
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index 1082698..b0b275f 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -338,6 +338,9 @@ enum dma_slave_buswidth {
> * @device_fc: Flow Controller Settings. Only valid for slave channels. Fill
> * with 'true' if peripheral should be flow controller. Direction will be
> * selected at Runtime.
> + * @slave_id: Slave requester id. Only valid for slave channels. The dma
> + * slave peripheral will have unique id as dma requester which need to be
> + * pass as slave config.
> *
> * This struct is passed in as configuration data to a DMA engine
> * in order to set up a certain channel for DMA transport at runtime.
> @@ -365,6 +368,7 @@ struct dma_slave_config {
> u32 src_maxburst;
> u32 dst_maxburst;
> bool device_fc;
> + int slave_id;
This wont be negative, so perhaps an unsigned value?
> };
>
> static inline const char *dma_chan_name(struct dma_chan *chan)
--
~Vinod
On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
> Adding dmaengine based NVIDIA's Tegra APB dma driver.
> This driver support the slave mode of data transfer from
> peripheral to memory and vice versa.
> The driver supports for the cyclic and non-cyclic mode
> of data transfer.
>
> Signed-off-by: Laxman Dewangan <[email protected]>
> ---
> Changes from V1:
> - Incorportaed review comments from Vinod.
> - get rid of the tegra_dma header.
> - Having clock control through runtime pm.
> - Remove regmap support.
>
> drivers/dma/Kconfig | 13 +
> drivers/dma/Makefile | 1 +
> drivers/dma/tegra_dma.c | 1714 +++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1728 insertions(+), 0 deletions(-)
> create mode 100644 drivers/dma/tegra_dma.c
>
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index ef378b5..26d9a23 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -148,6 +148,19 @@ config TXX9_DMAC
> Support the TXx9 SoC internal DMA controller. This can be
> integrated in chips such as the Toshiba TX4927/38/39.
>
> +config TEGRA_DMA
> + bool "NVIDIA Tegra DMA support"
> + depends on ARCH_TEGRA
> + select DMA_ENGINE
> + help
> + Support for the NVIDIA Tegra DMA controller driver. The DMA
> + controller is having multiple DMA channel which can be configured
> + for different peripherals like audio, UART, SPI, I2C etc which is
> + in APB bus.
> + This DMA controller transfers data from memory to peripheral fifo
> + address or vice versa. It does not support memory to memory data
> + transfer.
> +
> config SH_DMAE
> tristate "Renesas SuperH DMAC support"
> depends on (SUPERH && SH_DMA) || (ARM && ARCH_SHMOBILE)
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index 86b795b..3aaa63a 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_MXS_DMA) += mxs-dma.o
> obj-$(CONFIG_TIMB_DMA) += timb_dma.o
> obj-$(CONFIG_SIRF_DMA) += sirf-dma.o
> obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
> +obj-$(CONFIG_TEGRA_DMA) += tegra_dma.o
> obj-$(CONFIG_PL330_DMA) += pl330.o
> obj-$(CONFIG_PCH_DMA) += pch_dma.o
> obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
> diff --git a/drivers/dma/tegra_dma.c b/drivers/dma/tegra_dma.c
> new file mode 100644
> index 0000000..3c599ca
> --- /dev/null
> +++ b/drivers/dma/tegra_dma.c
> @@ -0,0 +1,1714 @@
> +/*
> + * DMA driver for Nvidia's Tegra APB DMA controller.
> + *
> + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/bitops.h>
> +#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/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +
> +#include <mach/clk.h>
> +#include "dmaengine.h"
> +
> +#define APB_DMA_GEN 0x0
> +#define GEN_ENABLE BIT(31)
> +
> +#define APB_DMA_CNTRL 0x010
> +#define APB_DMA_IRQ_MASK 0x01c
> +#define APB_DMA_IRQ_MASK_SET 0x020
> +
> +/* CSR register */
> +#define APB_DMA_CHAN_CSR 0x00
> +#define CSR_ENB BIT(31)
> +#define CSR_IE_EOC BIT(30)
> +#define CSR_HOLD BIT(29)
> +#define CSR_DIR BIT(28)
> +#define CSR_ONCE BIT(27)
> +#define CSR_FLOW BIT(21)
> +#define CSR_REQ_SEL_SHIFT 16
> +#define CSR_WCOUNT_MASK 0xFFFC
> +
> +/* STATUS register */
> +#define APB_DMA_CHAN_STA 0x004
> +#define STA_BUSY BIT(31)
> +#define STA_ISE_EOC BIT(30)
> +#define STA_HALT BIT(29)
> +#define STA_PING_PONG BIT(28)
> +#define STA_COUNT_SHIFT 2
> +#define STA_COUNT_MASK 0xFFFC
> +
> +/* AHB memory address */
> +#define APB_DMA_CHAN_AHB_PTR 0x010
> +
> +/* AHB sequence register */
> +#define APB_DMA_CHAN_AHB_SEQ 0x14
> +#define AHB_SEQ_INTR_ENB BIT(31)
> +#define AHB_SEQ_BUS_WIDTH_SHIFT 28
> +#define AHB_SEQ_BUS_WIDTH_8 (0 << AHB_SEQ_BUS_WIDTH_SHIFT)
> +#define AHB_SEQ_BUS_WIDTH_16 (1 << AHB_SEQ_BUS_WIDTH_SHIFT)
> +#define AHB_SEQ_BUS_WIDTH_32 (2 << AHB_SEQ_BUS_WIDTH_SHIFT)
> +#define AHB_SEQ_BUS_WIDTH_64 (3 << AHB_SEQ_BUS_WIDTH_SHIFT)
> +#define AHB_SEQ_BUS_WIDTH_128 (4 << AHB_SEQ_BUS_WIDTH_SHIFT)
> +#define AHB_SEQ_DATA_SWAP BIT(27)
> +#define AHB_SEQ_BURST_1 (4 << 24)
> +#define AHB_SEQ_BURST_4 (5 << 24)
> +#define AHB_SEQ_BURST_8 (6 << 24)
> +#define AHB_SEQ_DBL_BUF BIT(19)
> +#define AHB_SEQ_WRAP_SHIFT 16
> +#define AHB_SEQ_WRAP_NONE 0
> +
> +/* APB address */
> +#define APB_DMA_CHAN_APB_PTR 0x018
> +
> +/* APB sequence register */
> +#define APB_DMA_CHAN_APB_SEQ 0x01c
> +#define APB_SEQ_BUS_WIDTH_SHIFT 28
> +#define APB_SEQ_BUS_WIDTH_8 (0 << APB_SEQ_BUS_WIDTH_SHIFT)
> +#define APB_SEQ_BUS_WIDTH_16 (1 << APB_SEQ_BUS_WIDTH_SHIFT)
> +#define APB_SEQ_BUS_WIDTH_32 (2 << APB_SEQ_BUS_WIDTH_SHIFT)
> +#define APB_SEQ_BUS_WIDTH_64 (3 << APB_SEQ_BUS_WIDTH_SHIFT)
> +#define APB_SEQ_BUS_WIDTH_128 (4 << APB_SEQ_BUS_WIDTH_SHIFT)
> +#define APB_SEQ_DATA_SWAP BIT(27)
> +#define APB_SEQ_WRAP_SHIFT 16
> +#define APB_SEQ_WRAP_WORD_1 (1 << APB_SEQ_WRAP_SHIFT)
> +
> +/*
> + * If any burst is in flight and DMA paused then this is the time to complete
> + * on-flight burst and update DMA status register.
> + */
> +#define DMA_BURST_COMPLETE_TIME 20
> +
> +/* Channel base address offset from APBDMA base address */
> +#define DMA_CHANNEL_BASE_ADDRESS_OFFSET 0x1000
> +
> +/* DMA channel register space size */
> +#define DMA_CHANNEL_REGISTER_SIZE 0x20
> +
> +/*
> + * Initial number of descriptors to allocate for each channel during
> + * allocation. More descriptors will be allocated dynamically if
> + * client needs it.
> + */
> +#define DMA_NR_DESCS_PER_CHANNEL 4
> +#define DMA_NR_REQ_PER_DESC 8
all of these should be namespaced.
APB and AHB are fairly generic names.
> +
> +struct tegra_dma;
> +
> +/*
> + * tegra_dma_chip_data Tegra chip specific DMA data
> + * @nr_channels: Number of channels available in the controller.
> + * @max_dma_count: Maximum DMA transfer count supported by DMA controller.
> + */
> +struct tegra_dma_chip_data {
> + int nr_channels;
> + int max_dma_count;
> +};
> +
> +/*
> + * dma_transfer_mode: Different DMA transfer mode.
> + * DMA_MODE_ONCE: DMA transfer the configured buffer once and at the end of
> + * transfer, DMA stops automatically and generates interrupt
> + * if enabled. SW need to reprogram DMA for next transfer.
> + * DMA_MODE_CYCLE: DMA keeps transferring the same buffer again and again
> + * until DMA stopped explicitly by SW or another buffer
> + * configured. After transfer completes, DMA again starts
> + * transfer from beginning of buffer without SW intervention.
> + * If any new address/size is configured during buffer transfer
> + * then DMA start transfer with new configuration when the
> + * current transfer has completed otherwise it will keep
> + * transferring with old configuration. It also generates
> + * the interrupt each time the buffer transfer completes.
> + * DMA_MODE_CYCLE_HALF_NOTIFY: This mode is identical to DMA_MODE_CYCLE,
> + * except that an additional interrupt is generated half-way
> + * through the buffer. This is kind of ping-pong buffer mechanism.
> + * If SW wants to change the address/size of the buffer then
> + * it must only change when DMA is transferring the second
> + * half of buffer. In DMA configuration, it only need to
> + * configure starting of first buffer and size of the half buffer.
> + */
> +enum dma_transfer_mode {
> + DMA_MODE_NONE,
> + DMA_MODE_ONCE,
> + DMA_MODE_CYCLE,
> + DMA_MODE_CYCLE_HALF_NOTIFY,
> +};
I dont understand why you would need to keep track of these?
You get a request for DMA. You have properties of transfer. You prepare
you descriptors and then submit.
Why would you need to create above modes and remember them?
> +
> +/* Dma channel registers */
> +struct tegra_dma_channel_regs {
> + unsigned long csr;
> + unsigned long ahb_ptr;
> + unsigned long apb_ptr;
> + unsigned long ahb_seq;
> + unsigned long apb_seq;
> +};
> +
> +/*
> + * tegra_dma_sg_req: Dma request details to configure hardware. This
> + * contains the details for one transfer to configure DMA hw.
> + * The client's request for data transfer can be broken into multiple
> + * sub-transfer as per requester details and hw support.
> + * This sub transfer get added in the list of transfer and point to Tegra
> + * DMA descriptor which manages the transfer details.
> + */
> +struct tegra_dma_sg_req {
> + struct tegra_dma_channel_regs ch_regs;
> + int req_len;
> + bool configured;
> + bool last_sg;
> + bool half_done;
> + struct list_head node;
> + struct tegra_dma_desc *dma_desc;
> +};
> +
> +/*
> + * tegra_dma_desc: Tegra DMA descriptors which manages the client requests.
> + * This descriptor keep track of transfer status, callbacks and request
> + * counts etc.
> + */
> +struct tegra_dma_desc {
> + struct dma_async_tx_descriptor txd;
> + int bytes_requested;
> + int bytes_transferred;
> + enum dma_status dma_status;
> + struct list_head node;
> + struct list_head tx_list;
> + struct list_head cb_node;
> + bool ack_reqd;
> + bool cb_due;
what are these two for, seems redundant to me
> + dma_cookie_t cookie;
> +};
> +
> +struct tegra_dma_channel;
> +
> +typedef void (*dma_isr_handler)(struct tegra_dma_channel *tdc,
> + bool to_terminate);
> +
> +/* tegra_dma_channel: Channel specific information */
> +struct tegra_dma_channel {
> + struct dma_chan dma_chan;
> + bool config_init;
> + int id;
> + int irq;
> + unsigned long chan_base_offset;
> + spinlock_t lock;
> + bool busy;
> + enum dma_transfer_mode dma_mode;
> + int descs_allocated;
> + struct tegra_dma *tdma;
> +
> + /* Different lists for managing the requests */
> + struct list_head free_sg_req;
> + struct list_head pending_sg_req;
> + struct list_head free_dma_desc;
> + struct list_head wait_ack_dma_desc;
> + struct list_head cb_desc;
> +
> + /* isr handler and tasklet for bottom half of isr handling */
> + dma_isr_handler isr_handler;
> + struct tasklet_struct tasklet;
> + dma_async_tx_callback callback;
> + void *callback_param;
> +
> + /* Channel-slave specific configuration */
> + struct dma_slave_config dma_sconfig;
> +};
> +
> +/* tegra_dma: Tegra DMA specific information */
> +struct tegra_dma {
> + struct dma_device dma_dev;
> + struct device *dev;
> + struct clk *dma_clk;
> + spinlock_t global_lock;
> + void __iomem *base_addr;
> + struct tegra_dma_chip_data *chip_data;
> +
> + /* Some register need to be cache before suspend */
> + u32 reg_gen;
> +
> + /* Last member of the structure */
> + struct tegra_dma_channel channels[0];
> +};
> +
> +static inline void tdma_write(struct tegra_dma *tdma, u32 reg, u32 val)
> +{
> + writel(val, tdma->base_addr + reg);
> +}
> +
> +static inline u32 tdma_read(struct tegra_dma *tdma, u32 reg)
> +{
> + return readl(tdma->base_addr + reg);
> +}
> +
> +static inline void tdc_write(struct tegra_dma_channel *tdc,
> + u32 reg, u32 val)
> +{
> + writel(val, tdc->tdma->base_addr + tdc->chan_base_offset + reg);
> +}
> +
> +static inline u32 tdc_read(struct tegra_dma_channel *tdc, u32 reg)
> +{
> + return readl(tdc->tdma->base_addr + tdc->chan_base_offset + reg);
> +}
> +
> +static inline struct tegra_dma_channel *to_tegra_dma_chan(struct dma_chan *dc)
> +{
> + return container_of(dc, struct tegra_dma_channel, dma_chan);
> +}
> +
> +static inline struct tegra_dma_desc *txd_to_tegra_dma_desc(
> + struct dma_async_tx_descriptor *td)
> +{
> + return container_of(td, struct tegra_dma_desc, txd);
> +}
> +
> +static inline struct device *tdc2dev(struct tegra_dma_channel *tdc)
> +{
> + return &tdc->dma_chan.dev->device;
> +}
> +
> +static dma_cookie_t tegra_dma_tx_submit(struct dma_async_tx_descriptor *tx);
> +static int tegra_dma_runtime_suspend(struct device *dev);
> +static int tegra_dma_runtime_resume(struct device *dev);
> +
> +static int allocate_tegra_desc(struct tegra_dma_channel *tdc,
> + int ndma_desc, int nsg_req)
pls follow single convention for naming in driver eithe xxx_tegra_yyy or
tegra_xxx_yyy... BUT not both!
> +{
> + int i;
> + struct tegra_dma_desc *dma_desc;
> + struct tegra_dma_sg_req *sg_req;
> + struct dma_chan *dc = &tdc->dma_chan;
> + struct list_head dma_desc_list;
> + struct list_head sg_req_list;
> + unsigned long flags;
> +
> + INIT_LIST_HEAD(&dma_desc_list);
> + INIT_LIST_HEAD(&sg_req_list);
> +
> + /* Initialize DMA descriptors */
> + for (i = 0; i < ndma_desc; ++i) {
> + dma_desc = kzalloc(sizeof(struct tegra_dma_desc), GFP_ATOMIC);
kernel convention is kzalloc(sizeof(*dma_desc),....
> + if (!dma_desc) {
> + dev_err(tdc2dev(tdc),
> + "%s(): Memory allocation fails\n", __func__);
> + goto fail_alloc;
> + }
> +
> + dma_async_tx_descriptor_init(&dma_desc->txd, dc);
> + dma_desc->txd.tx_submit = tegra_dma_tx_submit;
> + dma_desc->txd.flags = DMA_CTRL_ACK;
> + list_add_tail(&dma_desc->node, &dma_desc_list);
> + }
> +
> + /* Initialize req descriptors */
> + for (i = 0; i < nsg_req; ++i) {
> + sg_req = kzalloc(sizeof(struct tegra_dma_sg_req), GFP_ATOMIC);
> + if (!sg_req) {
> + dev_err(tdc2dev(tdc),
> + "%s(): Memory allocation fails\n", __func__);
> + goto fail_alloc;
> + }
> + list_add_tail(&sg_req->node, &sg_req_list);
> + }
> +
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (ndma_desc) {
> + tdc->descs_allocated += ndma_desc;
> + list_splice(&dma_desc_list, &tdc->free_dma_desc);
> + }
> +
> + if (nsg_req)
> + list_splice(&sg_req_list, &tdc->free_sg_req);
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + return tdc->descs_allocated;
> +
> +fail_alloc:
> + while (!list_empty(&dma_desc_list)) {
> + dma_desc = list_first_entry(&dma_desc_list,
> + typeof(*dma_desc), node);
> + list_del(&dma_desc->node);
> + }
> +
> + while (!list_empty(&sg_req_list)) {
> + sg_req = list_first_entry(&sg_req_list, typeof(*sg_req), node);
> + list_del(&sg_req->node);
> + }
> + return 0;
> +}
> +
> +/* Get DMA desc from free list, if not there then allocate it */
> +static struct tegra_dma_desc *tegra_dma_desc_get(struct tegra_dma_channel *tdc)
> +{
> + struct tegra_dma_desc *dma_desc = NULL;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&tdc->lock, flags);
> +
> + /* Check from free list desc */
> + if (!list_empty(&tdc->free_dma_desc)) {
> + dma_desc = list_first_entry(&tdc->free_dma_desc,
> + typeof(*dma_desc), node);
> + list_del(&dma_desc->node);
> + goto end;
> + }
sorry I dont like this jumping and returning for two lines of code.
Makes much sense to return from here.
> +
> + /*
> + * Check list with desc which are waiting for ack, may be it
> + * got acked from client.
> + */
> + if (!list_empty(&tdc->wait_ack_dma_desc)) {
> + list_for_each_entry(dma_desc, &tdc->wait_ack_dma_desc, node) {
> + if (async_tx_test_ack(&dma_desc->txd)) {
> + list_del(&dma_desc->node);
> + goto end;
> + }
> + }
> + }
> +
> + /* There is no free desc, allocate it */
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + dev_dbg(tdc2dev(tdc),
> + "Allocating more descriptors for channel %d\n", tdc->id);
> + allocate_tegra_desc(tdc, DMA_NR_DESCS_PER_CHANNEL,
> + DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC);
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (list_empty(&tdc->free_dma_desc))
> + goto end;
> +
> + dma_desc = list_first_entry(&tdc->free_dma_desc,
> + typeof(*dma_desc), node);
> + list_del(&dma_desc->node);
> +end:
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + return dma_desc;
> +}
> +
> +static void tegra_dma_desc_put(struct tegra_dma_channel *tdc,
> + struct tegra_dma_desc *dma_desc)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (!list_empty(&dma_desc->tx_list))
> + list_splice_init(&dma_desc->tx_list, &tdc->free_sg_req);
> + list_add_tail(&dma_desc->node, &tdc->free_dma_desc);
> + spin_unlock_irqrestore(&tdc->lock, flags);
> +}
> +
> +static void tegra_dma_desc_done_locked(struct tegra_dma_channel *tdc,
> + struct tegra_dma_desc *dma_desc)
> +{
> + if (dma_desc->ack_reqd)
> + list_add_tail(&dma_desc->node, &tdc->wait_ack_dma_desc);
what does ack_reqd mean?
Do you ahve unlocked version of this function, name suggests so...
> + else
> + list_add_tail(&dma_desc->node, &tdc->free_dma_desc);
> +}
> +
> +static struct tegra_dma_sg_req *tegra_dma_sg_req_get(
> + struct tegra_dma_channel *tdc)
in this and other functions, you have used goto to unlock and return.
Rather than that why don't you simplify code by calling these while
holding lock
> +{
> + struct tegra_dma_sg_req *sg_req = NULL;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (list_empty(&tdc->free_sg_req)) {
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + dev_dbg(tdc2dev(tdc),
> + "Reallocating sg_req for channel %d\n", tdc->id);
> + allocate_tegra_desc(tdc, 0,
> + DMA_NR_DESCS_PER_CHANNEL * DMA_NR_REQ_PER_DESC);
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (list_empty(&tdc->free_sg_req)) {
> + dev_dbg(tdc2dev(tdc),
> + "Not found free sg_req for channel %d\n", tdc->id);
> + goto end;
> + }
> + }
> +
> + sg_req = list_first_entry(&tdc->free_sg_req, typeof(*sg_req), node);
> + list_del(&sg_req->node);
> +end:
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + return sg_req;
> +}
[snip]
> +
> +static void tegra_dma_abort_all(struct tegra_dma_channel *tdc)
> +{
> + struct tegra_dma_sg_req *sgreq;
> + struct tegra_dma_desc *dma_desc;
> + while (!list_empty(&tdc->pending_sg_req)) {
> + sgreq = list_first_entry(&tdc->pending_sg_req,
> + typeof(*sgreq), node);
> + list_del(&sgreq->node);
> + list_add_tail(&sgreq->node, &tdc->free_sg_req);
> + if (sgreq->last_sg) {
> + dma_desc = sgreq->dma_desc;
> + dma_desc->dma_status = DMA_ERROR;
> + tegra_dma_desc_done_locked(tdc, dma_desc);
> +
> + /* Add in cb list if it is not there. */
> + if (!dma_desc->cb_due) {
> + list_add_tail(&dma_desc->cb_node,
> + &tdc->cb_desc);
> + dma_desc->cb_due = true;
> + }
> + dma_cookie_complete(&dma_desc->txd);
does this make sense. You are marking an aborted descriptor as complete.
> + }
> + }
> + tdc->dma_mode = DMA_MODE_NONE;
> +}
> +
> +static bool handle_continuous_head_request(struct tegra_dma_channel *tdc,
> + struct tegra_dma_sg_req *last_sg_req, bool to_terminate)
> +{
> + struct tegra_dma_sg_req *hsgreq = NULL;
> +
> + if (list_empty(&tdc->pending_sg_req)) {
> + dev_err(tdc2dev(tdc),
> + "%s(): Dma is running without any req list\n",
> + __func__);
this is just waste of real estate and very ugly. Move them to 1/2 lines.
> + tegra_dma_stop(tdc);
> + return false;
> + }
> +
> + /*
> + * Check that head req on list should be in flight.
> + * If it is not in flight then abort transfer as
> + * transfer looping can not continue.
> + */
> + hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node);
> + if (!hsgreq->configured) {
> + tegra_dma_stop(tdc);
> + dev_err(tdc2dev(tdc),
> + "Error in dma transfer loop, aborting dma\n");
> + tegra_dma_abort_all(tdc);
> + return false;
> + }
> +
> + /* Configure next request in single buffer mode */
> + if (!to_terminate && (tdc->dma_mode == DMA_MODE_CYCLE))
> + tdc_configure_next_head_desc(tdc);
> + return true;
> +}
> +
> +static void tegra_dma_tasklet(unsigned long data)
> +{
> + struct tegra_dma_channel *tdc = (struct tegra_dma_channel *)data;
> + unsigned long flags;
> + dma_async_tx_callback callback = NULL;
> + void *callback_param = NULL;
> + struct tegra_dma_desc *dma_desc;
> + struct list_head cb_dma_desc_list;
> +
> + INIT_LIST_HEAD(&cb_dma_desc_list);
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (list_empty(&tdc->cb_desc)) {
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + return;
> + }
> + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
> + spin_unlock_irqrestore(&tdc->lock, flags);
> +
> + while (!list_empty(&cb_dma_desc_list)) {
> + dma_desc = list_first_entry(&cb_dma_desc_list,
> + typeof(*dma_desc), cb_node);
> + list_del(&dma_desc->cb_node);
> +
> + callback = dma_desc->txd.callback;
> + callback_param = dma_desc->txd.callback_param;
> + dma_desc->cb_due = false;
> + if (callback)
> + callback(callback_param);
You should mark the descriptor as complete before calling callback.
Also tasklet is supposed to move the next pending descriptor to the
engine, I dont see that happening here?
> + }
> +}
> +
> +static void tegra_dma_terminate_all(struct dma_chan *dc)
> +{
> + struct tegra_dma_channel *tdc = to_tegra_dma_chan(dc);
> + struct tegra_dma_sg_req *sgreq;
> + struct tegra_dma_desc *dma_desc;
> + unsigned long flags;
> + unsigned long status;
> + struct list_head new_list;
> + dma_async_tx_callback callback = NULL;
> + void *callback_param = NULL;
> + struct list_head cb_dma_desc_list;
> + bool was_busy;
> +
> + INIT_LIST_HEAD(&new_list);
> + INIT_LIST_HEAD(&cb_dma_desc_list);
> +
> + spin_lock_irqsave(&tdc->lock, flags);
> + if (list_empty(&tdc->pending_sg_req)) {
> + spin_unlock_irqrestore(&tdc->lock, flags);
> + return;
> + }
> +
> + if (!tdc->busy) {
> + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
> + goto skip_dma_stop;
> + }
> +
> + /* Pause DMA before checking the queue status */
> + tegra_dma_pause(tdc, true);
> +
> + status = tdc_read(tdc, APB_DMA_CHAN_STA);
> + if (status & STA_ISE_EOC) {
> + dev_dbg(tdc2dev(tdc), "%s():handling isr\n", __func__);
> + tdc->isr_handler(tdc, true);
> + status = tdc_read(tdc, APB_DMA_CHAN_STA);
> + }
> + list_splice_init(&tdc->cb_desc, &cb_dma_desc_list);
> +
> + was_busy = tdc->busy;
> + tegra_dma_stop(tdc);
> + if (!list_empty(&tdc->pending_sg_req) && was_busy) {
> + sgreq = list_first_entry(&tdc->pending_sg_req,
> + typeof(*sgreq), node);
> + sgreq->dma_desc->bytes_transferred +=
> + get_current_xferred_count(tdc, sgreq, status);
> + }
> + tegra_dma_resume(tdc);
> +
> +skip_dma_stop:
> + tegra_dma_abort_all(tdc);
> + /* Ignore callbacks pending list */
> + INIT_LIST_HEAD(&tdc->cb_desc);
> + spin_unlock_irqrestore(&tdc->lock, flags);
> +
> + /* Call callbacks if was pending before aborting requests */
> + while (!list_empty(&cb_dma_desc_list)) {
> + dma_desc = list_first_entry(&cb_dma_desc_list,
> + typeof(*dma_desc), cb_node);
> + list_del(&dma_desc->cb_node);
> + callback = dma_desc->txd.callback;
> + callback_param = dma_desc->txd.callback_param;
again, callback would be called from tasklet, so why would it need to be
called from here , and why would this be pending.
> + if (callback)
> + callback(callback_param);
> + }
> +}
> +
> +
--
~Vinod
Thanks for review.
On Wednesday 09 May 2012 02:19 PM, Vinod Koul wrote:
> On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
>> The dma controller like Nvidia's Tegra Dma controller
>> supports the different slave requestor id from different slave.
>> This need to be configure in dma controller to handle the request
>> properly.
>>
>> Adding the slave-id in the slave configuration so that information
>> can be passed from client when configuring for slave.
>>
>> u32 dst_maxburst;
>> bool device_fc;
>> + int slave_id;
> This wont be negative, so perhaps an unsigned value?
Fine, I will make it unsigned int in my next patch.
Thanks Vinod for reviewing code.
I am removing the code from this thread which is not require. Only
keeping code surrounding our discussion.
On Wednesday 09 May 2012 03:44 PM, Vinod Koul wrote:
> On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
>> + * Initial number of descriptors to allocate for each channel during
>> + * allocation. More descriptors will be allocated dynamically if
>> + * client needs it.
>> + */
>> +#define DMA_NR_DESCS_PER_CHANNEL 4
>> +#define DMA_NR_REQ_PER_DESC 8
> all of these should be namespaced.
> APB and AHB are fairly generic names.
Fine, then I will go as APB_DMA_NR_DESC_xxx and APB_DMA_NR_REQ_xxx
>> +
>> +enum dma_transfer_mode {
>> + DMA_MODE_NONE,
>> + DMA_MODE_ONCE,
>> + DMA_MODE_CYCLE,
>> + DMA_MODE_CYCLE_HALF_NOTIFY,
>> +};
> I dont understand why you would need to keep track of these?
> You get a request for DMA. You have properties of transfer. You prepare
> you descriptors and then submit.
> Why would you need to create above modes and remember them?
I am allowing multiple desc requests in dma through prep_slave and
prep_cyclic. So when dma channel does not have any request then it can
set its mode as NONE.
Once the desc is requested the mode is set based on request. This mode
will not get change until all dma request done and if new request come
to dma channel, it checks that it should not conflict with older mode.
The engine is configured in these mode and will change only when all
transfer completed.
>> +struct tegra_dma_desc {
>> + struct dma_async_tx_descriptor txd;
>> + int bytes_requested;
>> + int bytes_transferred;
>> + enum dma_status dma_status;
>> + struct list_head node;
>> + struct list_head tx_list;
>> + struct list_head cb_node;
>> + bool ack_reqd;
>> + bool cb_due;
> what are these two for, seems redundant to me
I am reusing the desc once the transfer completed from that desc, not
freeing it memory.
The ack_reqd tells that client need to ack that desc so that it can be
reused. So this flag tells that client need to ack that desc.
This is to track the flag DMA_CTRL_ACK which is passed by client in
prep_slave_xxx.
cb_due is having different purpose. then ack. Once dma transfer
completes, it schedules the tasklet and set the cb_due as callback
pending. Before tasklet get schedule, if again transfer completes and
re-enter into isr then it wil check that desc callback is pending or not
and based on that it queued it on the callback list.
>> +static int allocate_tegra_desc(struct tegra_dma_channel *tdc,
>> + int ndma_desc, int nsg_req)
> pls follow single convention for naming in driver eithe xxx_tegra_yyy or
> tegra_xxx_yyy... BUT not both!
Sure, i will do it.
>> + /* Initialize DMA descriptors */
>> + for (i = 0; i< ndma_desc; ++i) {
>> + dma_desc = kzalloc(sizeof(struct tegra_dma_desc), GFP_ATOMIC);
> kernel convention is kzalloc(sizeof(*dma_desc),....
yes I will do it, I missed this cleanup.
>> +
>> +
>> + /* Check from free list desc */
>> + if (!list_empty(&tdc->free_dma_desc)) {
>> + dma_desc = list_first_entry(&tdc->free_dma_desc,
>> + typeof(*dma_desc), node);
>> + list_del(&dma_desc->node);
>> + goto end;
>> + }
> sorry I dont like this jumping and returning for two lines of code.
> Makes much sense to return from here.
Then goto will be replace as
spin_unlock_irqrestore()
return.
and all three places.
I will do if it is OK,
>> +static void tegra_dma_desc_done_locked(struct tegra_dma_channel *tdc,
>> + struct tegra_dma_desc *dma_desc)
>> +{
>> + if (dma_desc->ack_reqd)
>> + list_add_tail(&dma_desc->node,&tdc->wait_ack_dma_desc);
> what does ack_reqd mean?
Client need to ack this desc before reusing it.
> Do you ahve unlocked version of this function, name suggests so...
Did not require the locked version of function. Will remove the locked
and add in comment.
>> + else
>> + list_add_tail(&dma_desc->node,&tdc->free_dma_desc);
>> +}
>> +
>> +static struct tegra_dma_sg_req *tegra_dma_sg_req_get(
>> + struct tegra_dma_channel *tdc)
> in this and other functions, you have used goto to unlock and return.
> Rather than that why don't you simplify code by calling these while
> holding lock
We are allocating memory also and that's why it is there.
But I can make it because I am callocating memory as GFP_ATOMIC.
However if the function call dma_async_tx_descriptor_init() can happen
in atomic context i.e. calling spin_lock_init() call.
>> + }
>> + dma_cookie_complete(&dma_desc->txd);
> does this make sense. You are marking an aborted descriptor as complete.
If I dont call the the complete cookie of that channel will not get
updated and on query, it will return as PROGRESS.
I need to update the dma channel completed cookie.
>> + if (list_empty(&tdc->pending_sg_req)) {
>> + dev_err(tdc2dev(tdc),
>> + "%s(): Dma is running without any req list\n",
>> + __func__);
> this is just waste of real estate and very ugly. Move them to 1/2 lines.
Let me see if I can save something here.
>> + tegra_dma_stop(tdc);
>> + return false;
>> + }
>> +
>> + /*
>> + * Check that head req on list should be in flight.
>> + * If it is not in flight then abort transfer as
>> + * transfer looping can not continue.
>> + */
>> + hsgreq = list_first_entry(&tdc->pending_sg_req, typeof(*hsgreq), node);
>> + if (!hsgreq->configured) {
>> + tegra_dma_stop(tdc);
>> + dev_err(tdc2dev(tdc),
>> + "Error in dma transfer loop, aborting dma\n");
>> + tegra_dma_abort_all(tdc);
>> + return false;
>> + }
>> +
>> + /* Configure next request in single buffer mode */
>> + if (!to_terminate&& (tdc->dma_mode == DMA_MODE_CYCLE))
>> + tdc_configure_next_head_desc(tdc);
>> + return true;
>> +}
>> +
>> +static void tegra_dma_tasklet(unsigned long data)
>> +{
>> + struct tegra_dma_channel *tdc = (struct tegra_dma_channel *)data;
>> + unsigned long flags;
>> + dma_async_tx_callback callback = NULL;
>> + void *callback_param = NULL;
>> + struct tegra_dma_desc *dma_desc;
>> + struct list_head cb_dma_desc_list;
>> +
>> + INIT_LIST_HEAD(&cb_dma_desc_list);
>> + spin_lock_irqsave(&tdc->lock, flags);
>> + if (list_empty(&tdc->cb_desc)) {
>> + spin_unlock_irqrestore(&tdc->lock, flags);
>> + return;
>> + }
>> + list_splice_init(&tdc->cb_desc,&cb_dma_desc_list);
>> + spin_unlock_irqrestore(&tdc->lock, flags);
>> +
>> + while (!list_empty(&cb_dma_desc_list)) {
>> + dma_desc = list_first_entry(&cb_dma_desc_list,
>> + typeof(*dma_desc), cb_node);
>> + list_del(&dma_desc->cb_node);
>> +
>> + callback = dma_desc->txd.callback;
>> + callback_param = dma_desc->txd.callback_param;
>> + dma_desc->cb_due = false;
>> + if (callback)
>> + callback(callback_param);
> You should mark the descriptor as complete before calling callback.
> Also tasklet is supposed to move the next pending descriptor to the
> engine, I dont see that happening here?
It is there. I am calling dma_cookie_complete from
handle_once_dma_done() which get called from ISR.
In cyclic mode, I am not calling dma_cookie_complete() until it is
aborted (for update the complete cookie) but cyclic/non-cyclic case I am
calling callback.
>> + /* Call callbacks if was pending before aborting requests */
>> + while (!list_empty(&cb_dma_desc_list)) {
>> + dma_desc = list_first_entry(&cb_dma_desc_list,
>> + typeof(*dma_desc), cb_node);
>> + list_del(&dma_desc->cb_node);
>> + callback = dma_desc->txd.callback;
>> + callback_param = dma_desc->txd.callback_param;
> again, callback would be called from tasklet, so why would it need to be
> called from here , and why would this be pending.
What happen if some callbacks are pending as tasklet does not get
scheduled and meantime, the dma terminated (specially in multi-core system)?
Should we ignore all callbacks in this case?
On Wed, 2012-05-09 at 16:31 +0530, Laxman Dewangan wrote:
> Thanks Vinod for reviewing code.
> I am removing the code from this thread which is not require. Only
> keeping code surrounding our discussion.
>
> On Wednesday 09 May 2012 03:44 PM, Vinod Koul wrote:
> > On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
> >> + * Initial number of descriptors to allocate for each channel during
> >> + * allocation. More descriptors will be allocated dynamically if
> >> + * client needs it.
> >> + */
> >> +#define DMA_NR_DESCS_PER_CHANNEL 4
> >> +#define DMA_NR_REQ_PER_DESC 8
> > all of these should be namespaced.
> > APB and AHB are fairly generic names.
> Fine, then I will go as APB_DMA_NR_DESC_xxx and APB_DMA_NR_REQ_xxx
NO. Many ppl use APB/AHB, so pls don't use or namespace them properly
>
> >> +
> >> +enum dma_transfer_mode {
> >> + DMA_MODE_NONE,
> >> + DMA_MODE_ONCE,
> >> + DMA_MODE_CYCLE,
> >> + DMA_MODE_CYCLE_HALF_NOTIFY,
> >> +};
> > I dont understand why you would need to keep track of these?
> > You get a request for DMA. You have properties of transfer. You prepare
> > you descriptors and then submit.
> > Why would you need to create above modes and remember them?
> I am allowing multiple desc requests in dma through prep_slave and
> prep_cyclic. So when dma channel does not have any request then it can
> set its mode as NONE.
Again NO.
This is not how dmaengine API is supposed to work.
Correct behavior would be:
You prepare descriptors, as many as you want.
You submit them. Dmaengine driver takes them and pushes them to pending
list. Submit does not start.
When .issue_pending is called, you start DMA by using first descriptor
in pending list. One completed you start next one untill you exhaust the
complete list.
So use your pending list for this.
> Once the desc is requested the mode is set based on request.
Again NO
> This mode
> will not get change until all dma request done and if new request come
> to dma channel, it checks that it should not conflict with older mode.
> The engine is configured in these mode and will change only when all
> transfer completed.
See above you absolutely dont need to track *modes*
> We are allocating memory also and that's why it is there.
> But I can make it because I am callocating memory as GFP_ATOMIC.
> However if the function call dma_async_tx_descriptor_init() can happen
> in atomic context i.e. calling spin_lock_init() call.
>
> >> + }
> >> + dma_cookie_complete(&dma_desc->txd);
> > does this make sense. You are marking an aborted descriptor as complete.
> If I dont call the the complete cookie of that channel will not get
> updated and on query, it will return as PROGRESS.
> I need to update the dma channel completed cookie.
No the transaction failed as it was aborted. So mark it as DMA_ERROR.
> >> + /* Call callbacks if was pending before aborting requests */
> >> + while (!list_empty(&cb_dma_desc_list)) {
> >> + dma_desc = list_first_entry(&cb_dma_desc_list,
> >> + typeof(*dma_desc), cb_node);
> >> + list_del(&dma_desc->cb_node);
> >> + callback = dma_desc->txd.callback;
> >> + callback_param = dma_desc->txd.callback_param;
> > again, callback would be called from tasklet, so why would it need to be
> > called from here , and why would this be pending.
> What happen if some callbacks are pending as tasklet does not get
> scheduled and meantime, the dma terminated (specially in multi-core system)?
> Should we ignore all callbacks in this case?
In termination case, client has already terminated and possibly in
process of cleanup.
So makes no sense in those cases to call the callback.
--
~Vinod
On Thursday 10 May 2012 09:04 AM, Vinod Koul wrote:
> On Wed, 2012-05-09 at 16:31 +0530, Laxman Dewangan wrote:
>> Thanks Vinod for reviewing code.
>> I am removing the code from this thread which is not require. Only
>> keeping code surrounding our discussion.
>>
>> On Wednesday 09 May 2012 03:44 PM, Vinod Koul wrote:
>>> On Thu, 2012-05-03 at 13:11 +0530, Laxman Dewangan wrote:
>>>> + * Initial number of descriptors to allocate for each channel during
>>>> + * allocation. More descriptors will be allocated dynamically if
>>>> + * client needs it.
>>>> + */
>>>> +#define DMA_NR_DESCS_PER_CHANNEL 4
>>>> +#define DMA_NR_REQ_PER_DESC 8
>>> all of these should be namespaced.
>>> APB and AHB are fairly generic names.
>> Fine, then I will go as APB_DMA_NR_DESC_xxx and APB_DMA_NR_REQ_xxx
> NO. Many ppl use APB/AHB, so pls don't use or namespace them properly
OK, then will say TEGRA_DMA_*** to namemspaced to Tegra domain.
>>>> +
>>>> +enum dma_transfer_mode {
>>>> + DMA_MODE_NONE,
>>>> + DMA_MODE_ONCE,
>>>> + DMA_MODE_CYCLE,
>>>> + DMA_MODE_CYCLE_HALF_NOTIFY,
>>>> +};
>>> I dont understand why you would need to keep track of these?
>>> You get a request for DMA. You have properties of transfer. You prepare
>>> you descriptors and then submit.
>>> Why would you need to create above modes and remember them?
>> I am allowing multiple desc requests in dma through prep_slave and
>> prep_cyclic. So when dma channel does not have any request then it can
>> set its mode as NONE.
> Again NO.
>
> This is not how dmaengine API is supposed to work.
> Correct behavior would be:
> You prepare descriptors, as many as you want.
> You submit them. Dmaengine driver takes them and pushes them to pending
> list. Submit does not start.
> When .issue_pending is called, you start DMA by using first descriptor
> in pending list. One completed you start next one untill you exhaust the
> complete list.
> So use your pending list for this.
Tegra dma engine support three type of mode, one shot, cyclic once and
cyclic double. The next transfer configuration is different in all these
modes, in oneshot, next request should be configure only after transfer
completes, in cyclic once, the next request should be configure before
current transfer completes so that hw can auto switch to next transfer
and in cyclic_double, the next request should be configure only after
half transfer.
For handling this and not complicating the code, I have separate isr
handler on these cases.
Now if any request come which can cause the switching of modes, like one
request for one_shot and next request for the cyclic_once then it will
be very complex to handle such situation as the configuration of request
depends on mode. I am simply trying to avoid that situation and not
allowing client to request which can cause conflict in engine.
>> Once the desc is requested the mode is set based on request.
> Again NO
>> This mode
>> will not get change until all dma request done and if new request come
>> to dma channel, it checks that it should not conflict with older mode.
>> The engine is configured in these mode and will change only when all
>> transfer completed.
> See above you absolutely dont need to track *modes*
>
>> We are allocating memory also and that's why it is there.
>> But I can make it because I am callocating memory as GFP_ATOMIC.
>> However if the function call dma_async_tx_descriptor_init() can happen
>> in atomic context i.e. calling spin_lock_init() call.
>>
>>>> + }
>>>> + dma_cookie_complete(&dma_desc->txd);
>>> does this make sense. You are marking an aborted descriptor as complete.
>> If I dont call the the complete cookie of that channel will not get
>> updated and on query, it will return as PROGRESS.
>> I need to update the dma channel completed cookie.
> No the transaction failed as it was aborted. So mark it as DMA_ERROR.
>
But when we update the chan->completed_cookie for aborted cookie?
Otherwise I will get the DMA_IN_PROGRSS when I call dma_cookie_status()
but actually it is aborted.
By calling complete(), I will get DMA_SUCCESS and desc->status can tell
the error or not.
>>>> + /* Call callbacks if was pending before aborting requests */
>>>> + while (!list_empty(&cb_dma_desc_list)) {
>>>> + dma_desc = list_first_entry(&cb_dma_desc_list,
>>>> + typeof(*dma_desc), cb_node);
>>>> + list_del(&dma_desc->cb_node);
>>>> + callback = dma_desc->txd.callback;
>>>> + callback_param = dma_desc->txd.callback_param;
>>> again, callback would be called from tasklet, so why would it need to be
>>> called from here , and why would this be pending.
>> What happen if some callbacks are pending as tasklet does not get
>> scheduled and meantime, the dma terminated (specially in multi-core system)?
>> Should we ignore all callbacks in this case?
> In termination case, client has already terminated and possibly in
> process of cleanup.
> So makes no sense in those cases to call the callback.
OK, fine with me.
On Thu, 2012-05-10 at 10:35 +0530, Laxman Dewangan wrote:
> But when we update the chan->completed_cookie for aborted cookie?
> Otherwise I will get the DMA_IN_PROGRSS when I call dma_cookie_status()
> but actually it is aborted.
no in .device_tx_status, you should check if the descriptor is aborted
or not and rteurn error for aborted descriptor.
--
~Vinod
On Friday 11 May 2012 02:11 PM, Vinod Koul wrote:
> On Thu, 2012-05-10 at 10:35 +0530, Laxman Dewangan wrote:
>
>> But when we update the chan->completed_cookie for aborted cookie?
>> Otherwise I will get the DMA_IN_PROGRSS when I call dma_cookie_status()
>> but actually it is aborted.
> no in .device_tx_status, you should check if the descriptor is aborted
> or not and rteurn error for aborted descriptor.
>
OK, I will not call this function.
Also I am dropping the ONCE_NOTIFY_HALF and will add later if require. I
realize that patch patch should be simple and sufficient so that other
driver development/upstream can be unblock. Later on we can add feature
based on necessary.
I am sending the new patch with considering all your feedback.
Thanks again for review.
Thanks,
Laxman