From patchwork Mon Sep 25 23:22:05 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 9970729 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 17D75602CB for ; Mon, 25 Sep 2017 23:25:54 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0962728C4C for ; Mon, 25 Sep 2017 23:25:54 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id F228D28C57; Mon, 25 Sep 2017 23:25:53 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.3 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 55F6A28D00 for ; Mon, 25 Sep 2017 23:25:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S966394AbdIYXZN (ORCPT ); Mon, 25 Sep 2017 19:25:13 -0400 Received: from mail-wr0-f194.google.com ([209.85.128.194]:34402 "EHLO mail-wr0-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S965878AbdIYXYb (ORCPT ); Mon, 25 Sep 2017 19:24:31 -0400 Received: by mail-wr0-f194.google.com with SMTP id k20so918650wre.1; Mon, 25 Sep 2017 16:24:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=PVgzROyVVbwVMXWDJwyudz2CcfTtl1EgQrSXN8qTxlM=; b=pP/mEWRMC7TmM9h1FOwOg+Ehz+yCiQ7aSgxsfSiV2juZFXUiDGBp7tHT16Gl/wnYEx mM39FDmTOqpcZpm/5abVhyj8enFLGaiBxm4sNoYZ8m66YplXXJdxjrmrLDKcxcji/Ff2 4roT7Bp3xK8888sSdaf4ocLBhicaqXFH0z5+ItE6zPm3Ji7Kp+JxTW3HbVEk6c75Qc/H Mur29kLiU0AszZfwknRDGalCVMT1KWxyF/j4cAlj6/vasxPpajJqvF/VCnObigrD/Y1j tmxVg1mqCBR4fsFIhBI04SCx3f+af+/wXNre9pSWgdERCBrjImElw6NzKhEinBMGg37+ uNPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:in-reply-to:references; bh=PVgzROyVVbwVMXWDJwyudz2CcfTtl1EgQrSXN8qTxlM=; b=N0tSK9JvubVTv1/b9usXWvaINIuPZaVP/BYL/v03V9kkTjo/vJyz503PBElXFPCbXR 02UBv3TF4RL9EYgHHOv16ND6Y4rP7Wb1RoLfycB7HvfaKBF36RLI9m8xgyHapyrKQKRp w6lqopUM2aRKce/VGn5Sky3l6JU/FrKpGOrZcI7hOsIMXzIYUcvQcbnzsom6hb2h3aTv eorEyX6F4mXINsouWu6zIOVWCY+rmUJH2MwYnZZH655ByuQgKpkhob6KMwGQyI6wbIy/ qWYvOjAVFAFxY8nPkuHSxzN2F6CuikN5REwq3DhPE0tVKX1zjkdxwCx4WD3N+J0wzyzS ypiw== X-Gm-Message-State: AHPjjUhsFBjJLBYe/1t3XJbWgWhNbxTcubCJRHYZ3lLe7WHY2BYMK4kq gFPWg0semntQy+/LbGJNu4M= X-Google-Smtp-Source: AOwi7QDCYQxT8ZT0BJXfRKFWy4rcyHLCZWlOpmB+xV9/iQ4oLCwfuOEF1ognRcLpWC0vcurCOlHR1A== X-Received: by 10.25.225.66 with SMTP id y63mr453351lfg.209.1506381869423; Mon, 25 Sep 2017 16:24:29 -0700 (PDT) Received: from localhost.localdomain (ppp109-252-90-109.pppoe.spdop.ru. [109.252.90.109]) by smtp.gmail.com with ESMTPSA id a139sm1161965lfa.39.2017.09.25.16.24.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 25 Sep 2017 16:24:28 -0700 (PDT) From: Dmitry Osipenko To: Thierry Reding , Jonathan Hunter , Laxman Dewangan , Peter De Schrijver , Prashant Gaikwad , Michael Turquette , Stephen Boyd , Rob Herring , Vinod Koul Cc: linux-tegra@vger.kernel.org, devicetree@vger.kernel.org, dmaengine@vger.kernel.org, linux-clk@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 4/5] dmaengine: Add driver for NVIDIA Tegra AHB DMA controller Date: Tue, 26 Sep 2017 02:22:05 +0300 Message-Id: <0a45e058baba72124b91c663ce1d908d275f4044.1506380746.git.digetx@gmail.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: References: In-Reply-To: References: Sender: linux-clk-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-clk@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP AHB DMA controller presents on Tegra20/30 SoC's, it supports transfers memory <-> AHB bus peripherals as well as mem-to-mem transfers. Driver doesn't yet implement transfers larger than 64K and scatter-gather transfers that have NENT > 1, HW doesn't have native support for these cases. Signed-off-by: Dmitry Osipenko --- drivers/dma/Kconfig | 9 + drivers/dma/Makefile | 1 + drivers/dma/tegra20-ahb-dma.c | 679 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 689 insertions(+) create mode 100644 drivers/dma/tegra20-ahb-dma.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index fadc4d8783bd..937c110a715b 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -503,6 +503,15 @@ config TXX9_DMAC Support the TXx9 SoC internal DMA controller. This can be integrated in chips such as the Toshiba TX4927/38/39. +config TEGRA20_AHB_DMA + tristate "NVIDIA Tegra20 AHB DMA support" + depends on ARCH_TEGRA + select DMA_ENGINE + help + Enable support for the NVIDIA Tegra20 AHB DMA controller driver. + This DMA controller transfers data from memory to AHB peripherals + or vice versa, it supports memory to memory data transfer as well. + config TEGRA20_APB_DMA bool "NVIDIA Tegra20 APB DMA support" depends on ARCH_TEGRA diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index f08f8de1b567..82d5d36b0379 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_STM32_DMA) += stm32-dma.o obj-$(CONFIG_S3C24XX_DMAC) += s3c24xx-dma.o obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o +obj-$(CONFIG_TEGRA20_AHB_DMA) += tegra20-ahb-dma.o obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o obj-$(CONFIG_TEGRA210_ADMA) += tegra210-adma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o diff --git a/drivers/dma/tegra20-ahb-dma.c b/drivers/dma/tegra20-ahb-dma.c new file mode 100644 index 000000000000..8316d64e35e1 --- /dev/null +++ b/drivers/dma/tegra20-ahb-dma.c @@ -0,0 +1,679 @@ +/* + * Copyright 2017 Dmitry Osipenko + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" + +#define TEGRA_AHBDMA_CMD 0x0 +#define TEGRA_AHBDMA_CMD_ENABLE BIT(31) + +#define TEGRA_AHBDMA_IRQ_ENB_MASK 0x20 +#define TEGRA_AHBDMA_IRQ_ENB_CH(ch) BIT(ch) + +#define TEGRA_AHBDMA_CHANNEL_BASE(ch) (0x1000 + (ch) * 0x20) + +#define TEGRA_AHBDMA_CHANNEL_CSR 0x0 +#define TEGRA_AHBDMA_CHANNEL_ADDR_WRAP BIT(18) +#define TEGRA_AHBDMA_CHANNEL_FLOW BIT(24) +#define TEGRA_AHBDMA_CHANNEL_ONCE BIT(26) +#define TEGRA_AHBDMA_CHANNEL_DIR_TO_XMB BIT(27) +#define TEGRA_AHBDMA_CHANNEL_IE_EOC BIT(30) +#define TEGRA_AHBDMA_CHANNEL_ENABLE BIT(31) +#define TEGRA_AHBDMA_CHANNEL_REQ_SEL_SHIFT 16 +#define TEGRA_AHBDMA_CHANNEL_WCOUNT_MASK 0xFFFC + +#define TEGRA_AHBDMA_CHANNEL_STA 0x4 +#define TEGRA_AHBDMA_CHANNEL_IS_EOC BIT(30) + +#define TEGRA_AHBDMA_CHANNEL_AHB_PTR 0x10 + +#define TEGRA_AHBDMA_CHANNEL_AHB_SEQ 0x14 +#define TEGRA_AHBDMA_CHANNEL_INTR_ENB BIT(31) +#define TEGRA_AHBDMA_CHANNEL_AHB_BURST_SHIFT 24 +#define TEGRA_AHBDMA_CHANNEL_AHB_BURST_1 2 +#define TEGRA_AHBDMA_CHANNEL_AHB_BURST_4 3 +#define TEGRA_AHBDMA_CHANNEL_AHB_BURST_8 4 + +#define TEGRA_AHBDMA_CHANNEL_XMB_PTR 0x18 + +#define TEGRA_AHBDMA_BUS_WIDTH BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) + +#define TEGRA_AHBDMA_DIRECTIONS BIT(DMA_DEV_TO_MEM) | \ + BIT(DMA_MEM_TO_DEV) + +struct tegra_ahbdma_tx_desc { + struct dma_async_tx_descriptor desc; + struct tasklet_struct tasklet; + struct list_head node; + enum dma_transfer_direction dir; + dma_addr_t mem_paddr; + unsigned long flags; + size_t size; + bool in_fly; + bool cyclic; +}; + +struct tegra_ahbdma_chan { + struct dma_chan dma_chan; + struct list_head active_list; + struct list_head pending_list; + struct completion idling; + void __iomem *regs; + spinlock_t lock; + unsigned int id; +}; + +struct tegra_ahbdma { + struct tegra_ahbdma_chan channels[4]; + struct dma_device dma_dev; + struct reset_control *rst; + struct clk *clk; + void __iomem *regs; +}; + +static inline struct tegra_ahbdma *to_ahbdma(struct dma_device *dev) +{ + return container_of(dev, struct tegra_ahbdma, dma_dev); +} + +static inline struct tegra_ahbdma_chan *to_ahbdma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct tegra_ahbdma_chan, dma_chan); +} + +static inline struct tegra_ahbdma_tx_desc *to_ahbdma_tx_desc( + struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct tegra_ahbdma_tx_desc, desc); +} + +static void tegra_ahbdma_submit_tx(struct tegra_ahbdma_chan *chan, + struct tegra_ahbdma_tx_desc *tx) +{ + u32 csr; + + writel_relaxed(tx->mem_paddr, + chan->regs + TEGRA_AHBDMA_CHANNEL_XMB_PTR); + + csr = readl_relaxed(chan->regs + TEGRA_AHBDMA_CHANNEL_CSR); + + csr &= ~TEGRA_AHBDMA_CHANNEL_WCOUNT_MASK; + csr &= ~TEGRA_AHBDMA_CHANNEL_DIR_TO_XMB; + csr |= TEGRA_AHBDMA_CHANNEL_ENABLE; + csr |= TEGRA_AHBDMA_CHANNEL_IE_EOC; + csr |= tx->size - sizeof(u32); + + if (tx->dir == DMA_DEV_TO_MEM) + csr |= TEGRA_AHBDMA_CHANNEL_DIR_TO_XMB; + + if (!tx->cyclic) + csr |= TEGRA_AHBDMA_CHANNEL_ONCE; + + writel_relaxed(csr, chan->regs + TEGRA_AHBDMA_CHANNEL_CSR); + + tx->in_fly = true; +} + +static void tegra_ahbdma_tasklet(unsigned long data) +{ + struct tegra_ahbdma_tx_desc *tx = (struct tegra_ahbdma_tx_desc *)data; + struct dma_async_tx_descriptor *desc = &tx->desc; + + dmaengine_desc_get_callback_invoke(desc, NULL); + + if (!tx->cyclic && !dmaengine_desc_test_reuse(desc)) + kfree(tx); +} + +static bool tegra_ahbdma_tx_completed(struct tegra_ahbdma_chan *chan, + struct tegra_ahbdma_tx_desc *tx) +{ + struct dma_async_tx_descriptor *desc = &tx->desc; + bool reuse = dmaengine_desc_test_reuse(desc); + bool interrupt = tx->flags & DMA_PREP_INTERRUPT; + bool completed = !tx->cyclic; + + if (completed) + dma_cookie_complete(desc); + + if (interrupt) + tasklet_schedule(&tx->tasklet); + + if (completed) { + list_del(&tx->node); + + if (reuse) + tx->in_fly = false; + + if (!interrupt && !reuse) + kfree(tx); + } + + return completed; +} + +static bool tegra_ahbdma_next_tx_issued(struct tegra_ahbdma_chan *chan) +{ + struct tegra_ahbdma_tx_desc *tx; + + tx = list_first_entry_or_null(&chan->active_list, + struct tegra_ahbdma_tx_desc, + node); + if (tx) + tegra_ahbdma_submit_tx(chan, tx); + + return !!tx; +} + +static void tegra_ahbdma_handle_channel(struct tegra_ahbdma_chan *chan) +{ + struct tegra_ahbdma_tx_desc *tx; + unsigned long flags; + u32 status; + + status = readl_relaxed(chan->regs + TEGRA_AHBDMA_CHANNEL_STA); + if (!(status & TEGRA_AHBDMA_CHANNEL_IS_EOC)) + return; + + writel_relaxed(TEGRA_AHBDMA_CHANNEL_IS_EOC, + chan->regs + TEGRA_AHBDMA_CHANNEL_STA); + + spin_lock_irqsave(&chan->lock, flags); + + if (!completion_done(&chan->idling)) { + tx = list_first_entry(&chan->active_list, + struct tegra_ahbdma_tx_desc, + node); + + if (tegra_ahbdma_tx_completed(chan, tx) && + !tegra_ahbdma_next_tx_issued(chan)) + complete_all(&chan->idling); + } + + spin_unlock_irqrestore(&chan->lock, flags); +} + +static irqreturn_t tegra_ahbdma_isr(int irq, void *dev_id) +{ + struct tegra_ahbdma *tdma = dev_id; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tdma->channels); i++) + tegra_ahbdma_handle_channel(&tdma->channels[i]); + + return IRQ_HANDLED; +} + +static dma_cookie_t tegra_ahbdma_tx_submit(struct dma_async_tx_descriptor *desc) +{ + struct tegra_ahbdma_tx_desc *tx = to_ahbdma_tx_desc(desc); + struct tegra_ahbdma_chan *chan = to_ahbdma_chan(desc->chan); + dma_cookie_t cookie; + + cookie = dma_cookie_assign(desc); + + spin_lock_irq(&chan->lock); + list_add_tail(&tx->node, &chan->pending_list); + spin_unlock_irq(&chan->lock); + + return cookie; +} + +static int tegra_ahbdma_tx_desc_free(struct dma_async_tx_descriptor *desc) +{ + kfree(to_ahbdma_tx_desc(desc)); + + return 0; +} + +static struct dma_async_tx_descriptor *tegra_ahbdma_prep_slave_sg( + struct dma_chan *chan, + struct scatterlist *sgl, + unsigned int sg_len, + enum dma_transfer_direction dir, + unsigned long flags, + void *context) +{ + struct tegra_ahbdma_tx_desc *tx; + + /* unimplemented */ + if (sg_len != 1 || sg_dma_len(sgl) > SZ_64K) + return NULL; + + tx = kzalloc(sizeof(*tx), GFP_KERNEL); + if (!tx) + return NULL; + + dma_async_tx_descriptor_init(&tx->desc, chan); + + tx->desc.tx_submit = tegra_ahbdma_tx_submit; + tx->desc.desc_free = tegra_ahbdma_tx_desc_free; + tx->mem_paddr = sg_dma_address(sgl); + tx->size = sg_dma_len(sgl); + tx->flags = flags; + tx->dir = dir; + + tasklet_init(&tx->tasklet, tegra_ahbdma_tasklet, (unsigned long)tx); + + return &tx->desc; +} + +static struct dma_async_tx_descriptor *tegra_ahbdma_prep_dma_cyclic( + struct dma_chan *chan, + dma_addr_t buf_addr, + size_t buf_len, + size_t period_len, + enum dma_transfer_direction dir, + unsigned long flags) +{ + struct tegra_ahbdma_tx_desc *tx; + + /* unimplemented */ + if (buf_len != period_len || buf_len > SZ_64K) + return NULL; + + tx = kzalloc(sizeof(*tx), GFP_KERNEL); + if (!tx) + return NULL; + + dma_async_tx_descriptor_init(&tx->desc, chan); + + tx->desc.tx_submit = tegra_ahbdma_tx_submit; + tx->mem_paddr = buf_addr; + tx->size = buf_len; + tx->flags = flags; + tx->cyclic = true; + tx->dir = dir; + + tasklet_init(&tx->tasklet, tegra_ahbdma_tasklet, (unsigned long)tx); + + return &tx->desc; +} + +static void tegra_ahbdma_issue_pending(struct dma_chan *chan) +{ + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); + struct tegra_ahbdma_tx_desc *tx; + struct list_head *entry, *tmp; + unsigned long flags; + + spin_lock_irqsave(&ahbdma_chan->lock, flags); + + list_for_each_safe(entry, tmp, &ahbdma_chan->pending_list) + list_move_tail(entry, &ahbdma_chan->active_list); + + if (completion_done(&ahbdma_chan->idling)) { + tx = list_first_entry_or_null(&ahbdma_chan->active_list, + struct tegra_ahbdma_tx_desc, + node); + if (tx) { + tegra_ahbdma_submit_tx(ahbdma_chan, tx); + reinit_completion(&ahbdma_chan->idling); + } + } + + spin_unlock_irqrestore(&ahbdma_chan->lock, flags); +} + +static enum dma_status tegra_ahbdma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *state) +{ + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); + struct tegra_ahbdma_tx_desc *tx; + enum dma_status cookie_status; + unsigned long flags; + size_t residual; + u32 status; + + spin_lock_irqsave(&ahbdma_chan->lock, flags); + + cookie_status = dma_cookie_status(chan, cookie, state); + if (cookie_status != DMA_COMPLETE) { + list_for_each_entry(tx, &ahbdma_chan->active_list, node) { + if (tx->desc.cookie == cookie) + goto found; + } + } + + goto unlock; + +found: + if (tx->in_fly) { + status = readl_relaxed( + ahbdma_chan->regs + TEGRA_AHBDMA_CHANNEL_STA); + status &= TEGRA_AHBDMA_CHANNEL_WCOUNT_MASK; + + residual = status; + } else + residual = tx->size; + + dma_set_residue(state, residual); + +unlock: + spin_unlock_irqrestore(&ahbdma_chan->lock, flags); + + return cookie_status; +} + +static int tegra_ahbdma_terminate_all(struct dma_chan *chan) +{ + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); + struct tegra_ahbdma_tx_desc *tx; + struct list_head *entry, *tmp; + u32 csr; + + spin_lock_irq(&ahbdma_chan->lock); + + csr = readl_relaxed(ahbdma_chan->regs + TEGRA_AHBDMA_CHANNEL_CSR); + csr &= ~TEGRA_AHBDMA_CHANNEL_ENABLE; + + writel_relaxed(csr, ahbdma_chan->regs + TEGRA_AHBDMA_CHANNEL_CSR); + + list_for_each_safe(entry, tmp, &ahbdma_chan->active_list) { + tx = list_entry(entry, struct tegra_ahbdma_tx_desc, node); + list_del(entry); + kfree(tx); + } + + list_for_each_safe(entry, tmp, &ahbdma_chan->pending_list) { + tx = list_entry(entry, struct tegra_ahbdma_tx_desc, node); + list_del(entry); + kfree(tx); + } + + complete_all(&ahbdma_chan->idling); + + spin_unlock_irq(&ahbdma_chan->lock); + + return 0; +} + +static int tegra_ahbdma_config(struct dma_chan *chan, + struct dma_slave_config *sconfig) +{ + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); + enum dma_transfer_direction dir = sconfig->direction; + u32 burst, ahb_seq, ahb_addr; + + if (sconfig->src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES || + sconfig->dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) + return -EINVAL; + + if (dir == DMA_DEV_TO_MEM) { + burst = sconfig->src_maxburst; + ahb_addr = sconfig->src_addr; + } else { + burst = sconfig->dst_maxburst; + ahb_addr = sconfig->dst_addr; + } + + switch (burst) { + case 1: burst = TEGRA_AHBDMA_CHANNEL_AHB_BURST_1; break; + case 4: burst = TEGRA_AHBDMA_CHANNEL_AHB_BURST_4; break; + case 8: burst = TEGRA_AHBDMA_CHANNEL_AHB_BURST_8; break; + default: + return -EINVAL; + } + + ahb_seq = burst << TEGRA_AHBDMA_CHANNEL_AHB_BURST_SHIFT; + ahb_seq |= TEGRA_AHBDMA_CHANNEL_ADDR_WRAP; + ahb_seq |= TEGRA_AHBDMA_CHANNEL_INTR_ENB; + + writel_relaxed(ahb_seq, + ahbdma_chan->regs + TEGRA_AHBDMA_CHANNEL_AHB_SEQ); + + writel_relaxed(ahb_addr, + ahbdma_chan->regs + TEGRA_AHBDMA_CHANNEL_AHB_PTR); + + return 0; +} + +static void tegra_ahbdma_synchronize(struct dma_chan *chan) +{ + wait_for_completion(&to_ahbdma_chan(chan)->idling); +} + +static void tegra_ahbdma_free_chan_resources(struct dma_chan *chan) +{ + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); + struct tegra_ahbdma_tx_desc *tx; + struct list_head *entry, *tmp; + + list_for_each_safe(entry, tmp, &ahbdma_chan->pending_list) { + tx = list_entry(entry, struct tegra_ahbdma_tx_desc, node); + list_del(entry); + kfree(tx); + } +} + +static void tegra_ahbdma_init_channel(struct tegra_ahbdma *tdma, + unsigned int chan_id) +{ + struct tegra_ahbdma_chan *ahbdma_chan = &tdma->channels[chan_id]; + struct dma_chan *dma_chan = &ahbdma_chan->dma_chan; + struct dma_device *dma_dev = &tdma->dma_dev; + + INIT_LIST_HEAD(&ahbdma_chan->active_list); + INIT_LIST_HEAD(&ahbdma_chan->pending_list); + init_completion(&ahbdma_chan->idling); + spin_lock_init(&ahbdma_chan->lock); + complete(&ahbdma_chan->idling); + + ahbdma_chan->regs = tdma->regs + TEGRA_AHBDMA_CHANNEL_BASE(chan_id); + ahbdma_chan->id = chan_id; + + dma_cookie_init(dma_chan); + dma_chan->device = dma_dev; + + list_add_tail(&dma_chan->device_node, &dma_dev->channels); +} + +static struct dma_chan *tegra_ahbdma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct tegra_ahbdma *tdma = ofdma->of_dma_data; + struct dma_chan *chan; + u32 csr; + + chan = dma_get_any_slave_channel(&tdma->dma_dev); + if (!chan) + return NULL; + + /* enable channels flow control */ + if (dma_spec->args_count == 1) { + csr = TEGRA_AHBDMA_CHANNEL_FLOW; + csr |= dma_spec->args[0] << TEGRA_AHBDMA_CHANNEL_REQ_SEL_SHIFT; + + writel_relaxed(csr, + to_ahbdma_chan(chan)->regs + TEGRA_AHBDMA_CHANNEL_CSR); + } + + return chan; +} + +static int tegra_ahbdma_init_hw(struct tegra_ahbdma *tdma, struct device *dev) +{ + int err; + + err = reset_control_assert(tdma->rst); + if (err) { + dev_err(dev, "Failed to assert reset: %d\n", err); + return err; + } + + err = clk_prepare_enable(tdma->clk); + if (err) { + dev_err(dev, "Failed to enable clock: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + + err = reset_control_deassert(tdma->rst); + if (err) { + dev_err(dev, "Failed to deassert reset: %d\n", err); + return err; + } + + writel_relaxed(TEGRA_AHBDMA_CMD_ENABLE, tdma->regs + TEGRA_AHBDMA_CMD); + + writel_relaxed(TEGRA_AHBDMA_IRQ_ENB_CH(0) | + TEGRA_AHBDMA_IRQ_ENB_CH(1) | + TEGRA_AHBDMA_IRQ_ENB_CH(2) | + TEGRA_AHBDMA_IRQ_ENB_CH(3), + tdma->regs + TEGRA_AHBDMA_IRQ_ENB_MASK); + + return 0; +} + +static int tegra_ahbdma_probe(struct platform_device *pdev) +{ + struct dma_device *dma_dev; + struct tegra_ahbdma *tdma; + struct resource *res_regs; + unsigned int i; + int irq; + int err; + + tdma = devm_kzalloc(&pdev->dev, sizeof(*tdma), GFP_KERNEL); + if (!tdma) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + return irq; + } + + err = devm_request_irq(&pdev->dev, irq, tegra_ahbdma_isr, 0, + dev_name(&pdev->dev), tdma); + if (err) { + dev_err(&pdev->dev, "Failed to request IRQ\n"); + return -ENODEV; + } + + res_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_regs) + return -ENODEV; + + tdma->regs = devm_ioremap_resource(&pdev->dev, res_regs); + if (IS_ERR(tdma->regs)) + return PTR_ERR(tdma->regs); + + tdma->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tdma->clk)) { + dev_err(&pdev->dev, "Failed to get AHB-DMA clock\n"); + return PTR_ERR(tdma->clk); + } + + tdma->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(tdma->rst)) { + dev_err(&pdev->dev, "Failed to get AHB-DMA reset\n"); + return PTR_ERR(tdma->rst); + } + + err = tegra_ahbdma_init_hw(tdma, &pdev->dev); + if (err) + return err; + + dma_dev = &tdma->dma_dev; + + INIT_LIST_HEAD(&dma_dev->channels); + + for (i = 0; i < ARRAY_SIZE(tdma->channels); i++) + tegra_ahbdma_init_channel(tdma, i); + + dma_cap_set(DMA_PRIVATE, dma_dev->cap_mask); + dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask); + dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); + + dma_dev->max_burst = 8; + dma_dev->directions = TEGRA_AHBDMA_DIRECTIONS; + dma_dev->src_addr_widths = TEGRA_AHBDMA_BUS_WIDTH; + dma_dev->dst_addr_widths = TEGRA_AHBDMA_BUS_WIDTH; + dma_dev->descriptor_reuse = true; + dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + dma_dev->device_free_chan_resources = tegra_ahbdma_free_chan_resources; + dma_dev->device_prep_slave_sg = tegra_ahbdma_prep_slave_sg; + dma_dev->device_prep_dma_cyclic = tegra_ahbdma_prep_dma_cyclic; + dma_dev->device_terminate_all = tegra_ahbdma_terminate_all; + dma_dev->device_issue_pending = tegra_ahbdma_issue_pending; + dma_dev->device_tx_status = tegra_ahbdma_tx_status; + dma_dev->device_config = tegra_ahbdma_config; + dma_dev->device_synchronize = tegra_ahbdma_synchronize; + dma_dev->dev = &pdev->dev; + + err = dma_async_device_register(dma_dev); + if (err) { + dev_err(&pdev->dev, "Device registration failed %d\n", err); + return err; + } + + err = of_dma_controller_register(pdev->dev.of_node, + tegra_ahbdma_of_xlate, tdma); + if (err) { + dev_err(&pdev->dev, "OF registration failed %d\n", err); + dma_async_device_unregister(dma_dev); + return err; + } + + platform_set_drvdata(pdev, tdma); + + return 0; +} + +static int tegra_ahbdma_remove(struct platform_device *pdev) +{ + struct tegra_ahbdma *tdma = platform_get_drvdata(pdev); + + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&tdma->dma_dev); + clk_disable_unprepare(tdma->clk); + + return 0; +} + +static const struct of_device_id tegra_ahbdma_of_match[] = { + { .compatible = "nvidia,tegra20-ahbdma" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_ahbdma_of_match); + +static struct platform_driver tegra_ahbdma_driver = { + .driver = { + .name = "tegra-ahbdma", + .of_match_table = tegra_ahbdma_of_match, + }, + .probe = tegra_ahbdma_probe, + .remove = tegra_ahbdma_remove, +}; +module_platform_driver(tegra_ahbdma_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra AHB DMA Controller driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL");