Re: [PATCH v7 2/2] dmaengine: Loongson1: Add Loongson-1 APB DMA driver

From: Huacai Chen
Date: Wed Apr 03 2024 - 10:39:41 EST


On Wed, Apr 3, 2024 at 10:24 AM Keguang Zhang <keguang.zhang@xxxxxxxxx> wrote:
>
> On Tue, Apr 2, 2024 at 10:50 PM Huacai Chen <chenhuacai@xxxxxxxxxx> wrote:
> >
> > On Tue, Apr 2, 2024 at 6:51 PM Keguang Zhang <keguang.zhang@xxxxxxxxx> wrote:
> > >
> > > On Tue, Apr 2, 2024 at 5:04 PM Huacai Chen <chenhuacai@kernelorg> wrote:
> > > >
> > > > On Tue, Apr 2, 2024 at 9:56 AM Keguang Zhang <keguang.zhang@xxxxxxxxx> wrote:
> > > > >
> > > > > On Mon, Apr 1, 2024 at 9:24 PM Huacai Chen <chenhuacai@xxxxxxxxxx> wrote:
> > > > > >
> > > > > > On Mon, Apr 1, 2024 at 7:10 PM Keguang Zhang <keguang.zhang@xxxxxxxxx> wrote:
> > > > > > >
> > > > > > > On Mon, Apr 1, 2024 at 5:06 PM Huacai Chen <chenhuacai@xxxxxxxxxx> wrote:
> > > > > > > >
> > > > > > > > On Mon, Apr 1, 2024 at 10:45 AM Keguang Zhang <keguang.zhang@xxxxxxxxx> wrote:
> > > > > > > > >
> > > > > > > > > Hi Huacai,
> > > > > > > > >
> > > > > > > > > On Sat, Mar 30, 2024 at 9:59 PM Huacai Chen <chenhuacai@xxxxxxxxxx> wrote:
> > > > > > > > > >
> > > > > > > > > > Hi, Keguang,
> > > > > > > > > >
> > > > > > > > > > On Fri, Mar 29, 2024 at 7:28 PM Keguang Zhang via B4 Relay
> > > > > > > > > > <devnull+keguang.zhang.gmail.com@xxxxxxxxxx> wrote:
> > > > > > > > > > >
> > > > > > > > > > > From: Keguang Zhang <keguang.zhang@xxxxxxxxx>
> > > > > > > > > > >
> > > > > > > > > > > This patch adds APB DMA driver for Loongson-1 SoCs.
> > > > > > > > > > >
> > > > > > > > > > > Signed-off-by: Keguang Zhang <keguang.zhang@xxxxxxxxx>
> > > > > > > > > > > ---
> > > > > > > > > > > Changes in v7:
> > > > > > > > > > > - Change the comptible to 'loongson,ls1*-apbdma'
> > > > > > > > > > > - Update Kconfig and Makefile accordingly
> > > > > > > > > > > - Rename the file to loongson1-apb-dma.c to keep the consistency
> > > > > > > > > > >
> > > > > > > > > > > Changes in v6:
> > > > > > > > > > > - Implement .device_prep_dma_cyclic for Loongson1 audio driver,
> > > > > > > > > > > - as well as .device_pause and .device_resume.
> > > > > > > > > > > - Set the limitation LS1X_DMA_MAX_DESC and put all descriptors
> > > > > > > > > > > - into one page to save memory
> > > > > > > > > > > - Move dma_pool_zalloc() into ls1x_dma_alloc_desc()
> > > > > > > > > > > - Drop dma_slave_config structure
> > > > > > > > > > > - Use .remove_new instead of .remove
> > > > > > > > > > > - Use KBUILD_MODNAME for the driver name
> > > > > > > > > > > - Improve the debug information
> > > > > > > > > > >
> > > > > > > > > > > Changes in v5:
> > > > > > > > > > > - Add DT support
> > > > > > > > > > > - Use DT data instead of platform data
> > > > > > > > > > > - Use chan_id of struct dma_chan instead of own id
> > > > > > > > > > > - Use of_dma_xlate_by_chan_id() instead of ls1x_dma_filter()
> > > > > > > > > > > - Update the author information to my official name
> > > > > > > > > > >
> > > > > > > > > > > Changes in v4:
> > > > > > > > > > > - Use dma_slave_map to find the proper channel.
> > > > > > > > > > > - Explicitly call devm_request_irq() and tasklet_kill().
> > > > > > > > > > > - Fix namespace issue.
> > > > > > > > > > > - Some minor fixes and cleanups.
> > > > > > > > > > >
> > > > > > > > > > > Changes in v3:
> > > > > > > > > > > - Rename ls1x_dma_filter_fn to ls1x_dma_filter.
> > > > > > > > > > >
> > > > > > > > > > > Changes in v2:
> > > > > > > > > > > - Change the config from 'DMA_LOONGSON1' to 'LOONGSON1_DMA',
> > > > > > > > > > > - and rearrange it in alphabetical order in Kconfig and Makefile.
> > > > > > > > > > > - Fix comment style.
> > > > > > > > > > > ---
> > > > > > > > > > > drivers/dma/Kconfig | 9 +
> > > > > > > > > > > drivers/dma/Makefile | 1 +
> > > > > > > > > > > drivers/dma/loongson1-apb-dma.c | 665 ++++++++++++++++++++++++++++++++++++++++
> > > > > > > > > > > 3 files changed, 675 insertions(+)
> > > > > > > > > > >
> > > > > > > > > > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> > > > > > > > > > > index 002a5ec80620..f7b06c4cdf3f 100644
> > > > > > > > > > > --- a/drivers/dma/Kconfig
> > > > > > > > > > > +++ b/drivers/dma/Kconfig
> > > > > > > > > > > @@ -369,6 +369,15 @@ config K3_DMA
> > > > > > > > > > > Support the DMA engine for Hisilicon K3 platform
> > > > > > > > > > > devices.
> > > > > > > > > > >
> > > > > > > > > > > +config LOONGSON1_APB_DMA
> > > > > > > > > > > + tristate "Loongson1 APB DMA support"
> > > > > > > > > > > + depends on MACH_LOONGSON32 || COMPILE_TEST
> > > > > > > > > > > + select DMA_ENGINE
> > > > > > > > > > > + select DMA_VIRTUAL_CHANNELS
> > > > > > > > > > > + help
> > > > > > > > > > > + This selects support for the APB DMA controller in Loongson1 SoCs,
> > > > > > > > > > > + which is required by Loongson1 NAND and audio support.
> > > > > > > > > > Why not rename to LS1X_APB_DMA and put it just before LS2X_APB_DMA
> > > > > > > > > > (and also the driver file name)?
> > > > > > > > > >
> > > > > > > > > So far all Kconfig entries of Loongson-1 drivers are named with the
> > > > > > > > > keyword "LOONGSON1".
> > > > > > > > > The same is true for these file names.
> > > > > > > > > Therefore, I need to keep the consistency.
> > > > > > > > But I see LS1X_IRQ in drivers/irqchip/Kconfig
> > > > > > > >
> > > > > > > Indeed, that's an exception, which was submitted by Jiaxun several years ago.
> > > > > > > Actually, most drivers of Loongson family use the keyword "LOONGSON"
> > > > > > > for Kconfig and "loongson" for filename.
> > > > > > > Thus I take this keywork as the naming convention.
> > > > > > But I think keeping consistency in a same subsystem is better than
> > > > > > keeping consistency in a same SoC (but cross subsystems).
> > > > > >
> > > > > In my opinion, "LS*X" is too short and may be confused with other SoCs.
> > > > > Meanwhile, there are only four drivers that use this keyword.
> > > > > config I2C_LS2X
> > > > > config LS2K_RESET
> > > > > config LS2X_APB_DMA
> > > > > config LS1X_IRQ
> > > > > Then, my suggestion is to change these "LS*X" to "LOONGSON*" to get a
> > > > > clear meaning.
> > > > We have made a naming conversion some years before with Jiaxun.
> > > > 1, Use "Loongson" for CPU in arch code;
> > > > 2, Use "LS7A" or something like this for bridges and devices.
> > > > 3, For drivers in SoC, if the driver is specific to Loongson-1, use
> > > > LS1X, if it is to Loongson-2, use LS2X, if it is shared by both
> > > > Loongson-1 and Loongson-2, use LOONGSON.
> > > >
> > > OK. But the doesn't the answer the question of confusion, such as
> > > "Freescale LS1021A".
> > > The same problem happens to the filenames.
> > > ./drivers/gpu/drm/nouveau/nvkm/nvfw/ls.c
> > > ./drivers/gpu/drm/nouveau/nvkm/subdev/acr/lsfw.c
> > > ./drivers/gpu/drm/amd/amdgpu/lsdma_v6_0.c
> > > ./drivers/gpu/drm/amd/amdgpu/lsdma_v7_0.c
> > > ./arch/powerpc/platforms/embedded6xx/ls_uart.c
> > > Regarding "LS*X" itself, it contains the wildcard character "X" which
> > > itself is confusing.
> > > Therefore, I don't think "LS*X" is clear enough.
> > >
> The confusion problem remains.
> Honestly, I don't think "LS" is a good short for "LOONGSON".
>
> > > On the other hand, I see "LOONGSON2_*" strings are still there.
> This question remains.
>
> > > In addition, some of "LOONGSON_" definitions are not applicable for
> > > Loongson-1 at all, which breaks your convention.
> > > config SND_SOC_LOONGSON_I2S_PCI /* Loongson-1 doesn't support I2S */
> > > config SND_SOC_LOONGSON_CARD
> > They are shared by LS2K and LS7A.
> >
> > > config DWMAC_LOONGSON1
> > > config DWMAC_LOONGSON /* This glue layer doesn't support Loongson-1 */
> > > config COMMON_CLK_LOONGSON2
> unaddressed
>
> > > config RTC_DRV_LOONGSON
> > RTC is shared by LS2K and LS7A.
> >
> > > config SPI_LOONGSON_CORE
> > > config SPI_LOONGSON_PCI /* N/A for Loongson-1 */
> > > config SPI_LOONGSON_PLATFORM
> > SPI is also shared by LS2K and LS7A.
> >
> > > config LOONGSON2_CPUFREQ
> unaddressed
>
> > > config DRM_LOONGSON /* N/A for Loongson-1 */
> > DRM is also shared by LS2K and LS7A.
> >
> > > config LOONGSON1_WDT
> > > config CLKSRC_LOONGSON1_PWM
> > > config LOONGSON_LIOINTC /* N/A for Loongson-1 */
> > > config LOONGSON_EIOINTC /* N/A for Loongson-1 */
> > > config LOONGSON_HTPIC /* N/A for Loongson-1 */
> > > config LOONGSON_HTVEC /* N/A for Loongson-1 */
> > > config LOONGSON_PCH_PIC /* N/A for Loongson-1 */
> > > config LOONGSON_PCH_MSI /* N/A for Loongson-1 */
> > > config LOONGSON_PCH_LPC /* N/A for Loongson-1 */
> > All interrupt controllers are shared by Loongson-2 and Loongson-3.
> >
> > > config PINCTRL_LOONGSON2
> unaddressed
> > > config LOONGSON2_THERMAL
> ditto
> > > config LOONGSON2_GUTS
> ditto
> > > config LOONGSON2_PM
> ditto
> > > config LOONGSON_LAPTOP /* N/A for Loongson-1 */
> > Laptop driver is shared by Loongson-2 and Loongson-3.
> >
> > > config GPIO_LOONGSON
> > > config GPIO_LOONGSON_64BIT -> N/A for Loongson-1
> > > config GPIO_LOONGSON1
> > GPIO driver is shared by LS2K and LS7A.
> >
> > > config PCI_LOONGSON
> > PCI driver is shared by Loongson-2 and Loongson-3.
>
> You said "if it is shared by both Loongson-1 and Loongson-2, use LOONGSON"
> Now the rule changes from "Loongson-1 and Loongson-2" to "Loongson-2
> and Loongson-3".
> Then, when shall we use "LOONGSON"?
"If it is shared Loongson-1 and Loongson-2" is an example, if you need
an exact description, then "If it is shared by more than one series,
such as Loongson-1 and Loongson-2".

>
> Here is the situation: only are four drivers use "LS*".
> config I2C_LS2X
> config LS2K_RESET
> config LS2X_APB_DMA
> config LS1X_IRQ
> My suggestion is to use the intuitive "LOONGSON*" for both CPU and
> drivers, which is easy to understand.
> And replace the confusing and unclear "LS*X" with "LOONGSON*".
> Use "LOONGSON" when the driver/feature is shared with Loongson-1,
> Loongson-2 and Loongson-3.
> >
> > >
> > > What's your plan about the above Kconfig entries?
> > Yes, there are exceptions indeed, but very rare. And some of the
> > exceptions are due to the limited spare time of Jiaxun and me. But in
> > this case, it is better to keep consistency in the DMA subsystem.
> >
> Sorry, I'm not persuaded.
> Please consider my proposal.
Renaming Kconfig is a bad idea, it breaks existing config files, so we
cannot do that. Part of your proposal is reasonable, for example,
Loongson-3's CPUFreq driver will be LOONGSON3_CPUFREQ, to keep
consistency in the same subsystem. But for the DMA subsystem, it also
should follow the existing style, use LS1X.

Huacai

> Thanks!
>
> > Huacai
> >
> > > Why can't we use LOONGSON1/LOONGSON2 for drivers?
> > >
> > >
> > > > Huacai
> > > >
> > > > >
> > > > > > Huacai
> > > > > >
> > > > > > >
> > > > > > > > Huacai
> > > > > > > >
> > > > > > > > >
> > > > > > > > >
> > > > > > > > > > Huacai
> > > > > > > > > >
> > > > > > > > > > > +
> > > > > > > > > > > config LPC18XX_DMAMUX
> > > > > > > > > > > bool "NXP LPC18xx/43xx DMA MUX for PL080"
> > > > > > > > > > > depends on ARCH_LPC18XX || COMPILE_TEST
> > > > > > > > > > > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> > > > > > > > > > > index dfd40d14e408..b26f6677978a 100644
> > > > > > > > > > > --- a/drivers/dma/Makefile
> > > > > > > > > > > +++ b/drivers/dma/Makefile
> > > > > > > > > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> > > > > > > > > > > obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> > > > > > > > > > > obj-y += idxd/
> > > > > > > > > > > obj-$(CONFIG_K3_DMA) += k3dma.o
> > > > > > > > > > > +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > > > > > > > > > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> > > > > > > > > > > obj-$(CONFIG_LS2X_APB_DMA) += ls2x-apb-dma.o
> > > > > > > > > > > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> > > > > > > > > > > diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson1-apb-dma.c
> > > > > > > > > > > new file mode 100644
> > > > > > > > > > > index 000000000000..d474a2601e6e
> > > > > > > > > > > --- /dev/null
> > > > > > > > > > > +++ b/drivers/dma/loongson1-apb-dma.c
> > > > > > > > > > > @@ -0,0 +1,665 @@
> > > > > > > > > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > > > > > > > > +/*
> > > > > > > > > > > + * Driver for Loongson-1 APB DMA Controller
> > > > > > > > > > > + *
> > > > > > > > > > > + * Copyright (C) 2015-2024 Keguang Zhang <keguang.zhang@xxxxxxxxx>
> > > > > > > > > > > + */
> > > > > > > > > > > +
> > > > > > > > > > > +#include <linux/dmapool.h>
> > > > > > > > > > > +#include <linux/dma-mapping.h>
> > > > > > > > > > > +#include <linux/init.h>
> > > > > > > > > > > +#include <linux/interrupt.h>
> > > > > > > > > > > +#include <linux/iopoll.h>
> > > > > > > > > > > +#include <linux/module.h>
> > > > > > > > > > > +#include <linux/of.h>
> > > > > > > > > > > +#include <linux/of_dma.h>
> > > > > > > > > > > +#include <linux/platform_device.h>
> > > > > > > > > > > +#include <linux/slab.h>
> > > > > > > > > > > +
> > > > > > > > > > > +#include "dmaengine.h"
> > > > > > > > > > > +#include "virt-dma.h"
> > > > > > > > > > > +
> > > > > > > > > > > +/* Loongson-1 DMA Control Register */
> > > > > > > > > > > +#define DMA_CTRL 0x0
> > > > > > > > > > > +
> > > > > > > > > > > +/* DMA Control Register Bits */
> > > > > > > > > > > +#define DMA_STOP BIT(4)
> > > > > > > > > > > +#define DMA_START BIT(3)
> > > > > > > > > > > +#define DMA_ASK_VALID BIT(2)
> > > > > > > > > > > +
> > > > > > > > > > > +#define DMA_ADDR_MASK GENMASK(31, 6)
> > > > > > > > > > > +
> > > > > > > > > > > +/* DMA Next Field Bits */
> > > > > > > > > > > +#define DMA_NEXT_VALID BIT(0)
> > > > > > > > > > > +
> > > > > > > > > > > +/* DMA Command Field Bits */
> > > > > > > > > > > +#define DMA_RAM2DEV BIT(12)
> > > > > > > > > > > +#define DMA_INT BIT(1)
> > > > > > > > > > > +#define DMA_INT_MASK BIT(0)
> > > > > > > > > > > +
> > > > > > > > > > > +#define LS1X_DMA_MAX_CHANNELS 3
> > > > > > > > > > > +
> > > > > > > > > > > +/* Size of allocations for hardware descriptors */
> > > > > > > > > > > +#define LS1X_DMA_DESCS_SIZE PAGE_SIZE
> > > > > > > > > > > +#define LS1X_DMA_MAX_DESC \
> > > > > > > > > > > + (LS1X_DMA_DESCS_SIZE / sizeof(struct ls1x_dma_hwdesc))
> > > > > > > > > > > +
> > > > > > > > > > > +struct ls1x_dma_hwdesc {
> > > > > > > > > > > + u32 next; /* next descriptor address */
> > > > > > > > > > > + u32 saddr; /* memory DMA address */
> > > > > > > > > > > + u32 daddr; /* device DMA address */
> > > > > > > > > > > + u32 length;
> > > > > > > > > > > + u32 stride;
> > > > > > > > > > > + u32 cycles;
> > > > > > > > > > > + u32 cmd;
> > > > > > > > > > > + u32 stats;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +struct ls1x_dma_desc {
> > > > > > > > > > > + struct virt_dma_desc vdesc;
> > > > > > > > > > > + enum dma_transfer_direction dir;
> > > > > > > > > > > + enum dma_transaction_type type;
> > > > > > > > > > > + unsigned int bus_width;
> > > > > > > > > > > +
> > > > > > > > > > > + unsigned int nr_descs; /* number of descriptors */
> > > > > > > > > > > +
> > > > > > > > > > > + struct ls1x_dma_hwdesc *hwdesc;
> > > > > > > > > > > + dma_addr_t hwdesc_phys;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +struct ls1x_dma_chan {
> > > > > > > > > > > + struct virt_dma_chan vchan;
> > > > > > > > > > > + struct dma_pool *desc_pool;
> > > > > > > > > > > + phys_addr_t src_addr;
> > > > > > > > > > > + phys_addr_t dst_addr;
> > > > > > > > > > > + enum dma_slave_buswidth src_addr_width;
> > > > > > > > > > > + enum dma_slave_buswidth dst_addr_width;
> > > > > > > > > > > +
> > > > > > > > > > > + void __iomem *reg_base;
> > > > > > > > > > > + int irq;
> > > > > > > > > > > +
> > > > > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > > > > +
> > > > > > > > > > > + struct ls1x_dma_hwdesc *curr_hwdesc;
> > > > > > > > > > > + dma_addr_t curr_hwdesc_phys;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +struct ls1x_dma {
> > > > > > > > > > > + struct dma_device ddev;
> > > > > > > > > > > + void __iomem *reg_base;
> > > > > > > > > > > +
> > > > > > > > > > > + unsigned int nr_chans;
> > > > > > > > > > > + struct ls1x_dma_chan chan[];
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +#define to_ls1x_dma_chan(dchan) \
> > > > > > > > > > > + container_of(dchan, struct ls1x_dma_chan, vchan.chan)
> > > > > > > > > > > +
> > > > > > > > > > > +#define to_ls1x_dma_desc(vd) \
> > > > > > > > > > > + container_of(vd, struct ls1x_dma_desc, vdesc)
> > > > > > > > > > > +
> > > > > > > > > > > +/* macros for registers read/write */
> > > > > > > > > > > +#define chan_readl(chan, off) \
> > > > > > > > > > > + readl((chan)->reg_base + (off))
> > > > > > > > > > > +
> > > > > > > > > > > +#define chan_writel(chan, off, val) \
> > > > > > > > > > > + writel((val), (chan)->reg_base + (off))
> > > > > > > > > > > +
> > > > > > > > > > > +static inline struct device *chan2dev(struct dma_chan *chan)
> > > > > > > > > > > +{
> > > > > > > > > > > + return &chan->dev->device;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static inline int ls1x_dma_query(struct ls1x_dma_chan *chan,
> > > > > > > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > > > > + int val, ret;
> > > > > > > > > > > +
> > > > > > > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > > > > > > + val |= DMA_ASK_VALID;
> > > > > > > > > > > + val |= dchan->chan_id;
> > > > > > > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > > > > > > + ret = readl_poll_timeout_atomic(chan->reg_base + DMA_CTRL, val,
> > > > > > > > > > > + !(val & DMA_ASK_VALID), 0, 3000);
> > > > > > > > > > > + if (ret)
> > > > > > > > > > > + dev_err(chan2dev(dchan), "failed to query DMA\n");
> > > > > > > > > > > +
> > > > > > > > > > > + return ret;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static inline int ls1x_dma_start(struct ls1x_dma_chan *chan,
> > > > > > > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > > > > + int val, ret;
> > > > > > > > > > > +
> > > > > > > > > > > + dev_dbg(chan2dev(dchan), "cookie=%d, starting hwdesc=%x\n",
> > > > > > > > > > > + dchan->cookie, *hwdesc_phys);
> > > > > > > > > > > +
> > > > > > > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > > > > > > + val |= DMA_START;
> > > > > > > > > > > + val |= dchan->chan_id;
> > > > > > > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > > > > > > + ret = readl_poll_timeout(chan->reg_base + DMA_CTRL, val,
> > > > > > > > > > > + !(val & DMA_START), 0, 3000);
> > > > > > > > > > > + if (ret)
> > > > > > > > > > > + dev_err(chan2dev(dchan), "failed to start DMA\n");
> > > > > > > > > > > +
> > > > > > > > > > > + return ret;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static inline void ls1x_dma_stop(struct ls1x_dma_chan *chan)
> > > > > > > > > > > +{
> > > > > > > > > > > + chan_writel(chan, DMA_CTRL, chan_readl(chan, DMA_CTRL) | DMA_STOP);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static void ls1x_dma_free_chan_resources(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > +
> > > > > > > > > > > + dma_free_coherent(chan2dev(dchan), sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > > > > + chan->curr_hwdesc, chan->curr_hwdesc_phys);
> > > > > > > > > > > + vchan_free_chan_resources(&chan->vchan);
> > > > > > > > > > > + dma_pool_destroy(chan->desc_pool);
> > > > > > > > > > > + chan->desc_pool = NULL;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > +
> > > > > > > > > > > + chan->desc_pool = dma_pool_create(dma_chan_name(dchan),
> > > > > > > > > > > + chan2dev(dchan),
> > > > > > > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > > > > + __alignof__(struct ls1x_dma_hwdesc),
> > > > > > > > > > > + 0);
> > > > > > > > > > > + if (!chan->desc_pool)
> > > > > > > > > > > + return -ENOMEM;
> > > > > > > > > > > +
> > > > > > > > > > > + /* allocate memory for querying current HW descriptor */
> > > > > > > > > > > + dma_set_coherent_mask(chan2dev(dchan), DMA_BIT_MASK(32));
> > > > > > > > > > > + chan->curr_hwdesc = dma_alloc_coherent(chan2dev(dchan),
> > > > > > > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > > > > + &chan->curr_hwdesc_phys,
> > > > > > > > > > > + GFP_KERNEL);
> > > > > > > > > > > + if (!chan->curr_hwdesc)
> > > > > > > > > > > + return -ENOMEM;
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static void ls1x_dma_free_desc(struct virt_dma_desc *vdesc)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vdesc);
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(vdesc->tx.chan);
> > > > > > > > > > > +
> > > > > > > > > > > + dma_pool_free(chan->desc_pool, desc->hwdesc, desc->hwdesc_phys);
> > > > > > > > > > > + chan->desc = NULL;
> > > > > > > > > > > + kfree(desc);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static struct ls1x_dma_desc *
> > > > > > > > > > > +ls1x_dma_alloc_desc(struct dma_chan *dchan, int sg_len,
> > > > > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > > > > + enum dma_transaction_type type)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > > > > +
> > > > > > > > > > > + if (sg_len > LS1X_DMA_MAX_DESC) {
> > > > > > > > > > > + dev_err(chan2dev(dchan), "sg_len %u exceeds limit %lu",
> > > > > > > > > > > + sg_len, LS1X_DMA_MAX_DESC);
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
> > > > > > > > > > > + if (!desc)
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > +
> > > > > > > > > > > + /* allocate HW descriptors */
> > > > > > > > > > > + desc->hwdesc = dma_pool_zalloc(chan->desc_pool, GFP_NOWAIT,
> > > > > > > > > > > + &desc->hwdesc_phys);
> > > > > > > > > > > + if (!desc->hwdesc) {
> > > > > > > > > > > + dev_err(chan2dev(dchan), "failed to alloc HW descriptors\n");
> > > > > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + desc->dir = direction;
> > > > > > > > > > > + desc->type = type;
> > > > > > > > > > > + desc->nr_descs = sg_len;
> > > > > > > > > > > +
> > > > > > > > > > > + return desc;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_setup_hwdescs(struct dma_chan *dchan,
> > > > > > > > > > > + struct ls1x_dma_desc *desc,
> > > > > > > > > > > + struct scatterlist *sgl, unsigned int sg_len)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + dma_addr_t next_hwdesc_phys = desc->hwdesc_phys;
> > > > > > > > > > > +
> > > > > > > > > > > + struct scatterlist *sg;
> > > > > > > > > > > + unsigned int dev_addr, cmd, i;
> > > > > > > > > > > +
> > > > > > > > > > > + switch (desc->dir) {
> > > > > > > > > > > + case DMA_MEM_TO_DEV:
> > > > > > > > > > > + dev_addr = chan->dst_addr;
> > > > > > > > > > > + desc->bus_width = chan->dst_addr_width;
> > > > > > > > > > > + cmd = DMA_RAM2DEV | DMA_INT;
> > > > > > > > > > > + break;
> > > > > > > > > > > + case DMA_DEV_TO_MEM:
> > > > > > > > > > > + dev_addr = chan->src_addr;
> > > > > > > > > > > + desc->bus_width = chan->src_addr_width;
> > > > > > > > > > > + cmd = DMA_INT;
> > > > > > > > > > > + break;
> > > > > > > > > > > + default:
> > > > > > > > > > > + dev_err(chan2dev(dchan), "unsupported DMA direction: %s\n",
> > > > > > > > > > > + dmaengine_get_direction_text(desc->dir));
> > > > > > > > > > > + return -EINVAL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + /* setup HW descriptors */
> > > > > > > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > > > > > > + dma_addr_t buf_addr = sg_dma_address(sg);
> > > > > > > > > > > + size_t buf_len = sg_dma_len(sg);
> > > > > > > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > > > > > > +
> > > > > > > > > > > + if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) {
> > > > > > > > > > > + dev_err(chan2dev(dchan), "buffer is not aligned!\n");
> > > > > > > > > > > + return -EINVAL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + hwdesc->saddr = buf_addr;
> > > > > > > > > > > + hwdesc->daddr = dev_addr;
> > > > > > > > > > > + hwdesc->length = buf_len / desc->bus_width;
> > > > > > > > > > > + hwdesc->stride = 0;
> > > > > > > > > > > + hwdesc->cycles = 1;
> > > > > > > > > > > + hwdesc->cmd = cmd;
> > > > > > > > > > > +
> > > > > > > > > > > + if (i) {
> > > > > > > > > > > + next_hwdesc_phys += sizeof(*hwdesc);
> > > > > > > > > > > + desc->hwdesc[i - 1].next = next_hwdesc_phys
> > > > > > > > > > > + | DMA_NEXT_VALID;
> > > > > > > > > > > + }
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + if (desc->type == DMA_CYCLIC)
> > > > > > > > > > > + desc->hwdesc[i - 1].next = desc->hwdesc_phys | DMA_NEXT_VALID;
> > > > > > > > > > > +
> > > > > > > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > > > > > > +
> > > > > > > > > > > + print_hex_dump_debug("HW DESC: ", DUMP_PREFIX_OFFSET, 16, 4,
> > > > > > > > > > > + hwdesc, sizeof(*hwdesc), false);
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static struct dma_async_tx_descriptor *
> > > > > > > > > > > +ls1x_dma_prep_slave_sg(struct dma_chan *dchan,
> > > > > > > > > > > + struct scatterlist *sgl, unsigned int sg_len,
> > > > > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > > > > + unsigned long flags, void *context)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > > > > +
> > > > > > > > > > > + dev_dbg(chan2dev(dchan), "sg_len=%u flags=0x%lx dir=%s\n",
> > > > > > > > > > > + sg_len, flags, dmaengine_get_direction_text(direction));
> > > > > > > > > > > +
> > > > > > > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_SLAVE);
> > > > > > > > > > > + if (!desc)
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > +
> > > > > > > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static struct dma_async_tx_descriptor *
> > > > > > > > > > > +ls1x_dma_prep_dma_cyclic(struct dma_chan *dchan,
> > > > > > > > > > > + dma_addr_t buf_addr, size_t buf_len, size_t period_len,
> > > > > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > > > > + unsigned long flags)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > > > > + struct scatterlist *sgl;
> > > > > > > > > > > + unsigned int sg_len;
> > > > > > > > > > > + unsigned int i;
> > > > > > > > > > > +
> > > > > > > > > > > + dev_dbg(chan2dev(dchan),
> > > > > > > > > > > + "buf_len=%d period_len=%zu flags=0x%lx dir=%s\n", buf_len,
> > > > > > > > > > > + period_len, flags, dmaengine_get_direction_text(direction));
> > > > > > > > > > > +
> > > > > > > > > > > + sg_len = buf_len / period_len;
> > > > > > > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_CYCLIC);
> > > > > > > > > > > + if (!desc)
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > +
> > > > > > > > > > > + /* allocate the scatterlist */
> > > > > > > > > > > + sgl = kmalloc_array(sg_len, sizeof(*sgl), GFP_NOWAIT);
> > > > > > > > > > > + if (!sgl)
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > +
> > > > > > > > > > > + sg_init_table(sgl, sg_len);
> > > > > > > > > > > + for (i = 0; i < sg_len; ++i) {
> > > > > > > > > > > + sg_set_page(&sgl[i], pfn_to_page(PFN_DOWN(buf_addr)),
> > > > > > > > > > > + period_len, offset_in_page(buf_addr));
> > > > > > > > > > > + sg_dma_address(&sgl[i]) = buf_addr;
> > > > > > > > > > > + sg_dma_len(&sgl[i]) = period_len;
> > > > > > > > > > > + buf_addr += period_len;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > > > > + return NULL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + kfree(sgl);
> > > > > > > > > > > +
> > > > > > > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_slave_config(struct dma_chan *dchan,
> > > > > > > > > > > + struct dma_slave_config *config)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > +
> > > > > > > > > > > + chan->src_addr = config->src_addr;
> > > > > > > > > > > + chan->src_addr_width = config->src_addr_width;
> > > > > > > > > > > + chan->dst_addr = config->dst_addr;
> > > > > > > > > > > + chan->dst_addr_width = config->dst_addr_width;
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_pause(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + unsigned long flags;
> > > > > > > > > > > + int ret;
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > > > > + ret = ls1x_dma_query(chan, &chan->curr_hwdesc_phys);
> > > > > > > > > > > + if (!ret)
> > > > > > > > > > > + ls1x_dma_stop(chan);
> > > > > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > > > > +
> > > > > > > > > > > + return ret;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_resume(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + unsigned long flags;
> > > > > > > > > > > + int ret;
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > > > > + ret = ls1x_dma_start(chan, &chan->curr_hwdesc_phys);
> > > > > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > > > > +
> > > > > > > > > > > + return ret;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_terminate_all(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + unsigned long flags;
> > > > > > > > > > > + LIST_HEAD(head);
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > > > > + ls1x_dma_stop(chan);
> > > > > > > > > > > + vchan_get_all_descriptors(&chan->vchan, &head);
> > > > > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > > > > +
> > > > > > > > > > > + vchan_dma_desc_free_list(&chan->vchan, &head);
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static enum dma_status ls1x_dma_tx_status(struct dma_chan *dchan,
> > > > > > > > > > > + dma_cookie_t cookie,
> > > > > > > > > > > + struct dma_tx_state *state)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + struct virt_dma_desc *vdesc;
> > > > > > > > > > > + enum dma_status status;
> > > > > > > > > > > + size_t bytes = 0;
> > > > > > > > > > > + unsigned long flags;
> > > > > > > > > > > +
> > > > > > > > > > > + status = dma_cookie_status(dchan, cookie, state);
> > > > > > > > > > > + if (status == DMA_COMPLETE)
> > > > > > > > > > > + return status;
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > > > > + vdesc = vchan_find_desc(&chan->vchan, cookie);
> > > > > > > > > > > + if (chan->desc && cookie == chan->desc->vdesc.tx.cookie) {
> > > > > > > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > > > > > > + int i;
> > > > > > > > > > > +
> > > > > > > > > > > + if (ls1x_dma_query(chan, &chan->curr_hwdesc_phys))
> > > > > > > > > > > + return status;
> > > > > > > > > > > +
> > > > > > > > > > > + /* locate the current HW descriptor */
> > > > > > > > > > > + for (i = 0; i < desc->nr_descs; i++)
> > > > > > > > > > > + if (desc->hwdesc[i].next == chan->curr_hwdesc->next)
> > > > > > > > > > > + break;
> > > > > > > > > > > +
> > > > > > > > > > > + /* count the residues */
> > > > > > > > > > > + for (; i < desc->nr_descs; i++)
> > > > > > > > > > > + bytes += desc->hwdesc[i].length * desc->bus_width;
> > > > > > > > > > > +
> > > > > > > > > > > + dma_set_residue(state, bytes);
> > > > > > > > > > > + }
> > > > > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > > > > +
> > > > > > > > > > > + return status;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static void ls1x_dma_issue_pending(struct dma_chan *dchan)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > > > > + struct virt_dma_desc *vdesc;
> > > > > > > > > > > + unsigned long flags;
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > > > > + if (vchan_issue_pending(&chan->vchan) && !chan->desc) {
> > > > > > > > > > > + vdesc = vchan_next_desc(&chan->vchan);
> > > > > > > > > > > + if (!vdesc) {
> > > > > > > > > > > + chan->desc = NULL;
> > > > > > > > > > > + return;
> > > > > > > > > > > + }
> > > > > > > > > > > + chan->desc = to_ls1x_dma_desc(vdesc);
> > > > > > > > > > > + ls1x_dma_start(chan, &chan->desc->hwdesc_phys);
> > > > > > > > > > > + }
> > > > > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static irqreturn_t ls1x_dma_irq_handler(int irq, void *data)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma_chan *chan = data;
> > > > > > > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > > > > +
> > > > > > > > > > > + if (!desc) {
> > > > > > > > > > > + dev_warn(chan2dev(dchan),
> > > > > > > > > > > + "IRQ %d with no active descriptor on channel %d\n",
> > > > > > > > > > > + irq, dchan->chan_id);
> > > > > > > > > > > + return IRQ_NONE;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + dev_dbg(chan2dev(dchan), "DMA IRQ %d on channel %d\n", irq,
> > > > > > > > > > > + dchan->chan_id);
> > > > > > > > > > > +
> > > > > > > > > > > + spin_lock(&chan->vchan.lock);
> > > > > > > > > > > +
> > > > > > > > > > > + if (desc->type == DMA_CYCLIC) {
> > > > > > > > > > > + vchan_cyclic_callback(&desc->vdesc);
> > > > > > > > > > > + } else {
> > > > > > > > > > > + list_del(&desc->vdesc.node);
> > > > > > > > > > > + vchan_cookie_complete(&desc->vdesc);
> > > > > > > > > > > + chan->desc = NULL;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + spin_unlock(&chan->vchan.lock);
> > > > > > > > > > > + return IRQ_HANDLED;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_chan_probe(struct platform_device *pdev,
> > > > > > > > > > > + struct ls1x_dma *dma, int chan_id)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct device *dev = &pdev->dev;
> > > > > > > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > > > > > > + char pdev_irqname[4];
> > > > > > > > > > > + char *irqname;
> > > > > > > > > > > + int ret;
> > > > > > > > > > > +
> > > > > > > > > > > + sprintf(pdev_irqname, "ch%u", chan_id);
> > > > > > > > > > > + chan->irq = platform_get_irq_byname(pdev, pdev_irqname);
> > > > > > > > > > > + if (chan->irq < 0)
> > > > > > > > > > > + return -ENODEV;
> > > > > > > > > > > +
> > > > > > > > > > > + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
> > > > > > > > > > > + dev_name(dev), pdev_irqname);
> > > > > > > > > > > + if (!irqname)
> > > > > > > > > > > + return -ENOMEM;
> > > > > > > > > > > +
> > > > > > > > > > > + ret = devm_request_irq(dev, chan->irq, ls1x_dma_irq_handler,
> > > > > > > > > > > + IRQF_SHARED, irqname, chan);
> > > > > > > > > > > + if (ret)
> > > > > > > > > > > + return dev_err_probe(dev, ret,
> > > > > > > > > > > + "failed to request IRQ %u!\n", chan->irq);
> > > > > > > > > > > +
> > > > > > > > > > > + chan->reg_base = dma->reg_base;
> > > > > > > > > > > + chan->vchan.desc_free = ls1x_dma_free_desc;
> > > > > > > > > > > + vchan_init(&chan->vchan, &dma->ddev);
> > > > > > > > > > > + dev_info(dev, "%s (irq %d) initialized\n", pdev_irqname, chan->irq);
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static void ls1x_dma_chan_remove(struct ls1x_dma *dma, int chan_id)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct device *dev = dma->ddev.dev;
> > > > > > > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > > > > > > +
> > > > > > > > > > > + devm_free_irq(dev, chan->irq, chan);
> > > > > > > > > > > + list_del(&chan->vchan.chan.device_node);
> > > > > > > > > > > + tasklet_kill(&chan->vchan.task);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int ls1x_dma_probe(struct platform_device *pdev)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct device *dev = &pdev->dev;
> > > > > > > > > > > + struct dma_device *ddev;
> > > > > > > > > > > + struct ls1x_dma *dma;
> > > > > > > > > > > + int nr_chans, ret, i;
> > > > > > > > > > > +
> > > > > > > > > > > + nr_chans = platform_irq_count(pdev);
> > > > > > > > > > > + if (nr_chans <= 0)
> > > > > > > > > > > + return nr_chans;
> > > > > > > > > > > + if (nr_chans > LS1X_DMA_MAX_CHANNELS)
> > > > > > > > > > > + return dev_err_probe(dev, -EINVAL,
> > > > > > > > > > > + "nr_chans=%d exceeds the maximum\n",
> > > > > > > > > > > + nr_chans);
> > > > > > > > > > > +
> > > > > > > > > > > + dma = devm_kzalloc(dev, struct_size(dma, chan, nr_chans), GFP_KERNEL);
> > > > > > > > > > > + if (!dma)
> > > > > > > > > > > + return -ENOMEM;
> > > > > > > > > > > +
> > > > > > > > > > > + /* initialize DMA device */
> > > > > > > > > > > + dma->reg_base = devm_platform_ioremap_resource(pdev, 0);
> > > > > > > > > > > + if (IS_ERR(dma->reg_base))
> > > > > > > > > > > + return PTR_ERR(dma->reg_base);
> > > > > > > > > > > +
> > > > > > > > > > > + ddev = &dma->ddev;
> > > > > > > > > > > + ddev->dev = dev;
> > > > > > > > > > > + ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;
> > > > > > > > > > > + ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > > > > > > + ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > > > > > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > > > > > > > > + ddev->max_sg_burst = LS1X_DMA_MAX_DESC;
> > > > > > > > > > > + ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> > > > > > > > > > > + ddev->device_alloc_chan_resources = ls1x_dma_alloc_chan_resources;
> > > > > > > > > > > + ddev->device_free_chan_resources = ls1x_dma_free_chan_resources;
> > > > > > > > > > > + ddev->device_prep_slave_sg = ls1x_dma_prep_slave_sg;
> > > > > > > > > > > + ddev->device_prep_dma_cyclic = ls1x_dma_prep_dma_cyclic;
> > > > > > > > > > > + ddev->device_config = ls1x_dma_slave_config;
> > > > > > > > > > > + ddev->device_pause = ls1x_dma_pause;
> > > > > > > > > > > + ddev->device_resume = ls1x_dma_resume;
> > > > > > > > > > > + ddev->device_terminate_all = ls1x_dma_terminate_all;
> > > > > > > > > > > + ddev->device_tx_status = ls1x_dma_tx_status;
> > > > > > > > > > > + ddev->device_issue_pending = ls1x_dma_issue_pending;
> > > > > > > > > > > +
> > > > > > > > > > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > > > > > > > > + INIT_LIST_HEAD(&ddev->channels);
> > > > > > > > > > > +
> > > > > > > > > > > + /* initialize DMA channels */
> > > > > > > > > > > + for (i = 0; i < nr_chans; i++) {
> > > > > > > > > > > + ret = ls1x_dma_chan_probe(pdev, dma, i);
> > > > > > > > > > > + if (ret)
> > > > > > > > > > > + return ret;
> > > > > > > > > > > + }
> > > > > > > > > > > + dma->nr_chans = nr_chans;
> > > > > > > > > > > +
> > > > > > > > > > > + ret = dmaenginem_async_device_register(ddev);
> > > > > > > > > > > + if (ret) {
> > > > > > > > > > > + dev_err(dev, "failed to register DMA device! %d\n", ret);
> > > > > > > > > > > + return ret;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + ret =
> > > > > > > > > > > + of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id,
> > > > > > > > > > > + ddev);
> > > > > > > > > > > + if (ret) {
> > > > > > > > > > > + dev_err(dev, "failed to register DMA controller! %d\n", ret);
> > > > > > > > > > > + return ret;
> > > > > > > > > > > + }
> > > > > > > > > > > +
> > > > > > > > > > > + platform_set_drvdata(pdev, dma);
> > > > > > > > > > > + dev_info(dev, "Loongson1 DMA driver registered\n");
> > > > > > > > > > > +
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static void ls1x_dma_remove(struct platform_device *pdev)
> > > > > > > > > > > +{
> > > > > > > > > > > + struct ls1x_dma *dma = platform_get_drvdata(pdev);
> > > > > > > > > > > + int i;
> > > > > > > > > > > +
> > > > > > > > > > > + of_dma_controller_free(pdev->dev.of_node);
> > > > > > > > > > > +
> > > > > > > > > > > + for (i = 0; i < dma->nr_chans; i++)
> > > > > > > > > > > + ls1x_dma_chan_remove(dma, i);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static const struct of_device_id ls1x_dma_match[] = {
> > > > > > > > > > > + { .compatible = "loongson,ls1b-apbdma" },
> > > > > > > > > > > + { .compatible = "loongson,ls1c-apbdma" },
> > > > > > > > > > > + { /* sentinel */ }
> > > > > > > > > > > +};
> > > > > > > > > > > +MODULE_DEVICE_TABLE(of, ls1x_dma_match);
> > > > > > > > > > > +
> > > > > > > > > > > +static struct platform_driver ls1x_dma_driver = {
> > > > > > > > > > > + .probe = ls1x_dma_probe,
> > > > > > > > > > > + .remove_new = ls1x_dma_remove,
> > > > > > > > > > > + .driver = {
> > > > > > > > > > > + .name = KBUILD_MODNAME,
> > > > > > > > > > > + .of_match_table = ls1x_dma_match,
> > > > > > > > > > > + },
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +module_platform_driver(ls1x_dma_driver);
> > > > > > > > > > > +
> > > > > > > > > > > +MODULE_AUTHOR("Keguang Zhang <keguang.zhang@xxxxxxxxx>");
> > > > > > > > > > > +MODULE_DESCRIPTION("Loongson-1 APB DMA Controller driver");
> > > > > > > > > > > +MODULE_LICENSE("GPL");
> > > > > > > > > > >
> > > > > > > > > > > --
> > > > > > > > > > > 2.40.1
> > > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > >
> > > > > > > > >
> > > > > > > > >
> > > > > > > > > --
> > > > > > > > > Best regards,
> > > > > > > > >
> > > > > > > > > Keguang Zhang
> > > > > > > > >
> > > > > > >
> > > > > > >
> > > > > > >
> > > > > > > --
> > > > > > > Best regards,
> > > > > > >
> > > > > > > Keguang Zhang
> > > > >
> > > > >
> > > > >
> > > > > --
> > > > > Best regards,
> > > > >
> > > > > Keguang Zhang
> > >
> > >
> > >
> > > --
> > > Best regards,
> > >
> > > Keguang Zhang
>
>
>
> --
> Best regards,
>
> Keguang Zhang