Devicetree
 help / color / mirror / Atom feed
From: Frank Li <Frank.li@nxp.com>
To: Binbin Zhou <zhoubb.aaron@gmail.com>
Cc: Binbin Zhou <zhoubinbin@loongson.cn>,
	Huacai Chen <chenhuacai@loongson.cn>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, Vinod Koul <vkoul@kernel.org>,
	dmaengine@vger.kernel.org,
	Xiaochuang Mao <maoxiaochuan@loongson.cn>,
	Huacai Chen <chenhuacai@kernel.org>,
	Xuerui Wang <kernel@xen0n.name>,
	loongarch@lists.linux.dev, devicetree@vger.kernel.org,
	Keguang Zhang <keguang.zhang@gmail.com>,
	linux-mips@vger.kernel.org, jeffbai@aosc.io
Subject: Re: [PATCH v2 4/4] dmaengine: loongson: New driver for the Loongson Multi-Channel DMA controller
Date: Tue, 10 Feb 2026 10:27:16 -0500	[thread overview]
Message-ID: <aYtOVFsoDg2m6yhi@lizhi-Precision-Tower-5810> (raw)
In-Reply-To: <CAMpQs4KSyj3HFrY0Qn_ZByekVWu3-re__6TAE=nU+uC_VfKB8w@mail.gmail.com>

