Hello, Eric On 07.10.2017 09:21, Eric Pilmore wrote: > > > On Fri, Oct 6, 2017 at 12:11 PM, Dmitry Osipenko <digetx@xxxxxxxxx > <mailto:digetx@xxxxxxxxx>> wrote: > > On 04.10.2017 02:58, Dmitry Osipenko wrote: > > 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, mem-to-mem isn't implemented as well. > > > > Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx <mailto:digetx@xxxxxxxxx>> > > --- > > drivers/dma/Kconfig | 10 + > > drivers/dma/Makefile | 1 + > > drivers/dma/tegra20-ahb-dma.c | 630 > ++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 641 insertions(+) > > create mode 100644 drivers/dma/tegra20-ahb-dma.c > > > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig > > index 04e381b522b4..7d132aa85174 100644 > > --- a/drivers/dma/Kconfig > > +++ b/drivers/dma/Kconfig > > @@ -512,6 +512,16 @@ 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 || COMPILE_TEST > > + select DMA_ENGINE > > + select DMA_VIRTUAL_CHANNELS > > + 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 a145ad1426bc..f3d284bf6d65 100644 > > --- a/drivers/dma/Makefile > > +++ b/drivers/dma/Makefile > > @@ -62,6 +62,7 @@ obj-$(CONFIG_STM32_DMA) += stm32-dma.o > > obj-$(CONFIG_STM32_DMAMUX) += stm32-dmamux.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..2d176a5536aa > > --- /dev/null > > +++ b/drivers/dma/tegra20-ahb-dma.c > > @@ -0,0 +1,630 @@ > > +/* > > + * Copyright 2017 Dmitry Osipenko <digetx@xxxxxxxxx > <mailto:digetx@xxxxxxxxx>> > > + * > > + * 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/clk.h> > > +#include <linux/delay.h> > > +#include <linux/io.h> > > +#include <linux/module.h> > > +#include <linux/of_device.h> > > +#include <linux/of_dma.h> > > +#include <linux/platform_device.h> > > +#include <linux/reset.h> > > +#include <linux/slab.h> > > + > > +#include <dt-bindings/dma/tegra-ahb-dma.h> > > + > > +#include "virt-dma.h" > > + > > +#define AHBDMA_CMD 0x0 > > +#define AHBDMA_CMD_ENABLE BIT(31) > > + > > +#define AHBDMA_IRQ_ENB_MASK 0x20 > > +#define AHBDMA_IRQ_ENB_CH(ch) BIT(ch) > > + > > +#define AHBDMA_CH_BASE(ch) (0x1000 + (ch) * 0x20) > > + > > +#define AHBDMA_CH_CSR 0x0 > > +#define AHBDMA_CH_ADDR_WRAP BIT(18) > > +#define AHBDMA_CH_FLOW BIT(24) > > +#define AHBDMA_CH_ONCE BIT(26) > > +#define AHBDMA_CH_DIR_TO_XMB BIT(27) > > +#define AHBDMA_CH_IE_EOC BIT(30) > > +#define AHBDMA_CH_ENABLE BIT(31) > > +#define AHBDMA_CH_REQ_SEL_SHIFT 16 > > +#define AHBDMA_CH_WCOUNT_MASK GENMASK(15, 2) > > + > > +#define AHBDMA_CH_STA 0x4 > > +#define AHBDMA_CH_IS_EOC BIT(30) > > + > > +#define AHBDMA_CH_AHB_PTR 0x10 > > + > > +#define AHBDMA_CH_AHB_SEQ 0x14 > > +#define AHBDMA_CH_INTR_ENB BIT(31) > > +#define AHBDMA_CH_AHB_BURST_SHIFT 24 > > +#define AHBDMA_CH_AHB_BURST_1 2 > > +#define AHBDMA_CH_AHB_BURST_4 3 > > +#define AHBDMA_CH_AHB_BURST_8 4 > > + > > +#define AHBDMA_CH_XMB_PTR 0x18 > > + > > +#define AHBDMA_BUS_WIDTH BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) > > + > > +#define AHBDMA_DIRECTIONS BIT(DMA_DEV_TO_MEM) | \ > > + BIT(DMA_MEM_TO_DEV) > > + > > +#define AHBDMA_BURST_COMPLETE_TIME 20 > > + > > +struct tegra_ahbdma_tx_desc { > > + struct virt_dma_desc vdesc; > > + dma_addr_t mem_addr; > > + phys_addr_t ahb_addr; > > + u32 ahb_seq; > > + u32 csr; > > +}; > > + > > +struct tegra_ahbdma_chan { > > + struct tegra_ahbdma_tx_desc *active_tx; > > + struct virt_dma_chan vchan; > > + struct completion idling; > > + void __iomem *regs; > > + phys_addr_t ahb_addr; > > + u32 ahb_seq; > > + u32 csr; > > + unsigned int of_req_sel; > > + bool of_slave; > > +}; > > + > > +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_chan *to_ahbdma_chan(struct dma_chan *chan) > > +{ > > + return container_of(chan, struct tegra_ahbdma_chan, vchan.chan); > > +} > > + > > +static inline struct tegra_ahbdma_tx_desc *to_ahbdma_tx_desc( > > + struct virt_dma_desc *vdesc) > > +{ > > + return container_of(vdesc, struct tegra_ahbdma_tx_desc, vdesc); > > +} > > + > > +static struct tegra_ahbdma_tx_desc *tegra_ahbdma_get_next_tx( > > + struct tegra_ahbdma_chan *chan) > > +{ > > + struct virt_dma_desc *vdesc = vchan_next_desc(&chan->vchan); > > + > > + if (vdesc) > > + list_del(&vdesc->node); > > I just noticed that this is incorrect. Node must be deleted after TX completion, > otherwise vchan_find_desc won't find TX and residual won't be reported by > dmaengine_tx_status. > > Jon, I think you ADMA driver has the same issue, as well as several other DMA > drivers that use virt-dma. > > > + > > + return vdesc ? to_ahbdma_tx_desc(vdesc) : NULL; > > +} > > + > > +static void tegra_ahbdma_issue_next_tx(struct tegra_ahbdma_chan *chan) > > +{ > > + struct tegra_ahbdma_tx_desc *tx = tegra_ahbdma_get_next_tx(chan); > > + > > + if (tx) { > > + writel_relaxed(tx->ahb_seq, chan->regs + AHBDMA_CH_AHB_SEQ); > > + writel_relaxed(tx->ahb_addr, chan->regs + AHBDMA_CH_AHB_PTR); > > + writel_relaxed(tx->mem_addr, chan->regs + AHBDMA_CH_XMB_PTR); > > + writel_relaxed(tx->csr, chan->regs + AHBDMA_CH_CSR); > > + > > + reinit_completion(&chan->idling); > > + } else > > + complete_all(&chan->idling); > > + > > + chan->active_tx = tx; > > +} > > + > > +static bool tegra_ahbdma_clear_interrupt(struct tegra_ahbdma_chan *chan) > > +{ > > + u32 status = readl_relaxed(chan->regs + AHBDMA_CH_STA); > > + > > + if (status & AHBDMA_CH_IS_EOC) { > > + writel_relaxed(AHBDMA_CH_IS_EOC, chan->regs + AHBDMA_CH_STA); > > + > > + return true; > > + } > > + > > + return false; > > +} > > + > > +static bool tegra_ahbdma_handle_channel(struct tegra_ahbdma_chan *chan) > > +{ > > + struct tegra_ahbdma_tx_desc *tx; > > + unsigned long flags; > > + bool intr = false; > > + bool cyclic; > > + > > + spin_lock_irqsave(&chan->vchan.lock, flags); > > + > > + tx = chan->active_tx; > > + if (tx) > > + intr = tegra_ahbdma_clear_interrupt(chan); > > + > > + if (intr) { > > + cyclic = !(tx->csr & AHBDMA_CH_ONCE); > > + > > + if (!cyclic) > > + tegra_ahbdma_issue_next_tx(chan); > > + > > + if (cyclic) > > + vchan_cyclic_callback(&tx->vdesc); > > + else > > + vchan_cookie_complete(&tx->vdesc); > > + } > > + > > + spin_unlock_irqrestore(&chan->vchan.lock, flags); > > + > > + return intr; > > +} > > + > > +static irqreturn_t tegra_ahbdma_isr(int irq, void *dev_id) > > +{ > > + struct tegra_ahbdma *tdma = dev_id; > > + bool handled; > > + > > + handled = tegra_ahbdma_handle_channel(&tdma->channels[0]); > > + handled |= tegra_ahbdma_handle_channel(&tdma->channels[1]); > > + handled |= tegra_ahbdma_handle_channel(&tdma->channels[2]); > > + handled |= tegra_ahbdma_handle_channel(&tdma->channels[3]); > > + > > + return handled ? IRQ_HANDLED : IRQ_NONE; > > +} > > + > > +static void tegra_ahbdma_tx_desc_free(struct virt_dma_desc *vdesc) > > +{ > > + kfree(to_ahbdma_tx_desc(vdesc)); > > > > Can do devm_kfree() here instead. See devm_kzalloc() comment below and create a > chan2dev function. Then > Add the following field to your desc structure. This will get set when the > descriptor is created. > > struct tegra_ahmdma_tx_desc { > .... > struct tegra_ahbdma_chan *tchan; /* see tegra_ahbdma_prep() */ > .... > }; > > struct tegra_ahbdma_tx_desc *tx = to_ahbdma_tx_desc(vdesc); > struct device *dev = chan2dev(&tx->tchan->vchan.chan); > devm_kfree(dev, tx); > Unfortunately I'm thinking that your proposal isn't correct: 1) virt-dma manages descriptor allocations for us here, all desc's are free'd by vchan_free_chan_resources on channels release 2) we want to release all channels descriptors when *channel* is released, using devm_* just doesn't make sense > > > +} > > + > > +static struct dma_async_tx_descriptor *tegra_ahbdma_prep( > > + struct dma_chan *chan, > > + enum dma_transfer_direction dir, > > + unsigned long flags, > > + dma_addr_t paddr, > > + size_t size, > > + bool cyclic) > > +{ > > + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); > > + struct tegra_ahbdma_tx_desc *tx; > > + u32 csr = ahbdma_chan->csr; > > + > > + /* size and alignments should fulfill HW requirements */ > > + if (size < 4 || size & 3 || paddr & 3) > > + return NULL; > > + > > + tx = kzalloc(sizeof(*tx), GFP_NOWAIT); > > > > How about using devm_kzalloc() here? You can get access to your "dev" with a > function like the following: > > static inline struct device *chan2dev(struct dma_chan *chan) > { > return &chan->dev->device; > } > > > > + if (!tx) > > + return NULL; > > + > > + if (dir == DMA_DEV_TO_MEM) > > + csr |= AHBDMA_CH_DIR_TO_XMB; > > + > > + if (!cyclic) > > + csr |= AHBDMA_CH_ONCE; > > + > > + tx->csr = csr | (size - sizeof(u32)); > > + tx->ahb_seq = ahbdma_chan->ahb_seq; > > + tx->ahb_addr = ahbdma_chan->ahb_addr; > > + tx->mem_addr = paddr; > > > > Add setting of suggested new field: > tx->tchan = ahbdma_chan; > > > > + > > + return vchan_tx_prep(&ahbdma_chan->vchan, &tx->vdesc, flags); > > +} > > + > > +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) > > +{ > > + /* unimplemented */ > > + if (sg_len != 1 || sg_dma_len(sgl) > SZ_64K) > > + return NULL; > > + > > + return tegra_ahbdma_prep(chan, dir, flags, sg_dma_address(sgl), > > + sg_dma_len(sgl), false); > > +} > > + > > +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) > > +{ > > + /* unimplemented */ > > + if (buf_len != period_len || buf_len > SZ_64K) > > + return NULL; > > + > > + return tegra_ahbdma_prep(chan, dir, flags, buf_addr, buf_len, true); > > +} > > + > > +static void tegra_ahbdma_issue_pending(struct dma_chan *chan) > > +{ > > + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); > > + struct virt_dma_chan *vchan = &ahbdma_chan->vchan; > > + unsigned long flags; > > + > > + spin_lock_irqsave(&vchan->lock, flags); > > + > > + if (vchan_issue_pending(vchan) && !ahbdma_chan->active_tx) > > + tegra_ahbdma_issue_next_tx(ahbdma_chan); > > + > > + spin_unlock_irqrestore(&vchan->lock, flags); > > +} > > + > > +static size_t tegra_ahbdma_residual(struct tegra_ahbdma_chan *chan) > > +{ > > + u32 status = readl_relaxed(chan->regs + AHBDMA_CH_STA); > > + > > + return (status & AHBDMA_CH_WCOUNT_MASK); > > +} > > + > > +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; > > + struct virt_dma_desc *vdesc; > > + enum dma_status cookie_status; > > + unsigned long flags; > > + size_t residual; > > + > > + spin_lock_irqsave(&ahbdma_chan->vchan.lock, flags); > > + > > + cookie_status = dma_cookie_status(chan, cookie, state); > > + if (cookie_status == DMA_COMPLETE) > > + goto unlock; > > + > > + vdesc = vchan_find_desc(&ahbdma_chan->vchan, cookie); > > + if (!vdesc) > > + residual = 0; > > + else { > > + tx = to_ahbdma_tx_desc(vdesc); > > + > > + if (tx == ahbdma_chan->active_tx) > > + residual = tegra_ahbdma_residual(ahbdma_chan); > > + else > > + residual = tx->csr & AHBDMA_CH_WCOUNT_MASK; > > + > > + residual += sizeof(u32); > > + } > > + > > + dma_set_residue(state, residual); > > + > > +unlock: > > + spin_unlock_irqrestore(&ahbdma_chan->vchan.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); > > + unsigned long flags; > > + LIST_HEAD(head); > > + u32 csr; > > + > > + spin_lock_irqsave(&ahbdma_chan->vchan.lock, flags); > > + > > + csr = readl_relaxed(ahbdma_chan->regs + AHBDMA_CH_CSR); > > + writel_relaxed(csr & ~AHBDMA_CH_ENABLE, > > + ahbdma_chan->regs + AHBDMA_CH_CSR); > > + > > + if (ahbdma_chan->active_tx) { > > + udelay(AHBDMA_BURST_COMPLETE_TIME); > > + > > + writel_relaxed(AHBDMA_CH_IS_EOC, > > + ahbdma_chan->regs + AHBDMA_CH_STA); > > + > > + ahbdma_chan->active_tx = NULL; > > + } > > + > > + vchan_get_all_descriptors(&ahbdma_chan->vchan, &head); > > + complete_all(&ahbdma_chan->idling); > > + > > + spin_unlock_irqrestore(&ahbdma_chan->vchan.lock, flags); > > + > > + vchan_dma_desc_free_list(&ahbdma_chan->vchan, &head); > > + > > + 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, csr; > > + unsigned int slave_id; > > + phys_addr_t ahb_addr; > > + > > + if (sconfig->src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES || > > + sconfig->dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) > > + return -EINVAL; > > + > > + switch (dir) { > > + case DMA_DEV_TO_MEM: > > + burst = sconfig->src_maxburst; > > + ahb_addr = sconfig->src_addr; > > + break; > > + case DMA_MEM_TO_DEV: > > + burst = sconfig->dst_maxburst; > > + ahb_addr = sconfig->dst_addr; > > + break; > > + default: > > + return -EINVAL; > > + } > > + > > + switch (burst) { > > + case 1: > > + burst = AHBDMA_CH_AHB_BURST_1; > > + break; > > + case 4: > > + burst = AHBDMA_CH_AHB_BURST_4; > > + break; > > + case 8: > > + burst = AHBDMA_CH_AHB_BURST_8; > > + break; > > + default: > > + return -EINVAL; > > + } > > + > > + if (ahb_addr & 3) > > + return -EINVAL; > > + > > + ahb_seq = burst << AHBDMA_CH_AHB_BURST_SHIFT; > > + ahb_seq |= AHBDMA_CH_INTR_ENB; > > + > > + csr = AHBDMA_CH_ENABLE; > > + csr |= AHBDMA_CH_IE_EOC; > > + > > + if (ahbdma_chan->of_slave || sconfig->device_fc) { > > + if (ahbdma_chan->of_req_sel < TEGRA_AHBDMA_REQ_N_A) > > + slave_id = ahbdma_chan->of_req_sel; > > + else > > + slave_id = sconfig->slave_id; > > + > > + if (slave_id > 15) > > + return -EINVAL; > > + > > + ahb_seq |= AHBDMA_CH_ADDR_WRAP; > > + > > + csr |= slave_id << AHBDMA_CH_REQ_SEL_SHIFT; > > + csr |= AHBDMA_CH_FLOW; > > + } > > + > > + ahbdma_chan->csr = csr; > > + ahbdma_chan->ahb_seq = ahb_seq; > > + ahbdma_chan->ahb_addr = ahb_addr; > > + > > + return 0; > > +} > > + > > +static void tegra_ahbdma_synchronize(struct dma_chan *chan) > > +{ > > + struct tegra_ahbdma_chan *ahbdma_chan = to_ahbdma_chan(chan); > > + > > + wait_for_completion(&ahbdma_chan->idling); > > + vchan_synchronize(&ahbdma_chan->vchan); > > +} > > + > > +static void tegra_ahbdma_free_chan_resources(struct dma_chan *chan) > > +{ > > + vchan_free_chan_resources(to_virt_chan(chan)); > > +} > > + > > +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_device *dma_dev = &tdma->dma_dev; > > + > > + vchan_init(&ahbdma_chan->vchan, dma_dev); > > + init_completion(&ahbdma_chan->idling); > > + complete(&ahbdma_chan->idling); > > + > > + ahbdma_chan->regs = tdma->regs + AHBDMA_CH_BASE(chan_id); > > + ahbdma_chan->vchan.desc_free = tegra_ahbdma_tx_desc_free; > > + ahbdma_chan->of_req_sel = TEGRA_AHBDMA_REQ_N_A; > > +} > > + > > +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; > > + > > + chan = dma_get_any_slave_channel(&tdma->dma_dev); > > + if (!chan) > > + return NULL; > > + > > + to_ahbdma_chan(chan)->of_req_sel = dma_spec->args[0]; > > + to_ahbdma_chan(chan)->of_slave = true; > > + > > + 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(AHBDMA_CMD_ENABLE, tdma->regs + AHBDMA_CMD); > > + > > + writel_relaxed(AHBDMA_IRQ_ENB_CH(0) | > > + AHBDMA_IRQ_ENB_CH(1) | > > + AHBDMA_IRQ_ENB_CH(2) | > > + AHBDMA_IRQ_ENB_CH(3), > > + tdma->regs + 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 = AHBDMA_DIRECTIONS; > > + dma_dev->src_addr_widths = AHBDMA_BUS_WIDTH; > > + dma_dev->dst_addr_widths = 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 <digetx@xxxxxxxxx > <mailto:digetx@xxxxxxxxx>>"); > > +MODULE_LICENSE("GPL"); > > -- To unsubscribe from this list: send the line "unsubscribe dmaengine" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html