Linux CAN drivers development
 help / color / mirror / Atom feed
* [PATCH 0/2] Add Loongson CAN-FD controller driver
@ 2026-04-27  7:17 Binbin Zhou
  2026-04-27  7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
  2026-04-27  7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
  0 siblings, 2 replies; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27  7:17 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
	Bingxiong Li
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
	Binbin Zhou

Hi all:

This patchset adds support for the CAN-FD controller found on Loongson
CPUs.

Patch 1 introduces the basic CAN-FD controller driver with support
for classic CAN and CAN FD, including bit timing, error handling,
NAPI-based RX, and multiple TX buffers.

Patch 2 adds optional DMA support for RX path using the Loongson APB
CMC DMA engine, which significantly reduces CPU load under high
receive throughput.

The driver has been tested on Loongson-2K3000 platforms with various
CAN/CAN FD traffic patterns.

Finally, I'd like to thank Binxiong, the original author of this driver,
for his efforts in working on the patch.

Thanks.
Binbin

Binbin Zhou (2):
  can: Add Loongson CAN-FD controller support
  can: loongson_canfd: Add RXDMA support

 MAINTAINERS                                   |    7 +
 drivers/net/can/Kconfig                       |    1 +
 drivers/net/can/Makefile                      |    1 +
 drivers/net/can/loongson_canfd/Kconfig        |   16 +
 drivers/net/can/loongson_canfd/Makefile       |    6 +
 .../net/can/loongson_canfd/loongson_canfd.c   | 1298 +++++++++++++++++
 .../loongson_canfd/loongson_canfd_kframe.h    |  142 ++
 .../can/loongson_canfd/loongson_canfd_kregs.h |  315 ++++
 8 files changed, 1786 insertions(+)
 create mode 100644 drivers/net/can/loongson_canfd/Kconfig
 create mode 100644 drivers/net/can/loongson_canfd/Makefile
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h


base-commit: f1359c240191e686614847905fc861cbda480b47
-- 
2.52.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] can: Add Loongson CAN-FD controller support
  2026-04-27  7:17 [PATCH 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
@ 2026-04-27  7:17 ` 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
  1 sibling, 1 reply; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27  7:17 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
	Bingxiong Li
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
	Binbin Zhou

Add driver for the CAN-FD controller integrated into Loongson CPUs. The
controller supports:

- Classic CAN and CAN FD (ISO/non-ISO)
- Data rates up to 5 Mbps (nominal) and 10 Mbps (data)
- 8 independent TX buffers
- Hardware TX retransmission limit and one-shot mode
- Error counters, bus error reporting, arbitration lost capture
- NAPI-based RX path
- Loopback and listen-only modes

The driver is implemented as a standard Linux CAN network driver using
the CAN framework APIs.

Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 MAINTAINERS                                   |    7 +
 drivers/net/can/Kconfig                       |    1 +
 drivers/net/can/Makefile                      |    1 +
 drivers/net/can/loongson_canfd/Kconfig        |   16 +
 drivers/net/can/loongson_canfd/Makefile       |    6 +
 .../net/can/loongson_canfd/loongson_canfd.c   | 1159 +++++++++++++++++
 .../loongson_canfd/loongson_canfd_kframe.h    |  142 ++
 .../can/loongson_canfd/loongson_canfd_kregs.h |  315 +++++
 8 files changed, 1647 insertions(+)
 create mode 100644 drivers/net/can/loongson_canfd/Kconfig
 create mode 100644 drivers/net/can/loongson_canfd/Makefile
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
 create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 7a2ffd9d37d5..5534db894cdc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14935,6 +14935,13 @@ F:	arch/loongarch/
 F:	drivers/*/*loongarch*
 F:	drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:	Bingxiong Li <libingxiong@loongson.cn>
+M:	Binbin Zhou <zhoubinbin@loongson.cn>
+L:	linux-can@vger.kernel.org
+S:	Maintained
+F:	drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:	Yinbo Zhu <zhuyinbo@loongson.cn>
 L:	linux-gpio@vger.kernel.org
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e15e320db476..1306d2d80197 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -242,6 +242,7 @@ source "drivers/net/can/ifi_canfd/Kconfig"
 source "drivers/net/can/m_can/Kconfig"
 source "drivers/net/can/mscan/Kconfig"
 source "drivers/net/can/peak_canfd/Kconfig"
+source "drivers/net/can/loongson_canfd/Kconfig"
 source "drivers/net/can/rcar/Kconfig"
 source "drivers/net/can/rockchip/Kconfig"
 source "drivers/net/can/sja1000/Kconfig"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index d7bc10a6b8ea..a39e26727060 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_CAN_KVASER_PCIEFD)	+= kvaser_pciefd/
 obj-$(CONFIG_CAN_MSCAN)		+= mscan/
 obj-$(CONFIG_CAN_M_CAN)		+= m_can/
 obj-$(CONFIG_CAN_PEAK_PCIEFD)	+= peak_canfd/
+obj-$(CONFIG_CAN_LOONGSON_CANFD)	+= loongson_canfd/
 obj-$(CONFIG_CAN_SJA1000)	+= sja1000/
 obj-$(CONFIG_CAN_SUN4I)		+= sun4i_can.o
 obj-$(CONFIG_CAN_TI_HECC)	+= ti_hecc.o
diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
new file mode 100644
index 000000000000..5a2540bb5410
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#
+#
+config CAN_LOONGSON_CANFD
+	tristate "Loongson CAN-FD driver"
+	depends on HAS_IOMEM
+	select REGMAP_MMIO
+	help
+	  This is a canfd driver switch for the Loongson platform,
+	  integrated with the Loongson-2K series.
+
+          You can choose yes or no here.For detailed information about
+          the user manual, please log in to the Loongson official
+          website.(https://loongson.cn/)
diff --git a/drivers/net/can/loongson_canfd/Makefile b/drivers/net/can/loongson_canfd/Makefile
new file mode 100644
index 000000000000..38d1f91bd0b8
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+#  Makefile for the Loongson CAN-FD controller drivers.
+#
+
+obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
new file mode 100644
index 000000000000..20ac95dc528d
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
@@ -0,0 +1,1159 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LOONGSON CANFD controller
+ *
+ * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "loongson_canfd_kframe.h"
+#include "loongson_canfd_kregs.h"
+
+#define DEV_NAME			"loongson_canfd"
+#define LOONGSON_CANFD_TXBUF_NUM	8
+#define LOONGSON_CANFD_ID		0xBABE
+
+struct loongson_canfd_priv {
+	struct can_priv		can;		/* must be first member! */
+	struct napi_struct	napi;
+	struct regmap		*regmap;
+	struct resource		*res;
+	spinlock_t		tx_lock;	/* protect the sending queue */
+};
+
+enum loongson_canfd_tx_bs {
+	TX_BS_IDLE	= 0x0,
+	TX_BS_VALID	= 0x1,
+	TX_BS_FAIL	= 0x2,
+	TX_BS_CANCEL	= 0x3
+};
+
+enum loongson_canfd_txtb_command {
+	TXT_CMD_SET_ADD		= 0x01,
+	TXT_CMD_SET_CANCEL	= 0x02
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing_max = {
+	.name		= DEV_NAME,
+	.tseg1_min	= 2,
+	.tseg1_max	= 190,
+	.tseg2_min	= 2,
+	.tseg2_max	= 63,
+	.sjw_max	= 31,
+	.brp_min	= 1,
+	.brp_max	= 15,
+	.brp_inc	= 1,
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing_data_max = {
+	.name		= DEV_NAME,
+	.tseg1_min	= 2,
+	.tseg1_max	= 190,
+	.tseg2_min	= 2,
+	.tseg2_max	= 63,
+	.sjw_max	= 31,
+	.brp_min	= 1,
+	.brp_max	= 255,
+	.brp_inc	= 1,
+};
+
+static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv)
+{
+	u32 conf;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
+
+	return !!FIELD_GET(REG_CONF_ENA, conf);
+}
+
+static bool loongson_canfd_txtnf(struct loongson_canfd_priv *priv)
+{
+	u32 sts;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+	return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
+}
+
+static inline enum loongson_canfd_tx_bs
+loongson_canfd_get_bs(struct loongson_canfd_priv *priv, u8 bs_id)
+{
+	u32 sts, bs;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+	bs = FIELD_GET(REG_TX_STAT_BS, sts);
+
+	return FIELD_GET(GENMASK(1, 0), (bs >> (bs_id * 2)));
+}
+
+static unsigned int loongson_canfd_get_tx_id(struct loongson_canfd_priv *priv)
+{
+	unsigned int i;
+
+	for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++)
+		if (loongson_canfd_get_bs(priv, i))
+			break;
+
+	return i;
+}
+
+static bool loongson_canfd_txbuf_writable(struct loongson_canfd_priv *priv, u8 buf_id)
+{
+	enum loongson_canfd_tx_bs bs;
+	u32 sts;
+
+	bs = loongson_canfd_get_bs(priv, buf_id);
+	if (bs)
+		return false;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+	if (FIELD_GET(BIT(0), sts >> buf_id))
+		return false;
+
+	return true;
+}
+
+static int loongson_canfd_reset(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST);
+	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM);
+
+	return 0;
+}
+
+static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	u32 phase_seg1 = bt->phase_seg1;
+	u32 prop_seg = bt->prop_seg;
+	int max_ph1_len = 31;
+	u32 btr = 0;
+
+	if (loongson_canfd_enabled(priv)) {
+		netdev_err(ndev, "BUG! Cannot set bittiming - CAN is enabled\n");
+		return -EPERM;
+	}
+
+	if (nominal)
+		max_ph1_len = 63;
+
+	/*
+	 * The timing calculation functions have only constraints on tseg1,
+	 * which is prop_seg + phase1_seg combined.
+	 * tseg1 is then split in half and stored into prog_seg and phase_seg1.
+	 * In Loongson CAN-FD, PROP is 6/7 bits wide but PH1 only 6/5, so we must
+	 * re-distribute the values here.
+	 */
+	if (phase_seg1 > max_ph1_len) {
+		prop_seg += phase_seg1 - max_ph1_len;
+		phase_seg1 = max_ph1_len;
+		bt->prop_seg = prop_seg;
+		bt->phase_seg1 = phase_seg1;
+	}
+
+	if (nominal) {
+		btr = FIELD_PREP(REG_BTR_PROP, prop_seg) |
+		      FIELD_PREP(REG_BTR_PH1, phase_seg1) |
+		      FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) |
+		      FIELD_PREP(REG_BTR_BRP, bt->brp) |
+		      FIELD_PREP(REG_BTR_SJW, bt->sjw);
+
+		regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr);
+	} else {
+		btr = FIELD_PREP(REG_BTR_FD_PROP, prop_seg) |
+		      FIELD_PREP(REG_BTR_FD_PH1, phase_seg1) |
+		      FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) |
+		      FIELD_PREP(REG_BTR_FD_BRP, bt->brp) |
+		      FIELD_PREP(REG_BTR_FD_SJW, bt->sjw);
+
+		regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr);
+	}
+
+	return 0;
+}
+
+static int loongson_canfd_set_bittiming(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct can_bittiming *bt = &priv->can.bittiming;
+
+	/* Note that bt may be modified here */
+	return loongson_canfd_set_btr(ndev, bt, true);
+}
+
+static int loongson_canfd_set_data_bittiming(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+
+	/* Note that dbt may be modified here */
+	return loongson_canfd_set_btr(ndev, dbt, false);
+}
+
+static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+	int ssp_offset = 0;
+	u32 ssp_cfg = 0; /* No SSP by default */
+
+	if (loongson_canfd_enabled(priv)) {
+		netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n");
+		return -EPERM;
+	}
+
+	/* Use SSP for bit-rates above 1 Mbits/s */
+	if (dbt->bitrate > 1000000) {
+		/* Calculate SSP in minimal time quanta */
+		ssp_offset = (priv->can.clock.freq / 1000) * dbt->sample_point / dbt->bitrate;
+		if (ssp_offset > 127) {
+			netdev_warn(ndev, "SSP offset saturated to 127\n");
+			ssp_offset = 127;
+		}
+
+		ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, ssp_offset) |
+			  FIELD_PREP(REG_SSP_CFG_SRC, 0x0);
+	} else {
+		ssp_cfg |= FIELD_PREP(REG_SSP_CFG_SRC, 0x1);
+	}
+
+	regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg);
+
+	return 0;
+}
+
+static void loongson_canfd_set_mode(struct loongson_canfd_priv *priv,
+				    const struct can_ctrlmode *ctrlmode)
+{
+	u32 mode, conf;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode);
+
+	mode = (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY) ?
+	       (mode | REG_MODE_BMM) : (mode & ~REG_MODE_BMM);
+
+	mode = (ctrlmode->flags & CAN_CTRLMODE_FD) ?
+	       (mode | REG_MODE_FDE) : (mode & ~REG_MODE_FDE);
+
+	mode = (ctrlmode->flags & CAN_CTRLMODE_PRESUME_ACK) ?
+	       (mode | REG_MODE_ACF) : (mode & ~REG_MODE_ACF);
+
+	/*
+	 * Some bits fixed:
+	 * TSTM - Off, User shall not be able to change REC/TEC by hand
+	 * during operation
+	 */
+	mode &= ~REG_MODE_TSTM;
+	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode);
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
+
+	conf = (ctrlmode->flags & CAN_CTRLMODE_LOOPBACK) ?
+	       (conf | REG_CONF_ILBP) : (conf & ~REG_CONF_ILBP);
+
+	conf = (ctrlmode->flags & CAN_CTRLMODE_FD_NON_ISO) ?
+	       (conf | REG_CONF_NISOFD) : (conf & ~REG_CONF_NISOFD);
+
+	/* One shot mode supported indirectly via Retransmit limit */
+	conf &= ~FIELD_PREP(REG_CONF_RTRTH, 0xF);
+	conf = (ctrlmode->flags & CAN_CTRLMODE_ONE_SHOT) ?
+	       (conf | REG_CONF_RTRLE) :
+	       (conf | REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, 0xF));
+
+	regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf);
+}
+
+static int loongson_canfd_chip_start(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct can_ctrlmode mode;
+	u16 int_ena, int_msk;
+	int ret;
+
+	/* Configure bit-rates and ssp */
+	ret = loongson_canfd_set_bittiming(ndev);
+	if (ret < 0)
+		return ret;
+
+	ret = loongson_canfd_set_data_bittiming(ndev);
+	if (ret < 0)
+		return ret;
+
+	ret = loongson_canfd_set_secondary_sample_point(ndev);
+	if (ret < 0)
+		return ret;
+
+	/* Configure modes */
+	mode.flags = priv->can.ctrlmode;
+	mode.mask = 0xFFFFFFFF;
+	loongson_canfd_set_mode(priv, &mode);
+
+	/* Bus error reporting */
+	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_msk = ~int_ena; /* Mask all disabled interrupts */
+
+	/* It's after reset, so there is no need to clear anything */
+	regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, int_msk);
+	regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
+
+	/* Controller enters ERROR_ACTIVE on initial FCSI */
+	priv->can.state = CAN_STATE_STOPPED;
+
+	/* Enable the controller */
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA);
+
+	return 0;
+}
+
+static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+	int ret;
+
+	switch (mode) {
+	case CAN_MODE_START:
+		ret = loongson_canfd_reset(ndev);
+		if (ret < 0)
+			return ret;
+
+		ret = loongson_canfd_chip_start(ndev);
+		if (ret < 0) {
+			netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+			return ret;
+		}
+
+		netif_wake_queue(ndev);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv,
+					const struct canfd_frame *cf, u8 buf, bool isfdf)
+{
+	u32 meta0, meta1;
+
+	if (buf >= LOONGSON_CANFD_TXBUF_NUM)
+		return false;
+
+	if (!loongson_canfd_txbuf_writable(priv, buf))
+		return false;
+
+	/* Prepare identifier */
+	if (cf->can_id & CAN_EFF_FLAG) {
+		meta0 = cf->can_id & CAN_EFF_MASK;
+		meta0 |= REG_FRAME_FORMAT_W_XDT;
+	} else {
+		meta0 = FIELD_PREP(REG_IDENTIFIER_W_IDENTIFIER_BASE, cf->can_id & CAN_SFF_MASK);
+	}
+
+	/* Prepare Frame format */
+	if (cf->can_id & CAN_RTR_FLAG)
+		meta0 |= REG_FRAME_FORMAT_W_RTR;
+
+	if (isfdf) {
+		meta1 = REG_FRAME_FORMAT_W_FDF;
+
+		if (cf->flags & CANFD_BRS)
+			meta1 |= REG_FRAME_FORMAT_W_BRS;
+	}
+
+	meta1 |= FIELD_PREP(REG_FRAME_FORMAT_W_DLC, can_fd_len2dlc(cf->len));
+
+	/* TXT buffer select */
+	regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf);
+
+	/* Write ID, Frame format */
+	regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META0, meta0);
+	regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META1, meta1);
+
+	/* Write Data payload */
+	if (!(cf->can_id & CAN_RTR_FLAG)) {
+		for (unsigned int i = 0; i < cf->len; i += 4) {
+			regmap_write(priv->regmap,
+				     LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_DATA_1_4_W + i,
+				     le32_to_cpu(*(__le32 *)(cf->data + i)));
+		}
+	}
+
+	return true;
+}
+
+static void loongson_canfd_give_txtb_cmd(struct loongson_canfd_priv *priv,
+					 enum loongson_canfd_txtb_command cmd, u8 buf)
+{
+	u32 tx_cmd = (((cmd >> 1) << 8) | (cmd % 2)) << buf;
+
+	regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, tx_cmd);
+}
+
+static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	u32 txtb_id, tx_stat;
+	unsigned long flags;
+	u16 tx_bs;
+	u8 tx_brp;
+	bool ok;
+
+	if (can_dropped_invalid_skb(ndev, skb))
+		return NETDEV_TX_OK;
+
+	if (unlikely(!loongson_canfd_txtnf(priv))) {
+		netif_stop_queue(ndev);
+		netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
+	tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
+	tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
+
+	for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+		if ((((tx_brp >> i) & 0x1) == 0) &&
+		    ((tx_bs >> (i * 2) & 0x3) == 0)) {
+			txtb_id = i;
+			break;
+		}
+	}
+
+	ok = loongson_canfd_insert_frame(priv, cf, txtb_id, can_is_canfd_skb(skb));
+	if (!ok) {
+		netdev_err(ndev, "BUG! TXNF set but cannot insert frame into TXTB! HW Bug?");
+		kfree_skb(skb);
+		ndev->stats.tx_dropped++;
+		spin_unlock_irqrestore(&priv->tx_lock, flags);
+		return NETDEV_TX_OK;
+	}
+
+	can_put_echo_skb(skb, ndev, txtb_id, 0);
+
+	if (!(cf->can_id & CAN_RTR_FLAG))
+		stats->tx_bytes += cf->len;
+
+	loongson_canfd_give_txtb_cmd(priv, TXT_CMD_SET_ADD, txtb_id);
+
+	/* Check if all TX buffers are full */
+	if (!loongson_canfd_txtnf(priv))
+		netif_stop_queue(ndev);
+
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+	return NETDEV_TX_OK;
+}
+
+static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
+					 u32 meta0, u32 meta1)
+{
+	u32 data, i, wc, len;
+
+	/* Extended Identifier Type */
+	if (FIELD_GET(REG_FRAME_FORMAT_W_XDT, meta0))
+		cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG;
+	else
+		cf->can_id = FIELD_GET(REG_IDENTIFIER_W_IDENTIFIER_BASE, meta0) & CAN_SFF_MASK;
+
+	/* BRS, ESI, RTR Flags */
+	cf->flags = 0;
+
+	if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1)) {
+		if (FIELD_GET(REG_FRAME_FORMAT_W_BRS, meta1))
+			cf->flags |= CANFD_BRS;
+
+		if (FIELD_GET(REG_FRAME_FORMAT_W_ESI_RSV, meta0))
+			cf->flags |= CANFD_ESI;
+	} else if (FIELD_GET(REG_FRAME_FORMAT_W_RTR, meta0)) {
+		cf->can_id |= CAN_RTR_FLAG;
+	}
+
+	/* Timesamp */
+	cf->__res0 = meta1;
+	cf->__res1 = meta1 >> 8;
+
+	wc = FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1) - 2;
+
+	/* Data Length Code */
+	if (FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1) <= 8) {
+		len = FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1);
+	} else {
+		if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
+			len = wc << 2;
+		else
+			len = 8;
+	}
+
+	cf->len = len;
+	if (unlikely(len > wc * 4))
+		len = wc * 4;
+
+	/* Data */
+	for (i = 0; i < len; i += 4) {
+		regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+		*(__le32 *)(cf->data + i) = cpu_to_le32(data);
+	}
+
+	while (unlikely(i < wc * 4)) {
+		regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+		i += 4;
+	}
+}
+
+static int loongson_canfd_rx(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct canfd_frame *cf;
+	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);
+
+	/* Number of characters received */
+	if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
+		return -EAGAIN;
+
+	/* Flexible Data-rate Format */
+	if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
+		skb = alloc_canfd_skb(ndev, &cf);
+	else
+		skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+	loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
+
+	stats->rx_bytes += cf->len;
+	stats->rx_packets++;
+	netif_receive_skb(skb);
+
+	return 1;
+}
+
+static enum can_state loongson_canfd_read_fault_state(struct loongson_canfd_priv *priv)
+{
+	u32 fs, erl, rec_tec, ewl;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl);
+	regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fs);
+	regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec);
+
+	ewl = FIELD_GET(REG_ERL_EW, erl);
+
+	if (FIELD_GET(REG_FSTAT_ERA, fs)) {
+		if (ewl > FIELD_GET(REG_ERC_REC, rec_tec) &&
+		    ewl > FIELD_GET(REG_ERC_REC, rec_tec))
+			return CAN_STATE_ERROR_ACTIVE;
+		else
+			return CAN_STATE_ERROR_WARNING;
+	} else if (FIELD_GET(REG_FSTAT_ERP, fs)) {
+		return CAN_STATE_ERROR_PASSIVE;
+	} else if (FIELD_GET(REG_FSTAT_BOF, fs)) {
+		return CAN_STATE_BUS_OFF;
+	}
+
+	WARN(true, "Invalid error state");
+	return CAN_STATE_ERROR_PASSIVE;
+}
+
+static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec)
+{
+	u32 erc;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc);
+	bec->rxerr = FIELD_GET(REG_ERC_REC, erc);
+	bec->txerr = FIELD_GET(REG_ERC_REC, erc);
+}
+
+static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct can_berr_counter bec;
+	enum can_state state;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+	u32 err_capt, alc;
+	int dologerr = net_ratelimit();
+
+	loongson_canfd_get_bec(priv, &bec);
+
+	state = loongson_canfd_read_fault_state(priv);
+	regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt);
+	regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc);
+
+	if (dologerr)
+		netdev_info(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n",
+			    __func__, isr, bec.rxerr, bec.txerr,
+			    FIELD_GET(REG_ERR_CAPT_TYPE, err_capt),
+			    FIELD_GET(REG_ERR_CAPT_POS, err_capt),
+			    FIELD_GET(REG_ALC_ID_FIELD, alc),
+			    FIELD_GET(REG_ALC_BIT_POS, alc));
+
+	skb = alloc_can_err_skb(ndev, &cf);
+
+	/*
+	 * EWLI: error warning limit condition met
+	 * FCSI: fault confinement state changed
+	 * ALI:  arbitration lost (just informative)
+	 * BEI:  bus error interrupt
+	 */
+	if (FIELD_GET(REG_INT_STAT_FCSI, isr) || FIELD_GET(REG_INT_STAT_EWLI, isr)) {
+		netdev_info(ndev, "state changes from %s to %s\n",
+			    can_get_state_str(priv->can.state), can_get_state_str(state));
+
+		if (priv->can.state == state)
+			netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
+
+		isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
+		priv->can.state = state;
+		switch (state) {
+		case CAN_STATE_BUS_OFF:
+			priv->can.can_stats.bus_off++;
+			if (priv->can.restart_ms)
+				regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
+					     REG_CMD_ERCRST);
+
+			can_bus_off(ndev);
+			if (skb)
+				cf->can_id |= CAN_ERR_BUSOFF;
+			break;
+		case CAN_STATE_ERROR_PASSIVE:
+			priv->can.can_stats.error_passive++;
+			if (skb) {
+				cf->can_id |= CAN_ERR_CRTL;
+				cf->data[1] = (bec.rxerr > 127) ?
+					      CAN_ERR_CRTL_RX_PASSIVE : CAN_ERR_CRTL_TX_PASSIVE;
+				cf->data[6] = bec.txerr;
+				cf->data[7] = bec.rxerr;
+			}
+			break;
+		case CAN_STATE_ERROR_WARNING:
+			priv->can.can_stats.error_warning++;
+			if (skb) {
+				cf->can_id |= CAN_ERR_CRTL;
+				cf->data[1] |= (bec.txerr > bec.rxerr) ?
+					       CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING;
+				cf->data[6] = bec.txerr;
+				cf->data[7] = bec.rxerr;
+			}
+			break;
+		case CAN_STATE_ERROR_ACTIVE:
+			cf->data[1] = CAN_ERR_CRTL_ACTIVE;
+			cf->data[6] = bec.txerr;
+			cf->data[7] = bec.rxerr;
+			break;
+		default:
+			netdev_warn(ndev, "error state (%d:%s)!\n", state,
+				    can_get_state_str(state));
+			break;
+		}
+	}
+
+	/* Check for Arbitration Lost interrupt */
+	if (FIELD_GET(REG_INT_STAT_ALI, isr)) {
+		isr = REG_INT_STAT_ALI;
+
+		if (dologerr)
+			netdev_info(ndev, "arbitration lost\n");
+
+		priv->can.can_stats.arbitration_lost++;
+
+		if (skb) {
+			cf->can_id |= CAN_ERR_LOSTARB;
+			cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
+		}
+	}
+
+	/* Check for Bus Error interrupt */
+	if (FIELD_GET(REG_INT_STAT_BEI, isr)) {
+		isr = REG_INT_STAT_BEI;
+		netdev_info(ndev, "bus error\n");
+		priv->can.can_stats.bus_error++;
+		stats->rx_errors++;
+
+		if (skb) {
+			cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+			cf->data[2] = CAN_ERR_PROT_UNSPEC;
+			cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC;
+		}
+	}
+
+	if (skb) {
+		stats->rx_packets++;
+		stats->rx_bytes += cf->can_dlc;
+		netif_rx(skb);
+	}
+
+	regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr);
+	regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 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;
+
+	regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
+	rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
+
+	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);
+	}
+
+	/* Check for RX FIFO Overflow */
+	regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
+	if (FIELD_GET(REG_STAT_DOR, sts)) {
+		struct net_device_stats *stats = &ndev->stats;
+		struct can_frame *cf;
+		struct sk_buff *skb;
+
+		netdev_info(ndev, "rx_poll: rx fifo overflow\n");
+		stats->rx_over_errors++;
+		stats->rx_errors++;
+
+		skb = alloc_can_err_skb(ndev, &cf);
+		if (skb) {
+			cf->can_id |= CAN_ERR_CRTL;
+			cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+			stats->rx_packets++;
+			stats->rx_bytes += cf->can_dlc;
+			netif_rx(skb);
+		}
+
+		/* Clear Data Overrun */
+		regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO);
+	}
+
+	if (!rx_frc && res != 0) {
+		if (napi_complete_done(napi, work_done)) {
+			/*
+			 * Clear and enable RBNEI. 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));
+		}
+	}
+
+	return work_done;
+}
+
+static void loongson_canfd_tx_interrupt(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	enum loongson_canfd_tx_bs txtb_sts;
+	bool some_buffers_processed;
+	unsigned long flags;
+	u32 txtb_id, ctr;
+
+	do {
+		spin_lock_irqsave(&priv->tx_lock, flags);
+
+		some_buffers_processed = false;
+
+		while ((txtb_id = loongson_canfd_get_tx_id(priv)) < 8) {
+			txtb_sts = loongson_canfd_get_bs(priv, txtb_id);
+
+			switch (txtb_sts) {
+			case TX_BS_VALID:
+				stats->tx_bytes += can_get_echo_skb(ndev, txtb_id, NULL);
+				stats->tx_packets++;
+				break;
+			case TX_BS_FAIL:
+				/*
+				 * This indicated that retransmit limit has been reached.
+				 * Obviously we should not echo the frame, but also not
+				 * indicate any kind of error.
+				 * If desired, it was already reported (possible multiple
+				 * times) on each arbitration lost.
+				 */
+				regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &ctr);
+				netdev_warn(ndev, "TX_BS_FAIL txcnt=%x\n", ctr);
+				can_free_echo_skb(ndev, txtb_id, NULL);
+				stats->tx_dropped++;
+				break;
+			case TX_BS_CANCEL:
+				/*
+				 * Same as for TX_BS_CANCEL, only with different cause.
+				 * We *could* re-queue the frame, but multiqueue/abort is
+				 * not supported yet anyway.
+				 */
+				netdev_warn(ndev, "TX_BS_CANCEL\n");
+				can_free_echo_skb(ndev, txtb_id, NULL);
+				stats->tx_dropped++;
+				break;
+			case TX_BS_IDLE:
+				netdev_warn(ndev, "TX_BS_IDLE\n");
+				break;
+			}
+
+			regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD,
+				     0x1 << (txtb_id + 16));
+			some_buffers_processed = true;
+		}
+
+		spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+		/*
+		 * If no buffers were processed this time, we cannot clear - that would
+		 * introduce a race condition.
+		 */
+		if (some_buffers_processed) {
+			/*
+			 * Clear the interrupt again. We do not want to receive again interrupt
+			 * for the buffer already handled. If it is the last finished one then
+			 * it would cause log of spurious interrupt.
+			 */
+			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
+		}
+	} while (some_buffers_processed);
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+
+	/* Check if at least one TX buffer is free */
+	if (loongson_canfd_txtnf(priv))
+		netif_wake_queue(ndev);
+
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+}
+
+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;
+	u16 icr;
+
+	for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
+		/* Get the interrupt sts */
+		regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr);
+		if (!isr)
+			return irq_loops ? IRQ_HANDLED : IRQ_NONE;
+
+		/* Receive Buffer Not Empty Interrupt */
+		if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
+			/*
+			 * 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);
+			napi_schedule(&priv->napi);
+		}
+
+		/* TXT Buffer HW Command Interrupt */
+		if (FIELD_GET(REG_INT_STAT_TXBHCI, isr))
+			loongson_canfd_tx_interrupt(ndev);
+
+		/* Error interrupts */
+		if (FIELD_GET(REG_INT_STAT_EWLI, isr) ||
+		    FIELD_GET(REG_INT_STAT_FCSI, isr) ||
+		    FIELD_GET(REG_INT_STAT_ALI, isr)) {
+			icr = isr & (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI);
+			regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, icr);
+			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, icr);
+			loongson_canfd_err_interrupt(ndev, isr);
+		}
+
+		/* Ignore RI, TI, LFI, RFI, BSI */
+	}
+
+	netdev_err(ndev, "Error:isr<0x%08x>\n", isr);
+
+	if (FIELD_GET(REG_INT_STAT_TXBHCI, isr)) {
+		for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+			enum loongson_canfd_tx_bs txtb_sts = loongson_canfd_get_bs(priv, i);
+
+			netdev_err(ndev, "txb[%d]txbstatus=0x%01x\n", i, txtb_sts);
+		}
+	}
+
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+			   REG_INT_MASK_SET, REG_INT_MASK_SET);
+
+	return IRQ_HANDLED;
+}
+
+static void loongson_canfd_chip_stop(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+	/* Disable interrupts and disable CAN */
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+			   REG_INT_MASK_SET, REG_INT_MASK_SET);
+	regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0);
+
+	priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int loongson_canfd_open(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+	int ret;
+
+	ret = loongson_canfd_reset(ndev);
+	if (ret < 0)
+		return ret;
+
+	/* Common open */
+	ret = open_candev(ndev);
+	if (ret) {
+		netdev_warn(ndev, "open_candev failed!\n");
+		return ret;
+	}
+
+	ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev);
+	if (ret < 0) {
+		netdev_err(ndev, "irq allocation for CAN failed\n");
+		goto err_irq;
+	}
+
+	ret = loongson_canfd_chip_start(ndev);
+	if (ret < 0) {
+		netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+		goto err_chip_start;
+	}
+
+	netdev_info(ndev, "loongson_canfd_device registered\n");
+	napi_enable(&priv->napi);
+	netif_start_queue(ndev);
+
+	return 0;
+
+err_chip_start:
+	free_irq(ndev->irq, ndev);
+err_irq:
+	close_candev(ndev);
+	return ret;
+}
+
+static int loongson_canfd_close(struct net_device *ndev)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+	napi_disable(&priv->napi);
+	loongson_canfd_chip_stop(ndev);
+	free_irq(ndev->irq, ndev);
+	close_candev(ndev);
+
+	return 0;
+}
+
+static const struct net_device_ops loongson_canfd_netdev_ops = {
+	.ndo_open       = loongson_canfd_open,
+	.ndo_stop       = loongson_canfd_close,
+	.ndo_start_xmit = loongson_canfd_start_xmit,
+};
+
+static int loongson_canfd_get_berr_counter(const struct net_device *ndev,
+					   struct can_berr_counter *bec)
+{
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+	loongson_canfd_get_bec(priv, bec);
+	return 0;
+}
+
+static const struct regmap_range loongson_canfd_reg_table_wr_range[] = {
+	regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF),
+	regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD),
+	regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL),
+	regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES),
+	regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG),
+	regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL),
+	regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_range loongson_canfd_reg_table_rd_range[] = {
+	regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT),
+	regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE),
+	regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT),
+	regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_wr = {
+	.yes_ranges = loongson_canfd_reg_table_wr_range,
+	.n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_rd = {
+	.yes_ranges = loongson_canfd_reg_table_rd_range,
+	.n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range),
+};
+
+static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case LOONGSON_CANFD_MODE:
+	case LOONGSON_CANFD_CONF:
+	case LOONGSON_CANFD_STAT:
+	case LOONGSON_CANFD_INT_STAT:
+	case LOONGSON_CANFD_INT_ENA:
+	case LOONGSON_CANFD_INT_MASK:
+	case LOONGSON_CANFD_ERL:
+	case LOONGSON_CANFD_FSTAT:
+	case LOONGSON_CANFD_ERC:
+	case LOONGSON_CANFD_ERR_CAPT:
+	case LOONGSON_CANFD_ALC:
+	case LOONGSON_CANFD_TX_FR_CNT:
+	case LOONGSON_CANFD_RX_STAT:
+	case LOONGSON_CANFD_RX_DATA:
+	case LOONGSON_CANFD_TX_STAT:
+	case LOONGSON_CANFD_TX_SEL:
+		return true;
+	default:
+		return false;
+	};
+}
+
+static const struct regmap_config loongson_cangfd_regmap = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.wr_table	= &loongson_canfd_reg_table_wr,
+	.rd_table	= &loongson_canfd_reg_table_rd,
+	.volatile_reg	= loongson_canfd_volatile_reg,
+	.max_register	= LOONGSON_CANFD_TX_DATA_18,
+	.cache_type	= REGCACHE_MAPLE,
+};
+
+static int loongson_canfd_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct loongson_canfd_priv *priv;
+	struct net_device *ndev;
+	struct regmap *regmap;
+	struct resource *res;
+	void __iomem *base;
+	u32 clk_rate;
+	int ret, irq;
+
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	device_property_read_u32(dev, "clock-frequency", &clk_rate);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/* Create a CAN device instance */
+	ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
+	if (!ndev)
+		return -ENOMEM;
+
+	priv = netdev_priv(ndev);
+	spin_lock_init(&priv->tx_lock);
+	priv->regmap = regmap;
+	priv->res = res;
+
+	priv->can.clock.freq = clk_rate;
+	priv->can.bittiming_const = &loongson_canfd_bit_timing_max;
+	priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data_max;
+	priv->can.do_set_mode = loongson_canfd_do_set_mode;
+	priv->can.do_set_bittiming = loongson_canfd_set_bittiming;
+	priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming;
+	priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter;
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
+				       CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
+				       CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
+				       CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO;
+
+	ndev->irq = irq;
+	ndev->flags |= IFF_ECHO;	/* We support local echo */
+	platform_set_drvdata(pdev, ndev);
+	ndev->netdev_ops = &loongson_canfd_netdev_ops;
+	SET_NETDEV_DEV(ndev, dev);
+
+	ret = loongson_canfd_reset(ndev);
+	if (ret < 0)
+		goto err_candev_free;
+
+	netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
+
+	ret = register_candev(ndev);
+	if (ret) {
+		dev_err(dev, "fail to register failed (err=%d)\n", ret);
+		goto err_candev_free;
+	}
+
+	return 0;
+
+err_candev_free:
+	free_candev(ndev);
+	return ret;
+}
+
+static void loongson_canfd_remove(struct platform_device *pdev)
+{
+	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+	netdev_dbg(ndev, "loongson_canfd_remove");
+
+	unregister_candev(ndev);
+	netif_napi_del(&priv->napi);
+	free_candev(ndev);
+}
+
+static const struct acpi_device_id loongson_canfd_acpi_match[] = {
+	{ "LOON0015" },
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match);
+
+static struct platform_driver loongson_canfd_driver = {
+	.probe  = loongson_canfd_probe,
+	.remove = loongson_canfd_remove,
+	.driver = {
+		.name = DEV_NAME,
+		.acpi_match_table = loongson_canfd_acpi_match,
+	},
+};
+module_platform_driver(loongson_canfd_driver);
+
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
new file mode 100644
index 000000000000..d3517a7ff4a3
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef __LOONGSON_CANFD_KFRAME_H
+#define __LOONGSON_CANFD_KFRAME_H
+
+/* CAN_Frame_format memory map */
+#define LOONGSON_CANFD_META0		0x0
+#define LOONGSON_CANFD_META1		0x4
+#define LOONGSON_CANFD_DATA_1_4_W	0x8
+#define LOONGSON_CANFD_DATA_5_8_W	0xc
+#define LOONGSON_CANFD_DATA_9_12_W	0x10
+#define LOONGSON_CANFD_DATA_13_16_W	0x14
+#define LOONGSON_CANFD_DATA_17_20_W	0x18
+#define LOONGSON_CANFD_DATA_21_24_W	0x1c
+#define LOONGSON_CANFD_DATA_25_28_W	0x20
+#define LOONGSON_CANFD_DATA_29_32_W	0x24
+#define LOONGSON_CANFD_DATA_33_36_W	0x28
+#define LOONGSON_CANFD_DATA_37_40_W	0x2c
+#define LOONGSON_CANFD_DATA_41_44_W	0x30
+#define LOONGSON_CANFD_DATA_45_48_W	0x34
+#define LOONGSON_CANFD_DATA_49_52_W	0x38
+#define LOONGSON_CANFD_DATA_53_56_W	0x3c
+#define LOONGSON_CANFD_DATA_57_60_W	0x40
+#define LOONGSON_CANFD_DATA_61_64_W	0x44
+
+/* FRAME_META0 registers */
+#define REG_IDENTIFIER_W_IDENTIFIER_EXT		GENMASK(17, 0)
+#define REG_IDENTIFIER_W_IDENTIFIER_BASE	GENMASK(28, 18)
+#define REG_FRAME_FORMAT_W_RTR			BIT(29)
+#define REG_FRAME_FORMAT_W_XDT			BIT(30)
+#define REG_FRAME_FORMAT_W_ESI_RSV		BIT(31)
+
+/*  FRAME_META1 registers */
+#define REG_META1_W_TIMESTAMP			GENMASK(15, 0)
+#define REG_FRAME_FORMAT_W_DLC			GENMASK(19, 16)
+#define REG_FRAME_FORMAT_W_BRS			BIT(20)
+#define REG_FRAME_FORMAT_W_FDF			BIT(21)
+#define REG_FRAME_FORMAT_W_RWCNT		GENMASK(28, 24)
+
+/*  DATA_1_4_W registers */
+#define REG_DATA_1_4_W_DATA_1			GENMASK(7, 0)
+#define REG_DATA_1_4_W_DATA_2			GENMASK(15, 8)
+#define REG_DATA_1_4_W_DATA_3			GENMASK(23, 16)
+#define REG_DATA_1_4_W_DATA_4			GENMASK(31, 24)
+
+/*  DATA_5_8_W registers */
+#define REG_DATA_5_8_W_DATA_5			GENMASK(7, 0)
+#define REG_DATA_5_8_W_DATA_6			GENMASK(15, 8)
+#define REG_DATA_5_8_W_DATA_7			GENMASK(23, 16)
+#define REG_DATA_5_8_W_DATA_8			GENMASK(31, 24)
+
+/*  DATA_9_12_W registers */
+#define REG_DATA_9_12_W_DATA_9			GENMASK(7, 0)
+#define REG_DATA_9_12_W_DATA_10			GENMASK(15, 8)
+#define REG_DATA_9_12_W_DATA_11			GENMASK(23, 16)
+#define REG_DATA_9_12_W_DATA_12			GENMASK(31, 24)
+
+/*  DATA_13_16_W registers */
+#define REG_DATA_13_16_W_DATA_13		GENMASK(7, 0)
+#define REG_DATA_13_16_W_DATA_14		GENMASK(15, 8)
+#define REG_DATA_13_16_W_DATA_15		GENMASK(23, 16)
+#define REG_DATA_13_16_W_DATA_16		GENMASK(31, 24)
+
+/*  DATA_17_20_W registers */
+#define REG_DATA_17_20_W_DATA_17		GENMASK(7, 0)
+#define REG_DATA_17_20_W_DATA_18		GENMASK(15, 8)
+#define REG_DATA_17_20_W_DATA_19		GENMASK(23, 16)
+#define REG_DATA_17_20_W_DATA_20		GENMASK(31, 24)
+
+/*  DATA_21_24_W registers */
+#define REG_DATA_21_24_W_DATA_21		GENMASK(7, 0)
+#define REG_DATA_21_24_W_DATA_22		GENMASK(15, 8)
+#define REG_DATA_21_24_W_DATA_23		GENMASK(23, 16)
+#define REG_DATA_21_24_W_DATA_24		GENMASK(31, 24)
+
+/*  DATA_25_28_W registers */
+#define REG_DATA_25_28_W_DATA_25		GENMASK(7, 0)
+#define REG_DATA_25_28_W_DATA_26		GENMASK(15, 8)
+#define REG_DATA_25_28_W_DATA_27		GENMASK(23, 16)
+#define REG_DATA_25_28_W_DATA_28		GENMASK(31, 24)
+
+/*  DATA_29_32_W registers */
+#define REG_DATA_29_32_W_DATA_29		GENMASK(7, 0)
+#define REG_DATA_29_32_W_DATA_30		GENMASK(15, 8)
+#define REG_DATA_29_32_W_DATA_31		GENMASK(23, 16)
+#define REG_DATA_29_32_W_DATA_32		GENMASK(31, 24)
+
+/*  DATA_33_36_W registers */
+#define REG_DATA_33_36_W_DATA_33		GENMASK(7, 0)
+#define REG_DATA_33_36_W_DATA_34		GENMASK(15, 8)
+#define REG_DATA_33_36_W_DATA_35		GENMASK(23, 16)
+#define REG_DATA_33_36_W_DATA_36		GENMASK(31, 24)
+
+/*  DATA_37_40_W registers */
+#define REG_DATA_37_40_W_DATA_37		GENMASK(7, 0)
+#define REG_DATA_37_40_W_DATA_38		GENMASK(15, 8)
+#define REG_DATA_37_40_W_DATA_39		GENMASK(23, 16)
+#define REG_DATA_37_40_W_DATA_40		GENMASK(31, 24)
+
+/*  DATA_41_44_W registers */
+#define REG_DATA_41_44_W_DATA_41		GENMASK(7, 0)
+#define REG_DATA_41_44_W_DATA_42		GENMASK(15, 8)
+#define REG_DATA_41_44_W_DATA_43		GENMASK(23, 16)
+#define REG_DATA_41_44_W_DATA_44		GENMASK(31, 24)
+
+/*  DATA_45_48_W registers */
+#define REG_DATA_45_48_W_DATA_45		GENMASK(7, 0)
+#define REG_DATA_45_48_W_DATA_46		GENMASK(15, 8)
+#define REG_DATA_45_48_W_DATA_47		GENMASK(23, 16)
+#define REG_DATA_45_48_W_DATA_48		GENMASK(31, 24)
+
+/*  DATA_49_52_W registers */
+#define REG_DATA_49_52_W_DATA_49		GENMASK(7, 0)
+#define REG_DATA_49_52_W_DATA_50		GENMASK(15, 8)
+#define REG_DATA_49_52_W_DATA_51		GENMASK(23, 16)
+#define REG_DATA_49_52_W_DATA_52		GENMASK(31, 24)
+
+/*  DATA_53_56_W registers */
+#define REG_DATA_53_56_W_DATA_53		GENMASK(7, 0)
+#define REG_DATA_53_56_W_DATA_56		GENMASK(15, 8)
+#define REG_DATA_53_56_W_DATA_55		GENMASK(23, 16)
+#define REG_DATA_53_56_W_DATA_54		GENMASK(31, 24)
+
+/*  DATA_57_60_W registers */
+#define REG_DATA_57_60_W_DATA_57		GENMASK(7, 0)
+#define REG_DATA_57_60_W_DATA_58		GENMASK(15, 8)
+#define REG_DATA_57_60_W_DATA_59		GENMASK(23, 16)
+#define REG_DATA_57_60_W_DATA_60		GENMASK(31, 24)
+
+/*  DATA_61_64_W registers */
+#define REG_DATA_61_64_W_DATA_61		GENMASK(7, 0)
+#define REG_DATA_61_64_W_DATA_62		GENMASK(15, 8)
+#define REG_DATA_61_64_W_DATA_63		GENMASK(23, 16)
+#define REG_DATA_61_64_W_DATA_64		GENMASK(31, 24)
+
+/*  FRAME_TEST_W registers */
+#define REG_FRAME_TEST_W_FSTC			BIT(0)
+#define REG_FRAME_TEST_W_FCRC			BIT(1)
+#define REG_FRAME_TEST_W_SDLC			BIT(2)
+#define REG_FRAME_TEST_W_TPRM			GENMASK(12, 8)
+
+#endif
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
new file mode 100644
index 000000000000..5e8889e3a924
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _LOONGSON_CANFD_KREGS_H
+#define _LOONGSON_CANFD_KREGS_H
+
+#define LOONGSON_CANFD_DEVICE_ID	0x0	/* CANFD controller ID Register */
+#define LOONGSON_CANFD_MODE		0x4	/* Mode Configuration Register */
+#define LOONGSON_CANFD_CONF		0x8	/* Configure Register */
+#define LOONGSON_CANFD_STAT		0xc	/* Status Register */
+#define LOONGSON_CANFD_CMD		0x10	/* Command Register */
+#define LOONGSON_CANFD_INT_STAT		0x14	/* Interrupt Status Register */
+#define LOONGSON_CANFD_INT_ENA		0x18	/* Interrupt Enable Register */
+#define LOONGSON_CANFD_INT_MASK		0x1c	/* Interrupt Mask Register */
+#define LOONGSON_CANFD_BTR_NORM		0x20	/* Normal Rate Configuration Register */
+#define LOONGSON_CANFD_BTR_FD		0x24	/* FD Data Rate Configuration Register */
+#define LOONGSON_CANFD_ERL		0x28	/* Error Threshold Configuration Register */
+#define LOONGSON_CANFD_FSTAT		0x2c	/* Error Status Register */
+#define LOONGSON_CANFD_ERC		0x30	/* Error Count Register */
+#define LOONGSON_CANFD_BRE		0x34	/* Rate Error Count Register */
+#define LOONGSON_CANFD_CTR_PRES		0x38	/* Error Count Debug Register */
+#define LOONGSON_CANFD_ERR_CAPT		0x3c	/* Error Capture Status Register */
+#define LOONGSON_CANFD_RETX_CNT		0x40	/* Retransmission Count Register */
+#define LOONGSON_CANFD_ALC		0x44	/* Lost Arbitration Capture Register */
+#define LOONGSON_CANFD_TRV_DLY		0x48	/* Transmission Delay Measurement Register */
+#define LOONGSON_CANFD_SSP_CFG		0x4c	/* Second Sampling Point Configuration Register */
+#define LOONGSON_CANFD_RX_FR_CNT	0x50	/* Receive Message Count Register */
+#define LOONGSON_CANFD_TX_FR_CNT	0x54	/* Transmit Message Count Register */
+#define LOONGSON_CANFD_DEBUG		0x58	/* Debug Register */
+#define LOONGSON_CANFD_TS		0x5c	/* Timestamp Register */
+#define LOONGSON_CANFD_TX_FRM_TST	0x60	/* Transmit Message Debug Register */
+#define LOONGSON_CANFD_FRC_DIV		0x64	/* Fractional Divider Ratio Register */
+#define LOONGSON_CANFD_FLT_A_MASK	0x68	/* Filter A Mask Register */
+#define LOONGSON_CANFD_FLT_A_VAL	0x6c	/* Filter A value Register */
+#define LOONGSON_CANFD_FLT_B_MASK	0x70	/* Filter B Mask Register */
+#define LOONGSON_CANFD_FLT_B_VAL	0x74	/* Filter B value Register */
+#define LOONGSON_CANFD_FLT_C_MASK	0x78	/* Filter C Mask Register */
+#define LOONGSON_CANFD_FLT_C_VAL	0x7c	/* Filter C value Register */
+#define LOONGSON_CANFD_FLT_R_LOW	0x80	/* Range Filter Low Threshold Register */
+#define LOONGSON_CANFD_FLT_R_HI		0x84	/* Range Filter High Threshold Register */
+#define LOONGSON_CANFD_FLT_CTRL		0x88	/* Filter Control Register */
+#define LOONGSON_CANFD_RX_MEM_INFO	0x8c	/* Receive Buffer Information Register */
+#define LOONGSON_CANFD_RX_PRT		0x90	/* Receive Buffer Pointer Register */
+#define LOONGSON_CANFD_RX_STAT		0x94	/* Receive Buffer Status Register */
+#define LOONGSON_CANFD_RX_DATA		0x98	/* Receive Data Register */
+#define LOONGSON_CANFD_TX_STAT		0x9c	/* Transmit Buffer Status Register */
+#define LOONGSON_CANFD_TX_CMD		0xa0	/* Transmit Command Register */
+#define LOONGSON_CANFD_TX_SEL		0xa4	/* Transmit Buffer Selection Register */
+#define LOONGSON_CANFD_TX_DATA_1	0xb0
+#define LOONGSON_CANFD_TX_DATA_2	0xb4
+#define LOONGSON_CANFD_TX_DATA_3	0xb8
+#define LOONGSON_CANFD_TX_DATA_4	0xbc
+#define LOONGSON_CANFD_TX_DATA_5	0xc0
+#define LOONGSON_CANFD_TX_DATA_6	0xc4
+#define LOONGSON_CANFD_TX_DATA_7	0xc8
+#define LOONGSON_CANFD_TX_DATA_8	0xcc
+#define LOONGSON_CANFD_TX_DATA_9	0xd0
+#define LOONGSON_CANFD_TX_DATA_10	0xd4
+#define LOONGSON_CANFD_TX_DATA_11	0xd8
+#define LOONGSON_CANFD_TX_DATA_12	0xdc
+#define LOONGSON_CANFD_TX_DATA_13	0xe0
+#define LOONGSON_CANFD_TX_DATA_14	0xe4
+#define LOONGSON_CANFD_TX_DATA_15	0xe8
+#define LOONGSON_CANFD_TX_DATA_16	0xec
+#define LOONGSON_CANFD_TX_DATA_17	0xf0
+#define LOONGSON_CANFD_TX_DATA_18	0xf4
+
+/* Bitfields of CANFD controller ID register */
+#define REG_ID_ID			GENMASK(15, 0)
+#define REG_ID_VER_MIN			GENMASK(23, 16)
+#define REG_ID_VER_MAJ			GENMASK(31, 24)
+
+/* Bitfields of Mode Configuration register */
+#define REG_MODE_RST			BIT(0)
+#define REG_MODE_BMM			BIT(1)
+#define REG_MODE_STM			BIT(2)
+#define REG_MODE_AFM			BIT(3)
+#define REG_MODE_FDE			BIT(4)
+#define REG_MODE_TTTM			BIT(5)
+#define REG_MODE_ROM			BIT(6)
+#define REG_MODE_ACF			BIT(7)
+#define REG_MODE_TSTM			BIT(8)
+#define REG_MODE_RXBAM			BIT(9)
+#define REG_MODE_ITSM			BIT(10)
+#define REG_MODE_RTSOP			BIT(12)
+#define REG_MODE_BUFM			BIT(13)
+
+/* Bitfields of Configure register */
+#define REG_CONF_RTRLE			BIT(0)
+#define REG_CONF_RTRTH			GENMASK(4, 1)
+#define REG_CONF_ILBP			BIT(5)
+#define REG_CONF_ENA			BIT(6)
+#define REG_CONF_NISOFD			BIT(7)
+#define REG_CONF_PEX			BIT(8)
+#define REG_CONF_FDRF			BIT(10)
+
+/* Bitfields of Status register */
+#define REG_STAT_RXNE			BIT(0)
+#define REG_STAT_DOR			BIT(1)
+#define REG_STAT_TXNF			BIT(2)
+#define REG_STAT_EFT			BIT(3)
+#define REG_STAT_RXS			BIT(4)
+#define REG_STAT_TXS			BIT(5)
+#define REG_STAT_EWL			BIT(6)
+#define REG_STAT_IDLE			BIT(7)
+#define REG_STAT_PEXS			BIT(8)
+#define REG_STAT_STCNT			BIT(16)
+
+/* Bitfields of Command register */
+#define REG_CMD_RXRPMV			BIT(1)
+#define REG_CMD_RRB			BIT(2)
+#define REG_CMD_CDO			BIT(3)
+#define REG_CMD_ERCRST			BIT(4)
+#define REG_CMD_RXFCRST			BIT(5)
+#define REG_CMD_TXFCRST			BIT(6)
+#define REG_CMD_CPEXS			BIT(7)
+
+/* Bitfields of Interrupt Status register */
+#define REG_INT_STAT_RXI		BIT(0)
+#define REG_INT_STAT_TXI		BIT(1)
+#define REG_INT_STAT_EWLI		BIT(2)
+#define REG_INT_STAT_DOI		BIT(3)
+#define REG_INT_STAT_FCSI		BIT(4)
+#define REG_INT_STAT_ALI		BIT(5)
+#define REG_INT_STAT_BEI		BIT(6)
+#define REG_INT_STAT_RXFI		BIT(7)
+#define REG_INT_STAT_BSI		BIT(8)
+#define REG_INT_STAT_RBNEI		BIT(9)
+#define REG_INT_STAT_TXBHCI		BIT(10)
+#define REG_INT_STAT_OFI		BIT(11)
+#define REG_INT_STAT_DMADI		BIT(12)
+
+#define REG_INT_STAT_ERRORI	(REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI)
+
+/* Bitfields of Interrupt Enable register */
+#define REG_INT_ENA_CLR			GENMASK(28, 16)
+#define REG_INT_ENA_SET			GENMASK(12, 0)
+
+/* Bitfields of Interrupt Mask register */
+#define REG_INT_MASK_SET		GENMASK(12, 0)
+#define REG_INT_MASK_CLR		GENMASK(28, 16)
+
+/* Bitfields of Normal Rate Configuration register */
+#define REG_BTR_PROP			GENMASK(6, 0)
+#define REG_BTR_PH1			GENMASK(12, 7)
+#define REG_BTR_PH2			GENMASK(18, 13)
+#define REG_BTR_BRP			GENMASK(22, 19)
+#define REG_BTR_SJW			GENMASK(31, 27)
+
+/* Bitfields of FD Data Rate Configuration register */
+#define REG_BTR_FD_PROP			GENMASK(6, 0)
+#define REG_BTR_FD_PH1			GENMASK(11, 7)
+#define REG_BTR_FD_PH2			GENMASK(17, 13)
+#define REG_BTR_FD_BRP			GENMASK(26, 19)
+#define REG_BTR_FD_SJW			GENMASK(31, 27)
+
+/* Bitfields of Error Threshold Configuration register */
+#define REG_ERL_ERP			GENMASK(7, 0)
+#define REG_ERL_EW			GENMASK(23, 16)
+
+/* Bitfields of Error Status register */
+#define REG_FSTAT_ERA			BIT(0)
+#define REG_FSTAT_ERP			BIT(1)
+#define REG_FSTAT_BOF			BIT(2)
+
+/* Bitfields of Error Count register */
+#define REG_ERC_TEC			GENMASK(8, 0)
+#define REG_ERC_REC			GENMASK(24, 16)
+
+/* Bitfields of Rate Error Count register */
+#define REG_BRE_NORM			GENMASK(15, 0)
+#define REG_BRE_FD_DATA			GENMASK(31, 16)
+
+/* Bitfields of Error Count Debug register */
+#define REG_CTR_PRES_CTPV		GENMASK(8, 0)
+#define REG_CTR_PRES_PTX		BIT(9)
+#define REG_CTR_PRES_PRX		BIT(10)
+
+/* Bitfields of Error Capture Status register */
+#define REG_ERR_CAPT_POS		GENMASK(4, 0)
+#define REG_ERR_CAPT_TYPE		GENMASK(7, 5)
+
+/* Bitfields of Retransmission Count register */
+#define REG_RETX_CNT_VAL		GENMASK(3, 0)
+
+/* Bitfields of Lost Arbitration Capture register */
+#define REG_ALC_BIT_POS			GENMASK(4, 0)
+#define REG_ALC_ID_FIELD		GENMASK(7, 5)
+
+/* Bitfields of Transmission Delay Measurement register */
+#define REG_TRV_DLY_VAL			GENMASK(6, 0)
+
+/* Bitfields of Second Sampling Point Configuration register */
+#define REG_SSP_CFG_OFF			GENMASK(7, 0)
+#define REG_SSP_CFG_SRC			GENMASK(9, 8)
+#define REG_SSP_CFG_SAT			BIT(10)
+
+/* Bitfields of Receive Message Count register */
+#define REG_RX_FR_CNT_VAL		GENMASK(31, 0)
+
+/* Bitfields of Transmit Message Count register */
+#define REG_TX_FR_CNT_VAL		GENMASK(31, 0)
+
+/* Bitfields of Debug register */
+#define REG_DEBUG_STF_CNT		GENMASK(2, 0)
+#define REG_DEBUG_DSTF_CNT		GENMASK(5, 3)
+#define REG_DEBUG_PC_ARB		BIT(6)
+#define REG_DEBUG_PC_CON		BIT(7)
+#define REG_DEBUG_PC_DAT		BIT(8)
+#define REG_DEBUG_PC_STC		BIT(9)
+#define REG_DEBUG_PC_CRC		BIT(10)
+#define REG_DEBUG_PC_CRCD		BIT(11)
+#define REG_DEBUG_PC_ACK		BIT(12)
+#define REG_DEBUG_PC_ACKD		BIT(13)
+#define REG_DEBUG_PC_EOF		BIT(14)
+#define REG_DEBUG_PC_INT		BIT(15)
+#define REG_DEBUG_PC_SUSP		BIT(16)
+#define REG_DEBUG_PC_OVR		BIT(17)
+#define REG_DEBUG_PC_SOF		BIT(18)
+
+/* Bitfields of Timestamp register */
+#define REG_TS_TIMESTAMP		GENMASK(15, 0)
+#define REG_TS_PSC			GENMASK(24, 16)
+
+/* Bitfields of Fractional Divider Ratio register */
+#define REG_FRC_FRC_DBT			GENMASK(15, 8)
+#define REG_FRC_FRC_NBT			GENMASK(7, 0)
+
+/* Bitfields of Filter A Mask register */
+#define REG_FIL_A_MASK			GENMASK(28, 0)
+
+/* Bitfields of Filter A value register */
+#define REG_FIL_A_VAL			GENMASK(28, 0)
+
+/* Bitfields of Filter B Mask register */
+#define REG_FIL_B_MASK			GENMASK(28, 0)
+
+/* Bitfields of Filter B value register */
+#define REG_FIL_B_VAL			GENMASK(28, 0)
+
+/* Bitfields of Filter C Mask register */
+#define REG_FIL_C_MASK			GENMASK(28, 0)
+
+/* Bitfields of Filter C value register */
+#define REG_FIL_C_VAL			GENMASK(28, 0)
+
+/* Bitfields of Range Filter Low Threshold register */
+#define REG_FIL_R_LOW_VAL		GENMASK(28, 0)
+
+/* Bitfields of Range Filter High Threshold register */
+#define REG_FIL_R_HI_VAL		GENMASK(28, 0)
+
+/* Bitfields of Filter Control register */
+#define REG_FIL_CTRL_FANB		BIT(0)
+#define REG_FIL_CTRL_FANE		BIT(1)
+#define REG_FIL_CTRL_FAFB		BIT(2)
+#define REG_FIL_CTRL_FAFE		BIT(3)
+#define REG_FIL_CTRL_FBNB		BIT(4)
+#define REG_FIL_CTRL_FBNE		BIT(5)
+#define REG_FIL_CTRL_FBFB		BIT(6)
+#define REG_FIL_CTRL_FBFE		BIT(7)
+#define REG_FIL_CTRL_FCNB		BIT(8)
+#define REG_FIL_CTRL_FCNE		BIT(9)
+#define REG_FIL_CTRL_FCFB		BIT(10)
+#define REG_FIL_CTRL_FCFE		BIT(11)
+#define REG_FIL_CTRL_FRNB		BIT(12)
+#define REG_FIL_CTRL_FRNE		BIT(13)
+#define REG_FIL_CTRL_FRFB		BIT(14)
+#define REG_FIL_CTRL_FRFE		BIT(15)
+#define REG_FIL_CTRL_SFA		BIT(16)
+#define REG_FIL_CTRL_SFB		BIT(17)
+#define REG_FIL_CTRL_SFC		BIT(18)
+#define REG_FIL_CTRL_SFR		BIT(19)
+
+/* Bitfields of Receive Buffer Information register */
+#define REG_RX_MEM_INFO_BUFF_SIZE	GENMASK(12, 0)
+#define REG_RX_MEM_INFO_MEM_FREE	GENMASK(28, 16)
+
+/* Bitfields of Receive Buffer Pointer register */
+#define REG_RX_PTR_WPP			GENMASK(11, 0)
+#define REG_RX_PTR_RPP			GENMASK(27, 16)
+
+/* Bitfields of Receive Buffer Status register */
+#define REG_RX_STAT_RXE			BIT(0)
+#define REG_RX_STAT_RXF			BIT(1)
+#define REG_RX_STAT_RXMOF		BIT(2)
+#define REG_RX_STAT_RXFRC		GENMASK(14, 4)
+#define REG_RX_STAT_RTSOP		BIT(16)
+
+/* Bitfields of Receive Data register */
+#define REG_RX_DATA_VAL			GENMASK(31, 0)
+
+/* Bitfields of Transmit Buffer Status register */
+#define REG_TX_STAT_BRP			GENMASK(7, 0)
+#define REG_TX_STAT_TXS			GENMASK(10, 8)
+#define REG_TX_STAT_BS			GENMASK(31, 16)
+
+#define REG_TX_STAT_BS_TX1		GENMASK(17, 16)
+#define REG_TX_STAT_BS_TX2		GENMASK(19, 18)
+#define REG_TX_STAT_BS_TX3		GENMASK(21, 20)
+#define REG_TX_STAT_BS_TX4		GENMASK(23, 22)
+#define REG_TX_STAT_BS_TX5		GENMASK(25, 24)
+#define REG_TX_STAT_BS_TX6		GENMASK(27, 26)
+#define REG_TX_STAT_BS_TX7		GENMASK(29, 28)
+#define REG_TX_STAT_BS_TX8		GENMASK(31, 30)
+
+/* Bitfields of Transmit Command register */
+#define REG_TX_CMD_BAR			GENMASK(7, 0)
+#define REG_TX_CMD_BCR			GENMASK(15, 8)
+#define REG_TX_CMD_BSC			GENMASK(23, 16)
+
+/* Bitfields of Transmit Buffer Selection register */
+#define REG_TX_SEL_BUF_SEL		GENMASK(3, 0)
+#define REG_TX_SEL_BUF_CNT		GENMASK(7, 4)
+
+#endif
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] can: loongson_canfd: Add RXDMA support
  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-04-27  7:18 ` Binbin Zhou
  2026-05-06 17:51   ` Vincent Mailhol
  1 sibling, 1 reply; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27  7:18 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
	Bingxiong Li
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
	Binbin Zhou

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
 	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;
+}
+
+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");
 MODULE_AUTHOR("Loongson Technology Corporation Limited");
 MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
 MODULE_LICENSE("GPL");
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support
  2026-04-27  7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
@ 2026-05-06 17:50   ` Vincent Mailhol
  0 siblings, 0 replies; 5+ messages in thread
From: Vincent Mailhol @ 2026-05-06 17:50 UTC (permalink / raw)
  To: Binbin Zhou, Binbin Zhou, Huacai Chen, Marc Kleine-Budde,
	Bingxiong Li
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai

Hi Binbin

A first review. The comments are not yet exhaustive. Please fix not only
the occurences on which I commented, but please also fix the other
similar patterns.

On 27/04/2026 at 09:17, Binbin Zhou wrote:
> Add driver for the CAN-FD controller integrated into Loongson CPUs. The
> controller supports:
> 
> - Classic CAN and CAN FD (ISO/non-ISO)
> - Data rates up to 5 Mbps (nominal) and 10 Mbps (data)
> - 8 independent TX buffers
> - Hardware TX retransmission limit and one-shot mode
> - Error counters, bus error reporting, arbitration lost capture
> - NAPI-based RX path
> - Loopback and listen-only modes
> 
> The driver is implemented as a standard Linux CAN network driver using
> the CAN framework APIs.
> 
> Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  MAINTAINERS                                   |    7 +
>  drivers/net/can/Kconfig                       |    1 +
>  drivers/net/can/Makefile                      |    1 +
>  drivers/net/can/loongson_canfd/Kconfig        |   16 +
>  drivers/net/can/loongson_canfd/Makefile       |    6 +
>  .../net/can/loongson_canfd/loongson_canfd.c   | 1159 +++++++++++++++++
>  .../loongson_canfd/loongson_canfd_kframe.h    |  142 ++
>  .../can/loongson_canfd/loongson_canfd_kregs.h |  315 +++++
>  8 files changed, 1647 insertions(+)
>  create mode 100644 drivers/net/can/loongson_canfd/Kconfig
>  create mode 100644 drivers/net/can/loongson_canfd/Makefile
>  create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
>  create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
>  create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h

Your driver is just one translation unit. I don't think you need a
folder for that. You can just create

  drivers/net/can/loongson_canfd.c

and add all your macro definitions at the top of your file.

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7a2ffd9d37d5..5534db894cdc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14935,6 +14935,13 @@ F:	arch/loongarch/
>  F:	drivers/*/*loongarch*
>  F:	drivers/cpufreq/loongson3_cpufreq.c
>  
> +LOONGSON CAN FD DRIVER
> +M:	Bingxiong Li <libingxiong@loongson.cn>
> +M:	Binbin Zhou <zhoubinbin@loongson.cn>
> +L:	linux-can@vger.kernel.org
> +S:	Maintained
> +F:	drivers/net/can/loongson_canfd/
> +
>  LOONGSON GPIO DRIVER
>  M:	Yinbo Zhu <zhuyinbo@loongson.cn>
>  L:	linux-gpio@vger.kernel.org
> diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
> index e15e320db476..1306d2d80197 100644
> --- a/drivers/net/can/Kconfig
> +++ b/drivers/net/can/Kconfig
> @@ -242,6 +242,7 @@ source "drivers/net/can/ifi_canfd/Kconfig"
>  source "drivers/net/can/m_can/Kconfig"
>  source "drivers/net/can/mscan/Kconfig"
>  source "drivers/net/can/peak_canfd/Kconfig"
> +source "drivers/net/can/loongson_canfd/Kconfig"
>  source "drivers/net/can/rcar/Kconfig"
>  source "drivers/net/can/rockchip/Kconfig"
>  source "drivers/net/can/sja1000/Kconfig"
> diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> index d7bc10a6b8ea..a39e26727060 100644
> --- a/drivers/net/can/Makefile
> +++ b/drivers/net/can/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_CAN_KVASER_PCIEFD)	+= kvaser_pciefd/
>  obj-$(CONFIG_CAN_MSCAN)		+= mscan/
>  obj-$(CONFIG_CAN_M_CAN)		+= m_can/
>  obj-$(CONFIG_CAN_PEAK_PCIEFD)	+= peak_canfd/
> +obj-$(CONFIG_CAN_LOONGSON_CANFD)	+= loongson_canfd/
>  obj-$(CONFIG_CAN_SJA1000)	+= sja1000/
>  obj-$(CONFIG_CAN_SUN4I)		+= sun4i_can.o
>  obj-$(CONFIG_CAN_TI_HECC)	+= ti_hecc.o
> diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
> new file mode 100644
> index 000000000000..5a2540bb5410
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Loongson canfd drivers
> +#
> +#
> +config CAN_LOONGSON_CANFD
> +	tristate "Loongson CAN-FD driver"
> +	depends on HAS_IOMEM
> +	select REGMAP_MMIO
> +	help
> +	  This is a canfd driver switch for the Loongson platform,
> +	  integrated with the Loongson-2K series.
> +
> +          You can choose yes or no here.For detailed information about
                            ^^^^^^^^^
This is a tristate configuration. The choices are yes, no or module.
Also, no need to state that. Just state the name of the module.
Something like:

	  To compile as a module, choose M here: the module will be 	
	  called loongson_canfd.

> +          the user manual, please log in to the Loongson official
> +          website.(https://loongson.cn/)
   ^^^^^^^^^^
Fix the indentation. You are using only spaces here where it should be
one tabulation and two spaces.

> diff --git a/drivers/net/can/loongson_canfd/Makefile b/drivers/net/can/loongson_canfd/Makefile
> new file mode 100644
> index 000000000000..38d1f91bd0b8
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +#  Makefile for the Loongson CAN-FD controller drivers.
> +#
> +
> +obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
> new file mode 100644
> index 000000000000..20ac95dc528d
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
> @@ -0,0 +1,1159 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * LOONGSON CANFD controller
> + *
> + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/can/dev.h>
> +#include <linux/can/error.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/skbuff.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include "loongson_canfd_kframe.h"
> +#include "loongson_canfd_kregs.h"
> +
> +#define DEV_NAME			"loongson_canfd"
> +#define LOONGSON_CANFD_TXBUF_NUM	8
> +#define LOONGSON_CANFD_ID		0xBABE
> +
> +struct loongson_canfd_priv {
> +	struct can_priv		can;		/* must be first member! */
> +	struct napi_struct	napi;
> +	struct regmap		*regmap;
> +	struct resource		*res;
> +	spinlock_t		tx_lock;	/* protect the sending queue */
> +};
> +
> +enum loongson_canfd_tx_bs {
> +	TX_BS_IDLE	= 0x0,
> +	TX_BS_VALID	= 0x1,
> +	TX_BS_FAIL	= 0x2,
> +	TX_BS_CANCEL	= 0x3
> +};
> +
> +enum loongson_canfd_txtb_command {
> +	TXT_CMD_SET_ADD		= 0x01,
> +	TXT_CMD_SET_CANCEL	= 0x02
> +};
> +
> +static const struct can_bittiming_const loongson_canfd_bit_timing_max = {
                                                                    ^^^^
The bittiming also contains minimum values. Drop this _max suffix.

> +	.name		= DEV_NAME,
> +	.tseg1_min	= 2,
> +	.tseg1_max	= 190,
> +	.tseg2_min	= 2,
> +	.tseg2_max	= 63,
> +	.sjw_max	= 31,
> +	.brp_min	= 1,
> +	.brp_max	= 15,
> +	.brp_inc	= 1,
> +};
> +
> +static const struct can_bittiming_const loongson_canfd_bit_timing_data_max = {

Same as above, drop the _max suffix.

> +	.name		= DEV_NAME,
> +	.tseg1_min	= 2,
> +	.tseg1_max	= 190,
> +	.tseg2_min	= 2,
> +	.tseg2_max	= 63,
> +	.sjw_max	= 31,
> +	.brp_min	= 1,
> +	.brp_max	= 255,
> +	.brp_inc	= 1,
> +};
> +
> +static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv)
> +{
> +	u32 conf;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
> +
> +	return !!FIELD_GET(REG_CONF_ENA, conf);
> +}
> +
> +static bool loongson_canfd_txtnf(struct loongson_canfd_priv *priv)
                              ^^^^^
What is the meaning of txtnf?

> +{
> +	u32 sts;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> +
> +	return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
                                                  ^^^^
Avoid this magic number.

Also, your function seems to be equivalent to:

	return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT,
				 REG_TX_STAT_BRP);

> +}
> +
> +static inline enum loongson_canfd_tx_bs
          ^^^^^^
No need for the inline. Let the compiler decide for you if this is worth
inlining.

> +loongson_canfd_get_bs(struct loongson_canfd_priv *priv, u8 bs_id)
> +{
> +	u32 sts, bs;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> +	bs = FIELD_GET(REG_TX_STAT_BS, sts);
> +
> +	return FIELD_GET(GENMASK(1, 0), (bs >> (bs_id * 2)));

Your use of FIELD_GET is odd. The intended way to use FIELD_GET is to
let it handle all the shift logic for you. You shouldn't be doing a
right shift by hand here.

Note that you have the field_get() function which can be used with
non-constanst masks.

> +}
> +
> +static unsigned int loongson_canfd_get_tx_id(struct loongson_canfd_priv *priv)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++)
> +		if (loongson_canfd_get_bs(priv, i))
> +			break;
> +
> +	return i;
> +}
> +
> +static bool loongson_canfd_txbuf_writable(struct loongson_canfd_priv *priv, u8 buf_id)
> +{
> +	enum loongson_canfd_tx_bs bs;
> +	u32 sts;
> +
> +	bs = loongson_canfd_get_bs(priv, buf_id);
> +	if (bs)
> +		return false;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> +	if (FIELD_GET(BIT(0), sts >> buf_id))> +		return false;

Here also, consider using regmap_test_bits().

> +	return true;
> +}
> +
> +static int loongson_canfd_reset(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> +	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST);
> +	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM);
> +
> +	return 0;
> +}
> +
> +static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	u32 phase_seg1 = bt->phase_seg1;
> +	u32 prop_seg = bt->prop_seg;
> +	int max_ph1_len = 31;
> +	u32 btr = 0;
> +
> +	if (loongson_canfd_enabled(priv)) {
> +		netdev_err(ndev, "BUG! Cannot set bittiming - CAN is enabled\n");
> +		return -EPERM;
> +	}
> +
> +	if (nominal)
> +		max_ph1_len = 63;
> +
> +	/*
> +	 * The timing calculation functions have only constraints on tseg1,
> +	 * which is prop_seg + phase1_seg combined.
> +	 * tseg1 is then split in half and stored into prog_seg and phase_seg1.
> +	 * In Loongson CAN-FD, PROP is 6/7 bits wide but PH1 only 6/5, so we must
> +	 * re-distribute the values here.
> +	 */
> +	if (phase_seg1 > max_ph1_len) {
> +		prop_seg += phase_seg1 - max_ph1_len;
> +		phase_seg1 = max_ph1_len;
> +		bt->prop_seg = prop_seg;
> +		bt->phase_seg1 = phase_seg1;
> +	}
> +
> +	if (nominal) {
> +		btr = FIELD_PREP(REG_BTR_PROP, prop_seg) |
> +		      FIELD_PREP(REG_BTR_PH1, phase_seg1) |
> +		      FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) |
> +		      FIELD_PREP(REG_BTR_BRP, bt->brp) |
> +		      FIELD_PREP(REG_BTR_SJW, bt->sjw);
> +
> +		regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr);
> +	} else {
> +		btr = FIELD_PREP(REG_BTR_FD_PROP, prop_seg) |
> +		      FIELD_PREP(REG_BTR_FD_PH1, phase_seg1) |
> +		      FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) |
> +		      FIELD_PREP(REG_BTR_FD_BRP, bt->brp) |
> +		      FIELD_PREP(REG_BTR_FD_SJW, bt->sjw);
> +
> +		regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr);
> +	}
> +
> +	return 0;
> +}
> +
> +static int loongson_canfd_set_bittiming(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct can_bittiming *bt = &priv->can.bittiming;
> +
> +	/* Note that bt may be modified here */
> +	return loongson_canfd_set_btr(ndev, bt, true);
> +}
> +
> +static int loongson_canfd_set_data_bittiming(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
> +
> +	/* Note that dbt may be modified here */
> +	return loongson_canfd_set_btr(ndev, dbt, false);
> +}
> +
> +static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
> +	int ssp_offset = 0;
> +	u32 ssp_cfg = 0; /* No SSP by default */
> +
> +	if (loongson_canfd_enabled(priv)) {
> +		netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n");
> +		return -EPERM;
> +	}
> +
> +	/* Use SSP for bit-rates above 1 Mbits/s */
> +	if (dbt->bitrate > 1000000) {
> +		/* Calculate SSP in minimal time quanta */
> +		ssp_offset = (priv->can.clock.freq / 1000) * dbt->sample_point / dbt->bitrate;
> +		if (ssp_offset > 127) {
> +			netdev_warn(ndev, "SSP offset saturated to 127\n");
> +			ssp_offset = 127;
> +		}
> +
> +		ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, ssp_offset) |
> +			  FIELD_PREP(REG_SSP_CFG_SRC, 0x0);
> +	} else {
> +		ssp_cfg |= FIELD_PREP(REG_SSP_CFG_SRC, 0x1);
> +	}

Use the CAN TDC framework to get the SSP value (c.f. struct can_tdc,
struct can_tdc_const and can_fd_tdc_is_enabled())

> +	regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg);
> +
> +	return 0;
> +}
> +
> +static void loongson_canfd_set_mode(struct loongson_canfd_priv *priv,
> +				    const struct can_ctrlmode *ctrlmode)
> +{
> +	u32 mode, conf;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode);
> +
> +	mode = (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY) ?
> +	       (mode | REG_MODE_BMM) : (mode & ~REG_MODE_BMM);
> +
> +	mode = (ctrlmode->flags & CAN_CTRLMODE_FD) ?
> +	       (mode | REG_MODE_FDE) : (mode & ~REG_MODE_FDE);
> +
> +	mode = (ctrlmode->flags & CAN_CTRLMODE_PRESUME_ACK) ?
> +	       (mode | REG_MODE_ACF) : (mode & ~REG_MODE_ACF);

Use an if/else instead of the ternary operator:

	if (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY)
		mode |= REG_MODE_BMM;
	else
		mode &= ~REG_MODE_BMM;

and so on…

> +	/*
> +	 * Some bits fixed:
> +	 * TSTM - Off, User shall not be able to change REC/TEC by hand
> +	 * during operation
> +	 */
> +	mode &= ~REG_MODE_TSTM;
> +	regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode);
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
> +
> +	conf = (ctrlmode->flags & CAN_CTRLMODE_LOOPBACK) ?
> +	       (conf | REG_CONF_ILBP) : (conf & ~REG_CONF_ILBP);
> +
> +	conf = (ctrlmode->flags & CAN_CTRLMODE_FD_NON_ISO) ?
> +	       (conf | REG_CONF_NISOFD) : (conf & ~REG_CONF_NISOFD);
> +
> +	/* One shot mode supported indirectly via Retransmit limit */
> +	conf &= ~FIELD_PREP(REG_CONF_RTRTH, 0xF);
                                            ^^^
What is this magic number?

> +	conf = (ctrlmode->flags & CAN_CTRLMODE_ONE_SHOT) ?
> +	       (conf | REG_CONF_RTRLE) :
> +	       (conf | REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, 0xF));
> +
> +	regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf);
> +}
> +
> +static int loongson_canfd_chip_start(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct can_ctrlmode mode;
> +	u16 int_ena, int_msk;
> +	int ret;
> +
> +	/* Configure bit-rates and ssp */
> +	ret = loongson_canfd_set_bittiming(ndev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = loongson_canfd_set_data_bittiming(ndev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = loongson_canfd_set_secondary_sample_point(ndev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Configure modes */
> +	mode.flags = priv->can.ctrlmode;
> +	mode.mask = 0xFFFFFFFF;
> +	loongson_canfd_set_mode(priv, &mode);
> +
> +	/* Bus error reporting */
> +	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_msk = ~int_ena; /* Mask all disabled interrupts */
> +
> +	/* It's after reset, so there is no need to clear anything */
> +	regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, int_msk);
> +	regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
> +
> +	/* Controller enters ERROR_ACTIVE on initial FCSI */
> +	priv->can.state = CAN_STATE_STOPPED;
> +
> +	/* Enable the controller */
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA);
> +
> +	return 0;
> +}
> +
> +static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> +	int ret;
> +
> +	switch (mode) {
> +	case CAN_MODE_START:
> +		ret = loongson_canfd_reset(ndev);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = loongson_canfd_chip_start(ndev);
> +		if (ret < 0) {
> +			netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
> +			return ret;
> +		}
> +
> +		netif_wake_queue(ndev);
> +		break;
> +	default:
> +		ret = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv,
> +					const struct canfd_frame *cf, u8 buf, bool isfdf)
> +{
> +	u32 meta0, meta1;
> +
> +	if (buf >= LOONGSON_CANFD_TXBUF_NUM)
> +		return false;
> +
> +	if (!loongson_canfd_txbuf_writable(priv, buf))
> +		return false;
> +
> +	/* Prepare identifier */
> +	if (cf->can_id & CAN_EFF_FLAG) {
> +		meta0 = cf->can_id & CAN_EFF_MASK;
> +		meta0 |= REG_FRAME_FORMAT_W_XDT;
> +	} else {
> +		meta0 = FIELD_PREP(REG_IDENTIFIER_W_IDENTIFIER_BASE, cf->can_id & CAN_SFF_MASK);
> +	}
> +
> +	/* Prepare Frame format */
> +	if (cf->can_id & CAN_RTR_FLAG)
> +		meta0 |= REG_FRAME_FORMAT_W_RTR;
> +
> +	if (isfdf) {
> +		meta1 = REG_FRAME_FORMAT_W_FDF;
> +
> +		if (cf->flags & CANFD_BRS)
> +			meta1 |= REG_FRAME_FORMAT_W_BRS;
> +	}
> +
> +	meta1 |= FIELD_PREP(REG_FRAME_FORMAT_W_DLC, can_fd_len2dlc(cf->len));
> +
> +	/* TXT buffer select */
> +	regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf);
> +
> +	/* Write ID, Frame format */
> +	regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META0, meta0);
> +	regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META1, meta1);
> +
> +	/* Write Data payload */
> +	if (!(cf->can_id & CAN_RTR_FLAG)) {
> +		for (unsigned int i = 0; i < cf->len; i += 4) {
> +			regmap_write(priv->regmap,
> +				     LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_DATA_1_4_W + i,
> +				     le32_to_cpu(*(__le32 *)(cf->data + i)));> +		}> +	}
> +
> +	return true;
> +}
> +
> +static void loongson_canfd_give_txtb_cmd(struct loongson_canfd_priv *priv,
> +					 enum loongson_canfd_txtb_command cmd, u8 buf)
> +{
> +	u32 tx_cmd = (((cmd >> 1) << 8) | (cmd % 2)) << buf;
> +
> +	regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, tx_cmd);
> +}
> +
> +static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> +	struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	u32 txtb_id, tx_stat;
> +	unsigned long flags;
> +	u16 tx_bs;
> +	u8 tx_brp;
> +	bool ok;
> +
> +	if (can_dropped_invalid_skb(ndev, skb))
> +		return NETDEV_TX_OK;
> +
> +	if (unlikely(!loongson_canfd_txtnf(priv))) {
> +		netif_stop_queue(ndev);
> +		netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	spin_lock_irqsave(&priv->tx_lock, flags);

Use:

	guard(spinlock_irqsave)(&priv->tx_lock);

> +	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
> +	tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
> +	tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
> +
> +	for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {

You want to iterate on all the non-set bits? If so, use for_each_clear_bit.

> +		if ((((tx_brp >> i) & 0x1) == 0) &&
> +		    ((tx_bs >> (i * 2) & 0x3) == 0)) {
> +			txtb_id = i;
> +			break;
> +		}
> +	}
> +
> +	ok = loongson_canfd_insert_frame(priv, cf, txtb_id, can_is_canfd_skb(skb));
> +	if (!ok) {

ok is only use once. Inline it:

	if (loongson_canfd_insert_frame(priv, cf, txtb_id,
 					can_is_canfd_skb(skb))) {

> +		netdev_err(ndev, "BUG! TXNF set but cannot insert frame into TXTB! HW Bug?");
> +		kfree_skb(skb);
> +		ndev->stats.tx_dropped++;
> +		spin_unlock_irqrestore(&priv->tx_lock, flags);
> +		return NETDEV_TX_OK;
> +	}
> +
> +	can_put_echo_skb(skb, ndev, txtb_id, 0);
> +
> +	if (!(cf->can_id & CAN_RTR_FLAG))
> +		stats->tx_bytes += cf->len;

Don't increase the statistics here. You are already doing it in
loongson_canfd_tx_interrupt(). That's double counting.

> +	loongson_canfd_give_txtb_cmd(priv, TXT_CMD_SET_ADD, txtb_id);
> +
> +	/* Check if all TX buffers are full */
> +	if (!loongson_canfd_txtnf(priv))
> +		netif_stop_queue(ndev);
> +
> +	spin_unlock_irqrestore(&priv->tx_lock, flags);
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
> +					 u32 meta0, u32 meta1)
> +{
> +	u32 data, i, wc, len;
> +
> +	/* Extended Identifier Type */
> +	if (FIELD_GET(REG_FRAME_FORMAT_W_XDT, meta0))
> +		cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG;
> +	else
> +		cf->can_id = FIELD_GET(REG_IDENTIFIER_W_IDENTIFIER_BASE, meta0) & CAN_SFF_MASK;
> +
> +	/* BRS, ESI, RTR Flags */
> +	cf->flags = 0;
> +
> +	if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1)) {
> +		if (FIELD_GET(REG_FRAME_FORMAT_W_BRS, meta1))

Don't use FIELD_GET() for a single bit. This is enough:

		if (meta1 & REG_FRAME_FORMAT_W_BRS) {

> +			cf->flags |= CANFD_BRS;
> +
> +		if (FIELD_GET(REG_FRAME_FORMAT_W_ESI_RSV, meta0))
> +			cf->flags |= CANFD_ESI;
> +	} else if (FIELD_GET(REG_FRAME_FORMAT_W_RTR, meta0)) {
> +		cf->can_id |= CAN_RTR_FLAG;
> +	}
> +
> +	/* Timesamp */
> +	cf->__res0 = meta1;
> +	cf->__res1 = meta1 >> 8;
> +
> +	wc = FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1) - 2;
> +
> +	/* Data Length Code */
> +	if (FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1) <= 8) {
> +		len = FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1);
> +	} else {
> +		if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
> +			len = wc << 2;
> +		else
> +			len = 8;
> +	}
> +
> +	cf->len = len;
> +	if (unlikely(len > wc * 4))
> +		len = wc * 4;
> +
> +	/* Data */
> +	for (i = 0; i < len; i += 4) {
> +		regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> +		*(__le32 *)(cf->data + i) = cpu_to_le32(data);
> +	}
> +
> +	while (unlikely(i < wc * 4)) {
> +		regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> +		i += 4;
> +	}
> +}
> +
> +static int loongson_canfd_rx(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct canfd_frame *cf;
> +	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);
> +
> +	/* Number of characters received */
> +	if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
> +		return -EAGAIN;
> +
> +	/* Flexible Data-rate Format */
> +	if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
> +		skb = alloc_canfd_skb(ndev, &cf);
> +	else
> +		skb = alloc_can_skb(ndev, (struct can_frame **)&cf);

Check for memory allocation failure.

> +	loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
> +
> +	stats->rx_bytes += cf->len;

Don't increment the rx_bytes stats when the frame is a RTR.

> +	stats->rx_packets++;
> +	netif_receive_skb(skb);
> +
> +	return 1;
> +}
> +
> +static enum can_state loongson_canfd_read_fault_state(struct loongson_canfd_priv *priv)
> +{
> +	u32 fs, erl, rec_tec, ewl;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl);
> +	regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fs);
> +	regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec);
> +
> +	ewl = FIELD_GET(REG_ERL_EW, erl);
> +
> +	if (FIELD_GET(REG_FSTAT_ERA, fs)) {
> +		if (ewl > FIELD_GET(REG_ERC_REC, rec_tec) &&
> +		    ewl > FIELD_GET(REG_ERC_REC, rec_tec))
> +			return CAN_STATE_ERROR_ACTIVE;
> +		else
> +			return CAN_STATE_ERROR_WARNING;
> +	} else if (FIELD_GET(REG_FSTAT_ERP, fs)) {
> +		return CAN_STATE_ERROR_PASSIVE;
> +	} else if (FIELD_GET(REG_FSTAT_BOF, fs)) {
> +		return CAN_STATE_BUS_OFF;
> +	}
> +
> +	WARN(true, "Invalid error state");
> +	return CAN_STATE_ERROR_PASSIVE;
> +}
> +
> +static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec)
> +{
> +	u32 erc;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc);
> +	bec->rxerr = FIELD_GET(REG_ERC_REC, erc);
> +	bec->txerr = FIELD_GET(REG_ERC_REC, erc);
> +}
> +
> +static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct can_berr_counter bec;
> +	enum can_state state;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +	u32 err_capt, alc;
> +	int dologerr = net_ratelimit();
> +
> +	loongson_canfd_get_bec(priv, &bec);
> +
> +	state = loongson_canfd_read_fault_state(priv);
> +	regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt);
> +	regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc);
> +
> +	if (dologerr)
> +		netdev_info(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n",
> +			    __func__, isr, bec.rxerr, bec.txerr,
> +			    FIELD_GET(REG_ERR_CAPT_TYPE, err_capt),
> +			    FIELD_GET(REG_ERR_CAPT_POS, err_capt),
> +			    FIELD_GET(REG_ALC_ID_FIELD, alc),
> +			    FIELD_GET(REG_ALC_BIT_POS, alc));

Try to be less verbose. You can either remove those message or keep them
as netdev_debug().

> +	skb = alloc_can_err_skb(ndev, &cf);
> +
> +	/*
> +	 * EWLI: error warning limit condition met
> +	 * FCSI: fault confinement state changed
> +	 * ALI:  arbitration lost (just informative)
> +	 * BEI:  bus error interrupt
> +	 */
> +	if (FIELD_GET(REG_INT_STAT_FCSI, isr) || FIELD_GET(REG_INT_STAT_EWLI, isr)) {
> +		netdev_info(ndev, "state changes from %s to %s\n",
> +			    can_get_state_str(priv->can.state), can_get_state_str(state));
> +
> +		if (priv->can.state == state)
> +			netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
> +
> +		isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
> +		priv->can.state = state;
> +		switch (state) {
> +		case CAN_STATE_BUS_OFF:
> +			priv->can.can_stats.bus_off++;
> +			if (priv->can.restart_ms)
> +				regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
> +					     REG_CMD_ERCRST);
> +
> +			can_bus_off(ndev);
> +			if (skb)
> +				cf->can_id |= CAN_ERR_BUSOFF;
> +			break;
> +		case CAN_STATE_ERROR_PASSIVE:
> +			priv->can.can_stats.error_passive++;
> +			if (skb) {
> +				cf->can_id |= CAN_ERR_CRTL;
> +				cf->data[1] = (bec.rxerr > 127) ?
                                                           ^^^
Replace the 127 magic number by CAN_ERROR_PASSIVE_THRESHOLD and replace
the ternary operator by an if/else:

	if (bec.rxerr >= CAN_ERROR_PASSIVE_THRESHOLD)
		cf->data[1] = /* ... */
	else
		cf->data[1] = /* ... */

> +					      CAN_ERR_CRTL_RX_PASSIVE : CAN_ERR_CRTL_TX_PASSIVE;

Why are you mixing RX and TX (CAN_ERR_CRTL_RX_PASSIVE,
CAN_ERR_CRTL_TX_PASSIVE)?		

> +				cf->data[6] = bec.txerr;
> +				cf->data[7] = bec.rxerr;

Set the CAN_ERR_CNT when reporting the bec so that the userland is aware
that the data are here (same comment applies below).

> +			}
> +			break;
> +		case CAN_STATE_ERROR_WARNING:
> +			priv->can.can_stats.error_warning++;
> +			if (skb) {
> +				cf->can_id |= CAN_ERR_CRTL;
> +				cf->data[1] |= (bec.txerr > bec.rxerr) ?
> +					       CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING;
> +				cf->data[6] = bec.txerr;
> +				cf->data[7] = bec.rxerr;
> +			}
> +			break;
> +		case CAN_STATE_ERROR_ACTIVE:
> +			cf->data[1] = CAN_ERR_CRTL_ACTIVE;
> +			cf->data[6] = bec.txerr;
> +			cf->data[7] = bec.rxerr;
> +			break;
> +		default:
> +			netdev_warn(ndev, "error state (%d:%s)!\n", state,
> +				    can_get_state_str(state));

Don't use netdev_warn() for that.

> +			break;
> +		}
> +	}
> +
> +	/* Check for Arbitration Lost interrupt */
> +	if (FIELD_GET(REG_INT_STAT_ALI, isr)) {
> +		isr = REG_INT_STAT_ALI;
> +
> +		if (dologerr)
> +			netdev_info(ndev, "arbitration lost\n");
> +
> +		priv->can.can_stats.arbitration_lost++;
> +
> +		if (skb) {
> +			cf->can_id |= CAN_ERR_LOSTARB;
> +			cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
> +		}
> +	}
> +
> +	/* Check for Bus Error interrupt */
> +	if (FIELD_GET(REG_INT_STAT_BEI, isr)) {
> +		isr = REG_INT_STAT_BEI;
> +		netdev_info(ndev, "bus error\n");
> +		priv->can.can_stats.bus_error++;
> +		stats->rx_errors++;
> +
> +		if (skb) {
> +			cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +			cf->data[2] = CAN_ERR_PROT_UNSPEC;
> +			cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC;
> +		}
> +	}
> +
> +	if (skb) {
> +		stats->rx_packets++;
> +		stats->rx_bytes += cf->can_dlc;

Error frames in Socket CAN do not correspond to actual data on the CAN
bus. No need to increase the rx_packets and rx_bytes statistics.

> +		netif_rx(skb);
> +	}
> +
> +	regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr);
> +	regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 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;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> +	rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> +
> +	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);
> +	}
> +
> +	/* Check for RX FIFO Overflow */
> +	regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
> +	if (FIELD_GET(REG_STAT_DOR, sts)) {
> +		struct net_device_stats *stats = &ndev->stats;
> +		struct can_frame *cf;
> +		struct sk_buff *skb;
> +
> +		netdev_info(ndev, "rx_poll: rx fifo overflow\n");
> +		stats->rx_over_errors++;
> +		stats->rx_errors++;
> +
> +		skb = alloc_can_err_skb(ndev, &cf);
> +		if (skb) {
> +			cf->can_id |= CAN_ERR_CRTL;
> +			cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
> +			stats->rx_packets++;
> +			stats->rx_bytes += cf->can_dlc;
> +			netif_rx(skb);
> +		}
> +
> +		/* Clear Data Overrun */
> +		regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO);
> +	}
> +
> +	if (!rx_frc && res != 0) {
> +		if (napi_complete_done(napi, work_done)) {
> +			/*
> +			 * Clear and enable RBNEI. 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));
> +		}
> +	}
> +
> +	return work_done;
> +}
> +
> +static void loongson_canfd_tx_interrupt(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	enum loongson_canfd_tx_bs txtb_sts;
> +	bool some_buffers_processed;
> +	unsigned long flags;
> +	u32 txtb_id, ctr;
> +
> +	do {
> +		spin_lock_irqsave(&priv->tx_lock, flags);

Use:
		scoped_guard(spinlock_irqsave)(&priv->tx_lock) {
			/* ... */
		}
		

> +		some_buffers_processed = false;
> +
> +		while ((txtb_id = loongson_canfd_get_tx_id(priv)) < 8) {
> +			txtb_sts = loongson_canfd_get_bs(priv, txtb_id);
> +
> +			switch (txtb_sts) {
> +			case TX_BS_VALID:
> +				stats->tx_bytes += can_get_echo_skb(ndev, txtb_id, NULL);
> +				stats->tx_packets++;
> +				break;
> +			case TX_BS_FAIL:
> +				/*
> +				 * This indicated that retransmit limit has been reached.
> +				 * Obviously we should not echo the frame, but also not
> +				 * indicate any kind of error.
> +				 * If desired, it was already reported (possible multiple
> +				 * times) on each arbitration lost.
> +				 */
> +				regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &ctr);
> +				netdev_warn(ndev, "TX_BS_FAIL txcnt=%x\n", ctr);
> +				can_free_echo_skb(ndev, txtb_id, NULL);
> +				stats->tx_dropped++;
> +				break;
> +			case TX_BS_CANCEL:
> +				/*
> +				 * Same as for TX_BS_CANCEL, only with different cause.
> +				 * We *could* re-queue the frame, but multiqueue/abort is
> +				 * not supported yet anyway.
> +				 */
> +				netdev_warn(ndev, "TX_BS_CANCEL\n");
> +				can_free_echo_skb(ndev, txtb_id, NULL);
> +				stats->tx_dropped++;
> +				break;
> +			case TX_BS_IDLE:
> +				netdev_warn(ndev, "TX_BS_IDLE\n");
> +				break;
> +			}
> +
> +			regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD,
> +				     0x1 << (txtb_id + 16));
> +			some_buffers_processed = true;

I think you can simply

			break;

here so that you do not need the some_buffers_processed variable anymore.

> +		}
> +
> +		spin_unlock_irqrestore(&priv->tx_lock, flags);
> +
> +		/*
> +		 * If no buffers were processed this time, we cannot clear - that would
> +		 * introduce a race condition.
> +		 */
> +		if (some_buffers_processed) {
> +			/*
> +			 * Clear the interrupt again. We do not want to receive again interrupt
> +			 * for the buffer already handled. If it is the last finished one then
> +			 * it would cause log of spurious interrupt.
> +			 */
> +			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
> +		}
> +	} while (some_buffers_processed);
> +
> +	spin_lock_irqsave(&priv->tx_lock, flags);
> +
> +	/* Check if at least one TX buffer is free */
> +	if (loongson_canfd_txtnf(priv))
> +		netif_wake_queue(ndev);
> +
> +	spin_unlock_irqrestore(&priv->tx_lock, flags);
> +}
> +
> +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;
> +	u16 icr;
> +
> +	for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
> +		/* Get the interrupt sts */
> +		regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr);
> +		if (!isr)
> +			return irq_loops ? IRQ_HANDLED : IRQ_NONE;
> +
> +		/* Receive Buffer Not Empty Interrupt */
> +		if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
> +			/*
> +			 * 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);
> +			napi_schedule(&priv->napi);
> +		}
> +
> +		/* TXT Buffer HW Command Interrupt */
> +		if (FIELD_GET(REG_INT_STAT_TXBHCI, isr))
> +			loongson_canfd_tx_interrupt(ndev);
> +
> +		/* Error interrupts */
> +		if (FIELD_GET(REG_INT_STAT_EWLI, isr) ||
> +		    FIELD_GET(REG_INT_STAT_FCSI, isr) ||
> +		    FIELD_GET(REG_INT_STAT_ALI, isr)) {
> +			icr = isr & (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI);
> +			regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, icr);
> +			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, icr);
> +			loongson_canfd_err_interrupt(ndev, isr);
> +		}
> +
> +		/* Ignore RI, TI, LFI, RFI, BSI */
> +	}
> +
> +	netdev_err(ndev, "Error:isr<0x%08x>\n", isr);
> +
> +	if (FIELD_GET(REG_INT_STAT_TXBHCI, isr)) {
> +		for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
> +			enum loongson_canfd_tx_bs txtb_sts = loongson_canfd_get_bs(priv, i);
> +
> +			netdev_err(ndev, "txb[%d]txbstatus=0x%01x\n", i, txtb_sts);
> +		}
> +	}
> +
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
> +			   REG_INT_MASK_SET, REG_INT_MASK_SET);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void loongson_canfd_chip_stop(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> +	/* Disable interrupts and disable CAN */
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
> +			   REG_INT_MASK_SET, REG_INT_MASK_SET);
> +	regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0);
> +
> +	priv->can.state = CAN_STATE_STOPPED;
> +}
> +
> +static int loongson_canfd_open(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	int ret;
> +
> +	ret = loongson_canfd_reset(ndev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Common open */
> +	ret = open_candev(ndev);
> +	if (ret) {
> +		netdev_warn(ndev, "open_candev failed!\n");
> +		return ret;
> +	}
> +
> +	ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev);
> +	if (ret < 0) {
> +		netdev_err(ndev, "irq allocation for CAN failed\n");
> +		goto err_irq;
> +	}
> +
> +	ret = loongson_canfd_chip_start(ndev);
> +	if (ret < 0) {
> +		netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
> +		goto err_chip_start;
> +	}
> +
> +	netdev_info(ndev, "loongson_canfd_device registered\n");
> +	napi_enable(&priv->napi);
> +	netif_start_queue(ndev);
> +
> +	return 0;
> +
> +err_chip_start:
> +	free_irq(ndev->irq, ndev);
> +err_irq:
> +	close_candev(ndev);
> +	return ret;
> +}
> +
> +static int loongson_canfd_close(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> +	netif_stop_queue(ndev);
> +	napi_disable(&priv->napi);
> +	loongson_canfd_chip_stop(ndev);
> +	free_irq(ndev->irq, ndev);
> +	close_candev(ndev);
> +
> +	return 0;
> +}
> +
> +static const struct net_device_ops loongson_canfd_netdev_ops = {
> +	.ndo_open       = loongson_canfd_open,
> +	.ndo_stop       = loongson_canfd_close,
> +	.ndo_start_xmit = loongson_canfd_start_xmit,
> +};
> +
> +static int loongson_canfd_get_berr_counter(const struct net_device *ndev,
> +					   struct can_berr_counter *bec)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> +	loongson_canfd_get_bec(priv, bec);
> +	return 0;
> +}
> +
> +static const struct regmap_range loongson_canfd_reg_table_wr_range[] = {
> +	regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF),
> +	regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD),
> +	regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL),
> +	regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES),
> +	regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG),
> +	regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL),
> +	regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18),
> +};
> +
> +static const struct regmap_range loongson_canfd_reg_table_rd_range[] = {
> +	regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT),
> +	regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE),
> +	regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT),
> +	regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18),
> +};
> +
> +static const struct regmap_access_table loongson_canfd_reg_table_wr = {
> +	.yes_ranges = loongson_canfd_reg_table_wr_range,
> +	.n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range),
> +};
> +
> +static const struct regmap_access_table loongson_canfd_reg_table_rd = {
> +	.yes_ranges = loongson_canfd_reg_table_rd_range,
> +	.n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range),
> +};
> +
> +static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LOONGSON_CANFD_MODE:
> +	case LOONGSON_CANFD_CONF:
> +	case LOONGSON_CANFD_STAT:
> +	case LOONGSON_CANFD_INT_STAT:
> +	case LOONGSON_CANFD_INT_ENA:
> +	case LOONGSON_CANFD_INT_MASK:
> +	case LOONGSON_CANFD_ERL:
> +	case LOONGSON_CANFD_FSTAT:
> +	case LOONGSON_CANFD_ERC:
> +	case LOONGSON_CANFD_ERR_CAPT:
> +	case LOONGSON_CANFD_ALC:
> +	case LOONGSON_CANFD_TX_FR_CNT:
> +	case LOONGSON_CANFD_RX_STAT:
> +	case LOONGSON_CANFD_RX_DATA:
> +	case LOONGSON_CANFD_TX_STAT:
> +	case LOONGSON_CANFD_TX_SEL:
> +		return true;
> +	default:
> +		return false;
> +	};
> +}
> +
> +static const struct regmap_config loongson_cangfd_regmap = {
> +	.reg_bits	= 32,
> +	.reg_stride	= 4,
> +	.val_bits	= 32,
> +	.wr_table	= &loongson_canfd_reg_table_wr,
> +	.rd_table	= &loongson_canfd_reg_table_rd,
> +	.volatile_reg	= loongson_canfd_volatile_reg,
> +	.max_register	= LOONGSON_CANFD_TX_DATA_18,
> +	.cache_type	= REGCACHE_MAPLE,
> +};
> +
> +static int loongson_canfd_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct loongson_canfd_priv *priv;
> +	struct net_device *ndev;
> +	struct regmap *regmap;
> +	struct resource *res;
> +	void __iomem *base;
> +	u32 clk_rate;
> +	int ret, irq;
> +
> +	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +
> +	regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	device_property_read_u32(dev, "clock-frequency", &clk_rate);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	/* Create a CAN device instance */
> +	ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
> +	if (!ndev)
> +		return -ENOMEM;
> +
> +	priv = netdev_priv(ndev);
> +	spin_lock_init(&priv->tx_lock);
> +	priv->regmap = regmap;
> +	priv->res = res;
> +
> +	priv->can.clock.freq = clk_rate;
> +	priv->can.bittiming_const = &loongson_canfd_bit_timing_max;
> +	priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data_max;
> +	priv->can.do_set_mode = loongson_canfd_do_set_mode;
> +	priv->can.do_set_bittiming = loongson_canfd_set_bittiming;
> +	priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming;
> +	priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter;
> +	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
> +				       CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
> +				       CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
> +				       CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO;
> +
> +	ndev->irq = irq;
> +	ndev->flags |= IFF_ECHO;	/* We support local echo */
> +	platform_set_drvdata(pdev, ndev);
> +	ndev->netdev_ops = &loongson_canfd_netdev_ops;
> +	SET_NETDEV_DEV(ndev, dev);
> +
> +	ret = loongson_canfd_reset(ndev);
> +	if (ret < 0)
> +		goto err_candev_free;
> +
> +	netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
> +
> +	ret = register_candev(ndev);
> +	if (ret) {
> +		dev_err(dev, "fail to register failed (err=%d)\n", ret);
> +		goto err_candev_free;
> +	}
> +
> +	return 0;
> +
> +err_candev_free:
> +	free_candev(ndev);
> +	return ret;
> +}
> +
> +static void loongson_canfd_remove(struct platform_device *pdev)
> +{
> +	struct net_device *ndev = platform_get_drvdata(pdev);
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> +	netdev_dbg(ndev, "loongson_canfd_remove");
> +
> +	unregister_candev(ndev);
> +	netif_napi_del(&priv->napi);
> +	free_candev(ndev);
> +}
> +
> +static const struct acpi_device_id loongson_canfd_acpi_match[] = {
> +	{ "LOON0015" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match);
> +
> +static struct platform_driver loongson_canfd_driver = {
> +	.probe  = loongson_canfd_probe,
> +	.remove = loongson_canfd_remove,
> +	.driver = {
> +		.name = DEV_NAME,
> +		.acpi_match_table = loongson_canfd_acpi_match,
> +	},
> +};
> +module_platform_driver(loongson_canfd_driver);
> +
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
> new file mode 100644
> index 000000000000..d3517a7ff4a3
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
> @@ -0,0 +1,142 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +
> +#ifndef __LOONGSON_CANFD_KFRAME_H
> +#define __LOONGSON_CANFD_KFRAME_H
> +
> +/* CAN_Frame_format memory map */
> +#define LOONGSON_CANFD_META0		0x0
> +#define LOONGSON_CANFD_META1		0x4
> +#define LOONGSON_CANFD_DATA_1_4_W	0x8
> +#define LOONGSON_CANFD_DATA_5_8_W	0xc
> +#define LOONGSON_CANFD_DATA_9_12_W	0x10
> +#define LOONGSON_CANFD_DATA_13_16_W	0x14
> +#define LOONGSON_CANFD_DATA_17_20_W	0x18
> +#define LOONGSON_CANFD_DATA_21_24_W	0x1c
> +#define LOONGSON_CANFD_DATA_25_28_W	0x20
> +#define LOONGSON_CANFD_DATA_29_32_W	0x24
> +#define LOONGSON_CANFD_DATA_33_36_W	0x28
> +#define LOONGSON_CANFD_DATA_37_40_W	0x2c
> +#define LOONGSON_CANFD_DATA_41_44_W	0x30
> +#define LOONGSON_CANFD_DATA_45_48_W	0x34
> +#define LOONGSON_CANFD_DATA_49_52_W	0x38
> +#define LOONGSON_CANFD_DATA_53_56_W	0x3c
> +#define LOONGSON_CANFD_DATA_57_60_W	0x40
> +#define LOONGSON_CANFD_DATA_61_64_W	0x44
> +
> +/* FRAME_META0 registers */
> +#define REG_IDENTIFIER_W_IDENTIFIER_EXT		GENMASK(17, 0)
> +#define REG_IDENTIFIER_W_IDENTIFIER_BASE	GENMASK(28, 18)
> +#define REG_FRAME_FORMAT_W_RTR			BIT(29)
> +#define REG_FRAME_FORMAT_W_XDT			BIT(30)
> +#define REG_FRAME_FORMAT_W_ESI_RSV		BIT(31)
> +
> +/*  FRAME_META1 registers */
> +#define REG_META1_W_TIMESTAMP			GENMASK(15, 0)
> +#define REG_FRAME_FORMAT_W_DLC			GENMASK(19, 16)
> +#define REG_FRAME_FORMAT_W_BRS			BIT(20)
> +#define REG_FRAME_FORMAT_W_FDF			BIT(21)
> +#define REG_FRAME_FORMAT_W_RWCNT		GENMASK(28, 24)
> +
> +/*  DATA_1_4_W registers */
> +#define REG_DATA_1_4_W_DATA_1			GENMASK(7, 0)
> +#define REG_DATA_1_4_W_DATA_2			GENMASK(15, 8)
> +#define REG_DATA_1_4_W_DATA_3			GENMASK(23, 16)
> +#define REG_DATA_1_4_W_DATA_4			GENMASK(31, 24)
> +
> +/*  DATA_5_8_W registers */
> +#define REG_DATA_5_8_W_DATA_5			GENMASK(7, 0)
> +#define REG_DATA_5_8_W_DATA_6			GENMASK(15, 8)
> +#define REG_DATA_5_8_W_DATA_7			GENMASK(23, 16)
> +#define REG_DATA_5_8_W_DATA_8			GENMASK(31, 24)
> +
> +/*  DATA_9_12_W registers */
> +#define REG_DATA_9_12_W_DATA_9			GENMASK(7, 0)
> +#define REG_DATA_9_12_W_DATA_10			GENMASK(15, 8)
> +#define REG_DATA_9_12_W_DATA_11			GENMASK(23, 16)
> +#define REG_DATA_9_12_W_DATA_12			GENMASK(31, 24)
> +
> +/*  DATA_13_16_W registers */
> +#define REG_DATA_13_16_W_DATA_13		GENMASK(7, 0)
> +#define REG_DATA_13_16_W_DATA_14		GENMASK(15, 8)
> +#define REG_DATA_13_16_W_DATA_15		GENMASK(23, 16)
> +#define REG_DATA_13_16_W_DATA_16		GENMASK(31, 24)
> +
> +/*  DATA_17_20_W registers */
> +#define REG_DATA_17_20_W_DATA_17		GENMASK(7, 0)
> +#define REG_DATA_17_20_W_DATA_18		GENMASK(15, 8)
> +#define REG_DATA_17_20_W_DATA_19		GENMASK(23, 16)
> +#define REG_DATA_17_20_W_DATA_20		GENMASK(31, 24)
> +
> +/*  DATA_21_24_W registers */
> +#define REG_DATA_21_24_W_DATA_21		GENMASK(7, 0)
> +#define REG_DATA_21_24_W_DATA_22		GENMASK(15, 8)
> +#define REG_DATA_21_24_W_DATA_23		GENMASK(23, 16)
> +#define REG_DATA_21_24_W_DATA_24		GENMASK(31, 24)
> +
> +/*  DATA_25_28_W registers */
> +#define REG_DATA_25_28_W_DATA_25		GENMASK(7, 0)
> +#define REG_DATA_25_28_W_DATA_26		GENMASK(15, 8)
> +#define REG_DATA_25_28_W_DATA_27		GENMASK(23, 16)
> +#define REG_DATA_25_28_W_DATA_28		GENMASK(31, 24)
> +
> +/*  DATA_29_32_W registers */
> +#define REG_DATA_29_32_W_DATA_29		GENMASK(7, 0)
> +#define REG_DATA_29_32_W_DATA_30		GENMASK(15, 8)
> +#define REG_DATA_29_32_W_DATA_31		GENMASK(23, 16)
> +#define REG_DATA_29_32_W_DATA_32		GENMASK(31, 24)
> +
> +/*  DATA_33_36_W registers */
> +#define REG_DATA_33_36_W_DATA_33		GENMASK(7, 0)
> +#define REG_DATA_33_36_W_DATA_34		GENMASK(15, 8)
> +#define REG_DATA_33_36_W_DATA_35		GENMASK(23, 16)
> +#define REG_DATA_33_36_W_DATA_36		GENMASK(31, 24)
> +
> +/*  DATA_37_40_W registers */
> +#define REG_DATA_37_40_W_DATA_37		GENMASK(7, 0)
> +#define REG_DATA_37_40_W_DATA_38		GENMASK(15, 8)
> +#define REG_DATA_37_40_W_DATA_39		GENMASK(23, 16)
> +#define REG_DATA_37_40_W_DATA_40		GENMASK(31, 24)
> +
> +/*  DATA_41_44_W registers */
> +#define REG_DATA_41_44_W_DATA_41		GENMASK(7, 0)
> +#define REG_DATA_41_44_W_DATA_42		GENMASK(15, 8)
> +#define REG_DATA_41_44_W_DATA_43		GENMASK(23, 16)
> +#define REG_DATA_41_44_W_DATA_44		GENMASK(31, 24)
> +
> +/*  DATA_45_48_W registers */
> +#define REG_DATA_45_48_W_DATA_45		GENMASK(7, 0)
> +#define REG_DATA_45_48_W_DATA_46		GENMASK(15, 8)
> +#define REG_DATA_45_48_W_DATA_47		GENMASK(23, 16)
> +#define REG_DATA_45_48_W_DATA_48		GENMASK(31, 24)
> +
> +/*  DATA_49_52_W registers */
> +#define REG_DATA_49_52_W_DATA_49		GENMASK(7, 0)
> +#define REG_DATA_49_52_W_DATA_50		GENMASK(15, 8)
> +#define REG_DATA_49_52_W_DATA_51		GENMASK(23, 16)
> +#define REG_DATA_49_52_W_DATA_52		GENMASK(31, 24)
> +
> +/*  DATA_53_56_W registers */
> +#define REG_DATA_53_56_W_DATA_53		GENMASK(7, 0)
> +#define REG_DATA_53_56_W_DATA_56		GENMASK(15, 8)
> +#define REG_DATA_53_56_W_DATA_55		GENMASK(23, 16)
> +#define REG_DATA_53_56_W_DATA_54		GENMASK(31, 24)
> +
> +/*  DATA_57_60_W registers */
> +#define REG_DATA_57_60_W_DATA_57		GENMASK(7, 0)
> +#define REG_DATA_57_60_W_DATA_58		GENMASK(15, 8)
> +#define REG_DATA_57_60_W_DATA_59		GENMASK(23, 16)
> +#define REG_DATA_57_60_W_DATA_60		GENMASK(31, 24)
> +
> +/*  DATA_61_64_W registers */
> +#define REG_DATA_61_64_W_DATA_61		GENMASK(7, 0)
> +#define REG_DATA_61_64_W_DATA_62		GENMASK(15, 8)
> +#define REG_DATA_61_64_W_DATA_63		GENMASK(23, 16)
> +#define REG_DATA_61_64_W_DATA_64		GENMASK(31, 24)

Are those REG_DATA_xx_yy_W_DATA_yy used anywhere? If not, please remove.

> +/*  FRAME_TEST_W registers */
> +#define REG_FRAME_TEST_W_FSTC			BIT(0)
> +#define REG_FRAME_TEST_W_FCRC			BIT(1)
> +#define REG_FRAME_TEST_W_SDLC			BIT(2)
> +#define REG_FRAME_TEST_W_TPRM			GENMASK(12, 8)
> +
> +#endif
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
> new file mode 100644
> index 000000000000..5e8889e3a924
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
> @@ -0,0 +1,315 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +
> +#ifndef _LOONGSON_CANFD_KREGS_H
> +#define _LOONGSON_CANFD_KREGS_H
> +
> +#define LOONGSON_CANFD_DEVICE_ID	0x0	/* CANFD controller ID Register */
> +#define LOONGSON_CANFD_MODE		0x4	/* Mode Configuration Register */
> +#define LOONGSON_CANFD_CONF		0x8	/* Configure Register */
> +#define LOONGSON_CANFD_STAT		0xc	/* Status Register */
> +#define LOONGSON_CANFD_CMD		0x10	/* Command Register */
> +#define LOONGSON_CANFD_INT_STAT		0x14	/* Interrupt Status Register */
> +#define LOONGSON_CANFD_INT_ENA		0x18	/* Interrupt Enable Register */
> +#define LOONGSON_CANFD_INT_MASK		0x1c	/* Interrupt Mask Register */
> +#define LOONGSON_CANFD_BTR_NORM		0x20	/* Normal Rate Configuration Register */
> +#define LOONGSON_CANFD_BTR_FD		0x24	/* FD Data Rate Configuration Register */
> +#define LOONGSON_CANFD_ERL		0x28	/* Error Threshold Configuration Register */
> +#define LOONGSON_CANFD_FSTAT		0x2c	/* Error Status Register */
> +#define LOONGSON_CANFD_ERC		0x30	/* Error Count Register */
> +#define LOONGSON_CANFD_BRE		0x34	/* Rate Error Count Register */
> +#define LOONGSON_CANFD_CTR_PRES		0x38	/* Error Count Debug Register */
> +#define LOONGSON_CANFD_ERR_CAPT		0x3c	/* Error Capture Status Register */
> +#define LOONGSON_CANFD_RETX_CNT		0x40	/* Retransmission Count Register */
> +#define LOONGSON_CANFD_ALC		0x44	/* Lost Arbitration Capture Register */
> +#define LOONGSON_CANFD_TRV_DLY		0x48	/* Transmission Delay Measurement Register */
> +#define LOONGSON_CANFD_SSP_CFG		0x4c	/* Second Sampling Point Configuration Register */
> +#define LOONGSON_CANFD_RX_FR_CNT	0x50	/* Receive Message Count Register */
> +#define LOONGSON_CANFD_TX_FR_CNT	0x54	/* Transmit Message Count Register */
> +#define LOONGSON_CANFD_DEBUG		0x58	/* Debug Register */
> +#define LOONGSON_CANFD_TS		0x5c	/* Timestamp Register */
> +#define LOONGSON_CANFD_TX_FRM_TST	0x60	/* Transmit Message Debug Register */
> +#define LOONGSON_CANFD_FRC_DIV		0x64	/* Fractional Divider Ratio Register */
> +#define LOONGSON_CANFD_FLT_A_MASK	0x68	/* Filter A Mask Register */
> +#define LOONGSON_CANFD_FLT_A_VAL	0x6c	/* Filter A value Register */
> +#define LOONGSON_CANFD_FLT_B_MASK	0x70	/* Filter B Mask Register */
> +#define LOONGSON_CANFD_FLT_B_VAL	0x74	/* Filter B value Register */
> +#define LOONGSON_CANFD_FLT_C_MASK	0x78	/* Filter C Mask Register */
> +#define LOONGSON_CANFD_FLT_C_VAL	0x7c	/* Filter C value Register */
> +#define LOONGSON_CANFD_FLT_R_LOW	0x80	/* Range Filter Low Threshold Register */
> +#define LOONGSON_CANFD_FLT_R_HI		0x84	/* Range Filter High Threshold Register */
> +#define LOONGSON_CANFD_FLT_CTRL		0x88	/* Filter Control Register */
> +#define LOONGSON_CANFD_RX_MEM_INFO	0x8c	/* Receive Buffer Information Register */
> +#define LOONGSON_CANFD_RX_PRT		0x90	/* Receive Buffer Pointer Register */
> +#define LOONGSON_CANFD_RX_STAT		0x94	/* Receive Buffer Status Register */
> +#define LOONGSON_CANFD_RX_DATA		0x98	/* Receive Data Register */
> +#define LOONGSON_CANFD_TX_STAT		0x9c	/* Transmit Buffer Status Register */
> +#define LOONGSON_CANFD_TX_CMD		0xa0	/* Transmit Command Register */
> +#define LOONGSON_CANFD_TX_SEL		0xa4	/* Transmit Buffer Selection Register */
> +#define LOONGSON_CANFD_TX_DATA_1	0xb0
> +#define LOONGSON_CANFD_TX_DATA_2	0xb4
> +#define LOONGSON_CANFD_TX_DATA_3	0xb8
> +#define LOONGSON_CANFD_TX_DATA_4	0xbc
> +#define LOONGSON_CANFD_TX_DATA_5	0xc0
> +#define LOONGSON_CANFD_TX_DATA_6	0xc4
> +#define LOONGSON_CANFD_TX_DATA_7	0xc8
> +#define LOONGSON_CANFD_TX_DATA_8	0xcc
> +#define LOONGSON_CANFD_TX_DATA_9	0xd0
> +#define LOONGSON_CANFD_TX_DATA_10	0xd4
> +#define LOONGSON_CANFD_TX_DATA_11	0xd8
> +#define LOONGSON_CANFD_TX_DATA_12	0xdc
> +#define LOONGSON_CANFD_TX_DATA_13	0xe0
> +#define LOONGSON_CANFD_TX_DATA_14	0xe4
> +#define LOONGSON_CANFD_TX_DATA_15	0xe8
> +#define LOONGSON_CANFD_TX_DATA_16	0xec
> +#define LOONGSON_CANFD_TX_DATA_17	0xf0
> +#define LOONGSON_CANFD_TX_DATA_18	0xf4
> +
> +/* Bitfields of CANFD controller ID register */
> +#define REG_ID_ID			GENMASK(15, 0)
> +#define REG_ID_VER_MIN			GENMASK(23, 16)
> +#define REG_ID_VER_MAJ			GENMASK(31, 24)
> +
> +/* Bitfields of Mode Configuration register */
> +#define REG_MODE_RST			BIT(0)
> +#define REG_MODE_BMM			BIT(1)
> +#define REG_MODE_STM			BIT(2)
> +#define REG_MODE_AFM			BIT(3)
> +#define REG_MODE_FDE			BIT(4)
> +#define REG_MODE_TTTM			BIT(5)
> +#define REG_MODE_ROM			BIT(6)
> +#define REG_MODE_ACF			BIT(7)
> +#define REG_MODE_TSTM			BIT(8)
> +#define REG_MODE_RXBAM			BIT(9)
> +#define REG_MODE_ITSM			BIT(10)
> +#define REG_MODE_RTSOP			BIT(12)
> +#define REG_MODE_BUFM			BIT(13)
> +
> +/* Bitfields of Configure register */
> +#define REG_CONF_RTRLE			BIT(0)
> +#define REG_CONF_RTRTH			GENMASK(4, 1)
> +#define REG_CONF_ILBP			BIT(5)
> +#define REG_CONF_ENA			BIT(6)
> +#define REG_CONF_NISOFD			BIT(7)
> +#define REG_CONF_PEX			BIT(8)
> +#define REG_CONF_FDRF			BIT(10)
> +
> +/* Bitfields of Status register */
> +#define REG_STAT_RXNE			BIT(0)
> +#define REG_STAT_DOR			BIT(1)
> +#define REG_STAT_TXNF			BIT(2)
> +#define REG_STAT_EFT			BIT(3)
> +#define REG_STAT_RXS			BIT(4)
> +#define REG_STAT_TXS			BIT(5)
> +#define REG_STAT_EWL			BIT(6)
> +#define REG_STAT_IDLE			BIT(7)
> +#define REG_STAT_PEXS			BIT(8)
> +#define REG_STAT_STCNT			BIT(16)
> +
> +/* Bitfields of Command register */
> +#define REG_CMD_RXRPMV			BIT(1)
> +#define REG_CMD_RRB			BIT(2)
> +#define REG_CMD_CDO			BIT(3)
> +#define REG_CMD_ERCRST			BIT(4)
> +#define REG_CMD_RXFCRST			BIT(5)
> +#define REG_CMD_TXFCRST			BIT(6)
> +#define REG_CMD_CPEXS			BIT(7)
> +
> +/* Bitfields of Interrupt Status register */
> +#define REG_INT_STAT_RXI		BIT(0)
> +#define REG_INT_STAT_TXI		BIT(1)
> +#define REG_INT_STAT_EWLI		BIT(2)
> +#define REG_INT_STAT_DOI		BIT(3)
> +#define REG_INT_STAT_FCSI		BIT(4)
> +#define REG_INT_STAT_ALI		BIT(5)
> +#define REG_INT_STAT_BEI		BIT(6)
> +#define REG_INT_STAT_RXFI		BIT(7)
> +#define REG_INT_STAT_BSI		BIT(8)
> +#define REG_INT_STAT_RBNEI		BIT(9)
> +#define REG_INT_STAT_TXBHCI		BIT(10)
> +#define REG_INT_STAT_OFI		BIT(11)
> +#define REG_INT_STAT_DMADI		BIT(12)
> +
> +#define REG_INT_STAT_ERRORI	(REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI)
> +
> +/* Bitfields of Interrupt Enable register */
> +#define REG_INT_ENA_CLR			GENMASK(28, 16)
> +#define REG_INT_ENA_SET			GENMASK(12, 0)
> +
> +/* Bitfields of Interrupt Mask register */
> +#define REG_INT_MASK_SET		GENMASK(12, 0)
> +#define REG_INT_MASK_CLR		GENMASK(28, 16)
> +
> +/* Bitfields of Normal Rate Configuration register */
> +#define REG_BTR_PROP			GENMASK(6, 0)
> +#define REG_BTR_PH1			GENMASK(12, 7)
> +#define REG_BTR_PH2			GENMASK(18, 13)
> +#define REG_BTR_BRP			GENMASK(22, 19)
> +#define REG_BTR_SJW			GENMASK(31, 27)
> +
> +/* Bitfields of FD Data Rate Configuration register */
> +#define REG_BTR_FD_PROP			GENMASK(6, 0)
> +#define REG_BTR_FD_PH1			GENMASK(11, 7)
> +#define REG_BTR_FD_PH2			GENMASK(17, 13)
> +#define REG_BTR_FD_BRP			GENMASK(26, 19)
> +#define REG_BTR_FD_SJW			GENMASK(31, 27)
> +
> +/* Bitfields of Error Threshold Configuration register */
> +#define REG_ERL_ERP			GENMASK(7, 0)
> +#define REG_ERL_EW			GENMASK(23, 16)
> +
> +/* Bitfields of Error Status register */
> +#define REG_FSTAT_ERA			BIT(0)
> +#define REG_FSTAT_ERP			BIT(1)
> +#define REG_FSTAT_BOF			BIT(2)
> +
> +/* Bitfields of Error Count register */
> +#define REG_ERC_TEC			GENMASK(8, 0)
> +#define REG_ERC_REC			GENMASK(24, 16)
> +
> +/* Bitfields of Rate Error Count register */
> +#define REG_BRE_NORM			GENMASK(15, 0)
> +#define REG_BRE_FD_DATA			GENMASK(31, 16)
> +
> +/* Bitfields of Error Count Debug register */
> +#define REG_CTR_PRES_CTPV		GENMASK(8, 0)
> +#define REG_CTR_PRES_PTX		BIT(9)
> +#define REG_CTR_PRES_PRX		BIT(10)
> +
> +/* Bitfields of Error Capture Status register */
> +#define REG_ERR_CAPT_POS		GENMASK(4, 0)
> +#define REG_ERR_CAPT_TYPE		GENMASK(7, 5)
> +
> +/* Bitfields of Retransmission Count register */
> +#define REG_RETX_CNT_VAL		GENMASK(3, 0)
> +
> +/* Bitfields of Lost Arbitration Capture register */
> +#define REG_ALC_BIT_POS			GENMASK(4, 0)
> +#define REG_ALC_ID_FIELD		GENMASK(7, 5)
> +
> +/* Bitfields of Transmission Delay Measurement register */
> +#define REG_TRV_DLY_VAL			GENMASK(6, 0)
> +
> +/* Bitfields of Second Sampling Point Configuration register */
> +#define REG_SSP_CFG_OFF			GENMASK(7, 0)
> +#define REG_SSP_CFG_SRC			GENMASK(9, 8)
> +#define REG_SSP_CFG_SAT			BIT(10)
> +
> +/* Bitfields of Receive Message Count register */
> +#define REG_RX_FR_CNT_VAL		GENMASK(31, 0)
> +
> +/* Bitfields of Transmit Message Count register */
> +#define REG_TX_FR_CNT_VAL		GENMASK(31, 0)
> +
> +/* Bitfields of Debug register */
> +#define REG_DEBUG_STF_CNT		GENMASK(2, 0)
> +#define REG_DEBUG_DSTF_CNT		GENMASK(5, 3)
> +#define REG_DEBUG_PC_ARB		BIT(6)
> +#define REG_DEBUG_PC_CON		BIT(7)
> +#define REG_DEBUG_PC_DAT		BIT(8)
> +#define REG_DEBUG_PC_STC		BIT(9)
> +#define REG_DEBUG_PC_CRC		BIT(10)
> +#define REG_DEBUG_PC_CRCD		BIT(11)
> +#define REG_DEBUG_PC_ACK		BIT(12)
> +#define REG_DEBUG_PC_ACKD		BIT(13)
> +#define REG_DEBUG_PC_EOF		BIT(14)
> +#define REG_DEBUG_PC_INT		BIT(15)
> +#define REG_DEBUG_PC_SUSP		BIT(16)
> +#define REG_DEBUG_PC_OVR		BIT(17)
> +#define REG_DEBUG_PC_SOF		BIT(18)
> +
> +/* Bitfields of Timestamp register */
> +#define REG_TS_TIMESTAMP		GENMASK(15, 0)
> +#define REG_TS_PSC			GENMASK(24, 16)
> +
> +/* Bitfields of Fractional Divider Ratio register */
> +#define REG_FRC_FRC_DBT			GENMASK(15, 8)
> +#define REG_FRC_FRC_NBT			GENMASK(7, 0)
> +
> +/* Bitfields of Filter A Mask register */
> +#define REG_FIL_A_MASK			GENMASK(28, 0)
> +
> +/* Bitfields of Filter A value register */
> +#define REG_FIL_A_VAL			GENMASK(28, 0)
> +
> +/* Bitfields of Filter B Mask register */
> +#define REG_FIL_B_MASK			GENMASK(28, 0)
> +
> +/* Bitfields of Filter B value register */
> +#define REG_FIL_B_VAL			GENMASK(28, 0)
> +
> +/* Bitfields of Filter C Mask register */
> +#define REG_FIL_C_MASK			GENMASK(28, 0)
> +
> +/* Bitfields of Filter C value register */
> +#define REG_FIL_C_VAL			GENMASK(28, 0)
> +
> +/* Bitfields of Range Filter Low Threshold register */
> +#define REG_FIL_R_LOW_VAL		GENMASK(28, 0)
> +
> +/* Bitfields of Range Filter High Threshold register */
> +#define REG_FIL_R_HI_VAL		GENMASK(28, 0)
> +
> +/* Bitfields of Filter Control register */
> +#define REG_FIL_CTRL_FANB		BIT(0)
> +#define REG_FIL_CTRL_FANE		BIT(1)
> +#define REG_FIL_CTRL_FAFB		BIT(2)
> +#define REG_FIL_CTRL_FAFE		BIT(3)
> +#define REG_FIL_CTRL_FBNB		BIT(4)
> +#define REG_FIL_CTRL_FBNE		BIT(5)
> +#define REG_FIL_CTRL_FBFB		BIT(6)
> +#define REG_FIL_CTRL_FBFE		BIT(7)
> +#define REG_FIL_CTRL_FCNB		BIT(8)
> +#define REG_FIL_CTRL_FCNE		BIT(9)
> +#define REG_FIL_CTRL_FCFB		BIT(10)
> +#define REG_FIL_CTRL_FCFE		BIT(11)
> +#define REG_FIL_CTRL_FRNB		BIT(12)
> +#define REG_FIL_CTRL_FRNE		BIT(13)
> +#define REG_FIL_CTRL_FRFB		BIT(14)
> +#define REG_FIL_CTRL_FRFE		BIT(15)
> +#define REG_FIL_CTRL_SFA		BIT(16)
> +#define REG_FIL_CTRL_SFB		BIT(17)
> +#define REG_FIL_CTRL_SFC		BIT(18)
> +#define REG_FIL_CTRL_SFR		BIT(19)
> +
> +/* Bitfields of Receive Buffer Information register */
> +#define REG_RX_MEM_INFO_BUFF_SIZE	GENMASK(12, 0)
> +#define REG_RX_MEM_INFO_MEM_FREE	GENMASK(28, 16)
> +
> +/* Bitfields of Receive Buffer Pointer register */
> +#define REG_RX_PTR_WPP			GENMASK(11, 0)
> +#define REG_RX_PTR_RPP			GENMASK(27, 16)
> +
> +/* Bitfields of Receive Buffer Status register */
> +#define REG_RX_STAT_RXE			BIT(0)
> +#define REG_RX_STAT_RXF			BIT(1)
> +#define REG_RX_STAT_RXMOF		BIT(2)
> +#define REG_RX_STAT_RXFRC		GENMASK(14, 4)
> +#define REG_RX_STAT_RTSOP		BIT(16)
> +
> +/* Bitfields of Receive Data register */
> +#define REG_RX_DATA_VAL			GENMASK(31, 0)
> +
> +/* Bitfields of Transmit Buffer Status register */
> +#define REG_TX_STAT_BRP			GENMASK(7, 0)
> +#define REG_TX_STAT_TXS			GENMASK(10, 8)
> +#define REG_TX_STAT_BS			GENMASK(31, 16)
> +
> +#define REG_TX_STAT_BS_TX1		GENMASK(17, 16)
> +#define REG_TX_STAT_BS_TX2		GENMASK(19, 18)
> +#define REG_TX_STAT_BS_TX3		GENMASK(21, 20)
> +#define REG_TX_STAT_BS_TX4		GENMASK(23, 22)
> +#define REG_TX_STAT_BS_TX5		GENMASK(25, 24)
> +#define REG_TX_STAT_BS_TX6		GENMASK(27, 26)
> +#define REG_TX_STAT_BS_TX7		GENMASK(29, 28)
> +#define REG_TX_STAT_BS_TX8		GENMASK(31, 30)
> +
> +/* Bitfields of Transmit Command register */
> +#define REG_TX_CMD_BAR			GENMASK(7, 0)
> +#define REG_TX_CMD_BCR			GENMASK(15, 8)
> +#define REG_TX_CMD_BSC			GENMASK(23, 16)
> +
> +/* Bitfields of Transmit Buffer Selection register */
> +#define REG_TX_SEL_BUF_SEL		GENMASK(3, 0)
> +#define REG_TX_SEL_BUF_CNT		GENMASK(7, 4)
> +
> +#endif


Yours sincerely,
Vincent Mailhol


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] can: loongson_canfd: Add RXDMA support
  2026-04-27  7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
@ 2026-05-06 17:51   ` Vincent Mailhol
  0 siblings, 0 replies; 5+ messages in thread
From: Vincent Mailhol @ 2026-05-06 17:51 UTC (permalink / raw)
  To: Binbin Zhou, Binbin Zhou, Huacai Chen, Marc Kleine-Budde,
	Bingxiong Li
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai

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


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-05-06 17:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox