Skip to contentSkip to conversation

Add Loongson CAN-FD controller driver

PATCH Superseded Binbin Zhou 3 participants 14 messages Apr 27 – Jun 5

Subsystems (2)

v1 of 9 Superseded Latest is v9 →

lore
Share & tools
lore
#0 lore raw

cover Add Loongson CAN-FD controller driver

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

Signature · 1 line
2.52.0
#1 lore raw

PATCH 1/2 can: Add Loongson CAN-FD controller support

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.

---
 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
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:	arch/loongarch/
 F:	drivers/*/*loongarch*
 F:	drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:	Bingxiong Li <[email protected]>
+M:	Binbin Zhou <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:	Yinbo Zhu <[email protected]>
 L:	[email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +16 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -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/)
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1159 -0

1,159 lines not on this page · raw message

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +142 -0

142 lines not on this page · raw message

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +315 -0

315 lines not on this page · raw message

Signature · 1 line
2.52.0
maintainer
#2 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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.

Quoted 52 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:	arch/loongarch/
 F:	drivers/*/*loongarch*
 F:	drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:	Bingxiong Li <[email protected]>
+M:	Binbin Zhou <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:	Yinbo Zhu <[email protected]>
 L:	[email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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.

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Quoted 58 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+	.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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+	.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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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?

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+	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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 14 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +14 -0
+}
+
+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)

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+	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().

Quoted 98 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +98 -0
+	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;

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+		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())

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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…

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+	/*
+	 * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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?

Quoted 146 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+	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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 438

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+	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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 9 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+		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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+		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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+	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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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) {
Quoted 49 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +49 -0
+			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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+	/* 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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+	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.

Quoted 55 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +55 -0
+	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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+	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().

Quoted 23 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+	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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+			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] = /* ... */

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+					      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)?

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 2 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+				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).

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+			}
+			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;

drivers/net/can/loongson_canfd/loongson_canfd.c line 672

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+			}
+			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.

Quoted 26 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +26 -0
+			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++;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+		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.

Quoted 65 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+		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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+	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) {
			/* ... */
		}
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+		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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+				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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+		}
+
+		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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

Quoted 318 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +305 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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)
+

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +10 -0
+/* 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

#3 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Hi Vincent:

Thanks so much for your thorough review.

Quoted 41 lines · Vincent Mailhol

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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
Quoted 6 lines

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.

Emm, I’ve thought about that before, but wouldn’t combining all the code into a single file make it too large and difficult to read?

Quoted 68 lines
Quoted 53 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:   arch/loongarch/
 F:   drivers/*/*loongarch*
 F:   drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:   Bingxiong Li <[email protected]>
+M:   Binbin Zhou <[email protected]>
+L:   [email protected]
+S:   Maintained
+F:   drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:   Yinbo Zhu <[email protected]>
 L:   [email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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:

Quoted 8 lines

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

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Emm, I will rewrite this part.

Quoted 109 lines
Quoted 59 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+     .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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+     .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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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)
                              ^^^^^
Quoted 1 line

What is the meaning of txtnf?

It means tx text not free.
Maybe I should add some comments and rename it to
loongson_canfd_txbuf_freed().

Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 7 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+     u32 sts;
+
+     regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+     return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
                                                  ^^^^
Quoted 7 lines

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);

ok, I’ll check the others as well.

Quoted 41 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 18 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +18 -0
+}
+
+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;
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+     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().

This seems a bit complicated. Here, we check whether the bit corresponding to the input ID is 0. The loongson_canfd_get_bs() function mentioned above works similarly, though the bs bit field is 2 bits wide.
I’ll give it another try.

Quoted 104 lines
Quoted 104 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +103 -0
+     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;
+             }
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+             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())

OK, I will rewrite this part.

Quoted 24 lines
Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

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

and so on…

Ok, I’ll check other similar sections of the driver.

Quoted 19 lines
Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+     /*
+      * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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);
                                            ^^^
Quoted 1 line

What is this magic number?

This is the retransmission threshold, indicating the upper limit for retransmissions.

Quoted 160 lines
Quoted 147 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 1 line
        guard(spinlock_irqsave)(&priv->tx_lock);

Emm, it seem scoped_guard(spinlock_irqsave)(&priv->tx_lock) instead.

Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 438

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     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.

No, here we’re trying to determine if tx_buf is idle. Each bit of tx_brp represents a buffer, while every two bits of tx_bs represent a transmission completion record for a buffer. If both are 0, it indicates that the channel is idle.

Quoted 61 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+             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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+             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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+     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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 2 lines
                if (meta1 & REG_FRAME_FORMAT_W_BRS) {

ok, I’ll check the others as well.

Quoted 53 lines
Quoted 53 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +53 -0
+                     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;
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+
+     /* 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.

ok.

Quoted 67 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+     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.

Quoted 60 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +60 -0
+     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",
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+                         __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().

I will keep them as netdev_debug().

Quoted 43 lines
Quoted 24 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+                     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] = /* ... */
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+                                           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)?

I'm sorry, but I don't quite understand what you mean by “mixing.”

As I understand it, CAN_STATE_ERROR_PASSIVE is set in loongson_canfd_read_fault_state() when REG_FSTAT_ERP is set. This is determined by the rxerr and txerr values obtained in loongson_canfd_get_bec()—whichever value reaches 127 first determines which passive state is displayed at the user level.

Quoted 28 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+                             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).

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                     }
+                     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;

drivers/net/can/loongson_canfd/loongson_canfd.c line 672

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                     }
+                     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));
Quoted 1 line

Don't use netdev_warn() for that.

netdev_err() replace.

Quoted 32 lines
Quoted 32 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+                     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;
+             }
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     }
+
+     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.

ok.

Quoted 932 lines
Quoted 66 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+             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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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) {
                        /* ... */
                }
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+             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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                             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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+             }
+
+             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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

Quoted 324 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +311 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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 */
Quoted 9 lines

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +4 -0
+#define REG_TX_SEL_BUF_SEL           GENMASK(3, 0)
+#define REG_TX_SEL_BUF_CNT           GENMASK(7, 4)
+
+#endif

Yours sincerely,
Vincent Mailhol

--
Thanks.
Binbin

maintainer
#4 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Quoted 49 lines · Binbin Zhou

Hi Vincent:

Thanks so much for your thorough review.

Quoted 44 lines · Vincent Mailhol

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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

Quoted 6 lines
Quoted 3 lines

drivers/net/can/loongson_canfd.c

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

Emm, I’ve thought about that before, but wouldn’t combining all the code into a single file make it too large and difficult to read?

If you look around at what the other drivers are doing, you will see that it is not common to have a single .c file with several local headers.

If your driver becomes more complex, you are welcome to split in in several .c files and add the headers as needed.

About your concern that the file may become too large, please refer to one of my other comment in which I asked you if all your #define were really needed (I didn't get an answer on that comment by the way). Once the remove the #define which you are not using, I don't think to resulting file will be too big.

Quoted 188 lines
Quoted 67 lines
Quoted 52 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:   arch/loongarch/
 F:   drivers/*/*loongarch*
 F:   drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:   Bingxiong Li <[email protected]>
+M:   Binbin Zhou <[email protected]>
+L:   [email protected]
+S:   Maintained
+F:   drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:   Yinbo Zhu <[email protected]>
 L:   [email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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:

Quoted 8 lines

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

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Emm, I will rewrite this part.

Quoted 109 lines
Quoted 59 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+     .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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+     .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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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)
                              ^^^^^
Quoted 1 line

What is the meaning of txtnf?

Quoted 3 lines

It means tx text not free.
Maybe I should add some comments and rename it to
loongson_canfd_txbuf_freed().

Yes, that's better. Try to avoid acronyms which can be too cryptic.

Quoted 66 lines
Quoted 7 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+     u32 sts;
+
+     regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+     return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
                                                  ^^^^
Quoted 7 lines

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);

ok, I’ll check the others as well.

Quoted 41 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 18 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +18 -0
+}
+
+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;
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+     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().

Quoted 5 lines

This seems a bit complicated. Here, we check whether the bit corresponding to the input ID is 0. The loongson_canfd_get_bs() function mentioned above works similarly, though the bs bit field is 2 bits wide.
I’ll give it another try.

I didn't try to understand the full logic (I am a bit lost with the weird use of FIELD_GET), but if bs is not yet in the good format, you may just have one operation to modify bs as needed and then pass it to regmap_test_bits().

Quoted 167 lines
Quoted 103 lines
Quoted 103 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +103 -0
+     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;
+             }
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+             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())

OK, I will rewrite this part.

Quoted 24 lines
Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

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

and so on…

Ok, I’ll check other similar sections of the driver.

Quoted 19 lines
Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+     /*
+      * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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);
                                            ^^^
Quoted 1 line

What is this magic number?

Quoted 2 lines

This is the retransmission threshold, indicating the upper limit for retransmissions.

OK. Add a macro to give a memorable name to that mask.

Generally speaking, try to avoid the magic numbers.

Quoted 159 lines
Quoted 159 lines
Quoted 146 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 3 lines
Quoted 1 line
        guard(spinlock_irqsave)(&priv->tx_lock);

Emm, it seem scoped_guard(spinlock_irqsave)(&priv->tx_lock) instead.

The scope_guard() is good when you want to release the lock before the end of your function.

The guard() will automatically release the lock when your function returns.

Use which one you think is the best, but at first glance, the guard() looked more appropriate to me here.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     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++) {
Quoted 6 lines
Quoted 1 line

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

No, here we’re trying to determine if tx_buf is idle. Each bit of tx_brp represents a buffer, while every two bits of tx_bs represent a transmission completion record for a buffer. If both are 0, it indicates that the channel is idle.

I am not sure if I made my point clear. What I have in mind is:

	for_each_clear_bit(unsigned int i = 0; tx_brp; LOONGSON_CANFD_TXBUF_NUM) {
		u32 transmission_complete_mask = 0x3 << (i * 2);

		if (!(tx_bs & transmission_complete_mask)) {
			txtb_id = i;
			break;
		}
	}

Isn't this equivalent to your code (but more readable)?

Quoted 252 lines
Quoted 60 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 9 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+             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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+             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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+     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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 2 lines
                if (meta1 & REG_FRAME_FORMAT_W_BRS) {

ok, I’ll check the others as well.

Quoted 53 lines
Quoted 53 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +53 -0
+                     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;
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+
+     /* 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.

ok.

Quoted 67 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+     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.

Quoted 60 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +60 -0
+     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",
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+                         __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().

I will keep them as netdev_debug().

Quoted 43 lines
Quoted 24 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+                     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] = /* ... */
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+                                           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)?

Quoted 7 lines

I'm sorry, but I don't quite understand what you mean by “mixing.”

As I understand it, CAN_STATE_ERROR_PASSIVE is set in loongson_canfd_read_fault_state() when REG_FSTAT_ERP is set. This is determined by the rxerr and txerr values obtained in loongson_canfd_get_bec()—whichever value reaches 127 first determines which passive state is displayed at the user level.

Ack. On second read, this looks correct.

Quoted 27 lines
Quoted 27 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 2 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+                             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).

Quoted 20 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +20 -0
+                     }
+                     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));
Quoted 3 lines
Quoted 1 line

Don't use netdev_warn() for that.

netdev_err() replace.

My bad, I was only looking at the message text which say "error state" and I was thinking that you were spamming the kernel log each time the device reported an error state. So my comment was more to ask you to reduce severity. But here, you goal is to catch a programming mistake. So the netdev_warn() was indeed appropriate.

Looking at the different states, the only state you are not trying in you enum is CAN_STATE_SLEEPING, which arguably is not an error state. So maybe this message would be more relevant:

	netdev_warn(ndev, "Unexpected state: %d, %s!\n", state,
		    can_get_state_str(state));

Also please avoid the use of parenthesis arround numbers, c.f. the Linux coding style.

Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+                     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;
+             }
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     }
+
+     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.

Quoted 608 lines

ok.

Quoted 607 lines
Quoted 66 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+             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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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) {
                        /* ... */
                }
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+             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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                             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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+             }
+
+             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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

This is the comment I was refering to when arguing that your driver should be in a single .c file.

(also, there are some other comments which you didn't reply to. Make sure to double check that you didn't miss anything before sending the v2).

Quoted 320 lines
Quoted 320 lines
Quoted 320 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +307 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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)
Quoted 8 lines
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +8 -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

#5 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Quoted 62 lines · Vincent Mailhol
Quoted 49 lines · Binbin Zhou

Hi Vincent:

Thanks so much for your thorough review.

Quoted 44 lines · Vincent Mailhol

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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

Quoted 6 lines
Quoted 3 lines

drivers/net/can/loongson_canfd.c

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

Emm, I’ve thought about that before, but wouldn’t combining all the code into a single file make it too large and difficult to read?

If you look around at what the other drivers are doing, you will see that it is not common to have a single .c file with several local headers.

Quoted 8 lines

If your driver becomes more complex, you are welcome to split in in several .c files and add the headers as needed.

About your concern that the file may become too large, please refer to one of my other comment in which I asked you if all your #define were really needed (I didn't get an answer on that comment by the way). Once the remove the #define which you are not using, I don't think to resulting file will be too big.

ok. I will put it in one file. About 1770 lines.

Quoted 267 lines
Quoted 189 lines
Quoted 68 lines
Quoted 53 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:   arch/loongarch/
 F:   drivers/*/*loongarch*
 F:   drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:   Bingxiong Li <[email protected]>
+M:   Binbin Zhou <[email protected]>
+L:   [email protected]
+S:   Maintained
+F:   drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:   Yinbo Zhu <[email protected]>
 L:   [email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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:

Quoted 8 lines

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

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Emm, I will rewrite this part.

Quoted 109 lines
Quoted 59 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+     .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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+     .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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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)
                              ^^^^^
Quoted 1 line

What is the meaning of txtnf?

Quoted 3 lines

It means tx text not free.
Maybe I should add some comments and rename it to
loongson_canfd_txbuf_freed().

Yes, that's better. Try to avoid acronyms which can be too cryptic.

Quoted 66 lines
Quoted 7 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+     u32 sts;
+
+     regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+     return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
                                                  ^^^^
Quoted 7 lines

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);

ok, I’ll check the others as well.

Quoted 41 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 18 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +18 -0
+}
+
+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;
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+     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().

Quoted 5 lines

This seems a bit complicated. Here, we check whether the bit corresponding to the input ID is 0. The loongson_canfd_get_bs() function mentioned above works similarly, though the bs bit field is 2 bits wide.
I’ll give it another try.

Quoted 4 lines

I didn't try to understand the full logic (I am a bit lost with the weird use of FIELD_GET), but if bs is not yet in the good format, you may just have one operation to modify bs as needed and then pass it to regmap_test_bits().

About this part, I have tried 2 ways:

One way:

loongson_canfd_get_tx_status():

        regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
        bs = FIELD_GET(REG_TX_STAT_BS, sts);

        return (bs >> (buf_id * 2)) & 0x3;

loongson_canfd_is_txtbuf_writable():

        regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
        if ((sts >> buf_id) & 0x1)
                return false;

        return true;

The other way, use marcos and regmap_test_bits()

loongson_canfd_get_tx_status():

        switch (buf_id) {
        case 0:
                mask = REG_TX_STAT_BS_TX0;
                break;
        case 1:
                mask = REG_TX_STAT_BS_TX1;
                break;
        case 2:
                mask = REG_TX_STAT_BS_TX2;
                break;
        case 3:
                mask = REG_TX_STAT_BS_TX3;
                break;
        case 4:
                mask = REG_TX_STAT_BS_TX4;
                break;
        case 5:
                mask = REG_TX_STAT_BS_TX5;
                break;
        case 6:
                mask = REG_TX_STAT_BS_TX6;
                break;
        case 7:
                mask = REG_TX_STAT_BS_TX7;
                break;
        default:
                netdev_err(ndev, "Illegal buf id: %x\n", buf_id);
                return;
        }

        regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);

        return field_get(mask, sts);
Quoted 1,664 lines
Quoted 168 lines
Quoted 104 lines
Quoted 104 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +103 -0
+     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;
+             }
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+             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())

OK, I will rewrite this part.

Quoted 24 lines
Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

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

and so on…

Ok, I’ll check other similar sections of the driver.

Quoted 19 lines
Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+     /*
+      * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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);
                                            ^^^
Quoted 1 line

What is this magic number?

Quoted 2 lines

This is the retransmission threshold, indicating the upper limit for retransmissions.

OK. Add a macro to give a memorable name to that mask.

Generally speaking, try to avoid the magic numbers.

Quoted 159 lines
Quoted 159 lines
Quoted 146 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 3 lines
Quoted 1 line
        guard(spinlock_irqsave)(&priv->tx_lock);

Emm, it seem scoped_guard(spinlock_irqsave)(&priv->tx_lock) instead.

The scope_guard() is good when you want to release the lock before the end of your function.

The guard() will automatically release the lock when your function returns.

Use which one you think is the best, but at first glance, the guard() looked more appropriate to me here.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     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++) {
Quoted 6 lines
Quoted 1 line

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

No, here we’re trying to determine if tx_buf is idle. Each bit of tx_brp represents a buffer, while every two bits of tx_bs represent a transmission completion record for a buffer. If both are 0, it indicates that the channel is idle.

I am not sure if I made my point clear. What I have in mind is:

        for_each_clear_bit(unsigned int i = 0; tx_brp; LOONGSON_CANFD_TXBUF_NUM) {
                u32 transmission_complete_mask = 0x3 << (i * 2);

                if (!(tx_bs & transmission_complete_mask)) {
                        txtb_id = i;
                        break;
                }
        }

Isn't this equivalent to your code (but more readable)?

Quoted 252 lines
Quoted 60 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 9 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+             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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+             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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+     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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 2 lines
                if (meta1 & REG_FRAME_FORMAT_W_BRS) {

ok, I’ll check the others as well.

Quoted 53 lines
Quoted 53 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +53 -0
+                     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;
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+
+     /* 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.

ok.

Quoted 67 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+     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.

Quoted 60 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +60 -0
+     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",
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+                         __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().

I will keep them as netdev_debug().

Quoted 43 lines
Quoted 24 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+                     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] = /* ... */
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+                                           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)?

Quoted 7 lines

I'm sorry, but I don't quite understand what you mean by “mixing.”

As I understand it, CAN_STATE_ERROR_PASSIVE is set in loongson_canfd_read_fault_state() when REG_FSTAT_ERP is set. This is determined by the rxerr and txerr values obtained in loongson_canfd_get_bec()—whichever value reaches 127 first determines which passive state is displayed at the user level.

Ack. On second read, this looks correct.

Quoted 27 lines
Quoted 27 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 2 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+                             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).

Quoted 20 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +20 -0
+                     }
+                     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));
Quoted 3 lines
Quoted 1 line

Don't use netdev_warn() for that.

netdev_err() replace.

My bad, I was only looking at the message text which say "error state" and I was thinking that you were spamming the kernel log each time the device reported an error state. So my comment was more to ask you to reduce severity. But here, you goal is to catch a programming mistake. So the netdev_warn() was indeed appropriate.

Looking at the different states, the only state you are not trying in you enum is CAN_STATE_SLEEPING, which arguably is not an error state. So maybe this message would be more relevant:

netdev_warn(ndev, "Unexpected state: %d, %s!\n", state,
can_get_state_str(state));

Also please avoid the use of parenthesis arround numbers, c.f. the Linux coding style.

Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+                     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;
+             }
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     }
+
+     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.

Quoted 608 lines

ok.

Quoted 607 lines
Quoted 66 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+             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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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) {
                        /* ... */
                }
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+             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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                             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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+             }
+
+             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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

This is the comment I was refering to when arguing that your driver should be in a single .c file.

(also, there are some other comments which you didn't reply to. Make sure to double check that you didn't miss anything before sending the v2).

Quoted 323 lines
Quoted 323 lines
Quoted 323 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +310 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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)
+
Quoted 9 lines
Quoted 5 lines
Quoted 5 lines

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +5 -0
+/* 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

--
Thanks.
Binbin

#6 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

In reply to Vincent Mailhol

Hi Vincent:

Sorry, please disregard the previous email; I sent it by mistake.

Quoted 62 lines · Vincent Mailhol
Quoted 49 lines · Binbin Zhou

Hi Vincent:

Thanks so much for your thorough review.

Quoted 44 lines · Vincent Mailhol

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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

Quoted 6 lines
Quoted 3 lines

drivers/net/can/loongson_canfd.c

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

Emm, I’ve thought about that before, but wouldn’t combining all the code into a single file make it too large and difficult to read?

If you look around at what the other drivers are doing, you will see that it is not common to have a single .c file with several local headers.

Quoted 8 lines

If your driver becomes more complex, you are welcome to split in in several .c files and add the headers as needed.

About your concern that the file may become too large, please refer to one of my other comment in which I asked you if all your #define were really needed (I didn't get an answer on that comment by the way). Once the remove the #define which you are not using, I don't think to resulting file will be too big.

ok. I will put all in one file. About 1770 lines.

Quoted 267 lines
Quoted 189 lines
Quoted 68 lines
Quoted 53 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:   arch/loongarch/
 F:   drivers/*/*loongarch*
 F:   drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:   Bingxiong Li <[email protected]>
+M:   Binbin Zhou <[email protected]>
+L:   [email protected]
+S:   Maintained
+F:   drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:   Yinbo Zhu <[email protected]>
 L:   [email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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:

Quoted 8 lines

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

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Emm, I will rewrite this part.

Quoted 109 lines
Quoted 59 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+     .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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+     .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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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)
                              ^^^^^
Quoted 1 line

What is the meaning of txtnf?

Quoted 3 lines

It means tx text not free.
Maybe I should add some comments and rename it to
loongson_canfd_txbuf_freed().

Yes, that's better. Try to avoid acronyms which can be too cryptic.

Quoted 66 lines
Quoted 7 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+     u32 sts;
+
+     regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+     return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
                                                  ^^^^
Quoted 7 lines

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);

ok, I’ll check the others as well.

Quoted 41 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 18 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +18 -0
+}
+
+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;
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+     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().

Quoted 5 lines

This seems a bit complicated. Here, we check whether the bit corresponding to the input ID is 0. The loongson_canfd_get_bs() function mentioned above works similarly, though the bs bit field is 2 bits wide.
I’ll give it another try.

Quoted 4 lines

I didn't try to understand the full logic (I am a bit lost with the weird use of FIELD_GET), but if bs is not yet in the good format, you may just have one operation to modify bs as needed and then pass it to regmap_test_bits().

I tried again. How about this:

loongson_canfd_get_tx_status():

        switch (buf_id) {
        case 0:
                mask = REG_TX_STAT_BS_TX0;
                break;
        case 1:
                mask = REG_TX_STAT_BS_TX1;
                break;
        case 2:
                mask = REG_TX_STAT_BS_TX2;
                break;
        case 3:
                mask = REG_TX_STAT_BS_TX3;
                break;
        case 4:
                mask = REG_TX_STAT_BS_TX4;
                break;
        case 5:
                mask = REG_TX_STAT_BS_TX5;
                break;
        case 6:
                mask = REG_TX_STAT_BS_TX6;
                break;
        case 7:
                mask = REG_TX_STAT_BS_TX7;
                break;
        default:
                netdev_err(ndev, "Illegal buf id: %x\n", buf_id);
                return;
        }

        regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);

        return field_get(mask, sts);

loongson_canfd_is_txtbuf_writable():

       !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, BIT(buf_id));
Quoted 168 lines
Quoted 168 lines
Quoted 104 lines
Quoted 104 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +103 -0
+     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;
+             }
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+             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())

OK, I will rewrite this part.

Quoted 24 lines
Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

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

and so on…

Ok, I’ll check other similar sections of the driver.

Quoted 20 lines
Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+     /*
+      * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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?

Quoted 4 lines
Quoted 2 lines

This is the retransmission threshold, indicating the upper limit for retransmissions.

OK. Add a macro to give a memorable name to that mask.

ok. I will do it.

Quoted 166 lines

Generally speaking, try to avoid the magic numbers.

Quoted 159 lines
Quoted 159 lines
Quoted 146 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 3 lines
Quoted 1 line
        guard(spinlock_irqsave)(&priv->tx_lock);

Emm, it seem scoped_guard(spinlock_irqsave)(&priv->tx_lock) instead.

Quoted 8 lines

The scope_guard() is good when you want to release the lock before the end of your function.

The guard() will automatically release the lock when your function returns.

Use which one you think is the best, but at first glance, the guard() looked more appropriate to me here.

Emm, indeed, guard() is enough.

Quoted 25 lines
Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     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++) {
Quoted 6 lines
Quoted 1 line

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

No, here we’re trying to determine if tx_buf is idle. Each bit of tx_brp represents a buffer, while every two bits of tx_bs represent a transmission completion record for a buffer. If both are 0, it indicates that the channel is idle.

I am not sure if I made my point clear. What I have in mind is:

        for_each_clear_bit(unsigned int i = 0; tx_brp; LOONGSON_CANFD_TXBUF_NUM) {
                u32 transmission_complete_mask = 0x3 << (i * 2);

                if (!(tx_bs & transmission_complete_mask)) {
                        txtb_id = i;
                        break;
                }
        }
Quoted 1 line

Isn't this equivalent to your code (but more readable)?

ok, I will do it.

Quoted 304 lines
Quoted 253 lines
Quoted 61 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+             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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+             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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+     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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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:

Quoted 2 lines
                if (meta1 & REG_FRAME_FORMAT_W_BRS) {

ok, I’ll check the others as well.

Quoted 53 lines
Quoted 53 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +53 -0
+                     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;
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+
+     /* 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.

ok.

Quoted 67 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+     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.

Quoted 60 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +60 -0
+     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",
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+                         __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().

I will keep them as netdev_debug().

Quoted 43 lines
Quoted 24 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+                     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] = /* ... */
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+                                           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)?

Quoted 7 lines

I'm sorry, but I don't quite understand what you mean by “mixing.”

As I understand it, CAN_STATE_ERROR_PASSIVE is set in loongson_canfd_read_fault_state() when REG_FSTAT_ERP is set. This is determined by the rxerr and txerr values obtained in loongson_canfd_get_bec()—whichever value reaches 127 first determines which passive state is displayed at the user level.

Ack. On second read, this looks correct.

Quoted 27 lines
Quoted 27 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 2 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+                             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).

Quoted 20 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +20 -0
+                     }
+                     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));
Quoted 3 lines
Quoted 1 line

Don't use netdev_warn() for that.

netdev_err() replace.

My bad, I was only looking at the message text which say "error state" and I was thinking that you were spamming the kernel log each time the device reported an error state. So my comment was more to ask you to reduce severity. But here, you goal is to catch a programming mistake. So the netdev_warn() was indeed appropriate.

Looking at the different states, the only state you are not trying in you enum is CAN_STATE_SLEEPING, which arguably is not an error state. So maybe this message would be more relevant:

Quoted 7 lines

netdev_warn(ndev, "Unexpected state: %d, %s!\n", state,
can_get_state_str(state));

Also please avoid the use of parenthesis arround numbers, c.f. the Linux coding style.

ok, I will check others.

Quoted 981 lines
Quoted 41 lines
Quoted 32 lines
Quoted 32 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+                     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;
+             }
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     }
+
+     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.

Quoted 608 lines

ok.

Quoted 607 lines
Quoted 66 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+             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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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) {
                        /* ... */
                }
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+             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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                             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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+             }
+
+             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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

This is the comment I was refering to when arguing that your driver should be in a single .c file.

(also, there are some other comments which you didn't reply to. Make sure to double check that you didn't miss anything before sending the v2).

Quoted 324 lines
Quoted 324 lines
Quoted 324 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +311 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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 */
Quoted 8 lines
Quoted 4 lines
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +4 -0
+#define REG_TX_SEL_BUF_SEL           GENMASK(3, 0)
+#define REG_TX_SEL_BUF_CNT           GENMASK(7, 4)
+
+#endif

Yours sincerely,
Vincent Mailhol

--
Thanks.
Binbin

#7 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

In reply to Vincent Mailhol

Hi Vincent:

Quoted 401 lines · Vincent Mailhol

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.

Quoted 17 lines · 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.

Quoted 15 lines
---
 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.

Quoted 52 lines
MAINTAINERS +7 -0
index 7a2ffd9d37d5..5534db894cdc 100644
@@ -14935,6 +14935,13 @@ F:   arch/loongarch/
 F:   drivers/*/*loongarch*
 F:   drivers/cpufreq/loongson3_cpufreq.c
 
+LOONGSON CAN FD DRIVER
+M:   Bingxiong Li <[email protected]>
+M:   Binbin Zhou <[email protected]>
+L:   [email protected]
+S:   Maintained
+F:   drivers/net/can/loongson_canfd/
+
 LOONGSON GPIO DRIVER
 M:   Yinbo Zhu <[email protected]>
 L:   [email protected]
drivers/net/can/Kconfig +1 -0
index e15e320db476..1306d2d80197 100644
@@ -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"
drivers/net/can/Makefile +1 -0
index d7bc10a6b8ea..a39e26727060 100644
@@ -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
drivers/net/can/loongson_canfd/Kconfig new file +4 -0
new file mode 100644
index 000000000000..5a2540bb5410
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#

drivers/net/can/loongson_canfd/Kconfig line 14

Quoted 10 lines
drivers/net/can/loongson_canfd/Kconfig new file +10 -0
+#
+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.

drivers/net/can/loongson_canfd/Kconfig line 16

Quoted 2 lines
drivers/net/can/loongson_canfd/Kconfig new file +2 -0
+          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.

Quoted 58 lines
drivers/net/can/loongson_canfd/Makefile new file +6 -0
new file mode 100644
index 000000000000..38d1f91bd0b8
@@ -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
drivers/net/can/loongson_canfd/loongson_canfd.c new file +40 -0
new file mode 100644
index 000000000000..20ac95dc528d
@@ -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,

drivers/net/can/loongson_canfd/loongson_canfd.c line 50

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 62

Quoted 12 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +12 -0
+     .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.

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+     .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,
+};
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 83

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+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?

drivers/net/can/loongson_canfd/loongson_canfd.c line 89

Quoted 6 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +6 -0
+{
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 92

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+}
+
+static inline enum loongson_canfd_tx_bs
          ^^^^^^

No need for the inline. Let the compiler decide for you if this is worth inlining.

drivers/net/can/loongson_canfd/loongson_canfd.c line 100

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+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.

Quoted 14 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +14 -0
+}
+
+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)

drivers/net/can/loongson_canfd/loongson_canfd.c

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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().

Quoted 103 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +103 -0
+     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;
+             }
+
Quoted 8 lines

drivers/net/can/loongson_canfd/loongson_canfd.c line 234

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+             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())

Last question:

In the general framework, the calculation condition for tdco is (dbt->brp == 1 || dbt->brp == 2), which does not seem to correspond to the current condition (dbt->bitrate > 1000000).

Although for CANFD, rates below 1 Mbit/s would negate the primary advantages of FD, from a controller hardware design perspective, I would still prefer to retain the condition (dbt->bitrate > 1000000).

Do you have any other suggestions?

Quoted 1,476 lines
Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 255

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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…

Quoted 8 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +8 -0
+     /*
+      * 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);
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 274

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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?

Quoted 146 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +146 -0
+     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;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 432

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 438

Quoted 5 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +5 -0
+     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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 447

Quoted 9 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +9 -0
+             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))) {

drivers/net/can/loongson_canfd/loongson_canfd.c line 458

Quoted 11 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +11 -0
+             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.

Quoted 17 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +17 -0
+     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 */

drivers/net/can/loongson_canfd/loongson_canfd.c line 486

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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) {
Quoted 49 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +49 -0
+                     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 546

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+     /* 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.

drivers/net/can/loongson_canfd/loongson_canfd.c line 550

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +3 -0
+     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.

Quoted 55 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +55 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 615

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+     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().

Quoted 23 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +23 -0
+     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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 649

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+
+                     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] = /* ... */

drivers/net/can/loongson_canfd/loongson_canfd.c line 650

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c new file +1 -0
+                                           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)?

drivers/net/can/loongson_canfd/loongson_canfd.c line 641

Quoted 2 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +2 -0
+                             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).

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                     }
+                     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;

drivers/net/can/loongson_canfd/loongson_canfd.c line 672

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                     }
+                     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.

Quoted 26 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +26 -0
+                     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++;
+

drivers/net/can/loongson_canfd/loongson_canfd.c line 708

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+             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.

Quoted 65 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +65 -0
+             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)

drivers/net/can/loongson_canfd/loongson_canfd.c line 783

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+{
+     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) {
                        /* ... */
                }
Quoted 31 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +31 -0
+             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);

drivers/net/can/loongson_canfd/loongson_canfd.c line 825

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +10 -0
+                             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.

Quoted 464 lines
drivers/net/can/loongson_canfd/loongson_canfd.c new file +334 -0
+             }
+
+             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");
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +124 -0
new file mode 100644
index 000000000000..d3517a7ff4a3
@@ -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 */

drivers/net/can/loongson_canfd/loongson_canfd_kframe.h line 134

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +10 -0
+#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.