On Tue, Feb 10, 2026 at 08:02:21PM +0800, Binbin Zhou wrote:
> Hi Frank:
>
> On Tue, Feb 10, 2026 at 3:41 PM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
> >
> > Hi Frank:
> >
> > Thanks for your reply.
> >
> > On Tue, Feb 10, 2026 at 1:05 AM Frank Li <Frank.li@nxp.com> wrote:
> > >
> > > On Mon, Feb 09, 2026 at 11:04:55AM +0800, Binbin Zhou wrote:
> > > > This DMA controller appears in Loongson-2K0300 and Loongson-2K3000.
> > > >
> > > > It is a chain multi-channel controller that enables data transfers from
> > > > memory to memory, device to memory, and memory to device, as well as
> > > > channel prioritization configurable through the channel configuration
> > > > registers.
> > > >
> > > > In addition, there are slight differences between Loongson-2K0300 and
> > > > Loongson-2K3000, such as channel register offsets and the number of
> > > > channels.
> > > >
> > > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > > > ---
> > > >  MAINTAINERS                                  |   1 +
> > > >  drivers/dma/loongson/Kconfig                 |  10 +
> > > >  drivers/dma/loongson/Makefile                |   1 +
> > > >  drivers/dma/loongson/loongson2-apb-cmc-dma.c | 736 +++++++++++++++++++
> > > >  4 files changed, 748 insertions(+)
> > > >  create mode 100644 drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index d3cb541aee2a..61a39070d7a0 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -14778,6 +14778,7 @@ L:    dmaengine@vger.kernel.org
> > > >  S:   Maintained
> > > >  F:   Documentation/devicetree/bindings/dma/loongson,ls2k0300-dma.yaml
> > > >  F:   Documentation/devicetree/bindings/dma/loongson,ls2x-apbdma.yaml
> > > > +F:   drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > >  F:   drivers/dma/loongson/loongson2-apb-dma.c
> > > >
> > > >  LOONGSON LS2X I2C DRIVER
> > > > diff --git a/drivers/dma/loongson/Kconfig b/drivers/dma/loongson/Kconfig
> > > > index 9dbdaef5a59f..28b3daeed4e3 100644
> > > > --- a/drivers/dma/loongson/Kconfig
> > > > +++ b/drivers/dma/loongson/Kconfig
> > > > @@ -25,4 +25,14 @@ config LOONGSON2_APB_DMA
> > > >         This DMA controller transfers data from memory to peripheral fifo.
> > > >         It does not support memory to memory data transfer.
> > > >
> > > > +config LOONGSON2_APB_CMC_DMA
> > > > +     tristate "Loongson2 Chain Multi-Channel DMA support"
> > > > +     select DMA_ENGINE
> > > > +     select DMA_VIRTUAL_CHANNELS
> > > > +     help
> > > > +       Support for the Loongson Chain Multi-Channel DMA controller driver.
> > > > +       It is discovered on the Loongson-2K chip (Loongson-2K0300/Loongson-2K3000),
> > > > +       which has 4/8 channels internally, enabling bidirectional data transfer
> > > > +       between devices and memory.
> > > > +
> > > >  endif
> > > > diff --git a/drivers/dma/loongson/Makefile b/drivers/dma/loongson/Makefile
> > > > index 6cdd08065e92..48c19781e729 100644
> > > > --- a/drivers/dma/loongson/Makefile
> > > > +++ b/drivers/dma/loongson/Makefile
> > > > @@ -1,3 +1,4 @@
> > > >  # SPDX-License-Identifier: GPL-2.0-only
> > > >  obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > >  obj-$(CONFIG_LOONGSON2_APB_DMA) += loongson2-apb-dma.o
> > > > +obj-$(CONFIG_LOONGSON2_APB_CMC_DMA) += loongson2-apb-cmc-dma.o
> > > > diff --git a/drivers/dma/loongson/loongson2-apb-cmc-dma.c b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > new file mode 100644
> > > > index 000000000000..f598ad095686
> > > > --- /dev/null
> > > > +++ b/drivers/dma/loongson/loongson2-apb-cmc-dma.c
> > > > @@ -0,0 +1,736 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > +/*
> > > > + * Looongson-2 Multi-Channel DMA Controller driver
> > > > + *
> > > > + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> > > > + */
> > > > +
> > > > +#include <linux/acpi.h>
> > > > +#include <linux/acpi_dma.h>
> > > > +#include <linux/bitfield.h>
> > > > +#include <linux/clk.h>
> > > > +#include <linux/dma-mapping.h>
> > > > +#include <linux/dmapool.h>
> > > > +#include <linux/interrupt.h>
> > > > +#include <linux/io.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"
> > > > +
> > > > +#define LOONGSON2_CMCDMA_ISR         0x0     /* DMA Interrupt Status Register */
> > > > +#define LOONGSON2_CMCDMA_IFCR                0x4     /* DMA Interrupt Flag Clear Register */
> > > > +#define LOONGSON2_CMCDMA_CCR         0x8     /* DMA Channel Configuration Register */
> > > > +#define LOONGSON2_CMCDMA_CNDTR               0xc     /* DMA Channel Transmit Count Register */
> > > > +#define LOONGSON2_CMCDMA_CPAR                0x10    /* DMA Channel Peripheral Address Register */
> > > > +#define LOONGSON2_CMCDMA_CMAR                0x14    /* DMA Channel Memory Address Register */
> > > > +
> > > > +/* Bitfields of DMA interrupt status register */
> > > > +#define LOONGSON2_CMCDMA_TCI         BIT(1) /* Transfer Complete Interrupt */
> > > > +#define LOONGSON2_CMCDMA_HTI         BIT(2) /* Half Transfer Interrupt */
> > > > +#define LOONGSON2_CMCDMA_TEI         BIT(3) /* Transfer Error Interrupt */
> > > > +
> > > > +#define LOONGSON2_CMCDMA_MASKI               \
> > > > +     (LOONGSON2_CMCDMA_TCI | LOONGSON2_CMCDMA_HTI | LOONGSON2_CMCDMA_TEI)
> > > > +
> > > > +/* Bitfields of DMA channel x Configuration Register */
> > > > +#define LOONGSON2_CMCDMA_CCR_EN              BIT(0) /* Stream Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_TCIE    BIT(1) /* Transfer Complete Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_HTIE    BIT(2) /* Half Transfer Complete Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_TEIE    BIT(3) /* Transfer Error Interrupt Enable */
> > > > +#define LOONGSON2_CMCDMA_CCR_DIR     BIT(4) /* Data Transfer Direction */
> > > > +#define LOONGSON2_CMCDMA_CCR_CIRC    BIT(5) /* Circular mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_PINC    BIT(6) /* Peripheral increment mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_MINC    BIT(7) /* Memory increment mode */
> > > > +#define LOONGSON2_CMCDMA_CCR_PSIZE_MASK      GENMASK(9, 8)
> > > > +#define LOONGSON2_CMCDMA_CCR_MSIZE_MASK      GENMASK(11, 10)
> > > > +#define LOONGSON2_CMCDMA_CCR_PL_MASK GENMASK(13, 12)
> > > > +#define LOONGSON2_CMCDMA_CCR_M2M     BIT(14)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_CCR_CFG_MASK        \
> > > > +     (LOONGSON2_CMCDMA_CCR_PINC | LOONGSON2_CMCDMA_CCR_MINC | LOONGSON2_CMCDMA_CCR_PL_MASK)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_CCR_IRQ_MASK        \
> > > > +     (LOONGSON2_CMCDMA_CCR_TCIE | LOONGSON2_CMCDMA_CCR_HTIE | LOONGSON2_CMCDMA_CCR_TEIE)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_STREAM_MASK \
> > > > +     (LOONGSON2_CMCDMA_CCR_CFG_MASK | LOONGSON2_CMCDMA_CCR_IRQ_MASK)
> > > > +
> > > > +#define LOONGSON2_CMCDMA_BUSWIDTHS   (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
> > > > +                                      BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> > > > +                                      BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> > > > +
> > > > +enum loongson2_cmc_dma_width {
> > > > +     LOONGSON2_CMCDMA_BYTE,
> > > > +     LOONGSON2_CMCDMA_HALF_WORD,
> > > > +     LOONGSON2_CMCDMA_WORD,
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_chan_reg {
> > > > +     u32 ccr;
> > > > +     u32 cndtr;
> > > > +     u32 cpar;
> > > > +     u32 cmar;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_sg_req {
> > > > +     u32 len;
> > > > +     struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_desc {
> > > > +     struct virt_dma_desc vdesc;
> > > > +     bool cyclic;
> > > > +     u32 num_sgs;
> > > > +     struct loongson2_cmc_dma_sg_req sg_req[] __counted_by(num_sgs);
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_chan {
> > > > +     struct virt_dma_chan vchan;
> > > > +     struct dma_slave_config dma_sconfig;
> > > > +     struct loongson2_cmc_dma_desc *desc;
> > > > +     u32 id;
> > > > +     u32 irq;
> > > > +     u32 next_sg;
> > > > +     struct loongson2_cmc_dma_chan_reg chan_reg;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_config {
> > > > +     u32 max_channels;
> > > > +     u32 chan_reg_offset;
> > > > +};
> > > > +
> > > > +struct loongson2_cmc_dma_dev {
> > > > +     struct dma_device ddev;
> > > > +     struct clk *dma_clk;
> > > > +     void __iomem *base;
> > > > +     u32 nr_channels;
> > > > +     u32 chan_reg_offset;
> > > > +     struct loongson2_cmc_dma_chan chan[] __counted_by(nr_channels);
> > > > +};
> > > > +
> > > > +static const struct loongson2_cmc_dma_config ls2k0300_cmc_dma_config = {
> > > > +     .max_channels = 8,
> > > > +     .chan_reg_offset = 0x14,
> > > > +};
> > > > +
> > > > +static const struct loongson2_cmc_dma_config ls2k3000_cmc_dma_config = {
> > > > +     .max_channels = 4,
> > > > +     .chan_reg_offset = 0x18,
> > > > +};
> > > > +
> > > > +static struct loongson2_cmc_dma_dev *lmdma_get_dev(struct loongson2_cmc_dma_chan *lchan)
> > > > +{
> > > > +     return container_of(lchan->vchan.chan.device, struct loongson2_cmc_dma_dev, ddev);
> > > > +}
> > > > +
> > > > +static struct loongson2_cmc_dma_chan *to_lmdma_chan(struct dma_chan *chan)
> > > > +{
> > > > +     return container_of(chan, struct loongson2_cmc_dma_chan, vchan.chan);
> > > > +}
> > > > +
> > > > +static struct loongson2_cmc_dma_desc *to_lmdma_desc(struct virt_dma_desc *vdesc)
> > > > +{
> > > > +     return container_of(vdesc, struct loongson2_cmc_dma_desc, vdesc);
> > > > +}
> > > > +
> > > > +static struct device *chan2dev(struct loongson2_cmc_dma_chan *lchan)
> > > > +{
> > > > +     return &lchan->vchan.chan.dev->device;
> > > > +}
> > > > +
> > > > +static u32 loongson2_cmc_dma_read(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id)
> > > > +{
> > > > +     return readl(lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_write(struct loongson2_cmc_dma_dev *lddev, u32 reg, u32 id, u32 val)
> > > > +{
> > > > +     writel(val, lddev->base + (reg + lddev->chan_reg_offset * id));
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_get_width(struct loongson2_cmc_dma_chan *lchan,
> > > > +                                    enum dma_slave_buswidth width)
> > > > +{
> > > > +     switch (width) {
> > > > +     case DMA_SLAVE_BUSWIDTH_1_BYTE:
> > > > +             return LOONGSON2_CMCDMA_BYTE;
> > > > +     case DMA_SLAVE_BUSWIDTH_2_BYTES:
> > > > +             return LOONGSON2_CMCDMA_HALF_WORD;
> > > > +     case DMA_SLAVE_BUSWIDTH_4_BYTES:
> > > > +             return LOONGSON2_CMCDMA_WORD;
> > >
> > > is ffs() helper in case your hardware support more buswidth in future?
> >
> > It seems there's no need for us to do this.
> > The data width setting bit in the DMA channel configuration register
> > only has two bits (LOONGSON2_CMCDMA_CCR_PSIZE_MASK). The bitmask
> > values are: 8-bit/16-bit/32-bit/reserved.
>
> Sorry, I checked again, the ffs() helper can make the code cleaner:
>
> static int loongson2_cmc_dma_get_width(enum dma_slave_buswidth width)
> {
>         switch (width) {
>         case DMA_SLAVE_BUSWIDTH_1_BYTE:
>         case DMA_SLAVE_BUSWIDTH_2_BYTES:
>         case DMA_SLAVE_BUSWIDTH_4_BYTES:
>                 return ffs(width) - 1;
>         default:
>                 return -EINVAL;
>         }
> }

if (width >= DMA_SLAVE_BUSWIDTH_4_BYTES)
	return -EINVAL;

return ffs(width) - 1;

>
> And the enum loongson2_cmc_dma_width{ } can be dropped.
>
> > >
> > > > +     default:
> > > > +             dev_err(chan2dev(lchan), "Dma bus width not supported\n");
> > > > +             return -EINVAL;
> > > > +     }
> > > > +}
> > > > +
...
> > > > +     if (status & LOONGSON2_CMCDMA_TCI)
> > > > +             loongson2_cmc_dma_handle_chan_done(lchan);
> > > > +
> > > > +     if (status & LOONGSON2_CMCDMA_HTI)
> > > > +             loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
> > > > +
> > > > +     if (status & LOONGSON2_CMCDMA_TEI)
> > > > +             dev_err(chan2dev(lchan), "DMA Transform Error\n");
> > > > +
> > > > +     loongson2_cmc_dma_irq_clear(lchan, status);
> > >
> > > irq clear should before loongson2_cmc_dma_handle_chan_done() incase you
> > > missed irq, if loongson2_cmc_dma_handle_chan_done() trigger new irq before
> > > your call irq_cler().
>
> Yes, this part should be refracted, how about the following code:
>
>         spin_lock(&lchan->vchan.lock);
>
>         ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
>         ists = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_ISR, 0);
>         status = (ists >> (4 * lchan->id)) & LOONGSON2_CMCDMA_MASKI;
>
>         if (status & LOONGSON2_CMCDMA_TCI) {
>                 loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_TCI);

if status is w1c, you can clean it unconditional.

>                 if (ccr & LOONGSON2_CMCDMA_CCR_TCIE)

Not sure your hardware, generally irq status register will not set if
enable bit have not set.

>                         loongson2_cmc_dma_handle_chan_done(lchan);
>                 status &= ~LOONGSON2_CMCDMA_TCI;
>         }
>
>         if (status & LOONGSON2_CMCDMA_HTI) {
>                 loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
>                 status &= ~LOONGSON2_CMCDMA_HTI;
>         }
>
>         if (status & LOONGSON2_CMCDMA_TEI) {
>                 loongson2_cmc_dma_irq_clear(lchan, LOONGSON2_CMCDMA_HTI);
>                 dev_err(chan2dev(lchan), "DMA Transform Error\n");
>                 if (!(ccr & LOONGSON2_CMCDMA_CCR_EN))
>                         dev_err(chan2dev(lchan), "chan disabled by HW\n");
>         }
>
>         spin_unlock(&lchan->vchan.lock);
>
> > >
> > > > +
> > > > +     spin_unlock(&lchan->vchan.lock);
> > > > +
> > > > +     return IRQ_HANDLED;
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_issue_pending(struct dma_chan *chan)
> > > > +{
> > > > +     struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > +     unsigned long flags;
> > > > +
> > > > +     spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > +     if (vchan_issue_pending(&lchan->vchan) && !lchan->desc) {
> > > > +             dev_dbg(chan2dev(lchan), "vchan %pK: issued\n", &lchan->vchan);
> > > > +             loongson2_cmc_dma_start_transfer(lchan);
> > > > +     }
> > > > +     spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > +}
> > > > +
...
> > > > +static struct dma_async_tx_descriptor *
> > > > +loongson2_cmc_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, u32 sg_len,
> > > > +                             enum dma_transfer_direction direction,
> > > > +                             unsigned long flags, void *context)
> > > > +{
> > > > +     struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > +     struct loongson2_cmc_dma_desc *desc;
> > > > +     enum dma_slave_buswidth buswidth;
> > > > +     struct scatterlist *sg;
> > > > +     u32 num_items, i;
> > > > +     int ret;
> > > > +
> > > > +     desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT);
> > > > +     if (!desc)
> > > > +             return NULL;
> > > > +
> > > > +     for_each_sg(sgl, sg, sg_len, i) {
> > > > +             ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, sg_dma_len(sg));
> > > > +             if (ret)
> > > > +                     return NULL;
> > > > +
> > > > +             desc->sg_req[i].len = sg_dma_len(sg);
> > > > +
> > > > +             num_items = desc->sg_req[i].len / buswidth;
> > > > +             if (num_items >= SZ_64K) {
> > > > +                     dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > +                     kfree(desc);
> > > > +                     return NULL;
> > >
> > > if use sg_nents_for_dma(), you can use multi sg to trasfer more than 64K
> > > data.
> >
> > Sorry, are you referring to sg_nents_for_len()?
> > 64K is a hardware limitation of the controller, so it seems impossible
> > to resolve it using that function, right?

you can use multi sg_req to implement it, which max 64K.

	sg_reqp[i + 0] -> 1st 64k
	sg_reqp[i + 1] -> 2nd 64k
	...

Only need allocate more at kzalloc with sg_nents_for_len(), in stead of
sg_len.

Frank


> >
> > >
> > > > +             }
> > > > +             desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > +             desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > +             desc->sg_req[i].chan_reg.cmar = sg_dma_address(sg);
> > > > +             desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > +     }
> > > > +
> > > > +     desc->num_sgs = sg_len;
> > > > +     desc->cyclic = false;
> > > > +
> > > > +     return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > +}
> > > > +
> > > > +static struct dma_async_tx_descriptor *
> > > > +loongson2_cmc_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
> > > > +                               size_t period_len, enum dma_transfer_direction direction,
> > > > +                               unsigned long flags)
> > > > +{
> > > > +     struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > +     struct loongson2_cmc_dma_desc *desc;
> > > > +     enum dma_slave_buswidth buswidth;
> > > > +     u32 num_periods, num_items, i;
> > > > +     int ret;
> > > > +
> > > > +     if (unlikely(buf_len % period_len))
> > > > +             return NULL;
> > > > +
> > > > +     ret = loongson2_cmc_dma_set_xfer_param(lchan, direction, &buswidth, period_len);
> > > > +     if (ret)
> > > > +             return NULL;
> > > > +
> > > > +     num_items = period_len / buswidth;
> > > > +     if (num_items >= SZ_64K) {
> > > > +             dev_err(chan2dev(lchan), "Number of items not supported\n");
> > > > +             return NULL;
> > > > +     }
> > > > +
> > > > +     /* Enable Circular mode */
> > > > +     if (buf_len == period_len)
> > > > +             lchan->chan_reg.ccr |= LOONGSON2_CMCDMA_CCR_CIRC;
> > > > +
> > > > +     num_periods = buf_len / period_len;
> > > > +     desc = kzalloc(struct_size(desc, sg_req, num_periods), GFP_NOWAIT);
> > > > +     if (!desc)
> > > > +             return NULL;
> > > > +
> > > > +     for (i = 0; i < num_periods; i++) {
> > > > +             desc->sg_req[i].len = period_len;
> > > > +             desc->sg_req[i].chan_reg.ccr = lchan->chan_reg.ccr;
> > > > +             desc->sg_req[i].chan_reg.cpar = lchan->chan_reg.cpar;
> > > > +             desc->sg_req[i].chan_reg.cmar = buf_addr;
> > > > +             desc->sg_req[i].chan_reg.cndtr = num_items;
> > > > +             buf_addr += period_len;
> > > > +     }
> > > > +
> > > > +     desc->num_sgs = num_periods;
> > > > +     desc->cyclic = true;
> > > > +
> > > > +     return vchan_tx_prep(&lchan->vchan, &desc->vdesc, flags);
> > > > +}
> > > > +
> > > > +static size_t loongson2_cmc_dma_desc_residue(struct loongson2_cmc_dma_chan *lchan,
> > > > +                                          struct loongson2_cmc_dma_desc *desc, u32 next_sg)
> > > > +{
> > > > +     struct loongson2_cmc_dma_dev *lddev = lmdma_get_dev(lchan);
> > > > +     u32 residue, width, ndtr, ccr, i;
> > > > +
> > > > +     ccr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CCR, lchan->id);
> > > > +     width = FIELD_GET(LOONGSON2_CMCDMA_CCR_PSIZE_MASK, ccr);
> > > > +
> > > > +     ndtr = loongson2_cmc_dma_read(lddev, LOONGSON2_CMCDMA_CNDTR, lchan->id);
> > > > +     residue = ndtr << width;
> > > > +
> > > > +     if (lchan->desc->cyclic && next_sg == 0)
> > > > +             return residue;
> > > > +
> > > > +     for (i = next_sg; i < desc->num_sgs; i++)
> > > > +             residue += desc->sg_req[i].len;
> > > > +
> > > > +     return residue;
> > > > +}
> > > > +
> > > > +static enum dma_status loongson2_cmc_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> > > > +                                                struct dma_tx_state *state)
> > > > +{
> > > > +     struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > +     struct virt_dma_desc *vdesc;
> > > > +     enum dma_status status;
> > > > +     unsigned long flags;
> > > > +
> > > > +     status = dma_cookie_status(chan, cookie, state);
> > > > +     if (status == DMA_COMPLETE || !state)
> > > > +             return status;
> > > > +
> > > > +     spin_lock_irqsave(&lchan->vchan.lock, flags);
> > > > +     vdesc = vchan_find_desc(&lchan->vchan, cookie);
> > > > +     if (lchan->desc && cookie == lchan->desc->vdesc.tx.cookie)
> > > > +             state->residue = loongson2_cmc_dma_desc_residue(lchan, lchan->desc, lchan->next_sg);
> > > > +     else if (vdesc)
> > > > +             state->residue = loongson2_cmc_dma_desc_residue(lchan, to_lmdma_desc(vdesc), 0);
> > > > +
> > > > +     spin_unlock_irqrestore(&lchan->vchan.lock, flags);
> > > > +
> > > > +     return status;
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_free_chan_resources(struct dma_chan *chan)
> > > > +{
> > > > +     vchan_free_chan_resources(to_virt_chan(chan));
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_desc_free(struct virt_dma_desc *vdesc)
> > > > +{
> > > > +     kfree(to_lmdma_desc(vdesc));
> > > > +}
> > > > +
> > > > +static bool loongson2_cmc_dma_acpi_filter(struct dma_chan *chan, void *param)
> > > > +{
> > > > +     struct loongson2_cmc_dma_chan *lchan = to_lmdma_chan(chan);
> > > > +     struct acpi_dma_spec *dma_spec = param;
> > > > +
> > > > +     memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > +     lchan->chan_reg.ccr = dma_spec->chan_id & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > +
> > > > +     return true;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_acpi_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > +{
> > > > +     struct device *dev = lddev->ddev.dev;
> > > > +     struct acpi_dma_filter_info *info;
> > > > +     int ret;
> > > > +
> > > > +     if (!has_acpi_companion(dev))
> > > > +             return 0;
> > > > +
> > > > +     info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> > > > +     if (!info)
> > > > +             return -ENOMEM;
> > > > +
> > > > +     dma_cap_zero(info->dma_cap);
> > > > +     info->dma_cap = lddev->ddev.cap_mask;
> > > > +     info->filter_fn = loongson2_cmc_dma_acpi_filter;
> > > > +
> > > > +     ret = devm_acpi_dma_controller_register(dev, acpi_dma_simple_xlate, info);
> > > > +     if (ret)
> > > > +             dev_err(dev, "could not register acpi_dma_controller\n");
> > > > +
> > > > +     return ret;
> > > > +}
> > > > +
> > > > +static struct dma_chan *loongson2_cmc_dma_of_xlate(struct of_phandle_args *dma_spec,
> > > > +                                                struct of_dma *ofdma)
> > > > +{
> > > > +     struct loongson2_cmc_dma_dev *lddev = ofdma->of_dma_data;
> > > > +     struct device *dev = lddev->ddev.dev;
> > > > +     struct loongson2_cmc_dma_chan *lchan;
> > > > +     struct dma_chan *chan;
> > > > +
> > > > +     if (dma_spec->args_count < 2)
> > > > +             return NULL;
> > > > +
> > > > +     if (dma_spec->args[0] >= lddev->nr_channels) {
> > > > +             dev_err(dev, "Invalid channel id\n");
> > > > +             return NULL;
> > > > +     }
> > > > +
> > > > +     lchan = &lddev->chan[dma_spec->args[0]];
> > > > +     chan = dma_get_slave_channel(&lchan->vchan.chan);
> > > > +     if (!chan) {
> > > > +             dev_err(dev, "No more channels available\n");
> > > > +             return NULL;
> > > > +     }
> > > > +
> > > > +     memset(&lchan->chan_reg, 0, sizeof(struct loongson2_cmc_dma_chan_reg));
> > > > +     lchan->chan_reg.ccr = dma_spec->args[1] & LOONGSON2_CMCDMA_STREAM_MASK;
> > > > +
> > > > +     return chan;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_of_controller_register(struct loongson2_cmc_dma_dev *lddev)
> > > > +{
> > > > +     struct device *dev = lddev->ddev.dev;
> > > > +     int ret;
> > > > +
> > > > +     if (!dev->of_node)
> > > > +             return 0;
> > > > +
> > > > +     ret = of_dma_controller_register(dev->of_node, loongson2_cmc_dma_of_xlate, lddev);
> > > > +     if (ret)
> > > > +             dev_err(dev, "could not register of_dma_controller\n");
> > > > +
> > > > +     return ret;
> > > > +}
> > > > +
> > > > +static int loongson2_cmc_dma_probe(struct platform_device *pdev)
> > > > +{
> > > > +     const struct loongson2_cmc_dma_config *config;
> > > > +     struct loongson2_cmc_dma_chan *lchan;
> > > > +     struct loongson2_cmc_dma_dev *lddev;
> > > > +     struct device *dev = &pdev->dev;
> > > > +     struct dma_device *ddev;
> > > > +     u32 nr_chans, i;
> > > > +     int ret;
> > > > +
> > > > +     config = (const struct loongson2_cmc_dma_config *)device_get_match_data(dev);
> > > > +     if (!config)
> > > > +             return -EINVAL;
> > > > +
> > > > +     ret = device_property_read_u32(dev, "dma-channels", &nr_chans);
> > > > +     if (ret || nr_chans > config->max_channels) {
> > > > +             dev_err(dev, "missing or invalid dma-channels property\n");
> > > > +             nr_chans = config->max_channels;
> > > > +     }
> > > > +
> > > > +     lddev = devm_kzalloc(dev, struct_size(lddev, chan, nr_chans), GFP_KERNEL);
> > > > +     if (!lddev)
> > > > +             return -ENOMEM;
> > > > +
> > > > +     lddev->base = devm_platform_ioremap_resource(pdev, 0);
> > > > +     if (IS_ERR(lddev->base))
> > > > +             return PTR_ERR(lddev->base);
> > > > +
> > > > +     platform_set_drvdata(pdev, lddev);
> > > > +     lddev->nr_channels = nr_chans;
> > > > +     lddev->chan_reg_offset = config->chan_reg_offset;
> > > > +
> > > > +     lddev->dma_clk = devm_clk_get_optional_enabled(dev, NULL);
> > > > +     if (IS_ERR(lddev->dma_clk))
> > > > +             return dev_err_probe(dev, PTR_ERR(lddev->dma_clk), "Failed to get dma clock\n");
> > > > +
> > > > +     ddev = &lddev->ddev;
> > > > +     ddev->dev = dev;
> > > > +
> > > > +     dma_cap_zero(ddev->cap_mask);
> > > > +     dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > +     dma_cap_set(DMA_PRIVATE, ddev->cap_mask);
> > > > +     dma_cap_set(DMA_CYCLIC, ddev->cap_mask);
> > > > +
> > > > +     ddev->device_free_chan_resources = loongson2_cmc_dma_free_chan_resources;
> > > > +     ddev->device_config = loongson2_cmc_dma_slave_config;
> > > > +     ddev->device_prep_slave_sg = loongson2_cmc_dma_prep_slave_sg;
> > > > +     ddev->device_prep_dma_cyclic = loongson2_cmc_dma_prep_dma_cyclic;
> > > > +     ddev->device_issue_pending = loongson2_cmc_dma_issue_pending;
> > > > +     ddev->device_synchronize = loongson2_cmc_dma_synchronize;
> > > > +     ddev->device_tx_status = loongson2_cmc_dma_tx_status;
> > > > +     ddev->device_terminate_all = loongson2_cmc_dma_terminate_all;
> > > > +
> > > > +     ddev->src_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > +     ddev->dst_addr_widths = LOONGSON2_CMCDMA_BUSWIDTHS;
> > > > +     ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > +     INIT_LIST_HEAD(&ddev->channels);
> > >
> > > where use this 'channels' ?
> >
> > It will be used by global functions such as `dma_async_device_register()`.

Okay, supposed it sould be done in dma_async_device_register().

Frank
> > >
> > > Frank
> > > > +
> > > > +     for (i = 0; i < nr_chans; i++) {
> > > > +             lchan = &lddev->chan[i];
> > > > +
> > > > +             lchan->id = i;
> > > > +             lchan->vchan.desc_free = loongson2_cmc_dma_desc_free;
> > > > +             vchan_init(&lchan->vchan, ddev);
> > > > +     }
> > > > +
> > > > +     ret = dmaenginem_async_device_register(ddev);
> > > > +     if (ret)
> > > > +             return ret;
> > > > +
> > > > +     for (i = 0; i < nr_chans; i++) {
> > > > +             lchan = &lddev->chan[i];
> > > > +
> > > > +             lchan->irq = platform_get_irq(pdev, i);
> > > > +             if (lchan->irq < 0)
> > > > +                     return lchan->irq;
> > > > +
> > > > +             ret = devm_request_irq(dev, lchan->irq, loongson2_cmc_dma_chan_irq, IRQF_SHARED,
> > > > +                                    dev_name(chan2dev(lchan)), lchan);
> > > > +             if (ret)
> > > > +                     return ret;
> > > > +     }
> > > > +
> > > > +     ret = loongson2_cmc_dma_acpi_controller_register(lddev);
> > > > +     if (ret)
> > > > +             return ret;
> > > > +
> > > > +     return loongson2_cmc_dma_of_controller_register(lddev);
> > > > +}
> > > > +
> > > > +static void loongson2_cmc_dma_remove(struct platform_device *pdev)
> > > > +{
> > > > +     of_dma_controller_free(pdev->dev.of_node);
> > > > +}
> > > > +
> > > > +static const struct of_device_id loongson2_cmc_dma_of_match[] = {
> > > > +     { .compatible = "loongson,ls2k0300-dma", .data = &ls2k0300_cmc_dma_config },
> > > > +     { .compatible = "loongson,ls2k3000-dma", .data = &ls2k3000_cmc_dma_config },
> > > > +     { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, loongson2_cmc_dma_of_match);
> > > > +
> > > > +static const struct acpi_device_id loongson2_cmc_dma_acpi_match[] = {
> > > > +     { "LOON0014", .driver_data = (kernel_ulong_t)&ls2k3000_cmc_dma_config },
> > > > +     { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(acpi, loongson2_cmc_dma_acpi_match);
> > > > +
> > > > +static struct platform_driver loongson2_cmc_dma_driver = {
> > > > +     .driver = {
> > > > +             .name = "loongson2-apb-cmc-dma",
> > > > +             .of_match_table = loongson2_cmc_dma_of_match,
> > > > +             .acpi_match_table = loongson2_cmc_dma_acpi_match,
> > > > +     },
> > > > +     .probe = loongson2_cmc_dma_probe,
> > > > +     .remove = loongson2_cmc_dma_remove,
> > > > +};
> > > > +module_platform_driver(loongson2_cmc_dma_driver);
> > > > +
> > > > +MODULE_DESCRIPTION("Looongson-2 Multi-Channel DMA Controller driver");
> > > > +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> > > > +MODULE_LICENSE("GPL");
> > > > --
> > > > 2.52.0
> > > >
> >
> > --
> > Thanks.
> > Binbin
>
> --
> Thanks.
> Binbin

  reply	other threads:[~2026-02-10 15:27 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-09  3:04 [PATCH v2 0/4] dmaengine: Add Loongson Multi-Channel DMA controller support Binbin Zhou
2026-02-09  3:04 ` [PATCH v2 1/4] dmaengine: loongson: New directory for Loongson DMA controllers drivers Binbin Zhou
2026-02-09 16:48   ` Frank Li
2026-02-10  1:27     ` Binbin Zhou
2026-02-26  8:07       ` Huacai Chen
2026-02-09  3:04 ` [PATCH v2 2/4] dmaengine: loongson: loongson2-apb: Convert to dmaenginem_async_device_register() Binbin Zhou
2026-02-09 16:51   ` Frank Li
2026-02-09  3:04 ` [PATCH v2 3/4] dt-bindings: dmaengine: Add Loongson Multi-Channel DMA controller Binbin Zhou
2026-02-10  3:03   ` Rob Herring
2026-02-10  6:02     ` Binbin Zhou
2026-02-09  3:04 ` [PATCH v2 4/4] dmaengine: loongson: New driver for the " Binbin Zhou
2026-02-09 17:04   ` Frank Li
2026-02-10  7:41     ` Binbin Zhou
2026-02-10 12:02       ` Binbin Zhou
2026-02-10 15:27         ` Frank Li [this message]
2026-02-24 12:59           ` Binbin Zhou

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aYtOVFsoDg2m6yhi@lizhi-Precision-Tower-5810 \
    --to=frank.li@nxp.com \
    --cc=chenhuacai@kernel.org \
    --cc=chenhuacai@loongson.cn \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmaengine@vger.kernel.org \
    --cc=jeffbai@aosc.io \
    --cc=keguang.zhang@gmail.com \
    --cc=kernel@xen0n.name \
    --cc=krzk+dt@kernel.org \
    --cc=linux-mips@vger.kernel.org \
    --cc=loongarch@lists.linux.dev \
    --cc=maoxiaochuan@loongson.cn \
    --cc=robh@kernel.org \
    --cc=vkoul@kernel.org \
    --cc=zhoubb.aaron@gmail.com \
    --cc=zhoubinbin@loongson.cn \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox