From: Jisheng Zhang <jszhang@kernel.org>
To: Marc Kleine-Budde <mkl@pengutronix.de>,
Vincent Mailhol <mailhol@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-can@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 2/2] can: arasan-canfd: add driver for arasan CAN-FD controller
Date: Mon, 3 Aug 2026 22:05:31 +0800 [thread overview]
Message-ID: <20260803140531.12692-3-jszhang@kernel.org> (raw)
In-Reply-To: <20260803140531.12692-1-jszhang@kernel.org>
Add driver for the arasan CAN-FD controller.
Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
drivers/net/can/Kconfig | 6 +
drivers/net/can/Makefile | 1 +
drivers/net/can/arasan-canfd.c | 1057 ++++++++++++++++++++++++++++++++
3 files changed, 1064 insertions(+)
create mode 100644 drivers/net/can/arasan-canfd.c
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index a8fad6fe5302..f81a46efc4a6 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -83,6 +83,12 @@ config CAN_CALC_BITTIMING
config CAN_RX_OFFLOAD
bool
+config CAN_ARASAN_CANFD
+ tristate "Arasan CAN FD controller"
+ depends on COMMON_CLK && HAS_IOMEM
+ help
+ Say Y here if you want to support for Arasan CAN FD.
+
config CAN_AT91
tristate "Atmel AT91 onchip CAN controller"
depends on (ARCH_AT91 || COMPILE_TEST) && HAS_IOMEM
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index 4010d17f8583..3b50bf39d473 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -15,6 +15,7 @@ obj-y += spi/
obj-y += usb/
obj-y += softing/
+obj-$(CONFIG_CAN_ARASAN_CANFD) += arasan-canfd.o
obj-$(CONFIG_CAN_AT91) += at91_can.o
obj-$(CONFIG_CAN_BXCAN) += bxcan.o
obj-$(CONFIG_CAN_CAN327) += can327.o
diff --git a/drivers/net/can/arasan-canfd.c b/drivers/net/can/arasan-canfd.c
new file mode 100644
index 000000000000..63a6191f84f6
--- /dev/null
+++ b/drivers/net/can/arasan-canfd.c
@@ -0,0 +1,1057 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arasan CANFD driver
+ *
+ * Copyright (C) 2026 Synaptics Incorporated
+ *
+ * Author: Jisheng Zhang <jszhang@kernel.org>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/phy/phy.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/u64_stats_sync.h>
+
+#define INTERRUPT_MASK0 0x3fc
+#define INTERRUPT_MASK1 0x400
+#define OPERATIONAL 0x404
+#define OPERATIONAL_EN BIT(0)
+#define LOOPBACK BIT(7)
+#define CONTROL 0x408
+#define SOFTRESET BIT(0)
+#define DONTCHECKID BIT(4)
+#define DISABLE_ECC BIT(5)
+#define LVL_ISOFD BIT(6)
+#define DMA_SOFTRESET BIT(20)
+#define STATUS 0x40c
+#define STATUS_REC_MASK GENMASK(9, 0)
+#define STATUS_TEC_MASK GENMASK(19, 10)
+#define HITXFIFO 0x410
+#define LOTXFIFO 0x414
+#define LVL_CLASSIC_BAUD 0x424
+#define LVL_FD_BAUD 0x428
+#define LVL_ALLOWABLE_CLASSIC_JUMP 0x42c
+#define LVL_ALLOWABLE_FD_JUMP 0x430
+#define LVL_CLASSIC_END_OF_SYNC_SEG 0x434
+#define LVL_FD_END_OF_SYNC_SEG 0x438
+#define LVL_CLASSIC_END_OF_PROP_SEG 0x43c
+#define LVL_FD_END_OF_PROP_SEG 0x440
+#define LVL_CLASSIC_END_OF_PHASE_SEG1 0x444
+#define LVL_FD_END_OF_PHASE_SEG1 0x448
+#define LVL_TUR_INCREMENT 0x450
+#define INTERRUPTS_FROM_HW 0x48c
+#define OVERLOAD BIT(0)
+#define GOOD_RX_FRAME BIT(1)
+#define TX_RETRANSMIT BIT(2)
+#define ERROR_RX_FRAME BIT(6)
+#define TX_FIFO_EMPTY BIT(8)
+#define USED_BUFFERS_NOT_EMPTY BIT(11)
+#define CL_NO_ACK BIT(12)
+#define FD_NO_ACK BIT(13)
+#define CLTX_GOOD BIT(14)
+#define FDTX_GOOD BIT(15)
+#define BUS_STATE1 BIT(16)
+#define BUS_STATE2 BIT(17)
+#define PANICS BIT(26)
+#define BASE_ADDRESS 0x490
+#define NUMBER_OF_PAIRS 0x494
+#define WATCHDOG_LIMIT 0x498
+#define SELF_ADDRESS 0x49c
+#define ALLOWED_RETRANSMITS 0x4a0
+#define RX_PTR_BUFS 0x4a4
+#define RX_PTR_BUFS_COUNT 0x4a8
+#define USED_BUFS_PTR 0x4ac
+#define TYPE_MASK GENMASK(1, 0)
+#define TYPE_TX 1
+#define TYPE_RX 2
+#define TYPE_TX_ABORT 3
+#define USED_BUFS_PTR_COUNT 0x4b0
+#define LVL_SSP_DELAY 0x4d0
+#define ACTUAL_SSP_DELAY 0x4d4
+#define R_BASE_ADDR 0xf00
+#define R_ECC_ADDR 0xf04
+#define W_BASE_ADDR 0xf08
+#define W_ECC_ADDR 0xf0c
+
+#define CAN_MAX_RX_QUEUE 8
+#define CAN_MAX_TX_QUEUE 8
+#define CAN_MAX_FILTER 255
+#define ARASAN_CANFD_NAPI_WEIGHT 8
+#define DEFAULT_SSP 17
+
+#define TX_HEADER0_ID GENMASK(22, 12)
+#define TX_HEADER0_ESI BIT(24)
+#define TX_HEADER0_BRS BIT(25)
+#define TX_HEADER0_RTR BIT(26)
+#define TX_HEADER0_IDE BIT(27)
+#define TX_HEADER0_TYPE_MSK GENMASK(31, 28)
+#define TX_HEADER0_TYPE_CL 1
+#define TX_HEADER0_TYPE_FD 2
+
+#define RX_HEADER0_TYPE_MSK GENMASK(1, 0)
+#define RX_HEADER0_TYPE_CL 1
+#define RX_HEADER0_TYPE_FD 2
+#define RX_HEADER0_B_EXT BIT(2)
+#define RX_HEADER0_B_RTR BIT(3)
+#define RX_HEADER0_STATUS_MSK GENMASK(13, 4)
+#define RX_HEADER0_DLC_MSK GENMASK(24, 14)
+#define RX_HEADER0_ID_0_6 GENMASK(31, 25)
+#define RX_HEADER1_ID_7_10 GENMASK(3, 0)
+#define RX_HEADER1_EXTID GENMASK(21, 4)
+#define RX_HEADER1_EXTID GENMASK(21, 4)
+#define RX_HEADER2_CL_RTR BIT(8)
+#define RX_HEADER2_CL_IDE BIT(9)
+#define RX_HEADER2_FD_IDE BIT(18)
+#define RX_HEADER2_FD_ESI BIT(19)
+#define RX_HEADER2_FD_BRS BIT(20)
+
+struct can_rx_filter {
+ u32 mask;
+ u32 id;
+};
+
+struct can_rx_frame {
+ struct {
+ u32 header0;
+ u32 header1;
+ u32 header2;
+ u32 resv_0;
+ u32 timestamp_l;
+ u32 timestamp_h;
+ } header;
+ u8 data[CANFD_MAX_DLEN];
+};
+
+struct can_tx_frame {
+ struct header {
+ u32 header0;
+ u32 ext_id;
+ u32 resv_0;
+ } header;
+ u8 data[CANFD_MAX_DLEN];
+};
+
+struct can_read_buffer {
+ struct can_tx_frame tx_frames[CAN_MAX_TX_QUEUE];
+ struct can_rx_filter rx_filters[CAN_MAX_FILTER];
+};
+
+struct can_write_buffer {
+ struct can_rx_frame rx_frames[CAN_MAX_RX_QUEUE];
+};
+
+struct can_ecc_buffer {
+ struct {
+ u8 tx_frames[CAN_MAX_TX_QUEUE][sizeof(struct can_tx_frame) / 4];
+ u8 rx_filters[CAN_MAX_FILTER][sizeof(struct can_rx_filter) / 4];
+ } read;
+
+ struct {
+ u8 rx_frames[CAN_MAX_RX_QUEUE][sizeof(struct can_rx_frame) / 4];
+ } write;
+};
+
+struct can_buffer {
+ struct can_read_buffer r_buffer;
+ struct can_write_buffer w_buffer;
+ struct can_ecc_buffer ecc_buffer;
+};
+
+struct arasan_canfd_priv {
+ struct can_priv can;
+ /* Lock for synchronizing TX handling */
+ spinlock_t tx_lock;
+ u32 txq_avail;
+ struct napi_struct napi;
+ struct device *dev;
+ void __iomem *base;
+ struct can_buffer *buf;
+ dma_addr_t buf_dma;
+ u32 ssp;
+ struct clk *sys_clk;
+ struct clk *can_clk;
+ struct reset_control *sys_rst;
+ struct reset_control *can_rst;
+ struct phy *transceiver;
+};
+
+static const struct can_bittiming_const arasan_canfd_bittiming_const = {
+ .name = KBUILD_MODNAME,
+ .tseg1_min = 1,
+ .tseg1_max = 256,
+ .tseg2_min = 1,
+ .tseg2_max = 128,
+ .sjw_max = 128,
+ .brp_min = 1,
+ .brp_max = 65536,
+ .brp_inc = 1,
+};
+
+static const struct can_bittiming_const arasan_canfd_data_bittiming_const = {
+ .name = KBUILD_MODNAME,
+ .tseg1_min = 1,
+ .tseg1_max = 256,
+ .tseg2_min = 1,
+ .tseg2_max = 128,
+ .sjw_max = 128,
+ .brp_min = 1,
+ .brp_max = 65536,
+ .brp_inc = 1,
+};
+
+static const u32 ecc_syndrom[7] = {
+ 0xC14840FF,
+ 0x2124FF90,
+ 0x6CFF0808,
+ 0xFF01A444,
+ 0x16F092A6,
+ 0x101F7161,
+ 0x8A820F1B,
+};
+
+static inline int parity(unsigned int x)
+{
+ /*
+ * public domain code snippet, lifted from
+ * http://www-graphics.stanford.edu/~seander/bithacks.html
+ */
+ x ^= x >> 1;
+ x ^= x >> 2;
+ x = (x & 0x11111111U) * 0x11111111U;
+ return (x >> 28) & 1;
+}
+
+static u8 ecc_calc(u32 data)
+{
+ int i;
+ u8 result = 0;
+
+ for (i = 0; i < 7; i++) {
+ if (parity(data & ecc_syndrom[i]))
+ result |= BIT(i);
+ }
+
+ return result;
+}
+
+static inline u32 arasan_canfd_read(const struct arasan_canfd_priv *priv, u32 off)
+{
+ return readl_relaxed(priv->base + off);
+}
+
+static inline void arasan_canfd_write(const struct arasan_canfd_priv *priv, u32 off,
+ u32 value)
+{
+ writel_relaxed(value, priv->base + off);
+}
+
+static int arasan_canfd_set_bittiming(struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *bt = &priv->can.bittiming;
+ struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+ u32 cl_2, cl_4, fd_2, fd_4, classic_cycle, fd_cycle;
+
+ if (arasan_canfd_read(priv, OPERATIONAL) & OPERATIONAL_EN) {
+ netdev_alert(ndev, "BUG! Cannot set bittiming when CAN operational is enabled\n");
+ return -EPERM;
+ }
+
+ arasan_canfd_write(priv, LVL_TUR_INCREMENT, 0x8000);
+
+ classic_cycle = priv->can.clock.freq / bt->bitrate;
+ cl_2 = classic_cycle / 2;
+ cl_4 = classic_cycle / 4;
+ arasan_canfd_write(priv, LVL_CLASSIC_BAUD, classic_cycle);
+ arasan_canfd_write(priv, LVL_ALLOWABLE_CLASSIC_JUMP, cl_2);
+ arasan_canfd_write(priv, LVL_CLASSIC_END_OF_SYNC_SEG, cl_4);
+ arasan_canfd_write(priv, LVL_CLASSIC_END_OF_PROP_SEG, cl_2 - 1);
+ arasan_canfd_write(priv, LVL_CLASSIC_END_OF_PHASE_SEG1,
+ classic_cycle * bt->sample_point / 1000);
+
+ fd_cycle = priv->can.clock.freq / dbt->bitrate;
+ fd_2 = fd_cycle / 2;
+ fd_4 = fd_cycle / 4;
+ arasan_canfd_write(priv, LVL_FD_BAUD, fd_cycle);
+ arasan_canfd_write(priv, LVL_ALLOWABLE_FD_JUMP, fd_2);
+ arasan_canfd_write(priv, LVL_FD_END_OF_SYNC_SEG, fd_4);
+ arasan_canfd_write(priv, LVL_FD_END_OF_PROP_SEG, fd_2 - 1);
+ arasan_canfd_write(priv, LVL_FD_END_OF_PHASE_SEG1, fd_cycle * dbt->sample_point / 1000);
+
+ if (dbt->bitrate > 1000000)
+ arasan_canfd_write(priv, LVL_SSP_DELAY, priv->ssp);
+
+ return 0;
+}
+
+static int arasan_canfd_chip_start(struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ u32 ier, ctrl, op = OPERATIONAL_EN;
+ int ret;
+
+ ret = arasan_canfd_set_bittiming(ndev);
+ if (ret < 0)
+ return ret;
+
+ ier = GENMASK(31, 0);
+ ier &= ~(GOOD_RX_FRAME | TX_FIFO_EMPTY | CLTX_GOOD | FDTX_GOOD | PANICS);
+ arasan_canfd_write(priv, INTERRUPT_MASK0, ier);
+
+ /* loopback? */
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ op |= LOOPBACK;
+
+ ctrl = arasan_canfd_read(priv, CONTROL);
+ ctrl |= DONTCHECKID;
+ arasan_canfd_write(priv, CONTROL, ctrl);
+
+ arasan_canfd_write(priv, OPERATIONAL, op);
+
+ priv->txq_avail = GENMASK(CAN_MAX_TX_QUEUE - 1, 0);
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+ return 0;
+}
+
+/**
+ * arasan_canfd_do_set_mode - This sets the mode of the driver
+ * @ndev: Pointer to net_device structure
+ * @mode: Tells the mode of the driver
+ *
+ * This check the drivers state and calls the corresponding modes to set.
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int arasan_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ ret = arasan_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "arasan_canfd_chip_start failed!\n");
+ return ret;
+ }
+ netif_wake_queue(ndev);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+static void arasan_canfd_err_interrupt(struct net_device *ndev, u32 isr)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ u32 val = arasan_canfd_read(priv, STATUS);
+ u32 txerr = FIELD_GET(STATUS_TEC_MASK, val);
+ u32 rxerr = FIELD_GET(STATUS_REC_MASK, val);
+ struct can_frame cf = { };
+
+ if (isr & OVERLOAD) {
+ cf.can_id |= CAN_ERR_PROT;
+ cf.data[2] |= CAN_ERR_PROT_OVERLOAD;
+ }
+
+ if (isr & ERROR_RX_FRAME) {
+ stats->rx_errors++;
+ cf.data[2] |= CAN_ERR_PROT_FORM;
+ }
+
+ if (isr & TX_RETRANSMIT) {
+ priv->can.can_stats.arbitration_lost++;
+ cf.can_id |= CAN_ERR_LOSTARB;
+ cf.data[0] |= CAN_ERR_LOSTARB_UNSPEC;
+ }
+
+ if (isr & (CL_NO_ACK | FD_NO_ACK)) {
+ stats->tx_errors++;
+ cf.can_id |= CAN_ERR_ACK;
+ cf.data[3] |= CAN_ERR_PROT_LOC_ACK;
+ }
+
+ if (isr & BUS_STATE2) {
+ priv->can.state = CAN_STATE_BUS_OFF;
+ priv->can.can_stats.bus_off++;
+ can_bus_off(ndev);
+ cf.can_id |= CAN_ERR_BUSOFF;
+ }
+
+ if (isr & BUS_STATE1) {
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ priv->can.can_stats.error_passive++;
+ cf.can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
+ cf.data[1] = txerr > rxerr ? CAN_ERR_CRTL_TX_PASSIVE :
+ CAN_ERR_CRTL_RX_PASSIVE;
+ cf.data[6] = txerr;
+ cf.data[7] = rxerr;
+ }
+
+ if (cf.can_id) {
+ struct can_frame *skb_cf;
+ struct sk_buff *skb = alloc_can_err_skb(ndev, &skb_cf);
+
+ if (skb) {
+ skb_cf->can_id |= cf.can_id;
+ memcpy(skb_cf->data, cf.data, CAN_ERR_DLC);
+ netif_rx(skb);
+ }
+ }
+}
+
+static void arasan_canfd_write_frame(struct net_device *ndev, struct sk_buff *skb, int idx)
+{
+ struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ struct can_tx_frame tx_frame;
+ u32 *data = (u32 *)&tx_frame;
+ u8 *ecc = priv->buf->ecc_buffer.read.tx_frames[idx];
+ int i;
+
+ tx_frame.header.header0 = can_fd_len2dlc(cf->len);
+
+ if (cf->can_id & CAN_EFF_FLAG) {
+ /* Extended CAN ID format */
+ tx_frame.header.header0 |= TX_HEADER0_IDE;
+ tx_frame.header.header0 |= FIELD_PREP(TX_HEADER0_ID,
+ (cf->can_id & CAN_EFF_MASK) >> 18);
+ tx_frame.header.ext_id = cf->can_id;
+ } else {
+ /* Standard CAN ID format */
+ tx_frame.header.header0 |= FIELD_PREP(TX_HEADER0_ID, cf->can_id & CAN_SFF_MASK);
+ tx_frame.header.ext_id = 0;
+ }
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ tx_frame.header.header0 |= TX_HEADER0_RTR;
+
+ if (can_is_canfd_skb(skb)) {
+ /* CAN FD frame format */
+ tx_frame.header.header0 |= FIELD_PREP(TX_HEADER0_TYPE_MSK, TX_HEADER0_TYPE_FD);
+
+ if (cf->flags & CANFD_BRS)
+ tx_frame.header.header0 |= TX_HEADER0_BRS;
+
+ if (cf->flags & CANFD_ESI)
+ tx_frame.header.header0 |= TX_HEADER0_ESI;
+
+ } else {
+ /* CAN 2.0 frame format */
+ tx_frame.header.header0 |= FIELD_PREP(TX_HEADER0_TYPE_MSK, TX_HEADER0_TYPE_CL);
+ }
+
+ memcpy(tx_frame.data, cf->data, cf->len);
+ priv->buf->r_buffer.tx_frames[idx] = tx_frame;
+
+ for (i = 0; i < sizeof(tx_frame) / sizeof(u32); i++)
+ ecc[i] = ecc_calc(data[i]);
+
+ arasan_canfd_write(priv, HITXFIFO,
+ priv->buf_dma + offsetof(struct can_buffer, r_buffer.tx_frames[idx]));
+
+ can_put_echo_skb(skb, ndev, idx % CAN_MAX_TX_QUEUE, 0);
+}
+
+static int arasan_canfd_start_xmit_queue(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ int idx;
+
+ guard(spinlock)(&priv->tx_lock);
+
+ idx = ffs(priv->txq_avail);
+ if (unlikely(!idx))
+ return -ENOSPC;
+
+ --idx;
+ priv->txq_avail &= ~(1 << idx);
+ arasan_canfd_write_frame(ndev, skb, idx);
+
+ /* Check if the TX queue is full */
+ if (!priv->txq_avail)
+ netif_stop_queue(ndev);
+
+ return 0;
+}
+
+static netdev_tx_t arasan_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ int ret;
+
+ if (can_dev_dropped_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ ret = arasan_canfd_start_xmit_queue(skb, ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "BUG!, TX full when queue awake!\n");
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ return NETDEV_TX_OK;
+}
+
+static int arasan_canfd_tx(struct net_device *ndev, unsigned long addr)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ int idx;
+
+ guard(spinlock)(&priv->tx_lock);
+
+ idx = (addr - priv->buf_dma - offsetof(struct can_buffer, r_buffer.tx_frames)) /
+ sizeof(struct can_tx_frame);
+ if (idx > CAN_MAX_TX_QUEUE)
+ return 0;
+
+ stats->tx_bytes += can_get_echo_skb(ndev, idx % CAN_MAX_TX_QUEUE, NULL);
+ stats->tx_packets++;
+ priv->txq_avail |= (1 << idx);
+
+ netif_wake_queue(ndev);
+
+ return 1;
+}
+
+static int arasan_canfd_rx(struct net_device *ndev, unsigned long addr)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct canfd_frame *cf;
+ struct sk_buff *skb;
+ u32 header0, header1, header2;
+ struct can_rx_frame *rx_frame;
+ int idx, type;
+
+ idx = (addr - priv->buf_dma - offsetof(struct can_buffer, w_buffer.rx_frames)) /
+ sizeof(struct can_rx_frame);
+ if (idx > CAN_MAX_RX_QUEUE)
+ return 0;
+
+ rx_frame = &priv->buf->w_buffer.rx_frames[idx];
+ header0 = rx_frame->header.header0;
+ header1 = rx_frame->header.header1;
+ header2 = rx_frame->header.header2;
+
+ type = FIELD_GET(RX_HEADER0_TYPE_MSK, header0);
+ if (type == RX_HEADER0_TYPE_FD)
+ skb = alloc_canfd_skb(ndev, &cf);
+ else
+ skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+ if (unlikely(!skb)) {
+ stats->rx_dropped++;
+ return 0;
+ }
+
+ if (type == RX_HEADER0_TYPE_FD) {
+ cf->len = can_fd_dlc2len(FIELD_GET(RX_HEADER0_DLC_MSK, header0));
+
+ if (header2 & RX_HEADER2_FD_BRS)
+ cf->flags |= CANFD_BRS;
+
+ if (header2 & RX_HEADER2_FD_ESI)
+ cf->flags |= CANFD_ESI;
+ } else {
+ cf->len = can_cc_dlc2len(FIELD_GET(RX_HEADER0_DLC_MSK, header0));
+ }
+
+ if (header0 & RX_HEADER0_B_EXT) {
+ /* The received frame is an Extended format frame */
+ cf->can_id = FIELD_GET(RX_HEADER1_EXTID, header1);
+ cf->can_id |= (FIELD_GET(RX_HEADER0_ID_0_6, header0) |
+ (FIELD_GET(RX_HEADER1_ID_7_10, header1) << 7) << 18);
+ cf->can_id |= CAN_EFF_FLAG;
+ } else {
+ /* The received frame is a standard format frame */
+ cf->can_id = FIELD_GET(RX_HEADER0_ID_0_6, header0) |
+ (FIELD_GET(RX_HEADER1_ID_7_10, header1) << 7);
+ }
+ if (header0 & RX_HEADER0_B_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+
+ memcpy(cf->data, rx_frame->data, cf->len);
+
+ if (!(cf->can_id & CAN_RTR_FLAG))
+ stats->rx_bytes += cf->len;
+ stats->rx_packets++;
+
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+static int arasan_canfd_poll(struct napi_struct *napi, int quota)
+{
+ struct net_device *ndev = napi->dev;
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ u32 buf_cnt, ier;
+ int type, work_done = 0;
+ unsigned long addr;
+
+ while ((buf_cnt = arasan_canfd_read(priv, USED_BUFS_PTR_COUNT)) > 0 &&
+ (work_done < quota)) {
+ addr = arasan_canfd_read(priv, USED_BUFS_PTR);
+ type = FIELD_GET(TYPE_MASK, addr);
+ addr &= ~TYPE_MASK;
+ switch (type) {
+ case TYPE_RX:
+ work_done += arasan_canfd_rx(ndev, addr);
+ arasan_canfd_write(priv, RX_PTR_BUFS, addr);
+ break;
+ case TYPE_TX:
+ work_done += arasan_canfd_tx(ndev, addr);
+ break;
+ case TYPE_TX_ABORT:
+ netdev_err(ndev, "tx abort\n");
+ break;
+ default:
+ netdev_err(ndev, "Unknown type: %d\n", type);
+ break;
+ }
+ }
+
+ if (work_done < quota) {
+ if (napi_complete_done(napi, work_done)) {
+ ier = arasan_canfd_read(priv, INTERRUPT_MASK0);
+ ier |= USED_BUFFERS_NOT_EMPTY;
+ arasan_canfd_write(priv, INTERRUPT_MASK0, ier);
+ }
+ }
+ return work_done;
+}
+
+static irqreturn_t arasan_canfd_isr(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *)dev_id;
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ u32 isr_errors, isr, ier;
+
+ isr = arasan_canfd_read(priv, INTERRUPTS_FROM_HW);
+ if (!isr)
+ return IRQ_NONE;
+
+ arasan_canfd_write(priv, INTERRUPTS_FROM_HW, isr);
+
+ ier = GENMASK(31, 0);
+ ier &= ~(GOOD_RX_FRAME | TX_FIFO_EMPTY | CLTX_GOOD | FDTX_GOOD | USED_BUFFERS_NOT_EMPTY);
+ isr_errors = isr & ier;
+ if (isr_errors)
+ arasan_canfd_err_interrupt(ndev, isr_errors);
+
+ if (isr & USED_BUFFERS_NOT_EMPTY) {
+ ier = arasan_canfd_read(priv, INTERRUPT_MASK0);
+ ier &= ~USED_BUFFERS_NOT_EMPTY;
+ arasan_canfd_write(priv, INTERRUPT_MASK0, ier);
+ napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void arasan_canfd_chip_stop(struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+
+ arasan_canfd_write(priv, OPERATIONAL, 0);
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int arasan_canfd_open(struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = phy_power_on(priv->transceiver);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_get_sync(priv->dev);
+ if (ret < 0) {
+ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", __func__, ret);
+ goto err;
+ }
+
+ ret = request_irq(ndev->irq, arasan_canfd_isr, 0, ndev->name, ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "Failed to request irq\n");
+ goto err;
+ }
+
+ ret = open_candev(ndev);
+ if (ret)
+ goto err_irq;
+
+ ret = arasan_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "arasan_canfd_chip_start failed!\n");
+ goto err_candev;
+ }
+
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+
+ return 0;
+
+err_candev:
+ close_candev(ndev);
+err_irq:
+ free_irq(ndev->irq, ndev);
+err:
+ pm_runtime_put(priv->dev);
+ phy_power_off(priv->transceiver);
+
+ return ret;
+}
+
+static int arasan_canfd_close(struct net_device *ndev)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+ arasan_canfd_chip_stop(ndev);
+ free_irq(ndev->irq, ndev);
+ close_candev(ndev);
+
+ pm_runtime_put(priv->dev);
+ phy_power_off(priv->transceiver);
+
+ return 0;
+}
+
+static int arasan_canfd_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+ u32 val;
+
+ ret = pm_runtime_get_sync(priv->dev);
+ if (ret < 0) {
+ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", __func__, ret);
+ pm_runtime_put(priv->dev);
+ return ret;
+ }
+
+ val = arasan_canfd_read(priv, STATUS);
+ bec->txerr = FIELD_GET(STATUS_TEC_MASK, val);
+ bec->rxerr = FIELD_GET(STATUS_REC_MASK, val);
+
+ pm_runtime_put(priv->dev);
+
+ return 0;
+}
+
+static void arasan_canfd_init(struct arasan_canfd_priv *priv)
+{
+ u32 val;
+ int i;
+
+ /* soft reset */
+ val = arasan_canfd_read(priv, CONTROL);
+ val |= (SOFTRESET | DMA_SOFTRESET);
+ arasan_canfd_write(priv, CONTROL, val);
+ udelay(1);
+ val &= ~(SOFTRESET | DMA_SOFTRESET);
+ arasan_canfd_write(priv, CONTROL, val);
+
+ /* setup features */
+ val |= LVL_ISOFD;
+ val &= ~DISABLE_ECC;
+ arasan_canfd_write(priv, CONTROL, val);
+ arasan_canfd_write(priv, SELF_ADDRESS, 0);
+ arasan_canfd_write(priv, ALLOWED_RETRANSMITS, 0);
+ arasan_canfd_write(priv, WATCHDOG_LIMIT, 0);
+
+ /* setup base registers */
+ arasan_canfd_write(priv, R_BASE_ADDR,
+ priv->buf_dma + offsetof(struct can_buffer, r_buffer));
+ arasan_canfd_write(priv, R_ECC_ADDR,
+ priv->buf_dma + offsetof(struct can_buffer, ecc_buffer.read));
+ arasan_canfd_write(priv, W_BASE_ADDR,
+ priv->buf_dma + offsetof(struct can_buffer, w_buffer));
+ arasan_canfd_write(priv, W_ECC_ADDR,
+ priv->buf_dma + offsetof(struct can_buffer, ecc_buffer.write));
+
+ /* setup RX buffers */
+ for (i = 0; i < CAN_MAX_RX_QUEUE; i++)
+ arasan_canfd_write(priv, RX_PTR_BUFS, priv->buf_dma +
+ offsetof(struct can_buffer, w_buffer.rx_frames[i]));
+
+ /* setup filter buffers */
+ arasan_canfd_write(priv, NUMBER_OF_PAIRS, 0);
+ arasan_canfd_write(priv, BASE_ADDRESS,
+ priv->buf_dma + offsetof(struct can_buffer, r_buffer.rx_filters[0]));
+}
+
+static const struct net_device_ops arasan_canfd_netdev_ops = {
+ .ndo_open = arasan_canfd_open,
+ .ndo_stop = arasan_canfd_close,
+ .ndo_start_xmit = arasan_canfd_start_xmit,
+};
+
+static const struct ethtool_ops arasan_canfd_ethtool_ops = {
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int arasan_canfd_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+
+ if (netif_running(ndev)) {
+ netif_stop_queue(ndev);
+ netif_device_detach(ndev);
+ arasan_canfd_chip_stop(ndev);
+ }
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int arasan_canfd_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret) {
+ dev_err(dev, "pm_runtime_force_resume failed on resume\n");
+ return ret;
+ }
+
+ arasan_canfd_init(priv);
+
+ if (netif_running(ndev)) {
+ ret = arasan_canfd_chip_start(ndev);
+ if (ret) {
+ dev_err(dev, "arasan_canfd_chip_start failed on resume\n");
+ return ret;
+ }
+
+ netif_device_attach(ndev);
+ netif_start_queue(ndev);
+ }
+
+ return 0;
+}
+
+static int arasan_canfd_runtime_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+
+ clk_disable_unprepare(priv->sys_clk);
+ clk_disable_unprepare(priv->can_clk);
+
+ return 0;
+}
+
+static int arasan_canfd_runtime_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->sys_clk);
+ if (ret) {
+ dev_err(dev, "Cannot enable clock.\n");
+ return ret;
+ }
+ ret = clk_prepare_enable(priv->can_clk);
+ if (ret) {
+ dev_err(dev, "Cannot enable clock.\n");
+ clk_disable_unprepare(priv->sys_clk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops arasan_canfd_dev_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(arasan_canfd_suspend, arasan_canfd_resume)
+ RUNTIME_PM_OPS(arasan_canfd_runtime_suspend, arasan_canfd_runtime_resume, NULL)
+};
+
+static int arasan_canfd_probe(struct platform_device *pdev)
+{
+ struct net_device *ndev;
+ struct arasan_canfd_priv *priv;
+ struct phy *transceiver;
+ void __iomem *addr;
+ int ret;
+
+ addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(addr))
+ return PTR_ERR(addr);
+
+ ndev = alloc_candev(sizeof(struct arasan_canfd_priv), CAN_MAX_TX_QUEUE);
+ if (!ndev)
+ return -ENOMEM;
+ priv = netdev_priv(ndev);
+
+ priv->sys_rst = devm_reset_control_get(&pdev->dev, "sys");
+ if (IS_ERR(priv->sys_rst)) {
+ dev_err(&pdev->dev, "failed to get sys reset\n");
+ ret = PTR_ERR(priv->sys_rst);
+ goto err_free;
+ }
+ reset_control_assert(priv->sys_rst);
+ reset_control_deassert(priv->sys_rst);
+
+ priv->can_rst = devm_reset_control_get(&pdev->dev, "can");
+ if (IS_ERR(priv->can_rst)) {
+ dev_err(&pdev->dev, "failed to get can reset\n");
+ ret = PTR_ERR(priv->can_rst);
+ goto err_reset_sys;
+ }
+ reset_control_assert(priv->can_rst);
+ reset_control_deassert(priv->can_rst);
+
+ priv->can_clk = devm_clk_get(&pdev->dev, "can");
+ if (IS_ERR(priv->can_clk)) {
+ ret = dev_err_probe(&pdev->dev, PTR_ERR(priv->can_clk),
+ "failed to get can clock\n");
+ goto err_reset;
+ }
+
+ priv->sys_clk = devm_clk_get(&pdev->dev, "sys");
+ if (IS_ERR(priv->sys_clk)) {
+ ret = dev_err_probe(&pdev->dev, PTR_ERR(priv->sys_clk),
+ "failed to get sys clock\n");
+ goto err_reset;
+ }
+
+ priv->ssp = DEFAULT_SSP;
+
+ transceiver = devm_phy_optional_get(&pdev->dev, NULL);
+ if (IS_ERR(transceiver)) {
+ ret = PTR_ERR(transceiver);
+ dev_err_probe(&pdev->dev, ret, "failed to get phy\n");
+ goto err_reset;
+ }
+ priv->transceiver = transceiver;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto err_reset;
+ ndev->irq = ret;
+
+ spin_lock_init(&priv->tx_lock);
+ platform_set_drvdata(pdev, ndev);
+ priv->dev = &pdev->dev;
+ priv->base = addr;
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0) {
+ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", __func__, ret);
+ goto err_disableclks;
+ }
+
+ priv->buf = dmam_alloc_coherent(&pdev->dev, sizeof(*priv->buf), &priv->buf_dma, GFP_KERNEL);
+ if (!priv->buf) {
+ ret = -ENOMEM;
+ goto err_disableclks;
+ }
+
+ priv->can.bittiming_const = &arasan_canfd_bittiming_const;
+ priv->can.fd.data_bittiming_const = &arasan_canfd_data_bittiming_const;
+ priv->can.do_set_mode = arasan_canfd_do_set_mode;
+ priv->can.do_get_berr_counter = arasan_canfd_get_berr_counter;
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_BERR_REPORTING |
+ CAN_CTRLMODE_FD;
+ priv->can.clock.freq = clk_get_rate(priv->can_clk);
+
+ ndev->flags |= IFF_ECHO;
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+ ndev->netdev_ops = &arasan_canfd_netdev_ops;
+ ndev->ethtool_ops = &arasan_canfd_ethtool_ops;
+
+ netif_napi_add_weight(ndev, &priv->napi, arasan_canfd_poll, ARASAN_CANFD_NAPI_WEIGHT);
+
+ arasan_canfd_init(priv);
+
+ ret = register_candev(ndev);
+ if (ret) {
+ dev_err(&pdev->dev, "fail to register failed (err=%d)\n", ret);
+ goto err_napidel;
+ }
+
+ of_can_transceiver(ndev);
+ pm_runtime_put(&pdev->dev);
+
+ return 0;
+
+err_napidel:
+ netif_napi_del(&priv->napi);
+err_disableclks:
+ pm_runtime_put(priv->dev);
+ pm_runtime_disable(&pdev->dev);
+err_reset:
+ reset_control_assert(priv->can_rst);
+err_reset_sys:
+ reset_control_assert(priv->sys_rst);
+err_free:
+ free_candev(ndev);
+
+ return ret;
+}
+
+static void arasan_canfd_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct arasan_canfd_priv *priv = netdev_priv(ndev);
+
+ unregister_candev(ndev);
+ netif_napi_del(&priv->napi);
+ pm_runtime_disable(&pdev->dev);
+ reset_control_assert(priv->sys_rst);
+ reset_control_assert(priv->can_rst);
+ free_candev(ndev);
+}
+
+static const struct of_device_id arasan_canfd_of_match[] = {
+ { .compatible = "arasan,canfd" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, arasan_canfd_of_match);
+
+static struct platform_driver arasan_canfd_driver = {
+ .probe = arasan_canfd_probe,
+ .remove = arasan_canfd_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .pm = pm_ptr(&arasan_canfd_dev_pm_ops),
+ .of_match_table = arasan_canfd_of_match,
+ },
+};
+module_platform_driver(arasan_canfd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
+MODULE_DESCRIPTION("Arasan CANFD driver");
--
2.53.0
next prev parent reply other threads:[~2026-08-03 14:25 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-03 14:05 [PATCH 0/2] can: add arasan canfd controller support Jisheng Zhang
2026-08-03 14:05 ` [PATCH 1/2] dt-bindings: can: add arasan canfd controller Jisheng Zhang
2026-08-03 14:31 ` sashiko-bot
2026-08-03 15:17 ` Krzysztof Kozlowski
2026-08-03 14:05 ` Jisheng Zhang [this message]
2026-08-03 14:43 ` [PATCH 2/2] can: arasan-canfd: add driver for arasan CAN-FD controller sashiko-bot
2026-08-03 15:21 ` Philipp Zabel
2026-08-03 16:05 ` Oliver Hartkopp
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260803140531.12692-3-jszhang@kernel.org \
--to=jszhang@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-can@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mailhol@kernel.org \
--cc=mkl@pengutronix.de \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.