Quoted 318 lines
drivers/net/can/loongson_canfd/loongson_canfd_kframe.h new file +7 -0
+/*  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
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +305 -0
new file mode 100644
index 000000000000..5e8889e3a924
@@ -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)
+

drivers/net/can/loongson_canfd/loongson_canfd_kregs.h line 315

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd_kregs.h new file +10 -0
+/* 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

Signature · 2 lines
Thanks.
Binbin
maintainer
#8 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Quoted 3 lines · Binbin Zhou

Hi Vincent:

On Thu, May 7, 2026 at 1:50 AM Vincent Mailhol <[email protected]> wrote:

(...)

Quoted 8 lines
Quoted 2 lines

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())

Last question:

In the general framework, the calculation condition for tdco is (dbt->brp == 1 || dbt->brp == 2), which does not seem to correspond to the current condition (dbt->bitrate > 1000000).

Where does the current condition comes from? Is it some empirical test?

FYI, the

  (dbt->brp == 1 || dbt->brp == 2)

check comes from ISO 11898.

Quoted 5 lines

Although for CANFD, rates below 1 Mbit/s would negate the primary advantages of FD, from a controller hardware design perspective, I would still prefer to retain the condition (dbt->bitrate > 1000000).

Do you have any other suggestions?

Using TDC on low bitrates is indeed unstable and can lead to bus errors. But the condition which you suggest

(dbt->bitrate > 1000000)

is indirectly linked with the brp values. That's why the brp check is sufficient (and more precise than the empirical 1 Mbit/s threshold value).

Are you able to trigger any bus errors when using TDC with a brp value of 1 or 2 and a bitrate below 1 Mbit/sec? If no, there is no reason to add this extra check and we can continue to rely on the framework test.

Yours sincerely,
Vincent Mailhol

#9 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Hi Vincent:

Sorry for the late reply.

Quoted 34 lines · Vincent Mailhol
Quoted 3 lines · Binbin Zhou

Hi Vincent:

On Thu, May 7, 2026 at 1:50 AM Vincent Mailhol <[email protected]> wrote:

(...)

Quoted 8 lines
Quoted 2 lines

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())

Last question:

In the general framework, the calculation condition for tdco is (dbt->brp == 1 || dbt->brp == 2), which does not seem to correspond to the current condition (dbt->bitrate > 1000000).

Where does the current condition comes from? Is it some empirical test?

FYI, the

  (dbt->brp == 1 || dbt->brp == 2)

check comes from ISO 11898.

Quoted 5 lines

Although for CANFD, rates below 1 Mbit/s would negate the primary advantages of FD, from a controller hardware design perspective, I would still prefer to retain the condition (dbt->bitrate > 1000000).

Do you have any other suggestions?

Using TDC on low bitrates is indeed unstable and can lead to bus errors. But the condition which you suggest

Quoted 8 lines

(dbt->bitrate > 1000000)

is indirectly linked with the brp values. That's why the brp check is sufficient (and more precise than the empirical 1 Mbit/s threshold value).

Are you able to trigger any bus errors when using TDC with a brp value of 1 or 2 and a bitrate below 1 Mbit/sec? If no, there is no reason to add this extra check and we can continue to rely on the framework test.

After offline discussions with our hardware engineers, we have decided to retain the existing decision criteria.

The threshold is set to 1 Mbit/s primarily based on the CAN 2.0 physical layer protocol, which supports a maximum rate of 1 Mbit/s; this decision also takes into account that at low baud rates, the bit duration is sufficiently long, so there is generally no need to enable the second sampling point. Conversely, if the CAN controller’s time quanta clock frequency is very high, causing the BRP to consistently exceed 2 while traversing parameters, TDC may not be enabled.

Additionally, there is a low probability of bus errors occurring during testing.

So, in your opinion, should we keep it as is?

Quoted 5 lines

Yours sincerely,
Vincent Mailhol

--
Thanks.
Binbin

maintainer
#10 lore raw

Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support

Quoted 23 lines · Binbin Zhou

Hi Vincent:

Sorry for the late reply.

Quoted 18 lines · Vincent Mailhol
Quoted 3 lines · Binbin Zhou

Hi Vincent:

On Thu, May 7, 2026 at 1:50 AM Vincent Mailhol <[email protected]> wrote:

(...)

Quoted 8 lines
Quoted 2 lines

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())

Last question:

In the general framework, the calculation condition for tdco is (dbt->brp == 1 || dbt->brp == 2), which does not seem to correspond to the current condition (dbt->bitrate > 1000000).

Where does the current condition comes from? Is it some empirical test?

You did not answer this question. Where does this idea come from? Is it an individual initiative from one of your hardware engineers or does it come from any literature or research paper?

Quoted 5 lines
Quoted 5 lines

FYI, the

  (dbt->brp == 1 || dbt->brp == 2)

check comes from ISO 11898.

My feeling is that you were not aware of the BRP <= 2 criteria. That criteria comes from ISO 11898-1. Was your hardware team aware of this BRP <= 2 ISO criteria? If yes, what made them decide to not use it?

Quoted 9 lines
Quoted 9 lines
Quoted 5 lines

Although for CANFD, rates below 1 Mbit/s would negate the primary advantages of FD, from a controller hardware design perspective, I would still prefer to retain the condition (dbt->bitrate > 1000000).

Do you have any other suggestions?

Using TDC on low bitrates is indeed unstable and can lead to bus errors. But the condition which you suggest

Quoted 8 lines
Quoted 8 lines

(dbt->bitrate > 1000000)

is indirectly linked with the brp values. That's why the brp check is sufficient (and more precise than the empirical 1 Mbit/s threshold value).

Are you able to trigger any bus errors when using TDC with a brp value of 1 or 2 and a bitrate below 1 Mbit/sec? If no, there is no reason to add this extra check and we can continue to rely on the framework test.

You also didn't answer this second question:

Are you able to trigger any bus errors when using TDC with a brp value of 1 or 2 and a bitrate below 1 Mbit/sec?

Quoted 11 lines

After offline discussions with our hardware engineers, we have decided to retain the existing decision criteria.

The threshold is set to 1 Mbit/s primarily based on the CAN 2.0 physical layer protocol, which supports a maximum rate of 1 Mbit/s; this decision also takes into account that at low baud rates, the bit duration is sufficiently long, so there is generally no need to enable the second sampling point. Conversely, if the CAN controller’s time quanta clock frequency is very high, causing the BRP to consistently exceed 2 while traversing parameters, TDC may not be enabled.

Quoted 1 line

Additionally, there is a low probability of bus errors occurring

during testing.

Can you prove that the 1Mbit/sec threshold gives a *lower* probability of bus errors occurring compared to the BRP <= 2 rule?

The point is that to be accepted, it is insufficient for your new criteria to just match the current criteria. It has to *improve* the current situation. Otherwise, more code for same performance means that we just bloated the kernel with a useless check.

Quoted 1 line

So, in your opinion, should we keep it as is?

An important aspect for the kernel maintainability is to centralize code as much as possible. If you can prove that your 1Mbit/sec threshold is *better* than the current rule, then this check must go in the framework (e.g. modify can_fd_tdc_is_enabled()) so that all the drivers can benefit from it. If not, it has no reason to exist.

As much as possible, we should avoid driver individual initiatives, so keeping this condition in your driver is a hard no for me (one exception for local workaround would only be if your device has a defect which need some unique logic for fixing).

So, either convince me through some tangible proof that your criteria improves the ISO criteria (e.g. empirical test showing bus error reduction when adding your rule) or drop the idea.

Yours sincerely,
Vincent Mailhol

#11 lore raw

PATCH 2/2 can: loongson_canfd: Add RXDMA support

In reply to 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.

---
 drivers/net/can/loongson_canfd/Kconfig        |   2 +-
 .../net/can/loongson_canfd/loongson_canfd.c   | 179 ++++++++++++++++--
 2 files changed, 160 insertions(+), 21 deletions(-)
drivers/net/can/loongson_canfd/Kconfig +1 -1
index 5a2540bb5410..8fe44b804991 100644
@@ -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,
drivers/net/can/loongson_canfd/loongson_canfd.c +159 -20

314 lines not on this page · raw message

Signature · 1 line
2.52.0
maintainer
#12 lore raw

Re: [PATCH 2/2] can: loongson_canfd: Add RXDMA support

Quoted 26 lines · 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.

---
 drivers/net/can/loongson_canfd/Kconfig        |   2 +-
 .../net/can/loongson_canfd/loongson_canfd.c   | 179 ++++++++++++++++--
 2 files changed, 160 insertions(+), 21 deletions(-)
drivers/net/can/loongson_canfd/Kconfig +0 -0
index 5a2540bb5410..8fe44b804991 100644

drivers/net/can/loongson_canfd/Kconfig line 8

Quoted 5 lines
drivers/net/can/loongson_canfd/Kconfig +1 -1
@@ -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)
Quoted 61 lines
drivers/net/can/loongson_canfd/Kconfig +0 -0
 	select REGMAP_MMIO
 	help
 	  This is a canfd driver switch for the Loongson platform,
drivers/net/can/loongson_canfd/loongson_canfd.c +25 -0
index 20ac95dc528d..ba9570c34c94 100644
@@ -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)
+{

drivers/net/can/loongson_canfd/loongson_canfd.c static int loongson_canfd_reset(struct net_device *ndev) · line 174

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +10 -0
+	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

Quoted 259 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +123 -20
+}
+
+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);

drivers/net/can/loongson_canfd/loongson_canfd.c static struct platform_driver loongson_canfd_driver = { · line 1295

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +1 -0
@@ -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?

drivers/net/can/loongson_canfd/loongson_canfd.c static struct platform_driver loongson_canfd_driver = { · line 1298

Quoted 3 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +0 -0
 MODULE_AUTHOR("Loongson Technology Corporation Limited");
 MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
 MODULE_LICENSE("GPL");

Yours sincerely,
Vincent Mailhol

#13 lore raw

Re: [PATCH 2/2] can: loongson_canfd: Add RXDMA support

Hi Vincent:

Thanks so much for your thorough review.

Quoted 36 lines · Vincent Mailhol
Quoted 26 lines · 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.

---
 drivers/net/can/loongson_canfd/Kconfig        |   2 +-
 .../net/can/loongson_canfd/loongson_canfd.c   | 179 ++++++++++++++++--
 2 files changed, 160 insertions(+), 21 deletions(-)
drivers/net/can/loongson_canfd/Kconfig +0 -0
index 5a2540bb5410..8fe44b804991 100644

drivers/net/can/loongson_canfd/Kconfig line 8

Quoted 5 lines
drivers/net/can/loongson_canfd/Kconfig +1 -1
@@ -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:

Quoted 1 line
        depends on HAS_IOMEM && (LOONGSON2_APB_CMC_DMA || COMPILE_TEST)

OK, I will add COMPILE_TEST in patch-1.

Quoted 338 lines
Quoted 62 lines
drivers/net/can/loongson_canfd/Kconfig +0 -0
      select REGMAP_MMIO
      help
        This is a canfd driver switch for the Loongson platform,
drivers/net/can/loongson_canfd/loongson_canfd.c +25 -0
index 20ac95dc528d..ba9570c34c94 100644
@@ -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)
+{

drivers/net/can/loongson_canfd/loongson_canfd.c static int loongson_canfd_reset(struct net_device *ndev) · line 174

Quoted 10 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +10 -0
+     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

Quoted 262 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +123 -20
+}
+
+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);
 
Quoted 4 lines

drivers/net/can/loongson_canfd/loongson_canfd.c static struct platform_driver loongson_canfd_driver = { · line 1295

Quoted 1 line
drivers/net/can/loongson_canfd/loongson_canfd.c +1 -0
+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?

It is required; otherwise, the DMA data transfer will fail immediately.

Quoted 9 lines

drivers/net/can/loongson_canfd/loongson_canfd.c static struct platform_driver loongson_canfd_driver = { · line 1298

Quoted 4 lines
drivers/net/can/loongson_canfd/loongson_canfd.c +0 -0
 MODULE_AUTHOR("Loongson Technology Corporation Limited");
 MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
 MODULE_LICENSE("GPL");

Yours sincerely,
Vincent Mailhol

--
Thanks.
Binbin