All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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.