* [PATCH 0/2] Add Loongson CAN-FD controller driver
@ 2026-04-27 7:17 Binbin Zhou
2026-04-27 7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
2026-04-27 7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
0 siblings, 2 replies; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27 7:17 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Hi all:
This patchset adds support for the CAN-FD controller found on Loongson
CPUs.
Patch 1 introduces the basic CAN-FD controller driver with support
for classic CAN and CAN FD, including bit timing, error handling,
NAPI-based RX, and multiple TX buffers.
Patch 2 adds optional DMA support for RX path using the Loongson APB
CMC DMA engine, which significantly reduces CPU load under high
receive throughput.
The driver has been tested on Loongson-2K3000 platforms with various
CAN/CAN FD traffic patterns.
Finally, I'd like to thank Binxiong, the original author of this driver,
for his efforts in working on the patch.
Thanks.
Binbin
Binbin Zhou (2):
can: Add Loongson CAN-FD controller support
can: loongson_canfd: Add RXDMA support
MAINTAINERS | 7 +
drivers/net/can/Kconfig | 1 +
drivers/net/can/Makefile | 1 +
drivers/net/can/loongson_canfd/Kconfig | 16 +
drivers/net/can/loongson_canfd/Makefile | 6 +
.../net/can/loongson_canfd/loongson_canfd.c | 1298 +++++++++++++++++
.../loongson_canfd/loongson_canfd_kframe.h | 142 ++
.../can/loongson_canfd/loongson_canfd_kregs.h | 315 ++++
8 files changed, 1786 insertions(+)
create mode 100644 drivers/net/can/loongson_canfd/Kconfig
create mode 100644 drivers/net/can/loongson_canfd/Makefile
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
base-commit: f1359c240191e686614847905fc861cbda480b47
--
2.52.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] can: Add Loongson CAN-FD controller support
2026-04-27 7:17 [PATCH 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
@ 2026-04-27 7:17 ` Binbin Zhou
2026-05-06 17:50 ` Vincent Mailhol
2026-04-27 7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
1 sibling, 1 reply; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27 7:17 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Add driver for the CAN-FD controller integrated into Loongson CPUs. The
controller supports:
- Classic CAN and CAN FD (ISO/non-ISO)
- Data rates up to 5 Mbps (nominal) and 10 Mbps (data)
- 8 independent TX buffers
- Hardware TX retransmission limit and one-shot mode
- Error counters, bus error reporting, arbitration lost capture
- NAPI-based RX path
- Loopback and listen-only modes
The driver is implemented as a standard Linux CAN network driver using
the CAN framework APIs.
Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
MAINTAINERS | 7 +
drivers/net/can/Kconfig | 1 +
drivers/net/can/Makefile | 1 +
drivers/net/can/loongson_canfd/Kconfig | 16 +
drivers/net/can/loongson_canfd/Makefile | 6 +
.../net/can/loongson_canfd/loongson_canfd.c | 1159 +++++++++++++++++
.../loongson_canfd/loongson_canfd_kframe.h | 142 ++
.../can/loongson_canfd/loongson_canfd_kregs.h | 315 +++++
8 files changed, 1647 insertions(+)
create mode 100644 drivers/net/can/loongson_canfd/Kconfig
create mode 100644 drivers/net/can/loongson_canfd/Makefile
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7a2ffd9d37d5..5534db894cdc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14935,6 +14935,13 @@ F: arch/loongarch/
F: drivers/*/*loongarch*
F: drivers/cpufreq/loongson3_cpufreq.c
+LOONGSON CAN FD DRIVER
+M: Bingxiong Li <libingxiong@loongson.cn>
+M: Binbin Zhou <zhoubinbin@loongson.cn>
+L: linux-can@vger.kernel.org
+S: Maintained
+F: drivers/net/can/loongson_canfd/
+
LOONGSON GPIO DRIVER
M: Yinbo Zhu <zhuyinbo@loongson.cn>
L: linux-gpio@vger.kernel.org
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e15e320db476..1306d2d80197 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -242,6 +242,7 @@ source "drivers/net/can/ifi_canfd/Kconfig"
source "drivers/net/can/m_can/Kconfig"
source "drivers/net/can/mscan/Kconfig"
source "drivers/net/can/peak_canfd/Kconfig"
+source "drivers/net/can/loongson_canfd/Kconfig"
source "drivers/net/can/rcar/Kconfig"
source "drivers/net/can/rockchip/Kconfig"
source "drivers/net/can/sja1000/Kconfig"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index d7bc10a6b8ea..a39e26727060 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/
obj-$(CONFIG_CAN_MSCAN) += mscan/
obj-$(CONFIG_CAN_M_CAN) += m_can/
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
+obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd/
obj-$(CONFIG_CAN_SJA1000) += sja1000/
obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
new file mode 100644
index 000000000000..5a2540bb5410
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Loongson canfd drivers
+#
+#
+config CAN_LOONGSON_CANFD
+ tristate "Loongson CAN-FD driver"
+ depends on HAS_IOMEM
+ select REGMAP_MMIO
+ help
+ This is a canfd driver switch for the Loongson platform,
+ integrated with the Loongson-2K series.
+
+ You can choose yes or no here.For detailed information about
+ the user manual, please log in to the Loongson official
+ website.(https://loongson.cn/)
diff --git a/drivers/net/can/loongson_canfd/Makefile b/drivers/net/can/loongson_canfd/Makefile
new file mode 100644
index 000000000000..38d1f91bd0b8
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for the Loongson CAN-FD controller drivers.
+#
+
+obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
new file mode 100644
index 000000000000..20ac95dc528d
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
@@ -0,0 +1,1159 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LOONGSON CANFD controller
+ *
+ * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "loongson_canfd_kframe.h"
+#include "loongson_canfd_kregs.h"
+
+#define DEV_NAME "loongson_canfd"
+#define LOONGSON_CANFD_TXBUF_NUM 8
+#define LOONGSON_CANFD_ID 0xBABE
+
+struct loongson_canfd_priv {
+ struct can_priv can; /* must be first member! */
+ struct napi_struct napi;
+ struct regmap *regmap;
+ struct resource *res;
+ spinlock_t tx_lock; /* protect the sending queue */
+};
+
+enum loongson_canfd_tx_bs {
+ TX_BS_IDLE = 0x0,
+ TX_BS_VALID = 0x1,
+ TX_BS_FAIL = 0x2,
+ TX_BS_CANCEL = 0x3
+};
+
+enum loongson_canfd_txtb_command {
+ TXT_CMD_SET_ADD = 0x01,
+ TXT_CMD_SET_CANCEL = 0x02
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing_max = {
+ .name = DEV_NAME,
+ .tseg1_min = 2,
+ .tseg1_max = 190,
+ .tseg2_min = 2,
+ .tseg2_max = 63,
+ .sjw_max = 31,
+ .brp_min = 1,
+ .brp_max = 15,
+ .brp_inc = 1,
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing_data_max = {
+ .name = DEV_NAME,
+ .tseg1_min = 2,
+ .tseg1_max = 190,
+ .tseg2_min = 2,
+ .tseg2_max = 63,
+ .sjw_max = 31,
+ .brp_min = 1,
+ .brp_max = 255,
+ .brp_inc = 1,
+};
+
+static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv)
+{
+ u32 conf;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
+
+ return !!FIELD_GET(REG_CONF_ENA, conf);
+}
+
+static bool loongson_canfd_txtnf(struct loongson_canfd_priv *priv)
+{
+ u32 sts;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+ return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
+}
+
+static inline enum loongson_canfd_tx_bs
+loongson_canfd_get_bs(struct loongson_canfd_priv *priv, u8 bs_id)
+{
+ u32 sts, bs;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+ bs = FIELD_GET(REG_TX_STAT_BS, sts);
+
+ return FIELD_GET(GENMASK(1, 0), (bs >> (bs_id * 2)));
+}
+
+static unsigned int loongson_canfd_get_tx_id(struct loongson_canfd_priv *priv)
+{
+ unsigned int i;
+
+ for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++)
+ if (loongson_canfd_get_bs(priv, i))
+ break;
+
+ return i;
+}
+
+static bool loongson_canfd_txbuf_writable(struct loongson_canfd_priv *priv, u8 buf_id)
+{
+ enum loongson_canfd_tx_bs bs;
+ u32 sts;
+
+ bs = loongson_canfd_get_bs(priv, buf_id);
+ if (bs)
+ return false;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+ if (FIELD_GET(BIT(0), sts >> buf_id))
+ return false;
+
+ return true;
+}
+
+static int loongson_canfd_reset(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST);
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM);
+
+ return 0;
+}
+
+static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 phase_seg1 = bt->phase_seg1;
+ u32 prop_seg = bt->prop_seg;
+ int max_ph1_len = 31;
+ u32 btr = 0;
+
+ if (loongson_canfd_enabled(priv)) {
+ netdev_err(ndev, "BUG! Cannot set bittiming - CAN is enabled\n");
+ return -EPERM;
+ }
+
+ if (nominal)
+ max_ph1_len = 63;
+
+ /*
+ * The timing calculation functions have only constraints on tseg1,
+ * which is prop_seg + phase1_seg combined.
+ * tseg1 is then split in half and stored into prog_seg and phase_seg1.
+ * In Loongson CAN-FD, PROP is 6/7 bits wide but PH1 only 6/5, so we must
+ * re-distribute the values here.
+ */
+ if (phase_seg1 > max_ph1_len) {
+ prop_seg += phase_seg1 - max_ph1_len;
+ phase_seg1 = max_ph1_len;
+ bt->prop_seg = prop_seg;
+ bt->phase_seg1 = phase_seg1;
+ }
+
+ if (nominal) {
+ btr = FIELD_PREP(REG_BTR_PROP, prop_seg) |
+ FIELD_PREP(REG_BTR_PH1, phase_seg1) |
+ FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) |
+ FIELD_PREP(REG_BTR_BRP, bt->brp) |
+ FIELD_PREP(REG_BTR_SJW, bt->sjw);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr);
+ } else {
+ btr = FIELD_PREP(REG_BTR_FD_PROP, prop_seg) |
+ FIELD_PREP(REG_BTR_FD_PH1, phase_seg1) |
+ FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) |
+ FIELD_PREP(REG_BTR_FD_BRP, bt->brp) |
+ FIELD_PREP(REG_BTR_FD_SJW, bt->sjw);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr);
+ }
+
+ return 0;
+}
+
+static int loongson_canfd_set_bittiming(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *bt = &priv->can.bittiming;
+
+ /* Note that bt may be modified here */
+ return loongson_canfd_set_btr(ndev, bt, true);
+}
+
+static int loongson_canfd_set_data_bittiming(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+
+ /* Note that dbt may be modified here */
+ return loongson_canfd_set_btr(ndev, dbt, false);
+}
+
+static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+ int ssp_offset = 0;
+ u32 ssp_cfg = 0; /* No SSP by default */
+
+ if (loongson_canfd_enabled(priv)) {
+ netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n");
+ return -EPERM;
+ }
+
+ /* Use SSP for bit-rates above 1 Mbits/s */
+ if (dbt->bitrate > 1000000) {
+ /* Calculate SSP in minimal time quanta */
+ ssp_offset = (priv->can.clock.freq / 1000) * dbt->sample_point / dbt->bitrate;
+ if (ssp_offset > 127) {
+ netdev_warn(ndev, "SSP offset saturated to 127\n");
+ ssp_offset = 127;
+ }
+
+ ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, ssp_offset) |
+ FIELD_PREP(REG_SSP_CFG_SRC, 0x0);
+ } else {
+ ssp_cfg |= FIELD_PREP(REG_SSP_CFG_SRC, 0x1);
+ }
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg);
+
+ return 0;
+}
+
+static void loongson_canfd_set_mode(struct loongson_canfd_priv *priv,
+ const struct can_ctrlmode *ctrlmode)
+{
+ u32 mode, conf;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode);
+
+ mode = (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY) ?
+ (mode | REG_MODE_BMM) : (mode & ~REG_MODE_BMM);
+
+ mode = (ctrlmode->flags & CAN_CTRLMODE_FD) ?
+ (mode | REG_MODE_FDE) : (mode & ~REG_MODE_FDE);
+
+ mode = (ctrlmode->flags & CAN_CTRLMODE_PRESUME_ACK) ?
+ (mode | REG_MODE_ACF) : (mode & ~REG_MODE_ACF);
+
+ /*
+ * Some bits fixed:
+ * TSTM - Off, User shall not be able to change REC/TEC by hand
+ * during operation
+ */
+ mode &= ~REG_MODE_TSTM;
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode);
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
+
+ conf = (ctrlmode->flags & CAN_CTRLMODE_LOOPBACK) ?
+ (conf | REG_CONF_ILBP) : (conf & ~REG_CONF_ILBP);
+
+ conf = (ctrlmode->flags & CAN_CTRLMODE_FD_NON_ISO) ?
+ (conf | REG_CONF_NISOFD) : (conf & ~REG_CONF_NISOFD);
+
+ /* One shot mode supported indirectly via Retransmit limit */
+ conf &= ~FIELD_PREP(REG_CONF_RTRTH, 0xF);
+ conf = (ctrlmode->flags & CAN_CTRLMODE_ONE_SHOT) ?
+ (conf | REG_CONF_RTRLE) :
+ (conf | REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, 0xF));
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf);
+}
+
+static int loongson_canfd_chip_start(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_ctrlmode mode;
+ u16 int_ena, int_msk;
+ int ret;
+
+ /* Configure bit-rates and ssp */
+ ret = loongson_canfd_set_bittiming(ndev);
+ if (ret < 0)
+ return ret;
+
+ ret = loongson_canfd_set_data_bittiming(ndev);
+ if (ret < 0)
+ return ret;
+
+ ret = loongson_canfd_set_secondary_sample_point(ndev);
+ if (ret < 0)
+ return ret;
+
+ /* Configure modes */
+ mode.flags = priv->can.ctrlmode;
+ mode.mask = 0xFFFFFFFF;
+ loongson_canfd_set_mode(priv, &mode);
+
+ /* Bus error reporting */
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+ int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
+
+ int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_RBNEI;
+ int_msk = ~int_ena; /* Mask all disabled interrupts */
+
+ /* It's after reset, so there is no need to clear anything */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, int_msk);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
+
+ /* Controller enters ERROR_ACTIVE on initial FCSI */
+ priv->can.state = CAN_STATE_STOPPED;
+
+ /* Enable the controller */
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA);
+
+ return 0;
+}
+
+static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ ret = loongson_canfd_reset(ndev);
+ if (ret < 0)
+ return ret;
+
+ ret = loongson_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+ return ret;
+ }
+
+ netif_wake_queue(ndev);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv,
+ const struct canfd_frame *cf, u8 buf, bool isfdf)
+{
+ u32 meta0, meta1;
+
+ if (buf >= LOONGSON_CANFD_TXBUF_NUM)
+ return false;
+
+ if (!loongson_canfd_txbuf_writable(priv, buf))
+ return false;
+
+ /* Prepare identifier */
+ if (cf->can_id & CAN_EFF_FLAG) {
+ meta0 = cf->can_id & CAN_EFF_MASK;
+ meta0 |= REG_FRAME_FORMAT_W_XDT;
+ } else {
+ meta0 = FIELD_PREP(REG_IDENTIFIER_W_IDENTIFIER_BASE, cf->can_id & CAN_SFF_MASK);
+ }
+
+ /* Prepare Frame format */
+ if (cf->can_id & CAN_RTR_FLAG)
+ meta0 |= REG_FRAME_FORMAT_W_RTR;
+
+ if (isfdf) {
+ meta1 = REG_FRAME_FORMAT_W_FDF;
+
+ if (cf->flags & CANFD_BRS)
+ meta1 |= REG_FRAME_FORMAT_W_BRS;
+ }
+
+ meta1 |= FIELD_PREP(REG_FRAME_FORMAT_W_DLC, can_fd_len2dlc(cf->len));
+
+ /* TXT buffer select */
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf);
+
+ /* Write ID, Frame format */
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META0, meta0);
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META1, meta1);
+
+ /* Write Data payload */
+ if (!(cf->can_id & CAN_RTR_FLAG)) {
+ for (unsigned int i = 0; i < cf->len; i += 4) {
+ regmap_write(priv->regmap,
+ LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_DATA_1_4_W + i,
+ le32_to_cpu(*(__le32 *)(cf->data + i)));
+ }
+ }
+
+ return true;
+}
+
+static void loongson_canfd_give_txtb_cmd(struct loongson_canfd_priv *priv,
+ enum loongson_canfd_txtb_command cmd, u8 buf)
+{
+ u32 tx_cmd = (((cmd >> 1) << 8) | (cmd % 2)) << buf;
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, tx_cmd);
+}
+
+static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ u32 txtb_id, tx_stat;
+ unsigned long flags;
+ u16 tx_bs;
+ u8 tx_brp;
+ bool ok;
+
+ if (can_dropped_invalid_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ if (unlikely(!loongson_canfd_txtnf(priv))) {
+ netif_stop_queue(ndev);
+ netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
+ tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
+ tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
+
+ for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+ if ((((tx_brp >> i) & 0x1) == 0) &&
+ ((tx_bs >> (i * 2) & 0x3) == 0)) {
+ txtb_id = i;
+ break;
+ }
+ }
+
+ ok = loongson_canfd_insert_frame(priv, cf, txtb_id, can_is_canfd_skb(skb));
+ if (!ok) {
+ netdev_err(ndev, "BUG! TXNF set but cannot insert frame into TXTB! HW Bug?");
+ kfree_skb(skb);
+ ndev->stats.tx_dropped++;
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+ return NETDEV_TX_OK;
+ }
+
+ can_put_echo_skb(skb, ndev, txtb_id, 0);
+
+ if (!(cf->can_id & CAN_RTR_FLAG))
+ stats->tx_bytes += cf->len;
+
+ loongson_canfd_give_txtb_cmd(priv, TXT_CMD_SET_ADD, txtb_id);
+
+ /* Check if all TX buffers are full */
+ if (!loongson_canfd_txtnf(priv))
+ netif_stop_queue(ndev);
+
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ return NETDEV_TX_OK;
+}
+
+static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
+ u32 meta0, u32 meta1)
+{
+ u32 data, i, wc, len;
+
+ /* Extended Identifier Type */
+ if (FIELD_GET(REG_FRAME_FORMAT_W_XDT, meta0))
+ cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG;
+ else
+ cf->can_id = FIELD_GET(REG_IDENTIFIER_W_IDENTIFIER_BASE, meta0) & CAN_SFF_MASK;
+
+ /* BRS, ESI, RTR Flags */
+ cf->flags = 0;
+
+ if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1)) {
+ if (FIELD_GET(REG_FRAME_FORMAT_W_BRS, meta1))
+ cf->flags |= CANFD_BRS;
+
+ if (FIELD_GET(REG_FRAME_FORMAT_W_ESI_RSV, meta0))
+ cf->flags |= CANFD_ESI;
+ } else if (FIELD_GET(REG_FRAME_FORMAT_W_RTR, meta0)) {
+ cf->can_id |= CAN_RTR_FLAG;
+ }
+
+ /* Timesamp */
+ cf->__res0 = meta1;
+ cf->__res1 = meta1 >> 8;
+
+ wc = FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1) - 2;
+
+ /* Data Length Code */
+ if (FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1) <= 8) {
+ len = FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1);
+ } else {
+ if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
+ len = wc << 2;
+ else
+ len = 8;
+ }
+
+ cf->len = len;
+ if (unlikely(len > wc * 4))
+ len = wc * 4;
+
+ /* Data */
+ for (i = 0; i < len; i += 4) {
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ *(__le32 *)(cf->data + i) = cpu_to_le32(data);
+ }
+
+ while (unlikely(i < wc * 4)) {
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ i += 4;
+ }
+}
+
+static int loongson_canfd_rx(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct canfd_frame *cf;
+ struct sk_buff *skb;
+ u32 meta0, meta1;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
+
+ /* Number of characters received */
+ if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
+ return -EAGAIN;
+
+ /* Flexible Data-rate Format */
+ if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
+ skb = alloc_canfd_skb(ndev, &cf);
+ else
+ skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+ loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
+
+ stats->rx_bytes += cf->len;
+ stats->rx_packets++;
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+static enum can_state loongson_canfd_read_fault_state(struct loongson_canfd_priv *priv)
+{
+ u32 fs, erl, rec_tec, ewl;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl);
+ regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fs);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec);
+
+ ewl = FIELD_GET(REG_ERL_EW, erl);
+
+ if (FIELD_GET(REG_FSTAT_ERA, fs)) {
+ if (ewl > FIELD_GET(REG_ERC_REC, rec_tec) &&
+ ewl > FIELD_GET(REG_ERC_REC, rec_tec))
+ return CAN_STATE_ERROR_ACTIVE;
+ else
+ return CAN_STATE_ERROR_WARNING;
+ } else if (FIELD_GET(REG_FSTAT_ERP, fs)) {
+ return CAN_STATE_ERROR_PASSIVE;
+ } else if (FIELD_GET(REG_FSTAT_BOF, fs)) {
+ return CAN_STATE_BUS_OFF;
+ }
+
+ WARN(true, "Invalid error state");
+ return CAN_STATE_ERROR_PASSIVE;
+}
+
+static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec)
+{
+ u32 erc;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc);
+ bec->rxerr = FIELD_GET(REG_ERC_REC, erc);
+ bec->txerr = FIELD_GET(REG_ERC_REC, erc);
+}
+
+static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_berr_counter bec;
+ enum can_state state;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ u32 err_capt, alc;
+ int dologerr = net_ratelimit();
+
+ loongson_canfd_get_bec(priv, &bec);
+
+ state = loongson_canfd_read_fault_state(priv);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc);
+
+ if (dologerr)
+ netdev_info(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n",
+ __func__, isr, bec.rxerr, bec.txerr,
+ FIELD_GET(REG_ERR_CAPT_TYPE, err_capt),
+ FIELD_GET(REG_ERR_CAPT_POS, err_capt),
+ FIELD_GET(REG_ALC_ID_FIELD, alc),
+ FIELD_GET(REG_ALC_BIT_POS, alc));
+
+ skb = alloc_can_err_skb(ndev, &cf);
+
+ /*
+ * EWLI: error warning limit condition met
+ * FCSI: fault confinement state changed
+ * ALI: arbitration lost (just informative)
+ * BEI: bus error interrupt
+ */
+ if (FIELD_GET(REG_INT_STAT_FCSI, isr) || FIELD_GET(REG_INT_STAT_EWLI, isr)) {
+ netdev_info(ndev, "state changes from %s to %s\n",
+ can_get_state_str(priv->can.state), can_get_state_str(state));
+
+ if (priv->can.state == state)
+ netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
+
+ isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
+ priv->can.state = state;
+ switch (state) {
+ case CAN_STATE_BUS_OFF:
+ priv->can.can_stats.bus_off++;
+ if (priv->can.restart_ms)
+ regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
+ REG_CMD_ERCRST);
+
+ can_bus_off(ndev);
+ if (skb)
+ cf->can_id |= CAN_ERR_BUSOFF;
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ priv->can.can_stats.error_passive++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = (bec.rxerr > 127) ?
+ CAN_ERR_CRTL_RX_PASSIVE : CAN_ERR_CRTL_TX_PASSIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_ERROR_WARNING:
+ priv->can.can_stats.error_warning++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] |= (bec.txerr > bec.rxerr) ?
+ CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_ERROR_ACTIVE:
+ cf->data[1] = CAN_ERR_CRTL_ACTIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ break;
+ default:
+ netdev_warn(ndev, "error state (%d:%s)!\n", state,
+ can_get_state_str(state));
+ break;
+ }
+ }
+
+ /* Check for Arbitration Lost interrupt */
+ if (FIELD_GET(REG_INT_STAT_ALI, isr)) {
+ isr = REG_INT_STAT_ALI;
+
+ if (dologerr)
+ netdev_info(ndev, "arbitration lost\n");
+
+ priv->can.can_stats.arbitration_lost++;
+
+ if (skb) {
+ cf->can_id |= CAN_ERR_LOSTARB;
+ cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
+ }
+ }
+
+ /* Check for Bus Error interrupt */
+ if (FIELD_GET(REG_INT_STAT_BEI, isr)) {
+ isr = REG_INT_STAT_BEI;
+ netdev_info(ndev, "bus error\n");
+ priv->can.can_stats.bus_error++;
+ stats->rx_errors++;
+
+ if (skb) {
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+ cf->data[2] = CAN_ERR_PROT_UNSPEC;
+ cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC;
+ }
+ }
+
+ if (skb) {
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);
+ }
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 16);
+}
+
+static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
+{
+ struct net_device *ndev = napi->dev;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int work_done = 0, res = 1;
+ u32 sts, rx_frc, rx_sts;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
+ rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
+
+ while (rx_frc && work_done < quota && res > 0) {
+ res = loongson_canfd_rx(ndev);
+ work_done++;
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
+ rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
+ }
+
+ /* Check for RX FIFO Overflow */
+ regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
+ if (FIELD_GET(REG_STAT_DOR, sts)) {
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ netdev_info(ndev, "rx_poll: rx fifo overflow\n");
+ stats->rx_over_errors++;
+ stats->rx_errors++;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);
+ }
+
+ /* Clear Data Overrun */
+ regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO);
+ }
+
+ if (!rx_frc && res != 0) {
+ if (napi_complete_done(napi, work_done)) {
+ /*
+ * Clear and enable RBNEI. It is level-triggered, so
+ * there is no race condition.
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT,
+ REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ (REG_INT_STAT_RBNEI << 16));
+ }
+ }
+
+ return work_done;
+}
+
+static void loongson_canfd_tx_interrupt(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ enum loongson_canfd_tx_bs txtb_sts;
+ bool some_buffers_processed;
+ unsigned long flags;
+ u32 txtb_id, ctr;
+
+ do {
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ some_buffers_processed = false;
+
+ while ((txtb_id = loongson_canfd_get_tx_id(priv)) < 8) {
+ txtb_sts = loongson_canfd_get_bs(priv, txtb_id);
+
+ switch (txtb_sts) {
+ case TX_BS_VALID:
+ stats->tx_bytes += can_get_echo_skb(ndev, txtb_id, NULL);
+ stats->tx_packets++;
+ break;
+ case TX_BS_FAIL:
+ /*
+ * This indicated that retransmit limit has been reached.
+ * Obviously we should not echo the frame, but also not
+ * indicate any kind of error.
+ * If desired, it was already reported (possible multiple
+ * times) on each arbitration lost.
+ */
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &ctr);
+ netdev_warn(ndev, "TX_BS_FAIL txcnt=%x\n", ctr);
+ can_free_echo_skb(ndev, txtb_id, NULL);
+ stats->tx_dropped++;
+ break;
+ case TX_BS_CANCEL:
+ /*
+ * Same as for TX_BS_CANCEL, only with different cause.
+ * We *could* re-queue the frame, but multiqueue/abort is
+ * not supported yet anyway.
+ */
+ netdev_warn(ndev, "TX_BS_CANCEL\n");
+ can_free_echo_skb(ndev, txtb_id, NULL);
+ stats->tx_dropped++;
+ break;
+ case TX_BS_IDLE:
+ netdev_warn(ndev, "TX_BS_IDLE\n");
+ break;
+ }
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD,
+ 0x1 << (txtb_id + 16));
+ some_buffers_processed = true;
+ }
+
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ /*
+ * If no buffers were processed this time, we cannot clear - that would
+ * introduce a race condition.
+ */
+ if (some_buffers_processed) {
+ /*
+ * Clear the interrupt again. We do not want to receive again interrupt
+ * for the buffer already handled. If it is the last finished one then
+ * it would cause log of spurious interrupt.
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
+ }
+ } while (some_buffers_processed);
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ /* Check if at least one TX buffer is free */
+ if (loongson_canfd_txtnf(priv))
+ netif_wake_queue(ndev);
+
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+}
+
+static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *)dev_id;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int irq_loops;
+ u32 isr;
+ u16 icr;
+
+ for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
+ /* Get the interrupt sts */
+ regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr);
+ if (!isr)
+ return irq_loops ? IRQ_HANDLED : IRQ_NONE;
+
+ /* Receive Buffer Not Empty Interrupt */
+ if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
+ /*
+ * Mask RXBNEI the first, then clear interrupt and schedule NAPI.
+ * Even if another IRQ fires, RBNEI will always be 0 (masked).
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
+ napi_schedule(&priv->napi);
+ }
+
+ /* TXT Buffer HW Command Interrupt */
+ if (FIELD_GET(REG_INT_STAT_TXBHCI, isr))
+ loongson_canfd_tx_interrupt(ndev);
+
+ /* Error interrupts */
+ if (FIELD_GET(REG_INT_STAT_EWLI, isr) ||
+ FIELD_GET(REG_INT_STAT_FCSI, isr) ||
+ FIELD_GET(REG_INT_STAT_ALI, isr)) {
+ icr = isr & (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, icr);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, icr);
+ loongson_canfd_err_interrupt(ndev, isr);
+ }
+
+ /* Ignore RI, TI, LFI, RFI, BSI */
+ }
+
+ netdev_err(ndev, "Error:isr<0x%08x>\n", isr);
+
+ if (FIELD_GET(REG_INT_STAT_TXBHCI, isr)) {
+ for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+ enum loongson_canfd_tx_bs txtb_sts = loongson_canfd_get_bs(priv, i);
+
+ netdev_err(ndev, "txb[%d]txbstatus=0x%01x\n", i, txtb_sts);
+ }
+ }
+
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ REG_INT_MASK_SET, REG_INT_MASK_SET);
+
+ return IRQ_HANDLED;
+}
+
+static void loongson_canfd_chip_stop(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ /* Disable interrupts and disable CAN */
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ REG_INT_MASK_SET, REG_INT_MASK_SET);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0);
+
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int loongson_canfd_open(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = loongson_canfd_reset(ndev);
+ if (ret < 0)
+ return ret;
+
+ /* Common open */
+ ret = open_candev(ndev);
+ if (ret) {
+ netdev_warn(ndev, "open_candev failed!\n");
+ return ret;
+ }
+
+ ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "irq allocation for CAN failed\n");
+ goto err_irq;
+ }
+
+ ret = loongson_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+ goto err_chip_start;
+ }
+
+ netdev_info(ndev, "loongson_canfd_device registered\n");
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+
+ return 0;
+
+err_chip_start:
+ free_irq(ndev->irq, ndev);
+err_irq:
+ close_candev(ndev);
+ return ret;
+}
+
+static int loongson_canfd_close(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+ loongson_canfd_chip_stop(ndev);
+ free_irq(ndev->irq, ndev);
+ close_candev(ndev);
+
+ return 0;
+}
+
+static const struct net_device_ops loongson_canfd_netdev_ops = {
+ .ndo_open = loongson_canfd_open,
+ .ndo_stop = loongson_canfd_close,
+ .ndo_start_xmit = loongson_canfd_start_xmit,
+};
+
+static int loongson_canfd_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ loongson_canfd_get_bec(priv, bec);
+ return 0;
+}
+
+static const struct regmap_range loongson_canfd_reg_table_wr_range[] = {
+ regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF),
+ regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD),
+ regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL),
+ regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES),
+ regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG),
+ regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL),
+ regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_range loongson_canfd_reg_table_rd_range[] = {
+ regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT),
+ regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE),
+ regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT),
+ regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_wr = {
+ .yes_ranges = loongson_canfd_reg_table_wr_range,
+ .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_rd = {
+ .yes_ranges = loongson_canfd_reg_table_rd_range,
+ .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range),
+};
+
+static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LOONGSON_CANFD_MODE:
+ case LOONGSON_CANFD_CONF:
+ case LOONGSON_CANFD_STAT:
+ case LOONGSON_CANFD_INT_STAT:
+ case LOONGSON_CANFD_INT_ENA:
+ case LOONGSON_CANFD_INT_MASK:
+ case LOONGSON_CANFD_ERL:
+ case LOONGSON_CANFD_FSTAT:
+ case LOONGSON_CANFD_ERC:
+ case LOONGSON_CANFD_ERR_CAPT:
+ case LOONGSON_CANFD_ALC:
+ case LOONGSON_CANFD_TX_FR_CNT:
+ case LOONGSON_CANFD_RX_STAT:
+ case LOONGSON_CANFD_RX_DATA:
+ case LOONGSON_CANFD_TX_STAT:
+ case LOONGSON_CANFD_TX_SEL:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static const struct regmap_config loongson_cangfd_regmap = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .wr_table = &loongson_canfd_reg_table_wr,
+ .rd_table = &loongson_canfd_reg_table_rd,
+ .volatile_reg = loongson_canfd_volatile_reg,
+ .max_register = LOONGSON_CANFD_TX_DATA_18,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int loongson_canfd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct loongson_canfd_priv *priv;
+ struct net_device *ndev;
+ struct regmap *regmap;
+ struct resource *res;
+ void __iomem *base;
+ u32 clk_rate;
+ int ret, irq;
+
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ device_property_read_u32(dev, "clock-frequency", &clk_rate);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* Create a CAN device instance */
+ ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ spin_lock_init(&priv->tx_lock);
+ priv->regmap = regmap;
+ priv->res = res;
+
+ priv->can.clock.freq = clk_rate;
+ priv->can.bittiming_const = &loongson_canfd_bit_timing_max;
+ priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data_max;
+ priv->can.do_set_mode = loongson_canfd_do_set_mode;
+ priv->can.do_set_bittiming = loongson_canfd_set_bittiming;
+ priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming;
+ priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter;
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
+ CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
+ CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
+ CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO;
+
+ ndev->irq = irq;
+ ndev->flags |= IFF_ECHO; /* We support local echo */
+ platform_set_drvdata(pdev, ndev);
+ ndev->netdev_ops = &loongson_canfd_netdev_ops;
+ SET_NETDEV_DEV(ndev, dev);
+
+ ret = loongson_canfd_reset(ndev);
+ if (ret < 0)
+ goto err_candev_free;
+
+ netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
+
+ ret = register_candev(ndev);
+ if (ret) {
+ dev_err(dev, "fail to register failed (err=%d)\n", ret);
+ goto err_candev_free;
+ }
+
+ return 0;
+
+err_candev_free:
+ free_candev(ndev);
+ return ret;
+}
+
+static void loongson_canfd_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ netdev_dbg(ndev, "loongson_canfd_remove");
+
+ unregister_candev(ndev);
+ netif_napi_del(&priv->napi);
+ free_candev(ndev);
+}
+
+static const struct acpi_device_id loongson_canfd_acpi_match[] = {
+ { "LOON0015" },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match);
+
+static struct platform_driver loongson_canfd_driver = {
+ .probe = loongson_canfd_probe,
+ .remove = loongson_canfd_remove,
+ .driver = {
+ .name = DEV_NAME,
+ .acpi_match_table = loongson_canfd_acpi_match,
+ },
+};
+module_platform_driver(loongson_canfd_driver);
+
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
new file mode 100644
index 000000000000..d3517a7ff4a3
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef __LOONGSON_CANFD_KFRAME_H
+#define __LOONGSON_CANFD_KFRAME_H
+
+/* CAN_Frame_format memory map */
+#define LOONGSON_CANFD_META0 0x0
+#define LOONGSON_CANFD_META1 0x4
+#define LOONGSON_CANFD_DATA_1_4_W 0x8
+#define LOONGSON_CANFD_DATA_5_8_W 0xc
+#define LOONGSON_CANFD_DATA_9_12_W 0x10
+#define LOONGSON_CANFD_DATA_13_16_W 0x14
+#define LOONGSON_CANFD_DATA_17_20_W 0x18
+#define LOONGSON_CANFD_DATA_21_24_W 0x1c
+#define LOONGSON_CANFD_DATA_25_28_W 0x20
+#define LOONGSON_CANFD_DATA_29_32_W 0x24
+#define LOONGSON_CANFD_DATA_33_36_W 0x28
+#define LOONGSON_CANFD_DATA_37_40_W 0x2c
+#define LOONGSON_CANFD_DATA_41_44_W 0x30
+#define LOONGSON_CANFD_DATA_45_48_W 0x34
+#define LOONGSON_CANFD_DATA_49_52_W 0x38
+#define LOONGSON_CANFD_DATA_53_56_W 0x3c
+#define LOONGSON_CANFD_DATA_57_60_W 0x40
+#define LOONGSON_CANFD_DATA_61_64_W 0x44
+
+/* FRAME_META0 registers */
+#define REG_IDENTIFIER_W_IDENTIFIER_EXT GENMASK(17, 0)
+#define REG_IDENTIFIER_W_IDENTIFIER_BASE GENMASK(28, 18)
+#define REG_FRAME_FORMAT_W_RTR BIT(29)
+#define REG_FRAME_FORMAT_W_XDT BIT(30)
+#define REG_FRAME_FORMAT_W_ESI_RSV BIT(31)
+
+/* FRAME_META1 registers */
+#define REG_META1_W_TIMESTAMP GENMASK(15, 0)
+#define REG_FRAME_FORMAT_W_DLC GENMASK(19, 16)
+#define REG_FRAME_FORMAT_W_BRS BIT(20)
+#define REG_FRAME_FORMAT_W_FDF BIT(21)
+#define REG_FRAME_FORMAT_W_RWCNT GENMASK(28, 24)
+
+/* DATA_1_4_W registers */
+#define REG_DATA_1_4_W_DATA_1 GENMASK(7, 0)
+#define REG_DATA_1_4_W_DATA_2 GENMASK(15, 8)
+#define REG_DATA_1_4_W_DATA_3 GENMASK(23, 16)
+#define REG_DATA_1_4_W_DATA_4 GENMASK(31, 24)
+
+/* DATA_5_8_W registers */
+#define REG_DATA_5_8_W_DATA_5 GENMASK(7, 0)
+#define REG_DATA_5_8_W_DATA_6 GENMASK(15, 8)
+#define REG_DATA_5_8_W_DATA_7 GENMASK(23, 16)
+#define REG_DATA_5_8_W_DATA_8 GENMASK(31, 24)
+
+/* DATA_9_12_W registers */
+#define REG_DATA_9_12_W_DATA_9 GENMASK(7, 0)
+#define REG_DATA_9_12_W_DATA_10 GENMASK(15, 8)
+#define REG_DATA_9_12_W_DATA_11 GENMASK(23, 16)
+#define REG_DATA_9_12_W_DATA_12 GENMASK(31, 24)
+
+/* DATA_13_16_W registers */
+#define REG_DATA_13_16_W_DATA_13 GENMASK(7, 0)
+#define REG_DATA_13_16_W_DATA_14 GENMASK(15, 8)
+#define REG_DATA_13_16_W_DATA_15 GENMASK(23, 16)
+#define REG_DATA_13_16_W_DATA_16 GENMASK(31, 24)
+
+/* DATA_17_20_W registers */
+#define REG_DATA_17_20_W_DATA_17 GENMASK(7, 0)
+#define REG_DATA_17_20_W_DATA_18 GENMASK(15, 8)
+#define REG_DATA_17_20_W_DATA_19 GENMASK(23, 16)
+#define REG_DATA_17_20_W_DATA_20 GENMASK(31, 24)
+
+/* DATA_21_24_W registers */
+#define REG_DATA_21_24_W_DATA_21 GENMASK(7, 0)
+#define REG_DATA_21_24_W_DATA_22 GENMASK(15, 8)
+#define REG_DATA_21_24_W_DATA_23 GENMASK(23, 16)
+#define REG_DATA_21_24_W_DATA_24 GENMASK(31, 24)
+
+/* DATA_25_28_W registers */
+#define REG_DATA_25_28_W_DATA_25 GENMASK(7, 0)
+#define REG_DATA_25_28_W_DATA_26 GENMASK(15, 8)
+#define REG_DATA_25_28_W_DATA_27 GENMASK(23, 16)
+#define REG_DATA_25_28_W_DATA_28 GENMASK(31, 24)
+
+/* DATA_29_32_W registers */
+#define REG_DATA_29_32_W_DATA_29 GENMASK(7, 0)
+#define REG_DATA_29_32_W_DATA_30 GENMASK(15, 8)
+#define REG_DATA_29_32_W_DATA_31 GENMASK(23, 16)
+#define REG_DATA_29_32_W_DATA_32 GENMASK(31, 24)
+
+/* DATA_33_36_W registers */
+#define REG_DATA_33_36_W_DATA_33 GENMASK(7, 0)
+#define REG_DATA_33_36_W_DATA_34 GENMASK(15, 8)
+#define REG_DATA_33_36_W_DATA_35 GENMASK(23, 16)
+#define REG_DATA_33_36_W_DATA_36 GENMASK(31, 24)
+
+/* DATA_37_40_W registers */
+#define REG_DATA_37_40_W_DATA_37 GENMASK(7, 0)
+#define REG_DATA_37_40_W_DATA_38 GENMASK(15, 8)
+#define REG_DATA_37_40_W_DATA_39 GENMASK(23, 16)
+#define REG_DATA_37_40_W_DATA_40 GENMASK(31, 24)
+
+/* DATA_41_44_W registers */
+#define REG_DATA_41_44_W_DATA_41 GENMASK(7, 0)
+#define REG_DATA_41_44_W_DATA_42 GENMASK(15, 8)
+#define REG_DATA_41_44_W_DATA_43 GENMASK(23, 16)
+#define REG_DATA_41_44_W_DATA_44 GENMASK(31, 24)
+
+/* DATA_45_48_W registers */
+#define REG_DATA_45_48_W_DATA_45 GENMASK(7, 0)
+#define REG_DATA_45_48_W_DATA_46 GENMASK(15, 8)
+#define REG_DATA_45_48_W_DATA_47 GENMASK(23, 16)
+#define REG_DATA_45_48_W_DATA_48 GENMASK(31, 24)
+
+/* DATA_49_52_W registers */
+#define REG_DATA_49_52_W_DATA_49 GENMASK(7, 0)
+#define REG_DATA_49_52_W_DATA_50 GENMASK(15, 8)
+#define REG_DATA_49_52_W_DATA_51 GENMASK(23, 16)
+#define REG_DATA_49_52_W_DATA_52 GENMASK(31, 24)
+
+/* DATA_53_56_W registers */
+#define REG_DATA_53_56_W_DATA_53 GENMASK(7, 0)
+#define REG_DATA_53_56_W_DATA_56 GENMASK(15, 8)
+#define REG_DATA_53_56_W_DATA_55 GENMASK(23, 16)
+#define REG_DATA_53_56_W_DATA_54 GENMASK(31, 24)
+
+/* DATA_57_60_W registers */
+#define REG_DATA_57_60_W_DATA_57 GENMASK(7, 0)
+#define REG_DATA_57_60_W_DATA_58 GENMASK(15, 8)
+#define REG_DATA_57_60_W_DATA_59 GENMASK(23, 16)
+#define REG_DATA_57_60_W_DATA_60 GENMASK(31, 24)
+
+/* DATA_61_64_W registers */
+#define REG_DATA_61_64_W_DATA_61 GENMASK(7, 0)
+#define REG_DATA_61_64_W_DATA_62 GENMASK(15, 8)
+#define REG_DATA_61_64_W_DATA_63 GENMASK(23, 16)
+#define REG_DATA_61_64_W_DATA_64 GENMASK(31, 24)
+
+/* FRAME_TEST_W registers */
+#define REG_FRAME_TEST_W_FSTC BIT(0)
+#define REG_FRAME_TEST_W_FCRC BIT(1)
+#define REG_FRAME_TEST_W_SDLC BIT(2)
+#define REG_FRAME_TEST_W_TPRM GENMASK(12, 8)
+
+#endif
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
new file mode 100644
index 000000000000..5e8889e3a924
--- /dev/null
+++ b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _LOONGSON_CANFD_KREGS_H
+#define _LOONGSON_CANFD_KREGS_H
+
+#define LOONGSON_CANFD_DEVICE_ID 0x0 /* CANFD controller ID Register */
+#define LOONGSON_CANFD_MODE 0x4 /* Mode Configuration Register */
+#define LOONGSON_CANFD_CONF 0x8 /* Configure Register */
+#define LOONGSON_CANFD_STAT 0xc /* Status Register */
+#define LOONGSON_CANFD_CMD 0x10 /* Command Register */
+#define LOONGSON_CANFD_INT_STAT 0x14 /* Interrupt Status Register */
+#define LOONGSON_CANFD_INT_ENA 0x18 /* Interrupt Enable Register */
+#define LOONGSON_CANFD_INT_MASK 0x1c /* Interrupt Mask Register */
+#define LOONGSON_CANFD_BTR_NORM 0x20 /* Normal Rate Configuration Register */
+#define LOONGSON_CANFD_BTR_FD 0x24 /* FD Data Rate Configuration Register */
+#define LOONGSON_CANFD_ERL 0x28 /* Error Threshold Configuration Register */
+#define LOONGSON_CANFD_FSTAT 0x2c /* Error Status Register */
+#define LOONGSON_CANFD_ERC 0x30 /* Error Count Register */
+#define LOONGSON_CANFD_BRE 0x34 /* Rate Error Count Register */
+#define LOONGSON_CANFD_CTR_PRES 0x38 /* Error Count Debug Register */
+#define LOONGSON_CANFD_ERR_CAPT 0x3c /* Error Capture Status Register */
+#define LOONGSON_CANFD_RETX_CNT 0x40 /* Retransmission Count Register */
+#define LOONGSON_CANFD_ALC 0x44 /* Lost Arbitration Capture Register */
+#define LOONGSON_CANFD_TRV_DLY 0x48 /* Transmission Delay Measurement Register */
+#define LOONGSON_CANFD_SSP_CFG 0x4c /* Second Sampling Point Configuration Register */
+#define LOONGSON_CANFD_RX_FR_CNT 0x50 /* Receive Message Count Register */
+#define LOONGSON_CANFD_TX_FR_CNT 0x54 /* Transmit Message Count Register */
+#define LOONGSON_CANFD_DEBUG 0x58 /* Debug Register */
+#define LOONGSON_CANFD_TS 0x5c /* Timestamp Register */
+#define LOONGSON_CANFD_TX_FRM_TST 0x60 /* Transmit Message Debug Register */
+#define LOONGSON_CANFD_FRC_DIV 0x64 /* Fractional Divider Ratio Register */
+#define LOONGSON_CANFD_FLT_A_MASK 0x68 /* Filter A Mask Register */
+#define LOONGSON_CANFD_FLT_A_VAL 0x6c /* Filter A value Register */
+#define LOONGSON_CANFD_FLT_B_MASK 0x70 /* Filter B Mask Register */
+#define LOONGSON_CANFD_FLT_B_VAL 0x74 /* Filter B value Register */
+#define LOONGSON_CANFD_FLT_C_MASK 0x78 /* Filter C Mask Register */
+#define LOONGSON_CANFD_FLT_C_VAL 0x7c /* Filter C value Register */
+#define LOONGSON_CANFD_FLT_R_LOW 0x80 /* Range Filter Low Threshold Register */
+#define LOONGSON_CANFD_FLT_R_HI 0x84 /* Range Filter High Threshold Register */
+#define LOONGSON_CANFD_FLT_CTRL 0x88 /* Filter Control Register */
+#define LOONGSON_CANFD_RX_MEM_INFO 0x8c /* Receive Buffer Information Register */
+#define LOONGSON_CANFD_RX_PRT 0x90 /* Receive Buffer Pointer Register */
+#define LOONGSON_CANFD_RX_STAT 0x94 /* Receive Buffer Status Register */
+#define LOONGSON_CANFD_RX_DATA 0x98 /* Receive Data Register */
+#define LOONGSON_CANFD_TX_STAT 0x9c /* Transmit Buffer Status Register */
+#define LOONGSON_CANFD_TX_CMD 0xa0 /* Transmit Command Register */
+#define LOONGSON_CANFD_TX_SEL 0xa4 /* Transmit Buffer Selection Register */
+#define LOONGSON_CANFD_TX_DATA_1 0xb0
+#define LOONGSON_CANFD_TX_DATA_2 0xb4
+#define LOONGSON_CANFD_TX_DATA_3 0xb8
+#define LOONGSON_CANFD_TX_DATA_4 0xbc
+#define LOONGSON_CANFD_TX_DATA_5 0xc0
+#define LOONGSON_CANFD_TX_DATA_6 0xc4
+#define LOONGSON_CANFD_TX_DATA_7 0xc8
+#define LOONGSON_CANFD_TX_DATA_8 0xcc
+#define LOONGSON_CANFD_TX_DATA_9 0xd0
+#define LOONGSON_CANFD_TX_DATA_10 0xd4
+#define LOONGSON_CANFD_TX_DATA_11 0xd8
+#define LOONGSON_CANFD_TX_DATA_12 0xdc
+#define LOONGSON_CANFD_TX_DATA_13 0xe0
+#define LOONGSON_CANFD_TX_DATA_14 0xe4
+#define LOONGSON_CANFD_TX_DATA_15 0xe8
+#define LOONGSON_CANFD_TX_DATA_16 0xec
+#define LOONGSON_CANFD_TX_DATA_17 0xf0
+#define LOONGSON_CANFD_TX_DATA_18 0xf4
+
+/* Bitfields of CANFD controller ID register */
+#define REG_ID_ID GENMASK(15, 0)
+#define REG_ID_VER_MIN GENMASK(23, 16)
+#define REG_ID_VER_MAJ GENMASK(31, 24)
+
+/* Bitfields of Mode Configuration register */
+#define REG_MODE_RST BIT(0)
+#define REG_MODE_BMM BIT(1)
+#define REG_MODE_STM BIT(2)
+#define REG_MODE_AFM BIT(3)
+#define REG_MODE_FDE BIT(4)
+#define REG_MODE_TTTM BIT(5)
+#define REG_MODE_ROM BIT(6)
+#define REG_MODE_ACF BIT(7)
+#define REG_MODE_TSTM BIT(8)
+#define REG_MODE_RXBAM BIT(9)
+#define REG_MODE_ITSM BIT(10)
+#define REG_MODE_RTSOP BIT(12)
+#define REG_MODE_BUFM BIT(13)
+
+/* Bitfields of Configure register */
+#define REG_CONF_RTRLE BIT(0)
+#define REG_CONF_RTRTH GENMASK(4, 1)
+#define REG_CONF_ILBP BIT(5)
+#define REG_CONF_ENA BIT(6)
+#define REG_CONF_NISOFD BIT(7)
+#define REG_CONF_PEX BIT(8)
+#define REG_CONF_FDRF BIT(10)
+
+/* Bitfields of Status register */
+#define REG_STAT_RXNE BIT(0)
+#define REG_STAT_DOR BIT(1)
+#define REG_STAT_TXNF BIT(2)
+#define REG_STAT_EFT BIT(3)
+#define REG_STAT_RXS BIT(4)
+#define REG_STAT_TXS BIT(5)
+#define REG_STAT_EWL BIT(6)
+#define REG_STAT_IDLE BIT(7)
+#define REG_STAT_PEXS BIT(8)
+#define REG_STAT_STCNT BIT(16)
+
+/* Bitfields of Command register */
+#define REG_CMD_RXRPMV BIT(1)
+#define REG_CMD_RRB BIT(2)
+#define REG_CMD_CDO BIT(3)
+#define REG_CMD_ERCRST BIT(4)
+#define REG_CMD_RXFCRST BIT(5)
+#define REG_CMD_TXFCRST BIT(6)
+#define REG_CMD_CPEXS BIT(7)
+
+/* Bitfields of Interrupt Status register */
+#define REG_INT_STAT_RXI BIT(0)
+#define REG_INT_STAT_TXI BIT(1)
+#define REG_INT_STAT_EWLI BIT(2)
+#define REG_INT_STAT_DOI BIT(3)
+#define REG_INT_STAT_FCSI BIT(4)
+#define REG_INT_STAT_ALI BIT(5)
+#define REG_INT_STAT_BEI BIT(6)
+#define REG_INT_STAT_RXFI BIT(7)
+#define REG_INT_STAT_BSI BIT(8)
+#define REG_INT_STAT_RBNEI BIT(9)
+#define REG_INT_STAT_TXBHCI BIT(10)
+#define REG_INT_STAT_OFI BIT(11)
+#define REG_INT_STAT_DMADI BIT(12)
+
+#define REG_INT_STAT_ERRORI (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI)
+
+/* Bitfields of Interrupt Enable register */
+#define REG_INT_ENA_CLR GENMASK(28, 16)
+#define REG_INT_ENA_SET GENMASK(12, 0)
+
+/* Bitfields of Interrupt Mask register */
+#define REG_INT_MASK_SET GENMASK(12, 0)
+#define REG_INT_MASK_CLR GENMASK(28, 16)
+
+/* Bitfields of Normal Rate Configuration register */
+#define REG_BTR_PROP GENMASK(6, 0)
+#define REG_BTR_PH1 GENMASK(12, 7)
+#define REG_BTR_PH2 GENMASK(18, 13)
+#define REG_BTR_BRP GENMASK(22, 19)
+#define REG_BTR_SJW GENMASK(31, 27)
+
+/* Bitfields of FD Data Rate Configuration register */
+#define REG_BTR_FD_PROP GENMASK(6, 0)
+#define REG_BTR_FD_PH1 GENMASK(11, 7)
+#define REG_BTR_FD_PH2 GENMASK(17, 13)
+#define REG_BTR_FD_BRP GENMASK(26, 19)
+#define REG_BTR_FD_SJW GENMASK(31, 27)
+
+/* Bitfields of Error Threshold Configuration register */
+#define REG_ERL_ERP GENMASK(7, 0)
+#define REG_ERL_EW GENMASK(23, 16)
+
+/* Bitfields of Error Status register */
+#define REG_FSTAT_ERA BIT(0)
+#define REG_FSTAT_ERP BIT(1)
+#define REG_FSTAT_BOF BIT(2)
+
+/* Bitfields of Error Count register */
+#define REG_ERC_TEC GENMASK(8, 0)
+#define REG_ERC_REC GENMASK(24, 16)
+
+/* Bitfields of Rate Error Count register */
+#define REG_BRE_NORM GENMASK(15, 0)
+#define REG_BRE_FD_DATA GENMASK(31, 16)
+
+/* Bitfields of Error Count Debug register */
+#define REG_CTR_PRES_CTPV GENMASK(8, 0)
+#define REG_CTR_PRES_PTX BIT(9)
+#define REG_CTR_PRES_PRX BIT(10)
+
+/* Bitfields of Error Capture Status register */
+#define REG_ERR_CAPT_POS GENMASK(4, 0)
+#define REG_ERR_CAPT_TYPE GENMASK(7, 5)
+
+/* Bitfields of Retransmission Count register */
+#define REG_RETX_CNT_VAL GENMASK(3, 0)
+
+/* Bitfields of Lost Arbitration Capture register */
+#define REG_ALC_BIT_POS GENMASK(4, 0)
+#define REG_ALC_ID_FIELD GENMASK(7, 5)
+
+/* Bitfields of Transmission Delay Measurement register */
+#define REG_TRV_DLY_VAL GENMASK(6, 0)
+
+/* Bitfields of Second Sampling Point Configuration register */
+#define REG_SSP_CFG_OFF GENMASK(7, 0)
+#define REG_SSP_CFG_SRC GENMASK(9, 8)
+#define REG_SSP_CFG_SAT BIT(10)
+
+/* Bitfields of Receive Message Count register */
+#define REG_RX_FR_CNT_VAL GENMASK(31, 0)
+
+/* Bitfields of Transmit Message Count register */
+#define REG_TX_FR_CNT_VAL GENMASK(31, 0)
+
+/* Bitfields of Debug register */
+#define REG_DEBUG_STF_CNT GENMASK(2, 0)
+#define REG_DEBUG_DSTF_CNT GENMASK(5, 3)
+#define REG_DEBUG_PC_ARB BIT(6)
+#define REG_DEBUG_PC_CON BIT(7)
+#define REG_DEBUG_PC_DAT BIT(8)
+#define REG_DEBUG_PC_STC BIT(9)
+#define REG_DEBUG_PC_CRC BIT(10)
+#define REG_DEBUG_PC_CRCD BIT(11)
+#define REG_DEBUG_PC_ACK BIT(12)
+#define REG_DEBUG_PC_ACKD BIT(13)
+#define REG_DEBUG_PC_EOF BIT(14)
+#define REG_DEBUG_PC_INT BIT(15)
+#define REG_DEBUG_PC_SUSP BIT(16)
+#define REG_DEBUG_PC_OVR BIT(17)
+#define REG_DEBUG_PC_SOF BIT(18)
+
+/* Bitfields of Timestamp register */
+#define REG_TS_TIMESTAMP GENMASK(15, 0)
+#define REG_TS_PSC GENMASK(24, 16)
+
+/* Bitfields of Fractional Divider Ratio register */
+#define REG_FRC_FRC_DBT GENMASK(15, 8)
+#define REG_FRC_FRC_NBT GENMASK(7, 0)
+
+/* Bitfields of Filter A Mask register */
+#define REG_FIL_A_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter A value register */
+#define REG_FIL_A_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter B Mask register */
+#define REG_FIL_B_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter B value register */
+#define REG_FIL_B_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter C Mask register */
+#define REG_FIL_C_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter C value register */
+#define REG_FIL_C_VAL GENMASK(28, 0)
+
+/* Bitfields of Range Filter Low Threshold register */
+#define REG_FIL_R_LOW_VAL GENMASK(28, 0)
+
+/* Bitfields of Range Filter High Threshold register */
+#define REG_FIL_R_HI_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter Control register */
+#define REG_FIL_CTRL_FANB BIT(0)
+#define REG_FIL_CTRL_FANE BIT(1)
+#define REG_FIL_CTRL_FAFB BIT(2)
+#define REG_FIL_CTRL_FAFE BIT(3)
+#define REG_FIL_CTRL_FBNB BIT(4)
+#define REG_FIL_CTRL_FBNE BIT(5)
+#define REG_FIL_CTRL_FBFB BIT(6)
+#define REG_FIL_CTRL_FBFE BIT(7)
+#define REG_FIL_CTRL_FCNB BIT(8)
+#define REG_FIL_CTRL_FCNE BIT(9)
+#define REG_FIL_CTRL_FCFB BIT(10)
+#define REG_FIL_CTRL_FCFE BIT(11)
+#define REG_FIL_CTRL_FRNB BIT(12)
+#define REG_FIL_CTRL_FRNE BIT(13)
+#define REG_FIL_CTRL_FRFB BIT(14)
+#define REG_FIL_CTRL_FRFE BIT(15)
+#define REG_FIL_CTRL_SFA BIT(16)
+#define REG_FIL_CTRL_SFB BIT(17)
+#define REG_FIL_CTRL_SFC BIT(18)
+#define REG_FIL_CTRL_SFR BIT(19)
+
+/* Bitfields of Receive Buffer Information register */
+#define REG_RX_MEM_INFO_BUFF_SIZE GENMASK(12, 0)
+#define REG_RX_MEM_INFO_MEM_FREE GENMASK(28, 16)
+
+/* Bitfields of Receive Buffer Pointer register */
+#define REG_RX_PTR_WPP GENMASK(11, 0)
+#define REG_RX_PTR_RPP GENMASK(27, 16)
+
+/* Bitfields of Receive Buffer Status register */
+#define REG_RX_STAT_RXE BIT(0)
+#define REG_RX_STAT_RXF BIT(1)
+#define REG_RX_STAT_RXMOF BIT(2)
+#define REG_RX_STAT_RXFRC GENMASK(14, 4)
+#define REG_RX_STAT_RTSOP BIT(16)
+
+/* Bitfields of Receive Data register */
+#define REG_RX_DATA_VAL GENMASK(31, 0)
+
+/* Bitfields of Transmit Buffer Status register */
+#define REG_TX_STAT_BRP GENMASK(7, 0)
+#define REG_TX_STAT_TXS GENMASK(10, 8)
+#define REG_TX_STAT_BS GENMASK(31, 16)
+
+#define REG_TX_STAT_BS_TX1 GENMASK(17, 16)
+#define REG_TX_STAT_BS_TX2 GENMASK(19, 18)
+#define REG_TX_STAT_BS_TX3 GENMASK(21, 20)
+#define REG_TX_STAT_BS_TX4 GENMASK(23, 22)
+#define REG_TX_STAT_BS_TX5 GENMASK(25, 24)
+#define REG_TX_STAT_BS_TX6 GENMASK(27, 26)
+#define REG_TX_STAT_BS_TX7 GENMASK(29, 28)
+#define REG_TX_STAT_BS_TX8 GENMASK(31, 30)
+
+/* Bitfields of Transmit Command register */
+#define REG_TX_CMD_BAR GENMASK(7, 0)
+#define REG_TX_CMD_BCR GENMASK(15, 8)
+#define REG_TX_CMD_BSC GENMASK(23, 16)
+
+/* Bitfields of Transmit Buffer Selection register */
+#define REG_TX_SEL_BUF_SEL GENMASK(3, 0)
+#define REG_TX_SEL_BUF_CNT GENMASK(7, 4)
+
+#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/2] can: loongson_canfd: Add RXDMA support
2026-04-27 7:17 [PATCH 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-04-27 7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
@ 2026-04-27 7:18 ` Binbin Zhou
2026-05-06 17:51 ` Vincent Mailhol
1 sibling, 1 reply; 5+ messages in thread
From: Binbin Zhou @ 2026-04-27 7:18 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Add optional DMA support for RX path using the Loongson APB CMC DMA
engine. When a DMA channel is successfully requested, the driver:
- Uses DMA cyclic transfers to write incoming CAN frames directly to
a coherent DMA buffer
- Replaces RXBNEI (RX buffer not empty interrupt) with DMADI (DMA
done interrupt)
- Dynamically switches between DMA and PIO modes based on channel
availability
This significantly reduces CPU intervention under high RX load,
especially beneficial for CAN FD at higher data rates.
Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/net/can/loongson_canfd/Kconfig | 2 +-
.../net/can/loongson_canfd/loongson_canfd.c | 179 ++++++++++++++++--
2 files changed, 160 insertions(+), 21 deletions(-)
diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
index 5a2540bb5410..8fe44b804991 100644
--- a/drivers/net/can/loongson_canfd/Kconfig
+++ b/drivers/net/can/loongson_canfd/Kconfig
@@ -5,7 +5,7 @@
#
config CAN_LOONGSON_CANFD
tristate "Loongson CAN-FD driver"
- depends on HAS_IOMEM
+ depends on HAS_IOMEM && LOONGSON2_APB_CMC_DMA
select REGMAP_MMIO
help
This is a canfd driver switch for the Loongson platform,
diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
index 20ac95dc528d..ba9570c34c94 100644
--- a/drivers/net/can/loongson_canfd/loongson_canfd.c
+++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
@@ -6,10 +6,14 @@
*/
#include <linux/acpi.h>
+#include <linux/acpi_dma.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/can/dev.h>
#include <linux/can/error.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@@ -26,13 +30,21 @@
#define DEV_NAME "loongson_canfd"
#define LOONGSON_CANFD_TXBUF_NUM 8
#define LOONGSON_CANFD_ID 0xBABE
+#define RX_BUF_SIZE SZ_1K
+#define LOONGSON_CANFD_DMA_RXDATA_NUM (RX_BUF_SIZE / DMA_SLAVE_BUSWIDTH_4_BYTES)
struct loongson_canfd_priv {
struct can_priv can; /* must be first member! */
struct napi_struct napi;
struct regmap *regmap;
struct resource *res;
+ struct dma_chan *rx_ch;
+ dma_addr_t rx_dma_buf; /* dma rx buffer bus address */
+ unsigned int *rx_buf; /* dma rx buffer cpu address */
+ u16 last_res;
spinlock_t tx_lock; /* protect the sending queue */
+ u32 (*get_rx_data)(struct loongson_canfd_priv *priv);
+ bool (*get_rx_pending)(struct loongson_canfd_priv *priv);
};
enum loongson_canfd_tx_bs {
@@ -137,6 +149,54 @@ static int loongson_canfd_reset(struct net_device *ndev)
return 0;
}
+static u32 loongson_canfd_get_rxdma_data(struct loongson_canfd_priv *priv)
+{
+ u32 c = 0;
+
+ c = priv->rx_buf[LOONGSON_CANFD_DMA_RXDATA_NUM - priv->last_res--];
+ if (priv->last_res == 0)
+ priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
+
+ return c;
+}
+
+static bool loongson_canfd_rxdma_pending(struct loongson_canfd_priv *priv)
+{
+ enum dma_status status;
+ struct dma_tx_state state;
+
+ status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state);
+
+ if (priv->last_res != (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES) &&
+ status == DMA_IN_PROGRESS)
+ return true;
+
+ return false;
+}
+
+static u32 loongson_canfd_get_poll_data(struct loongson_canfd_priv *priv)
+{
+ u32 data;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+
+ return data;
+}
+
+static bool loongson_canfd_rxpoll_pending(struct loongson_canfd_priv *priv)
+{
+ u32 rx_sts;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
+
+ return FIELD_GET(REG_RX_STAT_RXFRC, rx_sts) ? true : false;
+}
+
+static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev)
+{
+ dma_free_coherent(dev, RX_BUF_SIZE, priv->rx_buf, priv->rx_dma_buf);
+}
+
static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
{
struct loongson_canfd_priv *priv = netdev_priv(ndev);
@@ -308,7 +368,9 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
- int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_RBNEI;
+ int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
+ int_ena |= priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
+
int_msk = ~int_ena; /* Mask all disabled interrupts */
/* It's after reset, so there is no need to clear anything */
@@ -514,12 +576,12 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struc
/* Data */
for (i = 0; i < len; i += 4) {
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ data = priv->get_rx_data(priv);
*(__le32 *)(cf->data + i) = cpu_to_le32(data);
}
while (unlikely(i < wc * 4)) {
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ data = priv->get_rx_data(priv);
i += 4;
}
}
@@ -532,8 +594,8 @@ static int loongson_canfd_rx(struct net_device *ndev)
struct sk_buff *skb;
u32 meta0, meta1;
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
+ meta0 = priv->get_rx_data(priv);
+ meta1 = priv->get_rx_data(priv);
/* Number of characters received */
if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
@@ -718,16 +780,16 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
struct net_device *ndev = napi->dev;
struct loongson_canfd_priv *priv = netdev_priv(ndev);
int work_done = 0, res = 1;
- u32 sts, rx_frc, rx_sts;
+ int int_ena = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
+ u32 sts;
+ bool rx_frc;
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
- rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
+ rx_frc = priv->get_rx_pending(priv);
while (rx_frc && work_done < quota && res > 0) {
res = loongson_canfd_rx(ndev);
work_done++;
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
- rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
+ rx_frc = priv->get_rx_pending(priv);
}
/* Check for RX FIFO Overflow */
@@ -757,13 +819,11 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
if (!rx_frc && res != 0) {
if (napi_complete_done(napi, work_done)) {
/*
- * Clear and enable RBNEI. It is level-triggered, so
+ * Clear and enable RBNEI/DMADI. It is level-triggered, so
* there is no race condition.
*/
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT,
- REG_INT_STAT_RBNEI);
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
- (REG_INT_STAT_RBNEI << 16));
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, int_ena);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, (int_ena << 16));
}
}
@@ -855,7 +915,7 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
struct net_device *ndev = (struct net_device *)dev_id;
struct loongson_canfd_priv *priv = netdev_priv(ndev);
int irq_loops;
- u32 isr;
+ u32 isr, mask;
u16 icr;
for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
@@ -864,14 +924,16 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
if (!isr)
return irq_loops ? IRQ_HANDLED : IRQ_NONE;
+ mask = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
+
/* Receive Buffer Not Empty Interrupt */
- if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
+ if (isr & mask) {
/*
* Mask RXBNEI the first, then clear interrupt and schedule NAPI.
* Even if another IRQ fires, RBNEI will always be 0 (masked).
*/
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, mask);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, mask);
napi_schedule(&priv->napi);
}
@@ -1054,11 +1116,56 @@ static const struct regmap_config loongson_cangfd_regmap = {
.cache_type = REGCACHE_MAPLE,
};
+static int loongson_canfd_rxdma_init(struct loongson_canfd_priv *priv, struct device *dev)
+{
+ struct dma_slave_config config;
+ struct dma_async_tx_descriptor *desc = NULL;
+ int ret;
+
+ priv->rx_buf = dma_alloc_coherent(dev, RX_BUF_SIZE, &priv->rx_dma_buf, GFP_KERNEL);
+ if (!priv->rx_buf)
+ return -ENOMEM;
+
+ /* Configure DMA channel */
+ memset(&config, 0, sizeof(config));
+ config.src_addr = priv->res->start + LOONGSON_CANFD_RX_DATA;
+ config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ ret = dmaengine_slave_config(priv->rx_ch, &config);
+ if (ret < 0) {
+ dev_err(dev, "rx dma channel config failed\n");
+ goto err_config;
+ }
+
+ /* Prepare a DMA cyclic transaction */
+ desc = dmaengine_prep_dma_cyclic(priv->rx_ch, priv->rx_dma_buf,
+ RX_BUF_SIZE, RX_BUF_SIZE,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dev, "rx dma prep cyclic failed\n");
+ ret = -EBUSY;
+ goto err_config;
+ }
+
+ /* Push current DMA transaction in the pending queue */
+ dmaengine_submit(desc);
+
+ /* Issue pending DMA requests */
+ dma_async_issue_pending(priv->rx_ch);
+
+ return 0;
+
+err_config:
+ loongson_canfd_rxdma_remove(priv, dev);
+ return ret;
+}
+
static int loongson_canfd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct loongson_canfd_priv *priv;
struct net_device *ndev;
+ struct dma_chan *rx_ch;
struct regmap *regmap;
struct resource *res;
void __iomem *base;
@@ -1079,14 +1186,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
if (irq < 0)
return irq;
+ rx_ch = dma_request_chan(dev, "rx");
+ if (PTR_ERR(rx_ch) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (IS_ERR(rx_ch)) {
+ dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n");
+ rx_ch = NULL;
+ }
+
/* Create a CAN device instance */
ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
if (!ndev)
- return -ENOMEM;
+ goto err_dma_rx;
priv = netdev_priv(ndev);
spin_lock_init(&priv->tx_lock);
priv->regmap = regmap;
+ priv->rx_ch = rx_ch;
priv->res = res;
priv->can.clock.freq = clk_rate;
@@ -1111,6 +1228,19 @@ static int loongson_canfd_probe(struct platform_device *pdev)
if (ret < 0)
goto err_candev_free;
+ if (priv->rx_ch) {
+ priv->get_rx_data = loongson_canfd_get_rxdma_data;
+ priv->get_rx_pending = loongson_canfd_rxdma_pending;
+ priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
+ ret = loongson_canfd_rxdma_init(priv, dev);
+ if (ret) {
+ dev_err(dev, "interrupt mode used for rx (no dma)\n");
+ goto err_candev_free;
+ }
+ } else {
+ priv->get_rx_data = loongson_canfd_get_poll_data;
+ priv->get_rx_pending = loongson_canfd_rxpoll_pending;
+ }
netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
ret = register_candev(ndev);
@@ -1123,6 +1253,9 @@ static int loongson_canfd_probe(struct platform_device *pdev)
err_candev_free:
free_candev(ndev);
+err_dma_rx:
+ if (rx_ch)
+ dma_release_channel(rx_ch);
return ret;
}
@@ -1133,6 +1266,11 @@ static void loongson_canfd_remove(struct platform_device *pdev)
netdev_dbg(ndev, "loongson_canfd_remove");
+ if (priv->rx_ch) {
+ loongson_canfd_rxdma_remove(priv, &pdev->dev);
+ dma_release_channel(priv->rx_ch);
+ }
+
unregister_candev(ndev);
netif_napi_del(&priv->napi);
free_candev(ndev);
@@ -1154,6 +1292,7 @@ static struct platform_driver loongson_canfd_driver = {
};
module_platform_driver(loongson_canfd_driver);
+MODULE_SOFTDEP("pre: loongson2-apb-cmc-dma");
MODULE_AUTHOR("Loongson Technology Corporation Limited");
MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] can: Add Loongson CAN-FD controller support
2026-04-27 7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
@ 2026-05-06 17:50 ` Vincent Mailhol
0 siblings, 0 replies; 5+ messages in thread
From: Vincent Mailhol @ 2026-05-06 17:50 UTC (permalink / raw)
To: Binbin Zhou, Binbin Zhou, Huacai Chen, Marc Kleine-Budde,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai
Hi Binbin
A first review. The comments are not yet exhaustive. Please fix not only
the occurences on which I commented, but please also fix the other
similar patterns.
On 27/04/2026 at 09:17, Binbin Zhou wrote:
> Add driver for the CAN-FD controller integrated into Loongson CPUs. The
> controller supports:
>
> - Classic CAN and CAN FD (ISO/non-ISO)
> - Data rates up to 5 Mbps (nominal) and 10 Mbps (data)
> - 8 independent TX buffers
> - Hardware TX retransmission limit and one-shot mode
> - Error counters, bus error reporting, arbitration lost capture
> - NAPI-based RX path
> - Loopback and listen-only modes
>
> The driver is implemented as a standard Linux CAN network driver using
> the CAN framework APIs.
>
> Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> MAINTAINERS | 7 +
> drivers/net/can/Kconfig | 1 +
> drivers/net/can/Makefile | 1 +
> drivers/net/can/loongson_canfd/Kconfig | 16 +
> drivers/net/can/loongson_canfd/Makefile | 6 +
> .../net/can/loongson_canfd/loongson_canfd.c | 1159 +++++++++++++++++
> .../loongson_canfd/loongson_canfd_kframe.h | 142 ++
> .../can/loongson_canfd/loongson_canfd_kregs.h | 315 +++++
> 8 files changed, 1647 insertions(+)
> create mode 100644 drivers/net/can/loongson_canfd/Kconfig
> create mode 100644 drivers/net/can/loongson_canfd/Makefile
> create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd.c
> create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
> create mode 100644 drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
Your driver is just one translation unit. I don't think you need a
folder for that. You can just create
drivers/net/can/loongson_canfd.c
and add all your macro definitions at the top of your file.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7a2ffd9d37d5..5534db894cdc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14935,6 +14935,13 @@ F: arch/loongarch/
> F: drivers/*/*loongarch*
> F: drivers/cpufreq/loongson3_cpufreq.c
>
> +LOONGSON CAN FD DRIVER
> +M: Bingxiong Li <libingxiong@loongson.cn>
> +M: Binbin Zhou <zhoubinbin@loongson.cn>
> +L: linux-can@vger.kernel.org
> +S: Maintained
> +F: drivers/net/can/loongson_canfd/
> +
> LOONGSON GPIO DRIVER
> M: Yinbo Zhu <zhuyinbo@loongson.cn>
> L: linux-gpio@vger.kernel.org
> diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
> index e15e320db476..1306d2d80197 100644
> --- a/drivers/net/can/Kconfig
> +++ b/drivers/net/can/Kconfig
> @@ -242,6 +242,7 @@ source "drivers/net/can/ifi_canfd/Kconfig"
> source "drivers/net/can/m_can/Kconfig"
> source "drivers/net/can/mscan/Kconfig"
> source "drivers/net/can/peak_canfd/Kconfig"
> +source "drivers/net/can/loongson_canfd/Kconfig"
> source "drivers/net/can/rcar/Kconfig"
> source "drivers/net/can/rockchip/Kconfig"
> source "drivers/net/can/sja1000/Kconfig"
> diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> index d7bc10a6b8ea..a39e26727060 100644
> --- a/drivers/net/can/Makefile
> +++ b/drivers/net/can/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/
> obj-$(CONFIG_CAN_MSCAN) += mscan/
> obj-$(CONFIG_CAN_M_CAN) += m_can/
> obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
> +obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd/
> obj-$(CONFIG_CAN_SJA1000) += sja1000/
> obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
> obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
> diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
> new file mode 100644
> index 000000000000..5a2540bb5410
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Loongson canfd drivers
> +#
> +#
> +config CAN_LOONGSON_CANFD
> + tristate "Loongson CAN-FD driver"
> + depends on HAS_IOMEM
> + select REGMAP_MMIO
> + help
> + This is a canfd driver switch for the Loongson platform,
> + integrated with the Loongson-2K series.
> +
> + You can choose yes or no here.For detailed information about
^^^^^^^^^
This is a tristate configuration. The choices are yes, no or module.
Also, no need to state that. Just state the name of the module.
Something like:
To compile as a module, choose M here: the module will be
called loongson_canfd.
> + the user manual, please log in to the Loongson official
> + website.(https://loongson.cn/)
^^^^^^^^^^
Fix the indentation. You are using only spaces here where it should be
one tabulation and two spaces.
> diff --git a/drivers/net/can/loongson_canfd/Makefile b/drivers/net/can/loongson_canfd/Makefile
> new file mode 100644
> index 000000000000..38d1f91bd0b8
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Makefile for the Loongson CAN-FD controller drivers.
> +#
> +
> +obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
> new file mode 100644
> index 000000000000..20ac95dc528d
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
> @@ -0,0 +1,1159 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * LOONGSON CANFD controller
> + *
> + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/can/dev.h>
> +#include <linux/can/error.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/skbuff.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include "loongson_canfd_kframe.h"
> +#include "loongson_canfd_kregs.h"
> +
> +#define DEV_NAME "loongson_canfd"
> +#define LOONGSON_CANFD_TXBUF_NUM 8
> +#define LOONGSON_CANFD_ID 0xBABE
> +
> +struct loongson_canfd_priv {
> + struct can_priv can; /* must be first member! */
> + struct napi_struct napi;
> + struct regmap *regmap;
> + struct resource *res;
> + spinlock_t tx_lock; /* protect the sending queue */
> +};
> +
> +enum loongson_canfd_tx_bs {
> + TX_BS_IDLE = 0x0,
> + TX_BS_VALID = 0x1,
> + TX_BS_FAIL = 0x2,
> + TX_BS_CANCEL = 0x3
> +};
> +
> +enum loongson_canfd_txtb_command {
> + TXT_CMD_SET_ADD = 0x01,
> + TXT_CMD_SET_CANCEL = 0x02
> +};
> +
> +static const struct can_bittiming_const loongson_canfd_bit_timing_max = {
^^^^
The bittiming also contains minimum values. Drop this _max suffix.
> + .name = DEV_NAME,
> + .tseg1_min = 2,
> + .tseg1_max = 190,
> + .tseg2_min = 2,
> + .tseg2_max = 63,
> + .sjw_max = 31,
> + .brp_min = 1,
> + .brp_max = 15,
> + .brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const loongson_canfd_bit_timing_data_max = {
Same as above, drop the _max suffix.
> + .name = DEV_NAME,
> + .tseg1_min = 2,
> + .tseg1_max = 190,
> + .tseg2_min = 2,
> + .tseg2_max = 63,
> + .sjw_max = 31,
> + .brp_min = 1,
> + .brp_max = 255,
> + .brp_inc = 1,
> +};
> +
> +static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv)
> +{
> + u32 conf;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
> +
> + return !!FIELD_GET(REG_CONF_ENA, conf);
> +}
> +
> +static bool loongson_canfd_txtnf(struct loongson_canfd_priv *priv)
^^^^^
What is the meaning of txtnf?
> +{
> + u32 sts;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> +
> + return FIELD_GET(REG_TX_STAT_BRP, sts) != 0xff;
^^^^
Avoid this magic number.
Also, your function seems to be equivalent to:
return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT,
REG_TX_STAT_BRP);
> +}
> +
> +static inline enum loongson_canfd_tx_bs
^^^^^^
No need for the inline. Let the compiler decide for you if this is worth
inlining.
> +loongson_canfd_get_bs(struct loongson_canfd_priv *priv, u8 bs_id)
> +{
> + u32 sts, bs;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> + bs = FIELD_GET(REG_TX_STAT_BS, sts);
> +
> + return FIELD_GET(GENMASK(1, 0), (bs >> (bs_id * 2)));
Your use of FIELD_GET is odd. The intended way to use FIELD_GET is to
let it handle all the shift logic for you. You shouldn't be doing a
right shift by hand here.
Note that you have the field_get() function which can be used with
non-constanst masks.
> +}
> +
> +static unsigned int loongson_canfd_get_tx_id(struct loongson_canfd_priv *priv)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++)
> + if (loongson_canfd_get_bs(priv, i))
> + break;
> +
> + return i;
> +}
> +
> +static bool loongson_canfd_txbuf_writable(struct loongson_canfd_priv *priv, u8 buf_id)
> +{
> + enum loongson_canfd_tx_bs bs;
> + u32 sts;
> +
> + bs = loongson_canfd_get_bs(priv, buf_id);
> + if (bs)
> + return false;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
> + if (FIELD_GET(BIT(0), sts >> buf_id))> + return false;
Here also, consider using regmap_test_bits().
> + return true;
> +}
> +
> +static int loongson_canfd_reset(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST);
> + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM);
> +
> + return 0;
> +}
> +
> +static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + u32 phase_seg1 = bt->phase_seg1;
> + u32 prop_seg = bt->prop_seg;
> + int max_ph1_len = 31;
> + u32 btr = 0;
> +
> + if (loongson_canfd_enabled(priv)) {
> + netdev_err(ndev, "BUG! Cannot set bittiming - CAN is enabled\n");
> + return -EPERM;
> + }
> +
> + if (nominal)
> + max_ph1_len = 63;
> +
> + /*
> + * The timing calculation functions have only constraints on tseg1,
> + * which is prop_seg + phase1_seg combined.
> + * tseg1 is then split in half and stored into prog_seg and phase_seg1.
> + * In Loongson CAN-FD, PROP is 6/7 bits wide but PH1 only 6/5, so we must
> + * re-distribute the values here.
> + */
> + if (phase_seg1 > max_ph1_len) {
> + prop_seg += phase_seg1 - max_ph1_len;
> + phase_seg1 = max_ph1_len;
> + bt->prop_seg = prop_seg;
> + bt->phase_seg1 = phase_seg1;
> + }
> +
> + if (nominal) {
> + btr = FIELD_PREP(REG_BTR_PROP, prop_seg) |
> + FIELD_PREP(REG_BTR_PH1, phase_seg1) |
> + FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) |
> + FIELD_PREP(REG_BTR_BRP, bt->brp) |
> + FIELD_PREP(REG_BTR_SJW, bt->sjw);
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr);
> + } else {
> + btr = FIELD_PREP(REG_BTR_FD_PROP, prop_seg) |
> + FIELD_PREP(REG_BTR_FD_PH1, phase_seg1) |
> + FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) |
> + FIELD_PREP(REG_BTR_FD_BRP, bt->brp) |
> + FIELD_PREP(REG_BTR_FD_SJW, bt->sjw);
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr);
> + }
> +
> + return 0;
> +}
> +
> +static int loongson_canfd_set_bittiming(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct can_bittiming *bt = &priv->can.bittiming;
> +
> + /* Note that bt may be modified here */
> + return loongson_canfd_set_btr(ndev, bt, true);
> +}
> +
> +static int loongson_canfd_set_data_bittiming(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
> +
> + /* Note that dbt may be modified here */
> + return loongson_canfd_set_btr(ndev, dbt, false);
> +}
> +
> +static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
> + int ssp_offset = 0;
> + u32 ssp_cfg = 0; /* No SSP by default */
> +
> + if (loongson_canfd_enabled(priv)) {
> + netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n");
> + return -EPERM;
> + }
> +
> + /* Use SSP for bit-rates above 1 Mbits/s */
> + if (dbt->bitrate > 1000000) {
> + /* Calculate SSP in minimal time quanta */
> + ssp_offset = (priv->can.clock.freq / 1000) * dbt->sample_point / dbt->bitrate;
> + if (ssp_offset > 127) {
> + netdev_warn(ndev, "SSP offset saturated to 127\n");
> + ssp_offset = 127;
> + }
> +
> + ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, ssp_offset) |
> + FIELD_PREP(REG_SSP_CFG_SRC, 0x0);
> + } else {
> + ssp_cfg |= FIELD_PREP(REG_SSP_CFG_SRC, 0x1);
> + }
Use the CAN TDC framework to get the SSP value (c.f. struct can_tdc,
struct can_tdc_const and can_fd_tdc_is_enabled())
> + regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg);
> +
> + return 0;
> +}
> +
> +static void loongson_canfd_set_mode(struct loongson_canfd_priv *priv,
> + const struct can_ctrlmode *ctrlmode)
> +{
> + u32 mode, conf;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode);
> +
> + mode = (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY) ?
> + (mode | REG_MODE_BMM) : (mode & ~REG_MODE_BMM);
> +
> + mode = (ctrlmode->flags & CAN_CTRLMODE_FD) ?
> + (mode | REG_MODE_FDE) : (mode & ~REG_MODE_FDE);
> +
> + mode = (ctrlmode->flags & CAN_CTRLMODE_PRESUME_ACK) ?
> + (mode | REG_MODE_ACF) : (mode & ~REG_MODE_ACF);
Use an if/else instead of the ternary operator:
if (ctrlmode->flags & CAN_CTRLMODE_LISTENONLY)
mode |= REG_MODE_BMM;
else
mode &= ~REG_MODE_BMM;
and so on…
> + /*
> + * Some bits fixed:
> + * TSTM - Off, User shall not be able to change REC/TEC by hand
> + * during operation
> + */
> + mode &= ~REG_MODE_TSTM;
> + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode);
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
> +
> + conf = (ctrlmode->flags & CAN_CTRLMODE_LOOPBACK) ?
> + (conf | REG_CONF_ILBP) : (conf & ~REG_CONF_ILBP);
> +
> + conf = (ctrlmode->flags & CAN_CTRLMODE_FD_NON_ISO) ?
> + (conf | REG_CONF_NISOFD) : (conf & ~REG_CONF_NISOFD);
> +
> + /* One shot mode supported indirectly via Retransmit limit */
> + conf &= ~FIELD_PREP(REG_CONF_RTRTH, 0xF);
^^^
What is this magic number?
> + conf = (ctrlmode->flags & CAN_CTRLMODE_ONE_SHOT) ?
> + (conf | REG_CONF_RTRLE) :
> + (conf | REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, 0xF));
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf);
> +}
> +
> +static int loongson_canfd_chip_start(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct can_ctrlmode mode;
> + u16 int_ena, int_msk;
> + int ret;
> +
> + /* Configure bit-rates and ssp */
> + ret = loongson_canfd_set_bittiming(ndev);
> + if (ret < 0)
> + return ret;
> +
> + ret = loongson_canfd_set_data_bittiming(ndev);
> + if (ret < 0)
> + return ret;
> +
> + ret = loongson_canfd_set_secondary_sample_point(ndev);
> + if (ret < 0)
> + return ret;
> +
> + /* Configure modes */
> + mode.flags = priv->can.ctrlmode;
> + mode.mask = 0xFFFFFFFF;
> + loongson_canfd_set_mode(priv, &mode);
> +
> + /* Bus error reporting */
> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> + int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
> +
> + int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_RBNEI;
> + int_msk = ~int_ena; /* Mask all disabled interrupts */
> +
> + /* It's after reset, so there is no need to clear anything */
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, int_msk);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
> +
> + /* Controller enters ERROR_ACTIVE on initial FCSI */
> + priv->can.state = CAN_STATE_STOPPED;
> +
> + /* Enable the controller */
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA);
> +
> + return 0;
> +}
> +
> +static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> + int ret;
> +
> + switch (mode) {
> + case CAN_MODE_START:
> + ret = loongson_canfd_reset(ndev);
> + if (ret < 0)
> + return ret;
> +
> + ret = loongson_canfd_chip_start(ndev);
> + if (ret < 0) {
> + netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
> + return ret;
> + }
> +
> + netif_wake_queue(ndev);
> + break;
> + default:
> + ret = -EOPNOTSUPP;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv,
> + const struct canfd_frame *cf, u8 buf, bool isfdf)
> +{
> + u32 meta0, meta1;
> +
> + if (buf >= LOONGSON_CANFD_TXBUF_NUM)
> + return false;
> +
> + if (!loongson_canfd_txbuf_writable(priv, buf))
> + return false;
> +
> + /* Prepare identifier */
> + if (cf->can_id & CAN_EFF_FLAG) {
> + meta0 = cf->can_id & CAN_EFF_MASK;
> + meta0 |= REG_FRAME_FORMAT_W_XDT;
> + } else {
> + meta0 = FIELD_PREP(REG_IDENTIFIER_W_IDENTIFIER_BASE, cf->can_id & CAN_SFF_MASK);
> + }
> +
> + /* Prepare Frame format */
> + if (cf->can_id & CAN_RTR_FLAG)
> + meta0 |= REG_FRAME_FORMAT_W_RTR;
> +
> + if (isfdf) {
> + meta1 = REG_FRAME_FORMAT_W_FDF;
> +
> + if (cf->flags & CANFD_BRS)
> + meta1 |= REG_FRAME_FORMAT_W_BRS;
> + }
> +
> + meta1 |= FIELD_PREP(REG_FRAME_FORMAT_W_DLC, can_fd_len2dlc(cf->len));
> +
> + /* TXT buffer select */
> + regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf);
> +
> + /* Write ID, Frame format */
> + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META0, meta0);
> + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_META1, meta1);
> +
> + /* Write Data payload */
> + if (!(cf->can_id & CAN_RTR_FLAG)) {
> + for (unsigned int i = 0; i < cf->len; i += 4) {
> + regmap_write(priv->regmap,
> + LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_DATA_1_4_W + i,
> + le32_to_cpu(*(__le32 *)(cf->data + i)));> + }> + }
> +
> + return true;
> +}
> +
> +static void loongson_canfd_give_txtb_cmd(struct loongson_canfd_priv *priv,
> + enum loongson_canfd_txtb_command cmd, u8 buf)
> +{
> + u32 tx_cmd = (((cmd >> 1) << 8) | (cmd % 2)) << buf;
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, tx_cmd);
> +}
> +
> +static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> + struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + u32 txtb_id, tx_stat;
> + unsigned long flags;
> + u16 tx_bs;
> + u8 tx_brp;
> + bool ok;
> +
> + if (can_dropped_invalid_skb(ndev, skb))
> + return NETDEV_TX_OK;
> +
> + if (unlikely(!loongson_canfd_txtnf(priv))) {
> + netif_stop_queue(ndev);
> + netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
> + return NETDEV_TX_BUSY;
> + }
> +
> + spin_lock_irqsave(&priv->tx_lock, flags);
Use:
guard(spinlock_irqsave)(&priv->tx_lock);
> + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
> + tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
> + tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
> +
> + for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
You want to iterate on all the non-set bits? If so, use for_each_clear_bit.
> + if ((((tx_brp >> i) & 0x1) == 0) &&
> + ((tx_bs >> (i * 2) & 0x3) == 0)) {
> + txtb_id = i;
> + break;
> + }
> + }
> +
> + ok = loongson_canfd_insert_frame(priv, cf, txtb_id, can_is_canfd_skb(skb));
> + if (!ok) {
ok is only use once. Inline it:
if (loongson_canfd_insert_frame(priv, cf, txtb_id,
can_is_canfd_skb(skb))) {
> + netdev_err(ndev, "BUG! TXNF set but cannot insert frame into TXTB! HW Bug?");
> + kfree_skb(skb);
> + ndev->stats.tx_dropped++;
> + spin_unlock_irqrestore(&priv->tx_lock, flags);
> + return NETDEV_TX_OK;
> + }
> +
> + can_put_echo_skb(skb, ndev, txtb_id, 0);
> +
> + if (!(cf->can_id & CAN_RTR_FLAG))
> + stats->tx_bytes += cf->len;
Don't increase the statistics here. You are already doing it in
loongson_canfd_tx_interrupt(). That's double counting.
> + loongson_canfd_give_txtb_cmd(priv, TXT_CMD_SET_ADD, txtb_id);
> +
> + /* Check if all TX buffers are full */
> + if (!loongson_canfd_txtnf(priv))
> + netif_stop_queue(ndev);
> +
> + spin_unlock_irqrestore(&priv->tx_lock, flags);
> +
> + return NETDEV_TX_OK;
> +}
> +
> +static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
> + u32 meta0, u32 meta1)
> +{
> + u32 data, i, wc, len;
> +
> + /* Extended Identifier Type */
> + if (FIELD_GET(REG_FRAME_FORMAT_W_XDT, meta0))
> + cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG;
> + else
> + cf->can_id = FIELD_GET(REG_IDENTIFIER_W_IDENTIFIER_BASE, meta0) & CAN_SFF_MASK;
> +
> + /* BRS, ESI, RTR Flags */
> + cf->flags = 0;
> +
> + if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1)) {
> + if (FIELD_GET(REG_FRAME_FORMAT_W_BRS, meta1))
Don't use FIELD_GET() for a single bit. This is enough:
if (meta1 & REG_FRAME_FORMAT_W_BRS) {
> + cf->flags |= CANFD_BRS;
> +
> + if (FIELD_GET(REG_FRAME_FORMAT_W_ESI_RSV, meta0))
> + cf->flags |= CANFD_ESI;
> + } else if (FIELD_GET(REG_FRAME_FORMAT_W_RTR, meta0)) {
> + cf->can_id |= CAN_RTR_FLAG;
> + }
> +
> + /* Timesamp */
> + cf->__res0 = meta1;
> + cf->__res1 = meta1 >> 8;
> +
> + wc = FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1) - 2;
> +
> + /* Data Length Code */
> + if (FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1) <= 8) {
> + len = FIELD_GET(REG_FRAME_FORMAT_W_DLC, meta1);
> + } else {
> + if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
> + len = wc << 2;
> + else
> + len = 8;
> + }
> +
> + cf->len = len;
> + if (unlikely(len > wc * 4))
> + len = wc * 4;
> +
> + /* Data */
> + for (i = 0; i < len; i += 4) {
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + *(__le32 *)(cf->data + i) = cpu_to_le32(data);
> + }
> +
> + while (unlikely(i < wc * 4)) {
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + i += 4;
> + }
> +}
> +
> +static int loongson_canfd_rx(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct canfd_frame *cf;
> + struct sk_buff *skb;
> + u32 meta0, meta1;
> +> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
> +
> + /* Number of characters received */
> + if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
> + return -EAGAIN;
> +
> + /* Flexible Data-rate Format */
> + if (FIELD_GET(REG_FRAME_FORMAT_W_FDF, meta1))
> + skb = alloc_canfd_skb(ndev, &cf);
> + else
> + skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
Check for memory allocation failure.
> + loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
> +
> + stats->rx_bytes += cf->len;
Don't increment the rx_bytes stats when the frame is a RTR.
> + stats->rx_packets++;
> + netif_receive_skb(skb);
> +
> + return 1;
> +}
> +
> +static enum can_state loongson_canfd_read_fault_state(struct loongson_canfd_priv *priv)
> +{
> + u32 fs, erl, rec_tec, ewl;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl);
> + regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fs);
> + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec);
> +
> + ewl = FIELD_GET(REG_ERL_EW, erl);
> +
> + if (FIELD_GET(REG_FSTAT_ERA, fs)) {
> + if (ewl > FIELD_GET(REG_ERC_REC, rec_tec) &&
> + ewl > FIELD_GET(REG_ERC_REC, rec_tec))
> + return CAN_STATE_ERROR_ACTIVE;
> + else
> + return CAN_STATE_ERROR_WARNING;
> + } else if (FIELD_GET(REG_FSTAT_ERP, fs)) {
> + return CAN_STATE_ERROR_PASSIVE;
> + } else if (FIELD_GET(REG_FSTAT_BOF, fs)) {
> + return CAN_STATE_BUS_OFF;
> + }
> +
> + WARN(true, "Invalid error state");
> + return CAN_STATE_ERROR_PASSIVE;
> +}
> +
> +static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec)
> +{
> + u32 erc;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc);
> + bec->rxerr = FIELD_GET(REG_ERC_REC, erc);
> + bec->txerr = FIELD_GET(REG_ERC_REC, erc);
> +}
> +
> +static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_berr_counter bec;
> + enum can_state state;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> + u32 err_capt, alc;
> + int dologerr = net_ratelimit();
> +
> + loongson_canfd_get_bec(priv, &bec);
> +
> + state = loongson_canfd_read_fault_state(priv);
> + regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt);
> + regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc);
> +
> + if (dologerr)
> + netdev_info(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n",
> + __func__, isr, bec.rxerr, bec.txerr,
> + FIELD_GET(REG_ERR_CAPT_TYPE, err_capt),
> + FIELD_GET(REG_ERR_CAPT_POS, err_capt),
> + FIELD_GET(REG_ALC_ID_FIELD, alc),
> + FIELD_GET(REG_ALC_BIT_POS, alc));
Try to be less verbose. You can either remove those message or keep them
as netdev_debug().
> + skb = alloc_can_err_skb(ndev, &cf);
> +
> + /*
> + * EWLI: error warning limit condition met
> + * FCSI: fault confinement state changed
> + * ALI: arbitration lost (just informative)
> + * BEI: bus error interrupt
> + */
> + if (FIELD_GET(REG_INT_STAT_FCSI, isr) || FIELD_GET(REG_INT_STAT_EWLI, isr)) {
> + netdev_info(ndev, "state changes from %s to %s\n",
> + can_get_state_str(priv->can.state), can_get_state_str(state));
> +
> + if (priv->can.state == state)
> + netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
> +
> + isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
> + priv->can.state = state;
> + switch (state) {
> + case CAN_STATE_BUS_OFF:
> + priv->can.can_stats.bus_off++;
> + if (priv->can.restart_ms)
> + regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
> + REG_CMD_ERCRST);
> +
> + can_bus_off(ndev);
> + if (skb)
> + cf->can_id |= CAN_ERR_BUSOFF;
> + break;
> + case CAN_STATE_ERROR_PASSIVE:
> + priv->can.can_stats.error_passive++;
> + if (skb) {
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] = (bec.rxerr > 127) ?
^^^
Replace the 127 magic number by CAN_ERROR_PASSIVE_THRESHOLD and replace
the ternary operator by an if/else:
if (bec.rxerr >= CAN_ERROR_PASSIVE_THRESHOLD)
cf->data[1] = /* ... */
else
cf->data[1] = /* ... */
> + CAN_ERR_CRTL_RX_PASSIVE : CAN_ERR_CRTL_TX_PASSIVE;
Why are you mixing RX and TX (CAN_ERR_CRTL_RX_PASSIVE,
CAN_ERR_CRTL_TX_PASSIVE)?
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
Set the CAN_ERR_CNT when reporting the bec so that the userland is aware
that the data are here (same comment applies below).
> + }
> + break;
> + case CAN_STATE_ERROR_WARNING:
> + priv->can.can_stats.error_warning++;
> + if (skb) {
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] |= (bec.txerr > bec.rxerr) ?
> + CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + }
> + break;
> + case CAN_STATE_ERROR_ACTIVE:
> + cf->data[1] = CAN_ERR_CRTL_ACTIVE;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + break;
> + default:
> + netdev_warn(ndev, "error state (%d:%s)!\n", state,
> + can_get_state_str(state));
Don't use netdev_warn() for that.
> + break;
> + }
> + }
> +
> + /* Check for Arbitration Lost interrupt */
> + if (FIELD_GET(REG_INT_STAT_ALI, isr)) {
> + isr = REG_INT_STAT_ALI;
> +
> + if (dologerr)
> + netdev_info(ndev, "arbitration lost\n");
> +
> + priv->can.can_stats.arbitration_lost++;
> +
> + if (skb) {
> + cf->can_id |= CAN_ERR_LOSTARB;
> + cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
> + }
> + }
> +
> + /* Check for Bus Error interrupt */
> + if (FIELD_GET(REG_INT_STAT_BEI, isr)) {
> + isr = REG_INT_STAT_BEI;
> + netdev_info(ndev, "bus error\n");
> + priv->can.can_stats.bus_error++;
> + stats->rx_errors++;
> +
> + if (skb) {
> + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> + cf->data[2] = CAN_ERR_PROT_UNSPEC;
> + cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC;
> + }
> + }
> +
> + if (skb) {
> + stats->rx_packets++;
> + stats->rx_bytes += cf->can_dlc;
Error frames in Socket CAN do not correspond to actual data on the CAN
bus. No need to increase the rx_packets and rx_bytes statistics.
> + netif_rx(skb);
> + }
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 16);
> +}
> +
> +static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> +{
> + struct net_device *ndev = napi->dev;
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + int work_done = 0, res = 1;
> + u32 sts, rx_frc, rx_sts;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> + rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> +
> + while (rx_frc && work_done < quota && res > 0) {
> + res = loongson_canfd_rx(ndev);
> + work_done++;
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> + rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> + }
> +
> + /* Check for RX FIFO Overflow */
> + regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
> + if (FIELD_GET(REG_STAT_DOR, sts)) {
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + netdev_info(ndev, "rx_poll: rx fifo overflow\n");
> + stats->rx_over_errors++;
> + stats->rx_errors++;
> +
> + skb = alloc_can_err_skb(ndev, &cf);
> + if (skb) {
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
> + stats->rx_packets++;
> + stats->rx_bytes += cf->can_dlc;
> + netif_rx(skb);
> + }
> +
> + /* Clear Data Overrun */
> + regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO);
> + }
> +
> + if (!rx_frc && res != 0) {
> + if (napi_complete_done(napi, work_done)) {
> + /*
> + * Clear and enable RBNEI. It is level-triggered, so
> + * there is no race condition.
> + */
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT,
> + REG_INT_STAT_RBNEI);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
> + (REG_INT_STAT_RBNEI << 16));
> + }
> + }
> +
> + return work_done;
> +}
> +
> +static void loongson_canfd_tx_interrupt(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + enum loongson_canfd_tx_bs txtb_sts;
> + bool some_buffers_processed;
> + unsigned long flags;
> + u32 txtb_id, ctr;
> +
> + do {
> + spin_lock_irqsave(&priv->tx_lock, flags);
Use:
scoped_guard(spinlock_irqsave)(&priv->tx_lock) {
/* ... */
}
> + some_buffers_processed = false;
> +
> + while ((txtb_id = loongson_canfd_get_tx_id(priv)) < 8) {
> + txtb_sts = loongson_canfd_get_bs(priv, txtb_id);
> +
> + switch (txtb_sts) {
> + case TX_BS_VALID:
> + stats->tx_bytes += can_get_echo_skb(ndev, txtb_id, NULL);
> + stats->tx_packets++;
> + break;
> + case TX_BS_FAIL:
> + /*
> + * This indicated that retransmit limit has been reached.
> + * Obviously we should not echo the frame, but also not
> + * indicate any kind of error.
> + * If desired, it was already reported (possible multiple
> + * times) on each arbitration lost.
> + */
> + regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &ctr);
> + netdev_warn(ndev, "TX_BS_FAIL txcnt=%x\n", ctr);
> + can_free_echo_skb(ndev, txtb_id, NULL);
> + stats->tx_dropped++;
> + break;
> + case TX_BS_CANCEL:
> + /*
> + * Same as for TX_BS_CANCEL, only with different cause.
> + * We *could* re-queue the frame, but multiqueue/abort is
> + * not supported yet anyway.
> + */
> + netdev_warn(ndev, "TX_BS_CANCEL\n");
> + can_free_echo_skb(ndev, txtb_id, NULL);
> + stats->tx_dropped++;
> + break;
> + case TX_BS_IDLE:
> + netdev_warn(ndev, "TX_BS_IDLE\n");
> + break;
> + }
> +
> + regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD,
> + 0x1 << (txtb_id + 16));
> + some_buffers_processed = true;
I think you can simply
break;
here so that you do not need the some_buffers_processed variable anymore.
> + }
> +
> + spin_unlock_irqrestore(&priv->tx_lock, flags);
> +
> + /*
> + * If no buffers were processed this time, we cannot clear - that would
> + * introduce a race condition.
> + */
> + if (some_buffers_processed) {
> + /*
> + * Clear the interrupt again. We do not want to receive again interrupt
> + * for the buffer already handled. If it is the last finished one then
> + * it would cause log of spurious interrupt.
> + */
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
> + }
> + } while (some_buffers_processed);
> +
> + spin_lock_irqsave(&priv->tx_lock, flags);
> +
> + /* Check if at least one TX buffer is free */
> + if (loongson_canfd_txtnf(priv))
> + netif_wake_queue(ndev);
> +
> + spin_unlock_irqrestore(&priv->tx_lock, flags);
> +}
> +
> +static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
> +{
> + struct net_device *ndev = (struct net_device *)dev_id;
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + int irq_loops;
> + u32 isr;
> + u16 icr;
> +
> + for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
> + /* Get the interrupt sts */
> + regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr);
> + if (!isr)
> + return irq_loops ? IRQ_HANDLED : IRQ_NONE;
> +
> + /* Receive Buffer Not Empty Interrupt */
> + if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
> + /*
> + * Mask RXBNEI the first, then clear interrupt and schedule NAPI.
> + * Even if another IRQ fires, RBNEI will always be 0 (masked).
> + */
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
> + napi_schedule(&priv->napi);
> + }
> +
> + /* TXT Buffer HW Command Interrupt */
> + if (FIELD_GET(REG_INT_STAT_TXBHCI, isr))
> + loongson_canfd_tx_interrupt(ndev);
> +
> + /* Error interrupts */
> + if (FIELD_GET(REG_INT_STAT_EWLI, isr) ||
> + FIELD_GET(REG_INT_STAT_FCSI, isr) ||
> + FIELD_GET(REG_INT_STAT_ALI, isr)) {
> + icr = isr & (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, icr);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, icr);
> + loongson_canfd_err_interrupt(ndev, isr);
> + }
> +
> + /* Ignore RI, TI, LFI, RFI, BSI */
> + }
> +
> + netdev_err(ndev, "Error:isr<0x%08x>\n", isr);
> +
> + if (FIELD_GET(REG_INT_STAT_TXBHCI, isr)) {
> + for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
> + enum loongson_canfd_tx_bs txtb_sts = loongson_canfd_get_bs(priv, i);
> +
> + netdev_err(ndev, "txb[%d]txbstatus=0x%01x\n", i, txtb_sts);
> + }
> + }
> +
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
> + REG_INT_MASK_SET, REG_INT_MASK_SET);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void loongson_canfd_chip_stop(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> + /* Disable interrupts and disable CAN */
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
> + REG_INT_MASK_SET, REG_INT_MASK_SET);
> + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0);
> +
> + priv->can.state = CAN_STATE_STOPPED;
> +}
> +
> +static int loongson_canfd_open(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> + int ret;
> +
> + ret = loongson_canfd_reset(ndev);
> + if (ret < 0)
> + return ret;
> +
> + /* Common open */
> + ret = open_candev(ndev);
> + if (ret) {
> + netdev_warn(ndev, "open_candev failed!\n");
> + return ret;
> + }
> +
> + ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev);
> + if (ret < 0) {
> + netdev_err(ndev, "irq allocation for CAN failed\n");
> + goto err_irq;
> + }
> +
> + ret = loongson_canfd_chip_start(ndev);
> + if (ret < 0) {
> + netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
> + goto err_chip_start;
> + }
> +
> + netdev_info(ndev, "loongson_canfd_device registered\n");
> + napi_enable(&priv->napi);
> + netif_start_queue(ndev);
> +
> + return 0;
> +
> +err_chip_start:
> + free_irq(ndev->irq, ndev);
> +err_irq:
> + close_candev(ndev);
> + return ret;
> +}
> +
> +static int loongson_canfd_close(struct net_device *ndev)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> + netif_stop_queue(ndev);
> + napi_disable(&priv->napi);
> + loongson_canfd_chip_stop(ndev);
> + free_irq(ndev->irq, ndev);
> + close_candev(ndev);
> +
> + return 0;
> +}
> +
> +static const struct net_device_ops loongson_canfd_netdev_ops = {
> + .ndo_open = loongson_canfd_open,
> + .ndo_stop = loongson_canfd_close,
> + .ndo_start_xmit = loongson_canfd_start_xmit,
> +};
> +
> +static int loongson_canfd_get_berr_counter(const struct net_device *ndev,
> + struct can_berr_counter *bec)
> +{
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> + loongson_canfd_get_bec(priv, bec);
> + return 0;
> +}
> +
> +static const struct regmap_range loongson_canfd_reg_table_wr_range[] = {
> + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF),
> + regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD),
> + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL),
> + regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES),
> + regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG),
> + regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL),
> + regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18),
> +};
> +
> +static const struct regmap_range loongson_canfd_reg_table_rd_range[] = {
> + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT),
> + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE),
> + regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT),
> + regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18),
> +};
> +
> +static const struct regmap_access_table loongson_canfd_reg_table_wr = {
> + .yes_ranges = loongson_canfd_reg_table_wr_range,
> + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range),
> +};
> +
> +static const struct regmap_access_table loongson_canfd_reg_table_rd = {
> + .yes_ranges = loongson_canfd_reg_table_rd_range,
> + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range),
> +};
> +
> +static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case LOONGSON_CANFD_MODE:
> + case LOONGSON_CANFD_CONF:
> + case LOONGSON_CANFD_STAT:
> + case LOONGSON_CANFD_INT_STAT:
> + case LOONGSON_CANFD_INT_ENA:
> + case LOONGSON_CANFD_INT_MASK:
> + case LOONGSON_CANFD_ERL:
> + case LOONGSON_CANFD_FSTAT:
> + case LOONGSON_CANFD_ERC:
> + case LOONGSON_CANFD_ERR_CAPT:
> + case LOONGSON_CANFD_ALC:
> + case LOONGSON_CANFD_TX_FR_CNT:
> + case LOONGSON_CANFD_RX_STAT:
> + case LOONGSON_CANFD_RX_DATA:
> + case LOONGSON_CANFD_TX_STAT:
> + case LOONGSON_CANFD_TX_SEL:
> + return true;
> + default:
> + return false;
> + };
> +}
> +
> +static const struct regmap_config loongson_cangfd_regmap = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + .wr_table = &loongson_canfd_reg_table_wr,
> + .rd_table = &loongson_canfd_reg_table_rd,
> + .volatile_reg = loongson_canfd_volatile_reg,
> + .max_register = LOONGSON_CANFD_TX_DATA_18,
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int loongson_canfd_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct loongson_canfd_priv *priv;
> + struct net_device *ndev;
> + struct regmap *regmap;
> + struct resource *res;
> + void __iomem *base;
> + u32 clk_rate;
> + int ret, irq;
> +
> + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + device_property_read_u32(dev, "clock-frequency", &clk_rate);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + /* Create a CAN device instance */
> + ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
> + if (!ndev)
> + return -ENOMEM;
> +
> + priv = netdev_priv(ndev);
> + spin_lock_init(&priv->tx_lock);
> + priv->regmap = regmap;
> + priv->res = res;
> +
> + priv->can.clock.freq = clk_rate;
> + priv->can.bittiming_const = &loongson_canfd_bit_timing_max;
> + priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data_max;
> + priv->can.do_set_mode = loongson_canfd_do_set_mode;
> + priv->can.do_set_bittiming = loongson_canfd_set_bittiming;
> + priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming;
> + priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter;
> + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
> + CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
> + CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
> + CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO;
> +
> + ndev->irq = irq;
> + ndev->flags |= IFF_ECHO; /* We support local echo */
> + platform_set_drvdata(pdev, ndev);
> + ndev->netdev_ops = &loongson_canfd_netdev_ops;
> + SET_NETDEV_DEV(ndev, dev);
> +
> + ret = loongson_canfd_reset(ndev);
> + if (ret < 0)
> + goto err_candev_free;
> +
> + netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
> +
> + ret = register_candev(ndev);
> + if (ret) {
> + dev_err(dev, "fail to register failed (err=%d)\n", ret);
> + goto err_candev_free;
> + }
> +
> + return 0;
> +
> +err_candev_free:
> + free_candev(ndev);
> + return ret;
> +}
> +
> +static void loongson_canfd_remove(struct platform_device *pdev)
> +{
> + struct net_device *ndev = platform_get_drvdata(pdev);
> + struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +
> + netdev_dbg(ndev, "loongson_canfd_remove");
> +
> + unregister_candev(ndev);
> + netif_napi_del(&priv->napi);
> + free_candev(ndev);
> +}
> +
> +static const struct acpi_device_id loongson_canfd_acpi_match[] = {
> + { "LOON0015" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match);
> +
> +static struct platform_driver loongson_canfd_driver = {
> + .probe = loongson_canfd_probe,
> + .remove = loongson_canfd_remove,
> + .driver = {
> + .name = DEV_NAME,
> + .acpi_match_table = loongson_canfd_acpi_match,
> + },
> +};
> +module_platform_driver(loongson_canfd_driver);
> +
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
> new file mode 100644
> index 000000000000..d3517a7ff4a3
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd_kframe.h
> @@ -0,0 +1,142 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +
> +#ifndef __LOONGSON_CANFD_KFRAME_H
> +#define __LOONGSON_CANFD_KFRAME_H
> +
> +/* CAN_Frame_format memory map */
> +#define LOONGSON_CANFD_META0 0x0
> +#define LOONGSON_CANFD_META1 0x4
> +#define LOONGSON_CANFD_DATA_1_4_W 0x8
> +#define LOONGSON_CANFD_DATA_5_8_W 0xc
> +#define LOONGSON_CANFD_DATA_9_12_W 0x10
> +#define LOONGSON_CANFD_DATA_13_16_W 0x14
> +#define LOONGSON_CANFD_DATA_17_20_W 0x18
> +#define LOONGSON_CANFD_DATA_21_24_W 0x1c
> +#define LOONGSON_CANFD_DATA_25_28_W 0x20
> +#define LOONGSON_CANFD_DATA_29_32_W 0x24
> +#define LOONGSON_CANFD_DATA_33_36_W 0x28
> +#define LOONGSON_CANFD_DATA_37_40_W 0x2c
> +#define LOONGSON_CANFD_DATA_41_44_W 0x30
> +#define LOONGSON_CANFD_DATA_45_48_W 0x34
> +#define LOONGSON_CANFD_DATA_49_52_W 0x38
> +#define LOONGSON_CANFD_DATA_53_56_W 0x3c
> +#define LOONGSON_CANFD_DATA_57_60_W 0x40
> +#define LOONGSON_CANFD_DATA_61_64_W 0x44
> +
> +/* FRAME_META0 registers */
> +#define REG_IDENTIFIER_W_IDENTIFIER_EXT GENMASK(17, 0)
> +#define REG_IDENTIFIER_W_IDENTIFIER_BASE GENMASK(28, 18)
> +#define REG_FRAME_FORMAT_W_RTR BIT(29)
> +#define REG_FRAME_FORMAT_W_XDT BIT(30)
> +#define REG_FRAME_FORMAT_W_ESI_RSV BIT(31)
> +
> +/* FRAME_META1 registers */
> +#define REG_META1_W_TIMESTAMP GENMASK(15, 0)
> +#define REG_FRAME_FORMAT_W_DLC GENMASK(19, 16)
> +#define REG_FRAME_FORMAT_W_BRS BIT(20)
> +#define REG_FRAME_FORMAT_W_FDF BIT(21)
> +#define REG_FRAME_FORMAT_W_RWCNT GENMASK(28, 24)
> +
> +/* DATA_1_4_W registers */
> +#define REG_DATA_1_4_W_DATA_1 GENMASK(7, 0)
> +#define REG_DATA_1_4_W_DATA_2 GENMASK(15, 8)
> +#define REG_DATA_1_4_W_DATA_3 GENMASK(23, 16)
> +#define REG_DATA_1_4_W_DATA_4 GENMASK(31, 24)
> +
> +/* DATA_5_8_W registers */
> +#define REG_DATA_5_8_W_DATA_5 GENMASK(7, 0)
> +#define REG_DATA_5_8_W_DATA_6 GENMASK(15, 8)
> +#define REG_DATA_5_8_W_DATA_7 GENMASK(23, 16)
> +#define REG_DATA_5_8_W_DATA_8 GENMASK(31, 24)
> +
> +/* DATA_9_12_W registers */
> +#define REG_DATA_9_12_W_DATA_9 GENMASK(7, 0)
> +#define REG_DATA_9_12_W_DATA_10 GENMASK(15, 8)
> +#define REG_DATA_9_12_W_DATA_11 GENMASK(23, 16)
> +#define REG_DATA_9_12_W_DATA_12 GENMASK(31, 24)
> +
> +/* DATA_13_16_W registers */
> +#define REG_DATA_13_16_W_DATA_13 GENMASK(7, 0)
> +#define REG_DATA_13_16_W_DATA_14 GENMASK(15, 8)
> +#define REG_DATA_13_16_W_DATA_15 GENMASK(23, 16)
> +#define REG_DATA_13_16_W_DATA_16 GENMASK(31, 24)
> +
> +/* DATA_17_20_W registers */
> +#define REG_DATA_17_20_W_DATA_17 GENMASK(7, 0)
> +#define REG_DATA_17_20_W_DATA_18 GENMASK(15, 8)
> +#define REG_DATA_17_20_W_DATA_19 GENMASK(23, 16)
> +#define REG_DATA_17_20_W_DATA_20 GENMASK(31, 24)
> +
> +/* DATA_21_24_W registers */
> +#define REG_DATA_21_24_W_DATA_21 GENMASK(7, 0)
> +#define REG_DATA_21_24_W_DATA_22 GENMASK(15, 8)
> +#define REG_DATA_21_24_W_DATA_23 GENMASK(23, 16)
> +#define REG_DATA_21_24_W_DATA_24 GENMASK(31, 24)
> +
> +/* DATA_25_28_W registers */
> +#define REG_DATA_25_28_W_DATA_25 GENMASK(7, 0)
> +#define REG_DATA_25_28_W_DATA_26 GENMASK(15, 8)
> +#define REG_DATA_25_28_W_DATA_27 GENMASK(23, 16)
> +#define REG_DATA_25_28_W_DATA_28 GENMASK(31, 24)
> +
> +/* DATA_29_32_W registers */
> +#define REG_DATA_29_32_W_DATA_29 GENMASK(7, 0)
> +#define REG_DATA_29_32_W_DATA_30 GENMASK(15, 8)
> +#define REG_DATA_29_32_W_DATA_31 GENMASK(23, 16)
> +#define REG_DATA_29_32_W_DATA_32 GENMASK(31, 24)
> +
> +/* DATA_33_36_W registers */
> +#define REG_DATA_33_36_W_DATA_33 GENMASK(7, 0)
> +#define REG_DATA_33_36_W_DATA_34 GENMASK(15, 8)
> +#define REG_DATA_33_36_W_DATA_35 GENMASK(23, 16)
> +#define REG_DATA_33_36_W_DATA_36 GENMASK(31, 24)
> +
> +/* DATA_37_40_W registers */
> +#define REG_DATA_37_40_W_DATA_37 GENMASK(7, 0)
> +#define REG_DATA_37_40_W_DATA_38 GENMASK(15, 8)
> +#define REG_DATA_37_40_W_DATA_39 GENMASK(23, 16)
> +#define REG_DATA_37_40_W_DATA_40 GENMASK(31, 24)
> +
> +/* DATA_41_44_W registers */
> +#define REG_DATA_41_44_W_DATA_41 GENMASK(7, 0)
> +#define REG_DATA_41_44_W_DATA_42 GENMASK(15, 8)
> +#define REG_DATA_41_44_W_DATA_43 GENMASK(23, 16)
> +#define REG_DATA_41_44_W_DATA_44 GENMASK(31, 24)
> +
> +/* DATA_45_48_W registers */
> +#define REG_DATA_45_48_W_DATA_45 GENMASK(7, 0)
> +#define REG_DATA_45_48_W_DATA_46 GENMASK(15, 8)
> +#define REG_DATA_45_48_W_DATA_47 GENMASK(23, 16)
> +#define REG_DATA_45_48_W_DATA_48 GENMASK(31, 24)
> +
> +/* DATA_49_52_W registers */
> +#define REG_DATA_49_52_W_DATA_49 GENMASK(7, 0)
> +#define REG_DATA_49_52_W_DATA_50 GENMASK(15, 8)
> +#define REG_DATA_49_52_W_DATA_51 GENMASK(23, 16)
> +#define REG_DATA_49_52_W_DATA_52 GENMASK(31, 24)
> +
> +/* DATA_53_56_W registers */
> +#define REG_DATA_53_56_W_DATA_53 GENMASK(7, 0)
> +#define REG_DATA_53_56_W_DATA_56 GENMASK(15, 8)
> +#define REG_DATA_53_56_W_DATA_55 GENMASK(23, 16)
> +#define REG_DATA_53_56_W_DATA_54 GENMASK(31, 24)
> +
> +/* DATA_57_60_W registers */
> +#define REG_DATA_57_60_W_DATA_57 GENMASK(7, 0)
> +#define REG_DATA_57_60_W_DATA_58 GENMASK(15, 8)
> +#define REG_DATA_57_60_W_DATA_59 GENMASK(23, 16)
> +#define REG_DATA_57_60_W_DATA_60 GENMASK(31, 24)
> +
> +/* DATA_61_64_W registers */
> +#define REG_DATA_61_64_W_DATA_61 GENMASK(7, 0)
> +#define REG_DATA_61_64_W_DATA_62 GENMASK(15, 8)
> +#define REG_DATA_61_64_W_DATA_63 GENMASK(23, 16)
> +#define REG_DATA_61_64_W_DATA_64 GENMASK(31, 24)
Are those REG_DATA_xx_yy_W_DATA_yy used anywhere? If not, please remove.
> +/* FRAME_TEST_W registers */
> +#define REG_FRAME_TEST_W_FSTC BIT(0)
> +#define REG_FRAME_TEST_W_FCRC BIT(1)
> +#define REG_FRAME_TEST_W_SDLC BIT(2)
> +#define REG_FRAME_TEST_W_TPRM GENMASK(12, 8)
> +
> +#endif
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
> new file mode 100644
> index 000000000000..5e8889e3a924
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd_kregs.h
> @@ -0,0 +1,315 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +
> +#ifndef _LOONGSON_CANFD_KREGS_H
> +#define _LOONGSON_CANFD_KREGS_H
> +
> +#define LOONGSON_CANFD_DEVICE_ID 0x0 /* CANFD controller ID Register */
> +#define LOONGSON_CANFD_MODE 0x4 /* Mode Configuration Register */
> +#define LOONGSON_CANFD_CONF 0x8 /* Configure Register */
> +#define LOONGSON_CANFD_STAT 0xc /* Status Register */
> +#define LOONGSON_CANFD_CMD 0x10 /* Command Register */
> +#define LOONGSON_CANFD_INT_STAT 0x14 /* Interrupt Status Register */
> +#define LOONGSON_CANFD_INT_ENA 0x18 /* Interrupt Enable Register */
> +#define LOONGSON_CANFD_INT_MASK 0x1c /* Interrupt Mask Register */
> +#define LOONGSON_CANFD_BTR_NORM 0x20 /* Normal Rate Configuration Register */
> +#define LOONGSON_CANFD_BTR_FD 0x24 /* FD Data Rate Configuration Register */
> +#define LOONGSON_CANFD_ERL 0x28 /* Error Threshold Configuration Register */
> +#define LOONGSON_CANFD_FSTAT 0x2c /* Error Status Register */
> +#define LOONGSON_CANFD_ERC 0x30 /* Error Count Register */
> +#define LOONGSON_CANFD_BRE 0x34 /* Rate Error Count Register */
> +#define LOONGSON_CANFD_CTR_PRES 0x38 /* Error Count Debug Register */
> +#define LOONGSON_CANFD_ERR_CAPT 0x3c /* Error Capture Status Register */
> +#define LOONGSON_CANFD_RETX_CNT 0x40 /* Retransmission Count Register */
> +#define LOONGSON_CANFD_ALC 0x44 /* Lost Arbitration Capture Register */
> +#define LOONGSON_CANFD_TRV_DLY 0x48 /* Transmission Delay Measurement Register */
> +#define LOONGSON_CANFD_SSP_CFG 0x4c /* Second Sampling Point Configuration Register */
> +#define LOONGSON_CANFD_RX_FR_CNT 0x50 /* Receive Message Count Register */
> +#define LOONGSON_CANFD_TX_FR_CNT 0x54 /* Transmit Message Count Register */
> +#define LOONGSON_CANFD_DEBUG 0x58 /* Debug Register */
> +#define LOONGSON_CANFD_TS 0x5c /* Timestamp Register */
> +#define LOONGSON_CANFD_TX_FRM_TST 0x60 /* Transmit Message Debug Register */
> +#define LOONGSON_CANFD_FRC_DIV 0x64 /* Fractional Divider Ratio Register */
> +#define LOONGSON_CANFD_FLT_A_MASK 0x68 /* Filter A Mask Register */
> +#define LOONGSON_CANFD_FLT_A_VAL 0x6c /* Filter A value Register */
> +#define LOONGSON_CANFD_FLT_B_MASK 0x70 /* Filter B Mask Register */
> +#define LOONGSON_CANFD_FLT_B_VAL 0x74 /* Filter B value Register */
> +#define LOONGSON_CANFD_FLT_C_MASK 0x78 /* Filter C Mask Register */
> +#define LOONGSON_CANFD_FLT_C_VAL 0x7c /* Filter C value Register */
> +#define LOONGSON_CANFD_FLT_R_LOW 0x80 /* Range Filter Low Threshold Register */
> +#define LOONGSON_CANFD_FLT_R_HI 0x84 /* Range Filter High Threshold Register */
> +#define LOONGSON_CANFD_FLT_CTRL 0x88 /* Filter Control Register */
> +#define LOONGSON_CANFD_RX_MEM_INFO 0x8c /* Receive Buffer Information Register */
> +#define LOONGSON_CANFD_RX_PRT 0x90 /* Receive Buffer Pointer Register */
> +#define LOONGSON_CANFD_RX_STAT 0x94 /* Receive Buffer Status Register */
> +#define LOONGSON_CANFD_RX_DATA 0x98 /* Receive Data Register */
> +#define LOONGSON_CANFD_TX_STAT 0x9c /* Transmit Buffer Status Register */
> +#define LOONGSON_CANFD_TX_CMD 0xa0 /* Transmit Command Register */
> +#define LOONGSON_CANFD_TX_SEL 0xa4 /* Transmit Buffer Selection Register */
> +#define LOONGSON_CANFD_TX_DATA_1 0xb0
> +#define LOONGSON_CANFD_TX_DATA_2 0xb4
> +#define LOONGSON_CANFD_TX_DATA_3 0xb8
> +#define LOONGSON_CANFD_TX_DATA_4 0xbc
> +#define LOONGSON_CANFD_TX_DATA_5 0xc0
> +#define LOONGSON_CANFD_TX_DATA_6 0xc4
> +#define LOONGSON_CANFD_TX_DATA_7 0xc8
> +#define LOONGSON_CANFD_TX_DATA_8 0xcc
> +#define LOONGSON_CANFD_TX_DATA_9 0xd0
> +#define LOONGSON_CANFD_TX_DATA_10 0xd4
> +#define LOONGSON_CANFD_TX_DATA_11 0xd8
> +#define LOONGSON_CANFD_TX_DATA_12 0xdc
> +#define LOONGSON_CANFD_TX_DATA_13 0xe0
> +#define LOONGSON_CANFD_TX_DATA_14 0xe4
> +#define LOONGSON_CANFD_TX_DATA_15 0xe8
> +#define LOONGSON_CANFD_TX_DATA_16 0xec
> +#define LOONGSON_CANFD_TX_DATA_17 0xf0
> +#define LOONGSON_CANFD_TX_DATA_18 0xf4
> +
> +/* Bitfields of CANFD controller ID register */
> +#define REG_ID_ID GENMASK(15, 0)
> +#define REG_ID_VER_MIN GENMASK(23, 16)
> +#define REG_ID_VER_MAJ GENMASK(31, 24)
> +
> +/* Bitfields of Mode Configuration register */
> +#define REG_MODE_RST BIT(0)
> +#define REG_MODE_BMM BIT(1)
> +#define REG_MODE_STM BIT(2)
> +#define REG_MODE_AFM BIT(3)
> +#define REG_MODE_FDE BIT(4)
> +#define REG_MODE_TTTM BIT(5)
> +#define REG_MODE_ROM BIT(6)
> +#define REG_MODE_ACF BIT(7)
> +#define REG_MODE_TSTM BIT(8)
> +#define REG_MODE_RXBAM BIT(9)
> +#define REG_MODE_ITSM BIT(10)
> +#define REG_MODE_RTSOP BIT(12)
> +#define REG_MODE_BUFM BIT(13)
> +
> +/* Bitfields of Configure register */
> +#define REG_CONF_RTRLE BIT(0)
> +#define REG_CONF_RTRTH GENMASK(4, 1)
> +#define REG_CONF_ILBP BIT(5)
> +#define REG_CONF_ENA BIT(6)
> +#define REG_CONF_NISOFD BIT(7)
> +#define REG_CONF_PEX BIT(8)
> +#define REG_CONF_FDRF BIT(10)
> +
> +/* Bitfields of Status register */
> +#define REG_STAT_RXNE BIT(0)
> +#define REG_STAT_DOR BIT(1)
> +#define REG_STAT_TXNF BIT(2)
> +#define REG_STAT_EFT BIT(3)
> +#define REG_STAT_RXS BIT(4)
> +#define REG_STAT_TXS BIT(5)
> +#define REG_STAT_EWL BIT(6)
> +#define REG_STAT_IDLE BIT(7)
> +#define REG_STAT_PEXS BIT(8)
> +#define REG_STAT_STCNT BIT(16)
> +
> +/* Bitfields of Command register */
> +#define REG_CMD_RXRPMV BIT(1)
> +#define REG_CMD_RRB BIT(2)
> +#define REG_CMD_CDO BIT(3)
> +#define REG_CMD_ERCRST BIT(4)
> +#define REG_CMD_RXFCRST BIT(5)
> +#define REG_CMD_TXFCRST BIT(6)
> +#define REG_CMD_CPEXS BIT(7)
> +
> +/* Bitfields of Interrupt Status register */
> +#define REG_INT_STAT_RXI BIT(0)
> +#define REG_INT_STAT_TXI BIT(1)
> +#define REG_INT_STAT_EWLI BIT(2)
> +#define REG_INT_STAT_DOI BIT(3)
> +#define REG_INT_STAT_FCSI BIT(4)
> +#define REG_INT_STAT_ALI BIT(5)
> +#define REG_INT_STAT_BEI BIT(6)
> +#define REG_INT_STAT_RXFI BIT(7)
> +#define REG_INT_STAT_BSI BIT(8)
> +#define REG_INT_STAT_RBNEI BIT(9)
> +#define REG_INT_STAT_TXBHCI BIT(10)
> +#define REG_INT_STAT_OFI BIT(11)
> +#define REG_INT_STAT_DMADI BIT(12)
> +
> +#define REG_INT_STAT_ERRORI (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI)
> +
> +/* Bitfields of Interrupt Enable register */
> +#define REG_INT_ENA_CLR GENMASK(28, 16)
> +#define REG_INT_ENA_SET GENMASK(12, 0)
> +
> +/* Bitfields of Interrupt Mask register */
> +#define REG_INT_MASK_SET GENMASK(12, 0)
> +#define REG_INT_MASK_CLR GENMASK(28, 16)
> +
> +/* Bitfields of Normal Rate Configuration register */
> +#define REG_BTR_PROP GENMASK(6, 0)
> +#define REG_BTR_PH1 GENMASK(12, 7)
> +#define REG_BTR_PH2 GENMASK(18, 13)
> +#define REG_BTR_BRP GENMASK(22, 19)
> +#define REG_BTR_SJW GENMASK(31, 27)
> +
> +/* Bitfields of FD Data Rate Configuration register */
> +#define REG_BTR_FD_PROP GENMASK(6, 0)
> +#define REG_BTR_FD_PH1 GENMASK(11, 7)
> +#define REG_BTR_FD_PH2 GENMASK(17, 13)
> +#define REG_BTR_FD_BRP GENMASK(26, 19)
> +#define REG_BTR_FD_SJW GENMASK(31, 27)
> +
> +/* Bitfields of Error Threshold Configuration register */
> +#define REG_ERL_ERP GENMASK(7, 0)
> +#define REG_ERL_EW GENMASK(23, 16)
> +
> +/* Bitfields of Error Status register */
> +#define REG_FSTAT_ERA BIT(0)
> +#define REG_FSTAT_ERP BIT(1)
> +#define REG_FSTAT_BOF BIT(2)
> +
> +/* Bitfields of Error Count register */
> +#define REG_ERC_TEC GENMASK(8, 0)
> +#define REG_ERC_REC GENMASK(24, 16)
> +
> +/* Bitfields of Rate Error Count register */
> +#define REG_BRE_NORM GENMASK(15, 0)
> +#define REG_BRE_FD_DATA GENMASK(31, 16)
> +
> +/* Bitfields of Error Count Debug register */
> +#define REG_CTR_PRES_CTPV GENMASK(8, 0)
> +#define REG_CTR_PRES_PTX BIT(9)
> +#define REG_CTR_PRES_PRX BIT(10)
> +
> +/* Bitfields of Error Capture Status register */
> +#define REG_ERR_CAPT_POS GENMASK(4, 0)
> +#define REG_ERR_CAPT_TYPE GENMASK(7, 5)
> +
> +/* Bitfields of Retransmission Count register */
> +#define REG_RETX_CNT_VAL GENMASK(3, 0)
> +
> +/* Bitfields of Lost Arbitration Capture register */
> +#define REG_ALC_BIT_POS GENMASK(4, 0)
> +#define REG_ALC_ID_FIELD GENMASK(7, 5)
> +
> +/* Bitfields of Transmission Delay Measurement register */
> +#define REG_TRV_DLY_VAL GENMASK(6, 0)
> +
> +/* Bitfields of Second Sampling Point Configuration register */
> +#define REG_SSP_CFG_OFF GENMASK(7, 0)
> +#define REG_SSP_CFG_SRC GENMASK(9, 8)
> +#define REG_SSP_CFG_SAT BIT(10)
> +
> +/* Bitfields of Receive Message Count register */
> +#define REG_RX_FR_CNT_VAL GENMASK(31, 0)
> +
> +/* Bitfields of Transmit Message Count register */
> +#define REG_TX_FR_CNT_VAL GENMASK(31, 0)
> +
> +/* Bitfields of Debug register */
> +#define REG_DEBUG_STF_CNT GENMASK(2, 0)
> +#define REG_DEBUG_DSTF_CNT GENMASK(5, 3)
> +#define REG_DEBUG_PC_ARB BIT(6)
> +#define REG_DEBUG_PC_CON BIT(7)
> +#define REG_DEBUG_PC_DAT BIT(8)
> +#define REG_DEBUG_PC_STC BIT(9)
> +#define REG_DEBUG_PC_CRC BIT(10)
> +#define REG_DEBUG_PC_CRCD BIT(11)
> +#define REG_DEBUG_PC_ACK BIT(12)
> +#define REG_DEBUG_PC_ACKD BIT(13)
> +#define REG_DEBUG_PC_EOF BIT(14)
> +#define REG_DEBUG_PC_INT BIT(15)
> +#define REG_DEBUG_PC_SUSP BIT(16)
> +#define REG_DEBUG_PC_OVR BIT(17)
> +#define REG_DEBUG_PC_SOF BIT(18)
> +
> +/* Bitfields of Timestamp register */
> +#define REG_TS_TIMESTAMP GENMASK(15, 0)
> +#define REG_TS_PSC GENMASK(24, 16)
> +
> +/* Bitfields of Fractional Divider Ratio register */
> +#define REG_FRC_FRC_DBT GENMASK(15, 8)
> +#define REG_FRC_FRC_NBT GENMASK(7, 0)
> +
> +/* Bitfields of Filter A Mask register */
> +#define REG_FIL_A_MASK GENMASK(28, 0)
> +
> +/* Bitfields of Filter A value register */
> +#define REG_FIL_A_VAL GENMASK(28, 0)
> +
> +/* Bitfields of Filter B Mask register */
> +#define REG_FIL_B_MASK GENMASK(28, 0)
> +
> +/* Bitfields of Filter B value register */
> +#define REG_FIL_B_VAL GENMASK(28, 0)
> +
> +/* Bitfields of Filter C Mask register */
> +#define REG_FIL_C_MASK GENMASK(28, 0)
> +
> +/* Bitfields of Filter C value register */
> +#define REG_FIL_C_VAL GENMASK(28, 0)
> +
> +/* Bitfields of Range Filter Low Threshold register */
> +#define REG_FIL_R_LOW_VAL GENMASK(28, 0)
> +
> +/* Bitfields of Range Filter High Threshold register */
> +#define REG_FIL_R_HI_VAL GENMASK(28, 0)
> +
> +/* Bitfields of Filter Control register */
> +#define REG_FIL_CTRL_FANB BIT(0)
> +#define REG_FIL_CTRL_FANE BIT(1)
> +#define REG_FIL_CTRL_FAFB BIT(2)
> +#define REG_FIL_CTRL_FAFE BIT(3)
> +#define REG_FIL_CTRL_FBNB BIT(4)
> +#define REG_FIL_CTRL_FBNE BIT(5)
> +#define REG_FIL_CTRL_FBFB BIT(6)
> +#define REG_FIL_CTRL_FBFE BIT(7)
> +#define REG_FIL_CTRL_FCNB BIT(8)
> +#define REG_FIL_CTRL_FCNE BIT(9)
> +#define REG_FIL_CTRL_FCFB BIT(10)
> +#define REG_FIL_CTRL_FCFE BIT(11)
> +#define REG_FIL_CTRL_FRNB BIT(12)
> +#define REG_FIL_CTRL_FRNE BIT(13)
> +#define REG_FIL_CTRL_FRFB BIT(14)
> +#define REG_FIL_CTRL_FRFE BIT(15)
> +#define REG_FIL_CTRL_SFA BIT(16)
> +#define REG_FIL_CTRL_SFB BIT(17)
> +#define REG_FIL_CTRL_SFC BIT(18)
> +#define REG_FIL_CTRL_SFR BIT(19)
> +
> +/* Bitfields of Receive Buffer Information register */
> +#define REG_RX_MEM_INFO_BUFF_SIZE GENMASK(12, 0)
> +#define REG_RX_MEM_INFO_MEM_FREE GENMASK(28, 16)
> +
> +/* Bitfields of Receive Buffer Pointer register */
> +#define REG_RX_PTR_WPP GENMASK(11, 0)
> +#define REG_RX_PTR_RPP GENMASK(27, 16)
> +
> +/* Bitfields of Receive Buffer Status register */
> +#define REG_RX_STAT_RXE BIT(0)
> +#define REG_RX_STAT_RXF BIT(1)
> +#define REG_RX_STAT_RXMOF BIT(2)
> +#define REG_RX_STAT_RXFRC GENMASK(14, 4)
> +#define REG_RX_STAT_RTSOP BIT(16)
> +
> +/* Bitfields of Receive Data register */
> +#define REG_RX_DATA_VAL GENMASK(31, 0)
> +
> +/* Bitfields of Transmit Buffer Status register */
> +#define REG_TX_STAT_BRP GENMASK(7, 0)
> +#define REG_TX_STAT_TXS GENMASK(10, 8)
> +#define REG_TX_STAT_BS GENMASK(31, 16)
> +
> +#define REG_TX_STAT_BS_TX1 GENMASK(17, 16)
> +#define REG_TX_STAT_BS_TX2 GENMASK(19, 18)
> +#define REG_TX_STAT_BS_TX3 GENMASK(21, 20)
> +#define REG_TX_STAT_BS_TX4 GENMASK(23, 22)
> +#define REG_TX_STAT_BS_TX5 GENMASK(25, 24)
> +#define REG_TX_STAT_BS_TX6 GENMASK(27, 26)
> +#define REG_TX_STAT_BS_TX7 GENMASK(29, 28)
> +#define REG_TX_STAT_BS_TX8 GENMASK(31, 30)
> +
> +/* Bitfields of Transmit Command register */
> +#define REG_TX_CMD_BAR GENMASK(7, 0)
> +#define REG_TX_CMD_BCR GENMASK(15, 8)
> +#define REG_TX_CMD_BSC GENMASK(23, 16)
> +
> +/* Bitfields of Transmit Buffer Selection register */
> +#define REG_TX_SEL_BUF_SEL GENMASK(3, 0)
> +#define REG_TX_SEL_BUF_CNT GENMASK(7, 4)
> +
> +#endif
Yours sincerely,
Vincent Mailhol
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 2/2] can: loongson_canfd: Add RXDMA support
2026-04-27 7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
@ 2026-05-06 17:51 ` Vincent Mailhol
0 siblings, 0 replies; 5+ messages in thread
From: Vincent Mailhol @ 2026-05-06 17:51 UTC (permalink / raw)
To: Binbin Zhou, Binbin Zhou, Huacai Chen, Marc Kleine-Budde,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai
On 27/04/2026 at 09:18, Binbin Zhou wrote:
> Add optional DMA support for RX path using the Loongson APB CMC DMA
> engine. When a DMA channel is successfully requested, the driver:
>
> - Uses DMA cyclic transfers to write incoming CAN frames directly to
> a coherent DMA buffer
> - Replaces RXBNEI (RX buffer not empty interrupt) with DMADI (DMA
> done interrupt)
> - Dynamically switches between DMA and PIO modes based on channel
> availability
>
> This significantly reduces CPU intervention under high RX load,
> especially beneficial for CAN FD at higher data rates.
>
> Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> drivers/net/can/loongson_canfd/Kconfig | 2 +-
> .../net/can/loongson_canfd/loongson_canfd.c | 179 ++++++++++++++++--
> 2 files changed, 160 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/net/can/loongson_canfd/Kconfig b/drivers/net/can/loongson_canfd/Kconfig
> index 5a2540bb5410..8fe44b804991 100644
> --- a/drivers/net/can/loongson_canfd/Kconfig
> +++ b/drivers/net/can/loongson_canfd/Kconfig
> @@ -5,7 +5,7 @@
> #
> config CAN_LOONGSON_CANFD
> tristate "Loongson CAN-FD driver"
> - depends on HAS_IOMEM
> + depends on HAS_IOMEM && LOONGSON2_APB_CMC_DMA
Allow people to compile test your driver:
depends on HAS_IOMEM && (LOONGSON2_APB_CMC_DMA || COMPILE_TEST)
> select REGMAP_MMIO
> help
> This is a canfd driver switch for the Loongson platform,
> diff --git a/drivers/net/can/loongson_canfd/loongson_canfd.c b/drivers/net/can/loongson_canfd/loongson_canfd.c
> index 20ac95dc528d..ba9570c34c94 100644
> --- a/drivers/net/can/loongson_canfd/loongson_canfd.c
> +++ b/drivers/net/can/loongson_canfd/loongson_canfd.c
> @@ -6,10 +6,14 @@
> */
>
> #include <linux/acpi.h>
> +#include <linux/acpi_dma.h>
> #include <linux/bitfield.h>
> #include <linux/bits.h>
> #include <linux/can/dev.h>
> #include <linux/can/error.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-direction.h>
> +#include <linux/dma-mapping.h>
> #include <linux/io.h>
> #include <linux/interrupt.h>
> #include <linux/module.h>
> @@ -26,13 +30,21 @@
> #define DEV_NAME "loongson_canfd"
> #define LOONGSON_CANFD_TXBUF_NUM 8
> #define LOONGSON_CANFD_ID 0xBABE
> +#define RX_BUF_SIZE SZ_1K
> +#define LOONGSON_CANFD_DMA_RXDATA_NUM (RX_BUF_SIZE / DMA_SLAVE_BUSWIDTH_4_BYTES)
>
> struct loongson_canfd_priv {
> struct can_priv can; /* must be first member! */
> struct napi_struct napi;
> struct regmap *regmap;
> struct resource *res;
> + struct dma_chan *rx_ch;
> + dma_addr_t rx_dma_buf; /* dma rx buffer bus address */
> + unsigned int *rx_buf; /* dma rx buffer cpu address */
> + u16 last_res;
> spinlock_t tx_lock; /* protect the sending queue */
> + u32 (*get_rx_data)(struct loongson_canfd_priv *priv);
> + bool (*get_rx_pending)(struct loongson_canfd_priv *priv);
> };
>
> enum loongson_canfd_tx_bs {
> @@ -137,6 +149,54 @@ static int loongson_canfd_reset(struct net_device *ndev)
> return 0;
> }
>
> +static u32 loongson_canfd_get_rxdma_data(struct loongson_canfd_priv *priv)
> +{
> + u32 c = 0;
> +
> + c = priv->rx_buf[LOONGSON_CANFD_DMA_RXDATA_NUM - priv->last_res--];
> + if (priv->last_res == 0)
> + priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
> +
> + return c;
> +}
> +
> +static bool loongson_canfd_rxdma_pending(struct loongson_canfd_priv *priv)
> +{
> + enum dma_status status;
> + struct dma_tx_state state;
> +
> + status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state);
> +
> + if (priv->last_res != (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES) &&
> + status == DMA_IN_PROGRESS)
> + return true;
> +
> + return false;
return priv->last_res != (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES)
&& status == DMA_IN_PROGRESS
> +}
> +
> +static u32 loongson_canfd_get_poll_data(struct loongson_canfd_priv *priv)
> +{
> + u32 data;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> +
> + return data;
> +}
> +
> +static bool loongson_canfd_rxpoll_pending(struct loongson_canfd_priv *priv)
> +{
> + u32 rx_sts;
> +
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> +
> + return FIELD_GET(REG_RX_STAT_RXFRC, rx_sts) ? true : false;
> +}
> +
> +static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev)
> +{
> + dma_free_coherent(dev, RX_BUF_SIZE, priv->rx_buf, priv->rx_dma_buf);
> +}
> +
> static int loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
> {
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> @@ -308,7 +368,9 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
> if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
>
> - int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_RBNEI;
> + int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
> + int_ena |= priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> +
> int_msk = ~int_ena; /* Mask all disabled interrupts */
>
> /* It's after reset, so there is no need to clear anything */
> @@ -514,12 +576,12 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struc
>
> /* Data */
> for (i = 0; i < len; i += 4) {
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + data = priv->get_rx_data(priv);
> *(__le32 *)(cf->data + i) = cpu_to_le32(data);
> }
>
> while (unlikely(i < wc * 4)) {
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + data = priv->get_rx_data(priv);
> i += 4;
> }
> }
> @@ -532,8 +594,8 @@ static int loongson_canfd_rx(struct net_device *ndev)
> struct sk_buff *skb;
> u32 meta0, meta1;
>
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
> + meta0 = priv->get_rx_data(priv);
> + meta1 = priv->get_rx_data(priv);
>
> /* Number of characters received */
> if (!FIELD_GET(REG_FRAME_FORMAT_W_RWCNT, meta1))
> @@ -718,16 +780,16 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> struct net_device *ndev = napi->dev;
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> int work_done = 0, res = 1;
> - u32 sts, rx_frc, rx_sts;
> + int int_ena = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> + u32 sts;
> + bool rx_frc;
>
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> - rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> + rx_frc = priv->get_rx_pending(priv);
>
> while (rx_frc && work_done < quota && res > 0) {
> res = loongson_canfd_rx(ndev);
> work_done++;
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_STAT, &rx_sts);
> - rx_frc = FIELD_GET(REG_RX_STAT_RXFRC, rx_sts);
> + rx_frc = priv->get_rx_pending(priv);
> }
>
> /* Check for RX FIFO Overflow */
> @@ -757,13 +819,11 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> if (!rx_frc && res != 0) {
> if (napi_complete_done(napi, work_done)) {
> /*
> - * Clear and enable RBNEI. It is level-triggered, so
> + * Clear and enable RBNEI/DMADI. It is level-triggered, so
> * there is no race condition.
> */
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT,
> - REG_INT_STAT_RBNEI);
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
> - (REG_INT_STAT_RBNEI << 16));
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, int_ena);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, (int_ena << 16));
> }
> }
>
> @@ -855,7 +915,7 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
> struct net_device *ndev = (struct net_device *)dev_id;
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
> int irq_loops;
> - u32 isr;
> + u32 isr, mask;
> u16 icr;
>
> for (irq_loops = 0; irq_loops < 10000; irq_loops++) {
> @@ -864,14 +924,16 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
> if (!isr)
> return irq_loops ? IRQ_HANDLED : IRQ_NONE;
>
> + mask = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
> +
> /* Receive Buffer Not Empty Interrupt */
> - if (FIELD_GET(REG_INT_STAT_RBNEI, isr)) {
> + if (isr & mask) {
> /*
> * Mask RXBNEI the first, then clear interrupt and schedule NAPI.
> * Even if another IRQ fires, RBNEI will always be 0 (masked).
> */
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
> - regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, mask);
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, mask);
> napi_schedule(&priv->napi);
> }
>
> @@ -1054,11 +1116,56 @@ static const struct regmap_config loongson_cangfd_regmap = {
> .cache_type = REGCACHE_MAPLE,
> };
>
> +static int loongson_canfd_rxdma_init(struct loongson_canfd_priv *priv, struct device *dev)
> +{
> + struct dma_slave_config config;
> + struct dma_async_tx_descriptor *desc = NULL;
> + int ret;
> +
> + priv->rx_buf = dma_alloc_coherent(dev, RX_BUF_SIZE, &priv->rx_dma_buf, GFP_KERNEL);
> + if (!priv->rx_buf)
> + return -ENOMEM;
> +
> + /* Configure DMA channel */
> + memset(&config, 0, sizeof(config));
> + config.src_addr = priv->res->start + LOONGSON_CANFD_RX_DATA;
> + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +
> + ret = dmaengine_slave_config(priv->rx_ch, &config);
> + if (ret < 0) {
> + dev_err(dev, "rx dma channel config failed\n");
> + goto err_config;
> + }
> +
> + /* Prepare a DMA cyclic transaction */
> + desc = dmaengine_prep_dma_cyclic(priv->rx_ch, priv->rx_dma_buf,
> + RX_BUF_SIZE, RX_BUF_SIZE,
> + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
> + if (!desc) {
> + dev_err(dev, "rx dma prep cyclic failed\n");
> + ret = -EBUSY;
> + goto err_config;
> + }
> +
> + /* Push current DMA transaction in the pending queue */
> + dmaengine_submit(desc);
> +
> + /* Issue pending DMA requests */
> + dma_async_issue_pending(priv->rx_ch);
> +
> + return 0;
> +
> +err_config:
> + loongson_canfd_rxdma_remove(priv, dev);
> + return ret;
> +}
> +
> static int loongson_canfd_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct loongson_canfd_priv *priv;
> struct net_device *ndev;
> + struct dma_chan *rx_ch;
> struct regmap *regmap;
> struct resource *res;
> void __iomem *base;
> @@ -1079,14 +1186,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> if (irq < 0)
> return irq;
>
> + rx_ch = dma_request_chan(dev, "rx");
> + if (PTR_ERR(rx_ch) == -EPROBE_DEFER)
> + return -EPROBE_DEFER;
> +
> + if (IS_ERR(rx_ch)) {
> + dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n");
> + rx_ch = NULL;
> + }
> +
> /* Create a CAN device instance */
> ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
> if (!ndev)
> - return -ENOMEM;
> + goto err_dma_rx;
>
> priv = netdev_priv(ndev);
> spin_lock_init(&priv->tx_lock);
> priv->regmap = regmap;
> + priv->rx_ch = rx_ch;
> priv->res = res;
>
> priv->can.clock.freq = clk_rate;
> @@ -1111,6 +1228,19 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> if (ret < 0)
> goto err_candev_free;
>
> + if (priv->rx_ch) {
> + priv->get_rx_data = loongson_canfd_get_rxdma_data;
> + priv->get_rx_pending = loongson_canfd_rxdma_pending;
> + priv->last_res = LOONGSON_CANFD_DMA_RXDATA_NUM;
> + ret = loongson_canfd_rxdma_init(priv, dev);
> + if (ret) {
> + dev_err(dev, "interrupt mode used for rx (no dma)\n");
> + goto err_candev_free;
> + }
> + } else {
> + priv->get_rx_data = loongson_canfd_get_poll_data;
> + priv->get_rx_pending = loongson_canfd_rxpoll_pending;
> + }
> netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
>
> ret = register_candev(ndev);
> @@ -1123,6 +1253,9 @@ static int loongson_canfd_probe(struct platform_device *pdev)
>
> err_candev_free:
> free_candev(ndev);
> +err_dma_rx:
> + if (rx_ch)
> + dma_release_channel(rx_ch);
> return ret;
> }
>
> @@ -1133,6 +1266,11 @@ static void loongson_canfd_remove(struct platform_device *pdev)
>
> netdev_dbg(ndev, "loongson_canfd_remove");
>
> + if (priv->rx_ch) {
> + loongson_canfd_rxdma_remove(priv, &pdev->dev);
> + dma_release_channel(priv->rx_ch);
> + }
> +
> unregister_candev(ndev);
> netif_napi_del(&priv->napi);
> free_candev(ndev);
> @@ -1154,6 +1292,7 @@ static struct platform_driver loongson_canfd_driver = {
> };
> module_platform_driver(loongson_canfd_driver);
>
> +MODULE_SOFTDEP("pre: loongson2-apb-cmc-dma");
Is this really needed? Isn't it enough to just put the
LOONGSON2_APB_CMC_DMA dependency in Kconfig?
> MODULE_AUTHOR("Loongson Technology Corporation Limited");
> MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
> MODULE_LICENSE("GPL");
Yours sincerely,
Vincent Mailhol
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-05-06 17:51 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-27 7:17 [PATCH 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-04-27 7:17 ` [PATCH 1/2] can: Add Loongson CAN-FD controller support Binbin Zhou
2026-05-06 17:50 ` Vincent Mailhol
2026-04-27 7:18 ` [PATCH 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
2026-05-06 17:51 ` Vincent Mailhol
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox