From: Vincent Mailhol <mailhol@kernel.org>
To: Binbin Zhou <zhoubinbin@loongson.cn>,
Binbin Zhou <zhoubb.aaron@gmail.com>,
Huacai Chen <chenhuacai@loongson.cn>,
Marc Kleine-Budde <mkl@pengutronix.de>,
Bingxiong Li <libingxiong@loongson.cn>
Cc: Huacai Chen <chenhuacai@kernel.org>,
Xuerui Wang <kernel@xen0n.name>,
loongarch@lists.linux.dev, linux-can@vger.kernel.org,
jeffbai@aosc.io
Subject: Re: [PATCH 2/2] can: loongson_canfd: Add RXDMA support
Date: Wed, 6 May 2026 19:51:30 +0200 [thread overview]
Message-ID: <10ea6570-56d7-47ab-9082-3fc8f96d989d@kernel.org> (raw)
In-Reply-To: <ee7e4e0d968428761b0fc50e56560165b4020161.1777273055.git.zhoubinbin@loongson.cn>
On 27/04/2026 at 09:18, Binbin Zhou wrote:
> Add optional DMA support for RX path using the Loongson APB CMC DMA
> engine. When a DMA channel is successfully requested, the driver:
>
> - Uses DMA cyclic transfers to write incoming CAN frames directly to
> a coherent DMA buffer
> - Replaces RXBNEI (RX buffer not empty interrupt) with DMADI (DMA
> done interrupt)
> - Dynamically switches between DMA and PIO modes based on channel
> availability
>
> This significantly reduces CPU intervention under high RX load,
> especially beneficial for CAN FD at higher data rates.
>
> Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> drivers/net/can/loongson_canfd/Kconfig | 2 +-
> .../net/can/loongson_canfd/loongson_canfd.c | 179 ++++++++++++++++--
> 2 files changed, 160 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
> index 5a2540bb5410..8fe44b804991 100644
> --- a/drivers/net/can/loongson_canfd/Kconfig
> +++ b/drivers/net/can/loongson_canfd/Kconfig
> @@ -5,7 +5,7 @@
> #
> config CAN_LOONGSON_CANFD
> tristate "Loongson CAN-FD driver"
> - depends on HAS_IOMEM
> + depends on HAS_IOMEM && LOONGSON2_APB_CMC_DMA
Allow people to compile test your driver:
depends on HAS_IOMEM && (LOONGSON2_APB_CMC_DMA || COMPILE_TEST)
> select REGMAP_MMIO
> help
> This is a canfd driver switch for the Loongson platform,
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
> index 20ac95dc528d..ba9570c34c94 100644
> --- a/drivers/net/can/loongson_canfd/loongson_canfd.c
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
> @@ -6,10 +6,14 @@
> */
>
> #include <linux/acpi.h>
> +#include <linux/acpi_dma.h>
> #include <linux/bitfield.h>
> #include <linux/bits.h>
> #include <linux/can/dev.h>
> #include <linux/can/error.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-direction.h>
> +#include <linux/dma-mapping.h>
> #include <linux/io.h>
> #include <linux/interrupt.h>
> #include <linux/module.h>
> @@ -26,13 +30,21 @@
> #define DEV_NAME "loongson_canfd"
> #define LOONGSON_CANFD_TXBUF_NUM 8
> #define LOONGSON_CANFD_ID 0xBABE
> +#define RX_BUF_SIZE SZ_1K
> +#define LOONGSON_CANFD_DMA_RXDATA_NUM (RX_BUF_SIZE / DMA_SLAVE_BUSWIDTH_4_BYTES)
>
> struct loongson_canfd_priv {
> struct can_priv can; /* must be first member! */
> struct napi_struct napi;
> struct regmap *regmap;
> struct resource *res;
> + struct dma_chan *rx_ch;
> + dma_addr_t rx_dma_buf; /* dma rx buffer bus address */
> + unsigned int *rx_buf; /* dma rx buffer cpu address */
> + u16 last_res;
> spinlock_t tx_lock; /* protect the sending queue */
> + u32 (*get_rx_data)(struct loongson_canfd_priv *priv);
> + bool (*get_rx_pending)(struct loongson_canfd_priv *priv);
> };
>
> enum loongson_canfd_tx_bs {
> @@ -137,6 +149,54 @@ static int loongson_canfd_reset(struct net_device *ndev)
> return 0;
> }
>
> +static u32 loongson_canfd_get_rxdma_data(struct loongson_canfd_priv *priv)
> +{
> + u32 c = 0;
> +
> + c = priv->rx_buf[LOONGSON_CANFD_DMA_RXDATA_NUM - priv->last_res--];
> + if (priv->last_res == 0)
> + priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
> +
> + return c;
> +}
> +
> +static bool loongson_canfd_rxdma_pending(struct loongson_canfd_priv *priv)
> +{
> + enum dma_status status;
> + struct dma_tx_state state;
> +
> + status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state);
> +
> + if (priv->last_res != (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES) &&
> + status == DMA_IN_PROGRESS)
> + return true;
> +
> + return false;
return priv->last_res != (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES)
&& status == DMA_IN_PROGRESS
> +}
> +
> +static u32 loongson_canfd_get_poll_data(struct loongson_canfd_priv *priv)
> +{
> + u32 data;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> +
> + return data;
> +}
> +
> +static bool loongson_canfd_rxpoll_pending(struct loongson_canfd_priv *priv)
> +{
> + u32 rx_sts;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> +
> + return FIELD_GET(REG_RX_STAT_RXFRC, rx_sts) ? true : false;
> +}
> +
> +static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev)
> +{
> + dma_free_coherent(dev, RX_BUF_SIZE, priv->rx_buf, priv->rx_dma_buf);
> +}
> +
> static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
> {
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> @@ -308,7 +368,9 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
> if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
>
> - int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_RBNEI;
> + int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
> + int_ena |= priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> +
> int_msk = ~int_ena; /* Mask all disabled interrupts */
>
> /* It's after reset, so there is no need to clear anything */
> @@ -514,12 +576,12 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struc
>
> /* Data */
> for (i = 0; i < len; i += 4) {
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + data = priv->get_rx_data(priv);
> *(__le32 *)(cf->data + i) = cpu_to_le32(data);
> }
>
> while (unlikely(i < wc * 4)) {
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + data = priv->get_rx_data(priv);
> i += 4;
> }
> }
> @@ -532,8 +594,8 @@ static int loongson_canfd_rx(struct net_device *ndev)
> struct sk_buff *skb;
> u32 meta0, meta1;
>
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
> + meta0 = priv->get_rx_data(priv);
> + meta1 = priv->get_rx_data(priv);
>
> /* Number of characters received */
> if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
> @@ -718,16 +780,16 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> struct net_device *ndev = napi->dev;
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> int work_done = 0, res = 1;
> - u32 sts, rx_frc, rx_sts;
> + int int_ena = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> + u32 sts;
> + bool rx_frc;
>
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> - rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> + rx_frc = priv->get_rx_pending(priv);
>
> while (rx_frc && work_done < quota && res > 0) {
> res = loongson_canfd_rx(ndev);
> work_done++;
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> - rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> + rx_frc = priv->get_rx_pending(priv);
> }
>
> /* Check for RX FIFO Overflow */
> @@ -757,13 +819,11 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> if (!rx_frc && res != 0) {
> if (napi_complete_done(napi, work_done)) {
> /*
> - * Clear and enable RBNEI. It is level-triggered, so
> + * Clear and enable RBNEI/DMADI. It is level-triggered, so
> * there is no race condition.
> */
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT,
> - REG_INT_STAT_RBNEI);
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
> - (REG_INT_STAT_RBNEI << 16));
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, int_ena);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, (int_ena << 16));
> }
> }
>
> @@ -855,7 +915,7 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
> struct net_device *ndev = (struct net_device *)dev_id;
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> int irq_loops;
> - u32 isr;
> + u32 isr, mask;
> u16 icr;
>
> for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
> @@ -864,14 +924,16 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
> if (!isr)
> return irq_loops ? IRQ_HANDLED : IRQ_NONE;
>
> + mask = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> +
> /* Receive Buffer Not Empty Interrupt */
> - if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
> + if (isr & mask) {
> /*
> * Mask RXBNEI the first, then clear interrupt and schedule NAPI.
> * Even if another IRQ fires, RBNEI will always be 0 (masked).
> */
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, mask);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, mask);
> napi_schedule(&priv->napi);
> }
>
> @@ -1054,11 +1116,56 @@ static const struct regmap_config loongson_cangfd_regmap = {
> .cache_type = REGCACHE_MAPLE,
> };
>
> +static int loongson_canfd_rxdma_init(struct loongson_canfd_priv *priv, struct device *dev)
> +{
> + struct dma_slave_config config;
> + struct dma_async_tx_descriptor *desc = NULL;
> + int ret;
> +
> + priv->rx_buf = dma_alloc_coherent(dev, RX_BUF_SIZE, &priv->rx_dma_buf, GFP_KERNEL);
> + if (!priv->rx_buf)
> + return -ENOMEM;
> +
> + /* Configure DMA channel */
> + memset(&config, 0, sizeof(config));
> + config.src_addr = priv->res->start + LOONGSON_CANFD_RX_DATA;
> + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +
> + ret = dmaengine_slave_config(priv->rx_ch, &config);
> + if (ret < 0) {
> + dev_err(dev, "rx dma channel config failed\n");
> + goto err_config;
> + }
> +
> + /* Prepare a DMA cyclic transaction */
> + desc = dmaengine_prep_dma_cyclic(priv->rx_ch, priv->rx_dma_buf,
> + RX_BUF_SIZE, RX_BUF_SIZE,
> + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
> + if (!desc) {
> + dev_err(dev, "rx dma prep cyclic failed\n");
> + ret = -EBUSY;
> + goto err_config;
> + }
> +
> + /* Push current DMA transaction in the pending queue */
> + dmaengine_submit(desc);
> +
> + /* Issue pending DMA requests */
> + dma_async_issue_pending(priv->rx_ch);
> +
> + return 0;
> +
> +err_config:
> + loongson_canfd_rxdma_remove(priv, dev);
> + return ret;
> +}
> +
> static int loongson_canfd_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct loongson_canfd_priv *priv;
> struct net_device *ndev;
> + struct dma_chan *rx_ch;
> struct regmap *regmap;
> struct resource *res;
> void __iomem *base;
> @@ -1079,14 +1186,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> if (irq < 0)
> return irq;
>
> + rx_ch = dma_request_chan(dev, "rx");
> + if (PTR_ERR(rx_ch) == -EPROBE_DEFER)
> + return -EPROBE_DEFER;
> +
> + if (IS_ERR(rx_ch)) {
> + dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n");
> + rx_ch = NULL;
> + }
> +
> /* Create a CAN device instance */
> ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
> if (!ndev)
> - return -ENOMEM;
> + goto err_dma_rx;
>
> priv = netdev_priv(ndev);
> spin_lock_init(&priv->tx_lock);
> priv->regmap = regmap;
> + priv->rx_ch = rx_ch;
> priv->res = res;
>
> priv->can.clock.freq = clk_rate;
> @@ -1111,6 +1228,19 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> if (ret < 0)
> goto err_candev_free;
>
> + if (priv->rx_ch) {
> + priv->get_rx_data = loongson_canfd_get_rxdma_data;
> + priv->get_rx_pending = loongson_canfd_rxdma_pending;
> + priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
> + ret = loongson_canfd_rxdma_init(priv, dev);
> + if (ret) {
> + dev_err(dev, "interrupt mode used for rx (no dma)\n");
> + goto err_candev_free;
> + }
> + } else {
> + priv->get_rx_data = loongson_canfd_get_poll_data;
> + priv->get_rx_pending = loongson_canfd_rxpoll_pending;
> + }
> netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
>
> ret = register_candev(ndev);
> @@ -1123,6 +1253,9 @@ static int loongson_canfd_probe(struct platform_device *pdev)
>
> err_candev_free:
> free_candev(ndev);
> +err_dma_rx:
> + if (rx_ch)
> + dma_release_channel(rx_ch);
> return ret;
> }
>
> @@ -1133,6 +1266,11 @@ static void loongson_canfd_remove(struct platform_device *pdev)
>
> netdev_dbg(ndev, "loongson_canfd_remove");
>
> + if (priv->rx_ch) {
> + loongson_canfd_rxdma_remove(priv, &pdev->dev);
> + dma_release_channel(priv->rx_ch);
> + }
> +
> unregister_candev(ndev);
> netif_napi_del(&priv->napi);
> free_candev(ndev);
> @@ -1154,6 +1292,7 @@ static struct platform_driver loongson_canfd_driver = {
> };
> module_platform_driver(loongson_canfd_driver);
>
> +MODULE_SOFTDEP("pre: loongson2-apb-cmc-dma");
Is this really needed? Isn't it enough to just put the
LOONGSON2_APB_CMC_DMA dependency in Kconfig?
> MODULE_AUTHOR("Loongson Technology Corporation Limited");
> MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
> MODULE_LICENSE("GPL");
Yours sincerely,
Vincent Mailhol
prev parent reply other threads:[~2026-05-06 17:51 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-27 7:17 [PATCH 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-04-27 7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
2026-05-06 17:50 ` Vincent Mailhol
2026-04-27 7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
2026-05-06 17:51 ` Vincent Mailhol [this message]
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=10ea6570-56d7-47ab-9082-3fc8f96d989d@kernel.org \
--to=mailhol@kernel.org \
--cc=chenhuacai@kernel.org \
--cc=chenhuacai@loongson.cn \
--cc=jeffbai@aosc.io \
--cc=kernel@xen0n.name \
--cc=libingxiong@loongson.cn \
--cc=linux-can@vger.kernel.org \
--cc=loongarch@lists.linux.dev \
--cc=mkl@pengutronix.de \
--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