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
next prev parent 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