All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marc Kleine-Budde <mkl@pengutronix.de>
To: linux-can@vger.kernel.org
Cc: manivannan.sadhasivam@linaro.org,
	dev.kurt@vandijck-laurijssen.be,
	Marc Kleine-Budde <mkl@pengutronix.de>
Subject: [PATCH v53 3/6] can: mcp25xxfd: add driver for Microchip MCP25xxFD SPI CAN
Date: Fri, 18 Sep 2020 19:25:33 +0200	[thread overview]
Message-ID: <20200918172536.2074504-4-mkl@pengutronix.de> (raw)
In-Reply-To: <20200918172536.2074504-1-mkl@pengutronix.de>

This patch adds support for the Microchip MCP25xxFD SPI CAN controller family.

Tested-by: Kurt Van Dijck <dev.kurt@vandijck-laurijssen.be>
Tested-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 drivers/net/can/spi/mcp25xxfd/Makefile        |    1 +
 .../net/can/spi/mcp25xxfd/mcp25xxfd-core.c    | 2907 +++++++++++++++++
 2 files changed, 2908 insertions(+)
 create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c

diff --git a/drivers/net/can/spi/mcp25xxfd/Makefile b/drivers/net/can/spi/mcp25xxfd/Makefile
index 9dadf0070b42..4e17f592e22e 100644
--- a/drivers/net/can/spi/mcp25xxfd/Makefile
+++ b/drivers/net/can/spi/mcp25xxfd/Makefile
@@ -3,5 +3,6 @@
 obj-$(CONFIG_CAN_MCP25XXFD) += mcp25xxfd.o
 
 mcp25xxfd-objs :=
+mcp25xxfd-objs += mcp25xxfd-core.o
 mcp25xxfd-objs += mcp25xxfd-crc16.o
 mcp25xxfd-objs += mcp25xxfd-regmap.o
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c
new file mode 100644
index 000000000000..10e4b6d07f95
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c
@@ -0,0 +1,2907 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// mcp25xxfd - Microchip MCP25xxFD Family CAN controller driver
+//
+// Copyright (c) 2019, 2020 Pengutronix,
+//                          Marc Kleine-Budde <kernel@pengutronix.de>
+//
+// Based on:
+//
+// CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+//
+// Copyright (c) 2019 Martin Sperl <kernel@martin.sperl.org>
+//
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <asm/unaligned.h>
+
+#include "mcp25xxfd.h"
+
+#define DEVICE_NAME "mcp25xxfd"
+
+static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp2517fd = {
+	.quirks = MCP25XXFD_QUIRK_MAB_NO_WARN | MCP25XXFD_QUIRK_CRC_REG |
+		MCP25XXFD_QUIRK_CRC_RX | MCP25XXFD_QUIRK_CRC_TX |
+		MCP25XXFD_QUIRK_ECC,
+	.model = MCP25XXFD_MODEL_MCP2517FD,
+};
+
+static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp2518fd = {
+	.quirks = MCP25XXFD_QUIRK_CRC_REG | MCP25XXFD_QUIRK_CRC_RX |
+		MCP25XXFD_QUIRK_CRC_TX | MCP25XXFD_QUIRK_ECC,
+	.model = MCP25XXFD_MODEL_MCP2518FD,
+};
+
+/* Autodetect model, start with CRC enabled. */
+static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp25xxfd = {
+	.quirks = MCP25XXFD_QUIRK_CRC_REG | MCP25XXFD_QUIRK_CRC_RX |
+		MCP25XXFD_QUIRK_CRC_TX | MCP25XXFD_QUIRK_ECC,
+	.model = MCP25XXFD_MODEL_MCP25XXFD,
+};
+
+static const struct can_bittiming_const mcp25xxfd_bittiming_const = {
+	.name = DEVICE_NAME,
+	.tseg1_min = 2,
+	.tseg1_max = 256,
+	.tseg2_min = 1,
+	.tseg2_max = 128,
+	.sjw_max = 128,
+	.brp_min = 1,
+	.brp_max = 256,
+	.brp_inc = 1,
+};
+
+static const struct can_bittiming_const mcp25xxfd_data_bittiming_const = {
+	.name = DEVICE_NAME,
+	.tseg1_min = 1,
+	.tseg1_max = 32,
+	.tseg2_min = 1,
+	.tseg2_max = 16,
+	.sjw_max = 16,
+	.brp_min = 1,
+	.brp_max = 256,
+	.brp_inc = 1,
+};
+
+static const char *__mcp25xxfd_get_model_str(enum mcp25xxfd_model model)
+{
+	switch (model) {
+	case MCP25XXFD_MODEL_MCP2517FD:
+		return "MCP2517FD"; break;
+	case MCP25XXFD_MODEL_MCP2518FD:
+		return "MCP2518FD"; break;
+	case MCP25XXFD_MODEL_MCP25XXFD:
+		return "MCP25xxFD"; break;
+	}
+
+	return "<unknown>";
+}
+
+static inline const char *
+mcp25xxfd_get_model_str(const struct mcp25xxfd_priv *priv)
+{
+	return __mcp25xxfd_get_model_str(priv->devtype_data.model);
+}
+
+static const char *mcp25xxfd_get_mode_str(const u8 mode)
+{
+	switch (mode) {
+	case MCP25XXFD_REG_CON_MODE_MIXED:
+		return "Mixed (CAN FD/CAN 2.0)"; break;
+	case MCP25XXFD_REG_CON_MODE_SLEEP:
+		return "Sleep"; break;
+	case MCP25XXFD_REG_CON_MODE_INT_LOOPBACK:
+		return "Internal Loopback"; break;
+	case MCP25XXFD_REG_CON_MODE_LISTENONLY:
+		return "Listen Only"; break;
+	case MCP25XXFD_REG_CON_MODE_CONFIG:
+		return "Configuration"; break;
+	case MCP25XXFD_REG_CON_MODE_EXT_LOOPBACK:
+		return "External Loopback"; break;
+	case MCP25XXFD_REG_CON_MODE_CAN2_0:
+		return "CAN 2.0"; break;
+	case MCP25XXFD_REG_CON_MODE_RESTRICTED:
+		return "Restricted Operation"; break;
+	}
+
+	return "<unknown>";
+}
+
+static inline int mcp25xxfd_vdd_enable(const struct mcp25xxfd_priv *priv)
+{
+	if (!priv->reg_vdd)
+		return 0;
+
+	return regulator_enable(priv->reg_vdd);
+}
+
+static inline int mcp25xxfd_vdd_disable(const struct mcp25xxfd_priv *priv)
+{
+	if (!priv->reg_vdd)
+		return 0;
+
+	return regulator_disable(priv->reg_vdd);
+}
+
+static inline int
+mcp25xxfd_transceiver_enable(const struct mcp25xxfd_priv *priv)
+{
+	if (!priv->reg_xceiver)
+		return 0;
+
+	return regulator_enable(priv->reg_xceiver);
+}
+
+static inline int
+mcp25xxfd_transceiver_disable(const struct mcp25xxfd_priv *priv)
+{
+	if (!priv->reg_xceiver)
+		return 0;
+
+	return regulator_disable(priv->reg_xceiver);
+}
+
+static int mcp25xxfd_clks_and_vdd_enable(const struct mcp25xxfd_priv *priv)
+{
+	int err;
+
+	err = clk_prepare_enable(priv->clk);
+	if (err)
+		return err;
+
+	err = mcp25xxfd_vdd_enable(priv);
+	if (err)
+		clk_disable_unprepare(priv->clk);
+
+	/* Wait for oscillator stabilisation time after power up */
+	usleep_range(MCP25XXFD_OSC_STAB_SLEEP_US,
+		     2 * MCP25XXFD_OSC_STAB_SLEEP_US);
+
+	return err;
+}
+
+static int mcp25xxfd_clks_and_vdd_disable(const struct mcp25xxfd_priv *priv)
+{
+	int err;
+
+	err = mcp25xxfd_vdd_disable(priv);
+	if (err)
+		return err;
+
+	clk_disable_unprepare(priv->clk);
+
+	return 0;
+}
+
+static inline u8
+mcp25xxfd_cmd_prepare_write_reg(const struct mcp25xxfd_priv *priv,
+				union mcp25xxfd_write_reg_buf *write_reg_buf,
+				const u16 reg, const u32 mask, const u32 val)
+{
+	u8 first_byte, last_byte, len;
+	u8 *data;
+	__le32 val_le32;
+
+	first_byte = mcp25xxfd_first_byte_set(mask);
+	last_byte = mcp25xxfd_last_byte_set(mask);
+	len = last_byte - first_byte + 1;
+
+	data = mcp25xxfd_spi_cmd_write(priv, write_reg_buf, reg + first_byte);
+	val_le32 = cpu_to_le32(val >> BITS_PER_BYTE * first_byte);
+	memcpy(data, &val_le32, len);
+
+	if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_REG) {
+		u16 crc;
+
+		mcp25xxfd_spi_cmd_crc_set_len_in_reg(&write_reg_buf->crc.cmd,
+						     len);
+		/* CRC */
+		len += sizeof(write_reg_buf->crc.cmd);
+		crc = mcp25xxfd_crc16_compute(&write_reg_buf->crc, len);
+		put_unaligned_be16(crc, (void *)write_reg_buf + len);
+
+		/* Total length */
+		len += sizeof(write_reg_buf->crc.crc);
+	} else {
+		len += sizeof(write_reg_buf->nocrc.cmd);
+	}
+
+	return len;
+}
+
+static inline int
+mcp25xxfd_tef_tail_get_from_chip(const struct mcp25xxfd_priv *priv,
+				 u8 *tef_tail)
+{
+	u32 tef_ua;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_TEFUA, &tef_ua);
+	if (err)
+		return err;
+
+	*tef_tail = tef_ua / sizeof(struct mcp25xxfd_hw_tef_obj);
+
+	return 0;
+}
+
+static inline int
+mcp25xxfd_tx_tail_get_from_chip(const struct mcp25xxfd_priv *priv,
+				u8 *tx_tail)
+{
+	u32 fifo_sta;
+	int err;
+
+	err = regmap_read(priv->map_reg,
+			  MCP25XXFD_REG_FIFOSTA(MCP25XXFD_TX_FIFO),
+			  &fifo_sta);
+	if (err)
+		return err;
+
+	*tx_tail = FIELD_GET(MCP25XXFD_REG_FIFOSTA_FIFOCI_MASK, fifo_sta);
+
+	return 0;
+}
+
+static inline int
+mcp25xxfd_rx_head_get_from_chip(const struct mcp25xxfd_priv *priv,
+				const struct mcp25xxfd_rx_ring *ring,
+				u8 *rx_head)
+{
+	u32 fifo_sta;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_FIFOSTA(ring->fifo_nr),
+			  &fifo_sta);
+	if (err)
+		return err;
+
+	*rx_head = FIELD_GET(MCP25XXFD_REG_FIFOSTA_FIFOCI_MASK, fifo_sta);
+
+	return 0;
+}
+
+static inline int
+mcp25xxfd_rx_tail_get_from_chip(const struct mcp25xxfd_priv *priv,
+				const struct mcp25xxfd_rx_ring *ring,
+				u8 *rx_tail)
+{
+	u32 fifo_ua;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_FIFOUA(ring->fifo_nr),
+			  &fifo_ua);
+	if (err)
+		return err;
+
+	fifo_ua -= ring->base - MCP25XXFD_RAM_START;
+	*rx_tail = fifo_ua / ring->obj_size;
+
+	return 0;
+}
+
+static void
+mcp25xxfd_tx_ring_init_tx_obj(const struct mcp25xxfd_priv *priv,
+			      const struct mcp25xxfd_tx_ring *ring,
+			      struct mcp25xxfd_tx_obj *tx_obj,
+			      const u8 rts_buf_len,
+			      const u8 n)
+{
+	struct spi_transfer *xfer;
+	u16 addr;
+
+	/* FIFO load */
+	addr = mcp25xxfd_get_tx_obj_addr(ring, n);
+	if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_TX)
+		mcp25xxfd_spi_cmd_write_crc_set_addr(&tx_obj->buf.crc.cmd,
+						     addr);
+	else
+		mcp25xxfd_spi_cmd_write_nocrc(&tx_obj->buf.nocrc.cmd,
+					      addr);
+
+	xfer = &tx_obj->xfer[0];
+	xfer->tx_buf = &tx_obj->buf;
+	xfer->len = 0;	/* actual len is assigned on the fly */
+	xfer->cs_change = 1;
+	xfer->cs_change_delay.value = 0;
+	xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+
+	/* FIFO request to send */
+	xfer = &tx_obj->xfer[1];
+	xfer->tx_buf = &ring->rts_buf;
+	xfer->len = rts_buf_len;
+
+	/* SPI message */
+	spi_message_init_with_transfers(&tx_obj->msg, tx_obj->xfer,
+					ARRAY_SIZE(tx_obj->xfer));
+}
+
+static void mcp25xxfd_ring_init(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_tx_ring *tx_ring;
+	struct mcp25xxfd_rx_ring *rx_ring, *prev_rx_ring = NULL;
+	struct mcp25xxfd_tx_obj *tx_obj;
+	u32 val;
+	u16 addr;
+	u8 len;
+	int i;
+
+	/* TEF */
+	priv->tef.head = 0;
+	priv->tef.tail = 0;
+
+	/* TX */
+	tx_ring = priv->tx;
+	tx_ring->head = 0;
+	tx_ring->tail = 0;
+	tx_ring->base = mcp25xxfd_get_tef_obj_addr(tx_ring->obj_num);
+
+	/* FIFO request to send */
+	addr = MCP25XXFD_REG_FIFOCON(MCP25XXFD_TX_FIFO);
+	val = MCP25XXFD_REG_FIFOCON_TXREQ | MCP25XXFD_REG_FIFOCON_UINC;
+	len = mcp25xxfd_cmd_prepare_write_reg(priv, &tx_ring->rts_buf,
+					      addr, val, val);
+
+	mcp25xxfd_for_each_tx_obj(tx_ring, tx_obj, i)
+		mcp25xxfd_tx_ring_init_tx_obj(priv, tx_ring, tx_obj, len, i);
+
+	/* RX */
+	mcp25xxfd_for_each_rx_ring(priv, rx_ring, i) {
+		rx_ring->head = 0;
+		rx_ring->tail = 0;
+		rx_ring->nr = i;
+		rx_ring->fifo_nr = MCP25XXFD_RX_FIFO(i);
+
+		if (!prev_rx_ring)
+			rx_ring->base =
+				mcp25xxfd_get_tx_obj_addr(tx_ring,
+							  tx_ring->obj_num);
+		else
+			rx_ring->base = prev_rx_ring->base +
+				prev_rx_ring->obj_size *
+				prev_rx_ring->obj_num;
+
+		prev_rx_ring = rx_ring;
+	}
+}
+
+static void mcp25xxfd_ring_free(struct mcp25xxfd_priv *priv)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(priv->rx) - 1; i > 0; i--) {
+		kfree(priv->rx[i]);
+		priv->rx[i] = NULL;
+	}
+}
+
+static int mcp25xxfd_ring_alloc(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_tx_ring *tx_ring;
+	struct mcp25xxfd_rx_ring *rx_ring;
+	int tef_obj_size, tx_obj_size, rx_obj_size;
+	int tx_obj_num;
+	int ram_free, i;
+
+	tef_obj_size = sizeof(struct mcp25xxfd_hw_tef_obj);
+	if (priv->can.ctrlmode & CAN_CTRLMODE_FD) {
+		tx_obj_num = MCP25XXFD_TX_OBJ_NUM_CANFD;
+		tx_obj_size = sizeof(struct mcp25xxfd_hw_tx_obj_canfd);
+		rx_obj_size = sizeof(struct mcp25xxfd_hw_rx_obj_canfd);
+	} else {
+		tx_obj_num = MCP25XXFD_TX_OBJ_NUM_CAN;
+		tx_obj_size = sizeof(struct mcp25xxfd_hw_tx_obj_can);
+		rx_obj_size = sizeof(struct mcp25xxfd_hw_rx_obj_can);
+	}
+
+	tx_ring = priv->tx;
+	tx_ring->obj_num = tx_obj_num;
+	tx_ring->obj_size = tx_obj_size;
+
+	ram_free = MCP25XXFD_RAM_SIZE - tx_obj_num *
+		(tef_obj_size + tx_obj_size);
+
+	for (i = 0;
+	     i < ARRAY_SIZE(priv->rx) && ram_free >= rx_obj_size;
+	     i++) {
+		int rx_obj_num;
+
+		rx_obj_num = ram_free / rx_obj_size;
+		rx_obj_num = min(1 << (fls(rx_obj_num) - 1), 32);
+
+		rx_ring = kzalloc(sizeof(*rx_ring) + rx_obj_size * rx_obj_num,
+				  GFP_KERNEL);
+		if (!rx_ring) {
+			mcp25xxfd_ring_free(priv);
+			return -ENOMEM;
+		}
+		rx_ring->obj_num = rx_obj_num;
+		rx_ring->obj_size = rx_obj_size;
+		priv->rx[i] = rx_ring;
+
+		ram_free -= rx_ring->obj_num * rx_ring->obj_size;
+	}
+	priv->rx_ring_num = i;
+
+	netdev_dbg(priv->ndev,
+		   "FIFO setup: TEF: %d*%d bytes = %d bytes, TX: %d*%d bytes = %d bytes\n",
+		   tx_obj_num, tef_obj_size, tef_obj_size * tx_obj_num,
+		   tx_obj_num, tx_obj_size, tx_obj_size * tx_obj_num);
+
+	mcp25xxfd_for_each_rx_ring(priv, rx_ring, i) {
+		netdev_dbg(priv->ndev,
+			   "FIFO setup: RX-%d: %d*%d bytes = %d bytes\n",
+			   i, rx_ring->obj_num, rx_ring->obj_size,
+			   rx_ring->obj_size * rx_ring->obj_num);
+	}
+
+	netdev_dbg(priv->ndev,
+		   "FIFO setup: free: %d bytes\n",
+		   ram_free);
+
+	return 0;
+}
+
+static inline int
+mcp25xxfd_chip_get_mode(const struct mcp25xxfd_priv *priv, u8 *mode)
+{
+	u32 val;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_CON, &val);
+	if (err)
+		return err;
+
+	*mode = FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK, val);
+
+	return 0;
+}
+
+static int
+__mcp25xxfd_chip_set_mode(const struct mcp25xxfd_priv *priv,
+			  const u8 mode_req, bool nowait)
+{
+	u32 con, con_reqop;
+	int err;
+
+	con_reqop = FIELD_PREP(MCP25XXFD_REG_CON_REQOP_MASK, mode_req);
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_CON,
+				 MCP25XXFD_REG_CON_REQOP_MASK, con_reqop);
+	if (err)
+		return err;
+
+	if (mode_req == MCP25XXFD_REG_CON_MODE_SLEEP || nowait)
+		return 0;
+
+	err = regmap_read_poll_timeout(priv->map_reg, MCP25XXFD_REG_CON, con,
+				       FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK,
+						 con) == mode_req,
+				       MCP25XXFD_POLL_SLEEP_US,
+				       MCP25XXFD_POLL_TIMEOUT_US);
+	if (err) {
+		u8 mode = FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK, con);
+
+		netdev_err(priv->ndev,
+			   "Controller failed to enter mode %s Mode (%u) and stays in %s Mode (%u).\n",
+			   mcp25xxfd_get_mode_str(mode_req), mode_req,
+			   mcp25xxfd_get_mode_str(mode), mode);
+		return err;
+	}
+
+	return 0;
+}
+
+static inline int
+mcp25xxfd_chip_set_mode(const struct mcp25xxfd_priv *priv,
+			const u8 mode_req)
+{
+	return __mcp25xxfd_chip_set_mode(priv, mode_req, false);
+}
+
+static inline int
+mcp25xxfd_chip_set_mode_nowait(const struct mcp25xxfd_priv *priv,
+			       const u8 mode_req)
+{
+	return __mcp25xxfd_chip_set_mode(priv, mode_req, true);
+}
+
+static inline bool mcp25xxfd_osc_invalid(u32 reg)
+{
+	return reg == 0x0 || reg == 0xffffffff;
+}
+
+static int mcp25xxfd_chip_clock_enable(const struct mcp25xxfd_priv *priv)
+{
+	u32 osc, osc_reference, osc_mask;
+	int err;
+
+	/* Set Power On Defaults for "Clock Output Divisor" and remove
+	 * "Oscillator Disable" bit.
+	 */
+	osc = FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK,
+			 MCP25XXFD_REG_OSC_CLKODIV_10);
+	osc_reference = MCP25XXFD_REG_OSC_OSCRDY;
+	osc_mask = MCP25XXFD_REG_OSC_OSCRDY | MCP25XXFD_REG_OSC_PLLRDY;
+
+	/* Note:
+	 *
+	 * If the controller is in Sleep Mode the following write only
+	 * removes the "Oscillator Disable" bit and powers it up. All
+	 * other bits are unaffected.
+	 */
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_OSC, osc);
+	if (err)
+		return err;
+
+	/* Wait for "Oscillator Ready" bit */
+	err = regmap_read_poll_timeout(priv->map_reg, MCP25XXFD_REG_OSC, osc,
+				       (osc & osc_mask) == osc_reference,
+				       MCP25XXFD_OSC_STAB_SLEEP_US,
+				       MCP25XXFD_OSC_STAB_TIMEOUT_US);
+	if (mcp25xxfd_osc_invalid(osc)) {
+		netdev_err(priv->ndev,
+			   "Failed to detect %s (osc=0x%08x).\n",
+			   mcp25xxfd_get_model_str(priv), osc);
+		return -ENODEV;
+	} else if (err == -ETIMEDOUT) {
+		netdev_err(priv->ndev,
+			   "Timeout waiting for Oscillator Ready (osc=0x%08x, osc_reference=0x%08x)\n",
+			   osc, osc_reference);
+		return -ETIMEDOUT;
+	} else if (err) {
+		return err;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_chip_softreset_do(const struct mcp25xxfd_priv *priv)
+{
+	const __be16 cmd = mcp25xxfd_cmd_reset();
+	int err;
+
+	/* The Set Mode and SPI Reset command only seems to works if
+	 * the controller is not in Sleep Mode.
+	 */
+	err = mcp25xxfd_chip_clock_enable(priv);
+	if (err)
+		return err;
+
+	err = mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_CONFIG);
+	if (err)
+		return err;
+
+	/* spi_write_then_read() works with non DMA-safe buffers */
+	return spi_write_then_read(priv->spi, &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int mcp25xxfd_chip_softreset_check(const struct mcp25xxfd_priv *priv)
+{
+	u32 osc, osc_reference;
+	u8 mode;
+	int err;
+
+	err = mcp25xxfd_chip_get_mode(priv, &mode);
+	if (err)
+		return err;
+
+	if (mode != MCP25XXFD_REG_CON_MODE_CONFIG) {
+		netdev_info(priv->ndev,
+			    "Controller not in Config Mode after reset, but in %s Mode (%u).\n",
+			    mcp25xxfd_get_mode_str(mode), mode);
+		return -ETIMEDOUT;
+	}
+
+	osc_reference = MCP25XXFD_REG_OSC_OSCRDY |
+		FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK,
+			   MCP25XXFD_REG_OSC_CLKODIV_10);
+
+	/* check reset defaults of OSC reg */
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_OSC, &osc);
+	if (err)
+		return err;
+
+	if (osc != osc_reference) {
+		netdev_info(priv->ndev,
+			    "Controller failed to reset. osc=0x%08x, reference value=0x%08x\n",
+			    osc, osc_reference);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_chip_softreset(const struct mcp25xxfd_priv *priv)
+{
+	int err, i;
+
+	for (i = 0; i < MCP25XXFD_SOFTRESET_RETRIES_MAX; i++) {
+		if (i)
+			netdev_info(priv->ndev,
+				    "Retrying to reset Controller.\n");
+
+		err = mcp25xxfd_chip_softreset_do(priv);
+		if (err == -ETIMEDOUT)
+			continue;
+		if (err)
+			return err;
+
+		err = mcp25xxfd_chip_softreset_check(priv);
+		if (err == -ETIMEDOUT)
+			continue;
+		if (err)
+			return err;
+
+		return 0;
+	}
+
+	if (err)
+		return err;
+
+	return -ETIMEDOUT;
+}
+
+static int mcp25xxfd_chip_clock_init(const struct mcp25xxfd_priv *priv)
+{
+	u32 osc;
+	int err;
+
+	/* Activate Low Power Mode on Oscillator Disable. This only
+	 * works on the MCP2518FD. The MCP2517FD will go into normal
+	 * Sleep Mode instead.
+	 */
+	osc = MCP25XXFD_REG_OSC_LPMEN |
+		FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK,
+			   MCP25XXFD_REG_OSC_CLKODIV_10);
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_OSC, osc);
+	if (err)
+		return err;
+
+	/* Set Time Base Counter Prescaler to 1.
+	 *
+	 * This means an overflow of the 32 bit Time Base Counter
+	 * register at 40 MHz every 107 seconds.
+	 */
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_TSCON,
+			    MCP25XXFD_REG_TSCON_TBCEN);
+}
+
+static int mcp25xxfd_set_bittiming(const struct mcp25xxfd_priv *priv)
+{
+	const struct can_bittiming *bt = &priv->can.bittiming;
+	const struct can_bittiming *dbt = &priv->can.data_bittiming;
+	u32 val = 0;
+	s8 tdco;
+	int err;
+
+	/* CAN Control Register
+	 *
+	 * - no transmit bandwidth sharing
+	 * - config mode
+	 * - disable transmit queue
+	 * - store in transmit FIFO event
+	 * - transition to restricted operation mode on system error
+	 * - ESI is transmitted recessive when ESI of message is high or
+	 *   CAN controller error passive
+	 * - restricted retransmission attempts,
+	 *   use TQXCON_TXAT and FIFOCON_TXAT
+	 * - wake-up filter bits T11FILTER
+	 * - use CAN bus line filter for wakeup
+	 * - protocol exception is treated as a form error
+	 * - Do not compare data bytes
+	 */
+	val = FIELD_PREP(MCP25XXFD_REG_CON_REQOP_MASK,
+			 MCP25XXFD_REG_CON_MODE_CONFIG) |
+		MCP25XXFD_REG_CON_STEF |
+		MCP25XXFD_REG_CON_ESIGM |
+		MCP25XXFD_REG_CON_RTXAT |
+		FIELD_PREP(MCP25XXFD_REG_CON_WFT_MASK,
+			   MCP25XXFD_REG_CON_WFT_T11FILTER) |
+		MCP25XXFD_REG_CON_WAKFIL |
+		MCP25XXFD_REG_CON_PXEDIS;
+
+	if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO))
+		val |= MCP25XXFD_REG_CON_ISOCRCEN;
+
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_CON, val);
+	if (err)
+		return err;
+
+	/* Nominal Bit Time */
+	val = FIELD_PREP(MCP25XXFD_REG_NBTCFG_BRP_MASK, bt->brp - 1) |
+		FIELD_PREP(MCP25XXFD_REG_NBTCFG_TSEG1_MASK,
+			   bt->prop_seg + bt->phase_seg1 - 1) |
+		FIELD_PREP(MCP25XXFD_REG_NBTCFG_TSEG2_MASK,
+			   bt->phase_seg2 - 1) |
+		FIELD_PREP(MCP25XXFD_REG_NBTCFG_SJW_MASK, bt->sjw - 1);
+
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_NBTCFG, val);
+	if (err)
+		return err;
+
+	if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD))
+		return 0;
+
+	/* Data Bit Time */
+	val = FIELD_PREP(MCP25XXFD_REG_DBTCFG_BRP_MASK, dbt->brp - 1) |
+		FIELD_PREP(MCP25XXFD_REG_DBTCFG_TSEG1_MASK,
+			   dbt->prop_seg + dbt->phase_seg1 - 1) |
+		FIELD_PREP(MCP25XXFD_REG_DBTCFG_TSEG2_MASK,
+			   dbt->phase_seg2 - 1) |
+		FIELD_PREP(MCP25XXFD_REG_DBTCFG_SJW_MASK, dbt->sjw - 1);
+
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_DBTCFG, val);
+	if (err)
+		return err;
+
+	/* Transmitter Delay Compensation */
+	tdco = clamp_t(int, dbt->brp * (dbt->prop_seg + dbt->phase_seg1),
+		       -64, 63);
+	val = FIELD_PREP(MCP25XXFD_REG_TDC_TDCMOD_MASK,
+			 MCP25XXFD_REG_TDC_TDCMOD_AUTO) |
+		FIELD_PREP(MCP25XXFD_REG_TDC_TDCO_MASK, tdco);
+
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_TDC, val);
+}
+
+static int mcp25xxfd_chip_rx_int_enable(const struct mcp25xxfd_priv *priv)
+{
+	u32 val;
+
+	if (!priv->rx_int)
+		return 0;
+
+	/* Configure GPIOs:
+	 * - PIN0: GPIO Input
+	 * - PIN1: GPIO Input/RX Interrupt
+	 *
+	 * PIN1 must be Input, otherwise there is a glitch on the
+	 * rx-INT line. It happens between setting the PIN as output
+	 * (in the first byte of the SPI transfer) and configuring the
+	 * PIN as interrupt (in the last byte of the SPI transfer).
+	 */
+	val = MCP25XXFD_REG_IOCON_PM0 | MCP25XXFD_REG_IOCON_TRIS1 |
+		MCP25XXFD_REG_IOCON_TRIS0;
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_IOCON, val);
+}
+
+static int mcp25xxfd_chip_rx_int_disable(const struct mcp25xxfd_priv *priv)
+{
+	u32 val;
+
+	if (!priv->rx_int)
+		return 0;
+
+	/* Configure GPIOs:
+	 * - PIN0: GPIO Input
+	 * - PIN1: GPIO Input
+	 */
+	val = MCP25XXFD_REG_IOCON_PM1 | MCP25XXFD_REG_IOCON_PM0 |
+		MCP25XXFD_REG_IOCON_TRIS1 | MCP25XXFD_REG_IOCON_TRIS0;
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_IOCON, val);
+}
+
+static int
+mcp25xxfd_chip_rx_fifo_init_one(const struct mcp25xxfd_priv *priv,
+				const struct mcp25xxfd_rx_ring *ring)
+{
+	u32 fifo_con;
+
+	/* Enable RXOVIE on _all_ RX FIFOs, not just the last one.
+	 *
+	 * FIFOs hit by a RX MAB overflow and RXOVIE enabled will
+	 * generate a RXOVIF, use this to properly detect RX MAB
+	 * overflows.
+	 */
+	fifo_con = FIELD_PREP(MCP25XXFD_REG_FIFOCON_FSIZE_MASK,
+			      ring->obj_num - 1) |
+		MCP25XXFD_REG_FIFOCON_RXTSEN |
+		MCP25XXFD_REG_FIFOCON_RXOVIE |
+		MCP25XXFD_REG_FIFOCON_TFNRFNIE;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+		fifo_con |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK,
+				       MCP25XXFD_REG_FIFOCON_PLSIZE_64);
+	else
+		fifo_con |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK,
+				       MCP25XXFD_REG_FIFOCON_PLSIZE_8);
+
+	return regmap_write(priv->map_reg,
+			    MCP25XXFD_REG_FIFOCON(ring->fifo_nr), fifo_con);
+}
+
+static int
+mcp25xxfd_chip_rx_filter_init_one(const struct mcp25xxfd_priv *priv,
+				  const struct mcp25xxfd_rx_ring *ring)
+{
+	u32 fltcon;
+
+	fltcon = MCP25XXFD_REG_FLTCON_FLTEN(ring->nr) |
+		MCP25XXFD_REG_FLTCON_FBP(ring->nr, ring->fifo_nr);
+
+	return regmap_update_bits(priv->map_reg,
+				  MCP25XXFD_REG_FLTCON(ring->nr >> 2),
+				  MCP25XXFD_REG_FLTCON_FLT_MASK(ring->nr),
+				  fltcon);
+}
+
+static int mcp25xxfd_chip_fifo_init(const struct mcp25xxfd_priv *priv)
+{
+	const struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	const struct mcp25xxfd_rx_ring *rx_ring;
+	u32 val;
+	int err, n;
+
+	/* TEF */
+	val = FIELD_PREP(MCP25XXFD_REG_TEFCON_FSIZE_MASK,
+			 tx_ring->obj_num - 1) |
+		MCP25XXFD_REG_TEFCON_TEFTSEN |
+		MCP25XXFD_REG_TEFCON_TEFOVIE |
+		MCP25XXFD_REG_TEFCON_TEFNEIE;
+
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_TEFCON, val);
+	if (err)
+		return err;
+
+	/* FIFO 1 - TX */
+	val = FIELD_PREP(MCP25XXFD_REG_FIFOCON_FSIZE_MASK,
+			 tx_ring->obj_num - 1) |
+		MCP25XXFD_REG_FIFOCON_TXEN |
+		MCP25XXFD_REG_FIFOCON_TXATIE;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+		val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK,
+				  MCP25XXFD_REG_FIFOCON_PLSIZE_64);
+	else
+		val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK,
+				  MCP25XXFD_REG_FIFOCON_PLSIZE_8);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+		val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_TXAT_MASK,
+				  MCP25XXFD_REG_FIFOCON_TXAT_ONE_SHOT);
+	else
+		val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_TXAT_MASK,
+				  MCP25XXFD_REG_FIFOCON_TXAT_UNLIMITED);
+
+	err = regmap_write(priv->map_reg,
+			   MCP25XXFD_REG_FIFOCON(MCP25XXFD_TX_FIFO),
+			   val);
+	if (err)
+		return err;
+
+	/* RX FIFOs */
+	mcp25xxfd_for_each_rx_ring(priv, rx_ring, n) {
+		err = mcp25xxfd_chip_rx_fifo_init_one(priv, rx_ring);
+		if (err)
+			return err;
+
+		err = mcp25xxfd_chip_rx_filter_init_one(priv, rx_ring);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_chip_ecc_init(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_ecc *ecc = &priv->ecc;
+	void *ram;
+	u32 val = 0;
+	int err;
+
+	ecc->ecc_stat = 0;
+
+	if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_ECC)
+		val = MCP25XXFD_REG_ECCCON_ECCEN;
+
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON,
+				 MCP25XXFD_REG_ECCCON_ECCEN, val);
+	if (err)
+		return err;
+
+	ram = kzalloc(MCP25XXFD_RAM_SIZE, GFP_KERNEL);
+	if (!ram)
+		return -ENOMEM;
+
+	err = regmap_raw_write(priv->map_reg, MCP25XXFD_RAM_START, ram,
+			       MCP25XXFD_RAM_SIZE);
+	kfree(ram);
+
+	return err;
+}
+
+static inline void mcp25xxfd_ecc_tefif_successful(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_ecc *ecc = &priv->ecc;
+
+	ecc->ecc_stat = 0;
+}
+
+static u8 mcp25xxfd_get_normal_mode(const struct mcp25xxfd_priv *priv)
+{
+	u8 mode;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+		mode = MCP25XXFD_REG_CON_MODE_MIXED;
+	else
+		mode = MCP25XXFD_REG_CON_MODE_CAN2_0;
+
+	return mode;
+}
+
+static int
+__mcp25xxfd_chip_set_normal_mode(const struct mcp25xxfd_priv *priv,
+				 bool nowait)
+{
+	u8 mode;
+
+	mode = mcp25xxfd_get_normal_mode(priv);
+
+	return __mcp25xxfd_chip_set_mode(priv, mode, nowait);
+}
+
+static inline int
+mcp25xxfd_chip_set_normal_mode(const struct mcp25xxfd_priv *priv)
+{
+	return __mcp25xxfd_chip_set_normal_mode(priv, false);
+}
+
+static inline int
+mcp25xxfd_chip_set_normal_mode_nowait(const struct mcp25xxfd_priv *priv)
+{
+	return __mcp25xxfd_chip_set_normal_mode(priv, true);
+}
+
+static int mcp25xxfd_chip_interrupts_enable(const struct mcp25xxfd_priv *priv)
+{
+	u32 val;
+	int err;
+
+	val = MCP25XXFD_REG_CRC_FERRIE | MCP25XXFD_REG_CRC_CRCERRIE;
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_CRC, val);
+	if (err)
+		return err;
+
+	val = MCP25XXFD_REG_ECCCON_DEDIE | MCP25XXFD_REG_ECCCON_SECIE;
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON, val, val);
+	if (err)
+		return err;
+
+	val = MCP25XXFD_REG_INT_CERRIE |
+		MCP25XXFD_REG_INT_SERRIE |
+		MCP25XXFD_REG_INT_RXOVIE |
+		MCP25XXFD_REG_INT_TXATIE |
+		MCP25XXFD_REG_INT_SPICRCIE |
+		MCP25XXFD_REG_INT_ECCIE |
+		MCP25XXFD_REG_INT_TEFIE |
+		MCP25XXFD_REG_INT_MODIE |
+		MCP25XXFD_REG_INT_RXIE;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+		val |= MCP25XXFD_REG_INT_IVMIE;
+
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_INT, val);
+}
+
+static int mcp25xxfd_chip_interrupts_disable(const struct mcp25xxfd_priv *priv)
+{
+	int err;
+	u32 mask;
+
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_INT, 0);
+	if (err)
+		return err;
+
+	mask = MCP25XXFD_REG_ECCCON_DEDIE | MCP25XXFD_REG_ECCCON_SECIE;
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON,
+				 mask, 0x0);
+	if (err)
+		return err;
+
+	return regmap_write(priv->map_reg, MCP25XXFD_REG_CRC, 0);
+}
+
+static int mcp25xxfd_chip_stop(struct mcp25xxfd_priv *priv,
+			       const enum can_state state)
+{
+	priv->can.state = state;
+
+	mcp25xxfd_chip_interrupts_disable(priv);
+	mcp25xxfd_chip_rx_int_disable(priv);
+	return mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_SLEEP);
+}
+
+static int mcp25xxfd_chip_start(struct mcp25xxfd_priv *priv)
+{
+	int err;
+
+	err = mcp25xxfd_chip_softreset(priv);
+	if (err)
+		goto out_chip_stop;
+
+	err = mcp25xxfd_chip_clock_init(priv);
+	if (err)
+		goto out_chip_stop;
+
+	err = mcp25xxfd_set_bittiming(priv);
+	if (err)
+		goto out_chip_stop;
+
+	err = mcp25xxfd_chip_rx_int_enable(priv);
+	if (err)
+		return err;
+
+	err = mcp25xxfd_chip_ecc_init(priv);
+	if (err)
+		goto out_chip_stop;
+
+	mcp25xxfd_ring_init(priv);
+
+	err = mcp25xxfd_chip_fifo_init(priv);
+	if (err)
+		goto out_chip_stop;
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	err = mcp25xxfd_chip_set_normal_mode(priv);
+	if (err)
+		goto out_chip_stop;
+
+	return 0;
+
+ out_chip_stop:
+	mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED);
+
+	return err;
+}
+
+static int mcp25xxfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+	int err;
+
+	switch (mode) {
+	case CAN_MODE_START:
+		err = mcp25xxfd_chip_start(priv);
+		if (err)
+			return err;
+
+		err = mcp25xxfd_chip_interrupts_enable(priv);
+		if (err) {
+			mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED);
+			return err;
+		}
+
+		netif_wake_queue(ndev);
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int __mcp25xxfd_get_berr_counter(const struct net_device *ndev,
+					struct can_berr_counter *bec)
+{
+	const struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+	u32 trec;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_TREC, &trec);
+	if (err)
+		return err;
+
+	if (trec & MCP25XXFD_REG_TREC_TXBO)
+		bec->txerr = 256;
+	else
+		bec->txerr = FIELD_GET(MCP25XXFD_REG_TREC_TEC_MASK, trec);
+	bec->rxerr = FIELD_GET(MCP25XXFD_REG_TREC_REC_MASK, trec);
+
+	return 0;
+}
+
+static int mcp25xxfd_get_berr_counter(const struct net_device *ndev,
+				      struct can_berr_counter *bec)
+{
+	const struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+
+	/* Avoid waking up the controller if the interface is down */
+	if (!(ndev->flags & IFF_UP))
+		return 0;
+
+	/* The controller is powered down during Bus Off, use saved
+	 * bec values.
+	 */
+	if (priv->can.state == CAN_STATE_BUS_OFF) {
+		*bec = priv->bec;
+		return 0;
+	}
+
+	return __mcp25xxfd_get_berr_counter(ndev, bec);
+}
+
+static int mcp25xxfd_check_tef_tail(const struct mcp25xxfd_priv *priv)
+{
+	u8 tef_tail_chip, tef_tail;
+	int err;
+
+	if (!IS_ENABLED(CONFIG_CAN_MCP25XXFD_SANITY))
+		return 0;
+
+	err = mcp25xxfd_tef_tail_get_from_chip(priv, &tef_tail_chip);
+	if (err)
+		return err;
+
+	tef_tail = mcp25xxfd_get_tef_tail(priv);
+	if (tef_tail_chip != tef_tail) {
+		netdev_err(priv->ndev,
+			   "TEF tail of chip (0x%02x) and ours (0x%08x) inconsistent.\n",
+			   tef_tail_chip, tef_tail);
+		return -EILSEQ;
+	}
+
+	return 0;
+}
+
+static int
+mcp25xxfd_check_rx_tail(const struct mcp25xxfd_priv *priv,
+			const struct mcp25xxfd_rx_ring *ring)
+{
+	u8 rx_tail_chip, rx_tail;
+	int err;
+
+	if (!IS_ENABLED(CONFIG_CAN_MCP25XXFD_SANITY))
+		return 0;
+
+	err = mcp25xxfd_rx_tail_get_from_chip(priv, ring, &rx_tail_chip);
+	if (err)
+		return err;
+
+	rx_tail = mcp25xxfd_get_rx_tail(ring);
+	if (rx_tail_chip != rx_tail) {
+		netdev_err(priv->ndev,
+			   "RX tail of chip (%d) and ours (%d) inconsistent.\n",
+			   rx_tail_chip, rx_tail);
+		return -EILSEQ;
+	}
+
+	return 0;
+}
+
+static int
+mcp25xxfd_handle_tefif_recover(const struct mcp25xxfd_priv *priv, const u32 seq)
+{
+	const struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	u32 tef_sta;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_TEFSTA, &tef_sta);
+	if (err)
+		return err;
+
+	if (tef_sta & MCP25XXFD_REG_TEFSTA_TEFOVIF) {
+		netdev_err(priv->ndev,
+			   "Transmit Event FIFO buffer overflow.\n");
+		return -ENOBUFS;
+	}
+
+	netdev_info(priv->ndev,
+		    "Transmit Event FIFO buffer %s. (seq=0x%08x, tef_tail=0x%08x, tef_head=0x%08x, tx_head=0x%08x)\n",
+		    tef_sta & MCP25XXFD_REG_TEFSTA_TEFFIF ?
+		    "full" : tef_sta & MCP25XXFD_REG_TEFSTA_TEFNEIF ?
+		    "not empty" : "empty",
+		    seq, priv->tef.tail, priv->tef.head, tx_ring->head);
+
+	/* The Sequence Number in the TEF doesn't match our tef_tail. */
+	return -EAGAIN;
+}
+
+static int
+mcp25xxfd_handle_tefif_one(struct mcp25xxfd_priv *priv,
+			   const struct mcp25xxfd_hw_tef_obj *hw_tef_obj)
+{
+	struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	struct net_device_stats *stats = &priv->ndev->stats;
+	u32 seq, seq_masked, tef_tail_masked;
+	int err;
+
+	seq = FIELD_GET(MCP25XXFD_OBJ_FLAGS_SEQ_MCP2518FD_MASK,
+			hw_tef_obj->flags);
+
+	/* Use the MCP2517FD mask on the MCP2518FD, too. We only
+	 * compare 7 bits, this should be enough to detect
+	 * net-yet-completed, i.e. old TEF objects.
+	 */
+	seq_masked = seq &
+		field_mask(MCP25XXFD_OBJ_FLAGS_SEQ_MCP2517FD_MASK);
+	tef_tail_masked = priv->tef.tail &
+		field_mask(MCP25XXFD_OBJ_FLAGS_SEQ_MCP2517FD_MASK);
+	if (seq_masked != tef_tail_masked)
+		return mcp25xxfd_handle_tefif_recover(priv, seq);
+
+	stats->tx_bytes +=
+		can_rx_offload_get_echo_skb(&priv->offload,
+					    mcp25xxfd_get_tef_tail(priv),
+					    hw_tef_obj->ts);
+	stats->tx_packets++;
+
+	/* finally increment the TEF pointer */
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_TEFCON,
+				 GENMASK(15, 8),
+				 MCP25XXFD_REG_TEFCON_UINC);
+	if (err)
+		return err;
+
+	priv->tef.tail++;
+	tx_ring->tail++;
+
+	return mcp25xxfd_check_tef_tail(priv);
+}
+
+static int mcp25xxfd_tef_ring_update(struct mcp25xxfd_priv *priv)
+{
+	const struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	unsigned int new_head;
+	u8 chip_tx_tail;
+	int err;
+
+	err = mcp25xxfd_tx_tail_get_from_chip(priv, &chip_tx_tail);
+	if (err)
+		return err;
+
+	/* chip_tx_tail, is the next TX-Object send by the HW.
+	 * The new TEF head must be >= the old head, ...
+	 */
+	new_head = round_down(priv->tef.head, tx_ring->obj_num) + chip_tx_tail;
+	if (new_head <= priv->tef.head)
+		new_head += tx_ring->obj_num;
+
+	/* ... but it cannot exceed the TX head. */
+	priv->tef.head = min(new_head, tx_ring->head);
+
+	return mcp25xxfd_check_tef_tail(priv);
+}
+
+static inline int
+mcp25xxfd_tef_obj_read(const struct mcp25xxfd_priv *priv,
+		       struct mcp25xxfd_hw_tef_obj *hw_tef_obj,
+		       const u8 offset, const u8 len)
+{
+	const struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+
+	if (IS_ENABLED(CONFIG_CAN_MCP25XXFD_SANITY) &&
+	    (offset > tx_ring->obj_num ||
+	     len > tx_ring->obj_num ||
+	     offset + len > tx_ring->obj_num)) {
+		netdev_err(priv->ndev,
+			   "Trying to read to many TEF objects (max=%d, offset=%d, len=%d).\n",
+			   tx_ring->obj_num, offset, len);
+		return -ERANGE;
+	}
+
+	return regmap_bulk_read(priv->map_rx,
+				mcp25xxfd_get_tef_obj_addr(offset),
+				hw_tef_obj,
+				sizeof(*hw_tef_obj) / sizeof(u32) * len);
+}
+
+static int mcp25xxfd_handle_tefif(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_hw_tef_obj hw_tef_obj[MCP25XXFD_TX_OBJ_NUM_MAX];
+	u8 tef_tail, len, l;
+	int err, i;
+
+	err = mcp25xxfd_tef_ring_update(priv);
+	if (err)
+		return err;
+
+	tef_tail = mcp25xxfd_get_tef_tail(priv);
+	len = mcp25xxfd_get_tef_len(priv);
+	l = mcp25xxfd_get_tef_linear_len(priv);
+	err = mcp25xxfd_tef_obj_read(priv, hw_tef_obj, tef_tail, l);
+	if (err)
+		return err;
+
+	if (l < len) {
+		err = mcp25xxfd_tef_obj_read(priv, &hw_tef_obj[l], 0, len - l);
+		if (err)
+			return err;
+	}
+
+	for (i = 0; i < len; i++) {
+		err = mcp25xxfd_handle_tefif_one(priv, &hw_tef_obj[i]);
+		/* -EAGAIN means the Sequence Number in the TEF
+		 * doesn't match our tef_tail. This can happen if we
+		 * read the TEF objects too early. Leave loop let the
+		 * interrupt handler call us again.
+		 */
+		if (err == -EAGAIN)
+			goto out_netif_wake_queue;
+		if (err)
+			return err;
+	}
+
+ out_netif_wake_queue:
+	mcp25xxfd_ecc_tefif_successful(priv);
+
+	if (mcp25xxfd_get_tx_free(priv->tx)) {
+		/* Make sure that anybody stopping the queue after
+		 * this sees the new tx_ring->tail.
+		 */
+		smp_mb();
+		netif_wake_queue(priv->ndev);
+	}
+
+	return 0;
+}
+
+static int
+mcp25xxfd_rx_ring_update(const struct mcp25xxfd_priv *priv,
+			 struct mcp25xxfd_rx_ring *ring)
+{
+	u32 new_head;
+	u8 chip_rx_head;
+	int err;
+
+	err = mcp25xxfd_rx_head_get_from_chip(priv, ring, &chip_rx_head);
+	if (err)
+		return err;
+
+	/* chip_rx_head, is the next RX-Object filled by the HW.
+	 * The new RX head must be >= the old head.
+	 */
+	new_head = round_down(ring->head, ring->obj_num) + chip_rx_head;
+	if (new_head <= ring->head)
+		new_head += ring->obj_num;
+
+	ring->head = new_head;
+
+	return mcp25xxfd_check_rx_tail(priv, ring);
+}
+
+static void
+mcp25xxfd_hw_rx_obj_to_skb(const struct mcp25xxfd_priv *priv,
+			   const struct mcp25xxfd_hw_rx_obj_canfd *hw_rx_obj,
+			   struct sk_buff *skb)
+{
+	struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
+
+	if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_IDE) {
+		u32 sid, eid;
+
+		eid = FIELD_GET(MCP25XXFD_OBJ_ID_EID_MASK, hw_rx_obj->id);
+		sid = FIELD_GET(MCP25XXFD_OBJ_ID_SID_MASK, hw_rx_obj->id);
+
+		cfd->can_id = CAN_EFF_FLAG |
+			FIELD_PREP(MCP25XXFD_REG_FRAME_EFF_EID_MASK, eid) |
+			FIELD_PREP(MCP25XXFD_REG_FRAME_EFF_SID_MASK, sid);
+	} else {
+		cfd->can_id = FIELD_GET(MCP25XXFD_OBJ_ID_SID_MASK,
+					hw_rx_obj->id);
+	}
+
+	/* CANFD */
+	if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_FDF) {
+		u8 dlc;
+
+		if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_ESI)
+			cfd->flags |= CANFD_ESI;
+
+		if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_BRS)
+			cfd->flags |= CANFD_BRS;
+
+		dlc = FIELD_GET(MCP25XXFD_OBJ_FLAGS_DLC, hw_rx_obj->flags);
+		cfd->len = can_dlc2len(get_canfd_dlc(dlc));
+	} else {
+		if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_RTR)
+			cfd->can_id |= CAN_RTR_FLAG;
+
+		cfd->len = get_can_dlc(FIELD_GET(MCP25XXFD_OBJ_FLAGS_DLC,
+						 hw_rx_obj->flags));
+	}
+
+	memcpy(cfd->data, hw_rx_obj->data, cfd->len);
+}
+
+static int
+mcp25xxfd_handle_rxif_one(struct mcp25xxfd_priv *priv,
+			  struct mcp25xxfd_rx_ring *ring,
+			  const struct mcp25xxfd_hw_rx_obj_canfd *hw_rx_obj)
+{
+	struct net_device_stats *stats = &priv->ndev->stats;
+	struct sk_buff *skb;
+	struct canfd_frame *cfd;
+	int err;
+
+	if (hw_rx_obj->flags & MCP25XXFD_OBJ_FLAGS_FDF)
+		skb = alloc_canfd_skb(priv->ndev, &cfd);
+	else
+		skb = alloc_can_skb(priv->ndev, (struct can_frame **)&cfd);
+
+	if (!cfd) {
+		stats->rx_dropped++;
+		return 0;
+	}
+
+	mcp25xxfd_hw_rx_obj_to_skb(priv, hw_rx_obj, skb);
+	err = can_rx_offload_queue_sorted(&priv->offload, skb, hw_rx_obj->ts);
+	if (err)
+		stats->rx_fifo_errors++;
+
+	ring->tail++;
+
+	/* finally increment the RX pointer */
+	return regmap_update_bits(priv->map_reg,
+				  MCP25XXFD_REG_FIFOCON(ring->fifo_nr),
+				  GENMASK(15, 8),
+				  MCP25XXFD_REG_FIFOCON_UINC);
+}
+
+static inline int
+mcp25xxfd_rx_obj_read(const struct mcp25xxfd_priv *priv,
+		      const struct mcp25xxfd_rx_ring *ring,
+		      struct mcp25xxfd_hw_rx_obj_canfd *hw_rx_obj,
+		      const u8 offset, const u8 len)
+{
+	int err;
+
+	err = regmap_bulk_read(priv->map_rx,
+			       mcp25xxfd_get_rx_obj_addr(ring, offset),
+			       hw_rx_obj,
+			       len * ring->obj_size / sizeof(u32));
+
+	return err;
+}
+
+static int
+mcp25xxfd_handle_rxif_ring(struct mcp25xxfd_priv *priv,
+			   struct mcp25xxfd_rx_ring *ring)
+{
+	struct mcp25xxfd_hw_rx_obj_canfd *hw_rx_obj = ring->obj;
+	u8 rx_tail, len;
+	int err, i;
+
+	err = mcp25xxfd_rx_ring_update(priv, ring);
+	if (err)
+		return err;
+
+	while ((len = mcp25xxfd_get_rx_linear_len(ring))) {
+		rx_tail = mcp25xxfd_get_rx_tail(ring);
+
+		err = mcp25xxfd_rx_obj_read(priv, ring, hw_rx_obj,
+					    rx_tail, len);
+		if (err)
+			return err;
+
+		for (i = 0; i < len; i++) {
+			err = mcp25xxfd_handle_rxif_one(priv, ring,
+							(void *)hw_rx_obj +
+							i * ring->obj_size);
+			if (err)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_handle_rxif(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_rx_ring *ring;
+	int err, n;
+
+	mcp25xxfd_for_each_rx_ring(priv, ring, n) {
+		err = mcp25xxfd_handle_rxif_ring(priv, ring);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static inline int mcp25xxfd_get_timestamp(const struct mcp25xxfd_priv *priv,
+					  u32 *timestamp)
+{
+	return regmap_read(priv->map_reg, MCP25XXFD_REG_TBC, timestamp);
+}
+
+static struct sk_buff *
+mcp25xxfd_alloc_can_err_skb(const struct mcp25xxfd_priv *priv,
+			    struct can_frame **cf, u32 *timestamp)
+{
+	int err;
+
+	err = mcp25xxfd_get_timestamp(priv, timestamp);
+	if (err)
+		return NULL;
+
+	return alloc_can_err_skb(priv->ndev, cf);
+}
+
+static int mcp25xxfd_handle_rxovif(struct mcp25xxfd_priv *priv)
+{
+	struct net_device_stats *stats = &priv->ndev->stats;
+	struct mcp25xxfd_rx_ring *ring;
+	struct sk_buff *skb;
+	struct can_frame *cf;
+	u32 timestamp, rxovif;
+	int err, i;
+
+	stats->rx_over_errors++;
+	stats->rx_errors++;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_RXOVIF, &rxovif);
+	if (err)
+		return err;
+
+	mcp25xxfd_for_each_rx_ring(priv, ring, i) {
+		if (!(rxovif & BIT(ring->fifo_nr)))
+			continue;
+
+		/* If SERRIF is active, there was a RX MAB overflow. */
+		if (priv->regs_status.intf & MCP25XXFD_REG_INT_SERRIF) {
+			netdev_info(priv->ndev,
+				    "RX-%d: MAB overflow detected.\n",
+				    ring->nr);
+		} else {
+			netdev_info(priv->ndev,
+				    "RX-%d: FIFO overflow.\n", ring->nr);
+		}
+
+		err = regmap_update_bits(priv->map_reg,
+					 MCP25XXFD_REG_FIFOSTA(ring->fifo_nr),
+					 MCP25XXFD_REG_FIFOSTA_RXOVIF,
+					 0x0);
+		if (err)
+			return err;
+	}
+
+	skb = mcp25xxfd_alloc_can_err_skb(priv, &cf, &timestamp);
+	if (!skb)
+		return 0;
+
+	cf->can_id |= CAN_ERR_CRTL;
+	cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+	err = can_rx_offload_queue_sorted(&priv->offload, skb, timestamp);
+	if (err)
+		stats->rx_fifo_errors++;
+
+	return 0;
+}
+
+static int mcp25xxfd_handle_txatif(struct mcp25xxfd_priv *priv)
+{
+	netdev_info(priv->ndev, "%s\n", __func__);
+
+	return 0;
+}
+
+static int mcp25xxfd_handle_ivmif(struct mcp25xxfd_priv *priv)
+{
+	struct net_device_stats *stats = &priv->ndev->stats;
+	u32 bdiag1, timestamp;
+	struct sk_buff *skb;
+	struct can_frame *cf = NULL;
+	int err;
+
+	err = mcp25xxfd_get_timestamp(priv, &timestamp);
+	if (err)
+		return err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_BDIAG1, &bdiag1);
+	if (err)
+		return err;
+
+	/* Write 0s to clear error bits, don't write 1s to non active
+	 * bits, as they will be set.
+	 */
+	err = regmap_write(priv->map_reg, MCP25XXFD_REG_BDIAG1, 0x0);
+	if (err)
+		return err;
+
+	priv->can.can_stats.bus_error++;
+
+	skb = alloc_can_err_skb(priv->ndev, &cf);
+	if (cf)
+		cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+	/* Controller misconfiguration */
+	if (WARN_ON(bdiag1 & MCP25XXFD_REG_BDIAG1_DLCMM))
+		netdev_err(priv->ndev,
+			   "recv'd DLC is larger than PLSIZE of FIFO element.");
+
+	/* RX errors */
+	if (bdiag1 & (MCP25XXFD_REG_BDIAG1_DCRCERR |
+		      MCP25XXFD_REG_BDIAG1_NCRCERR)) {
+		netdev_dbg(priv->ndev, "CRC error\n");
+
+		stats->rx_errors++;
+		if (cf)
+			cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+	}
+	if (bdiag1 & (MCP25XXFD_REG_BDIAG1_DSTUFERR |
+		      MCP25XXFD_REG_BDIAG1_NSTUFERR)) {
+		netdev_dbg(priv->ndev, "Stuff error\n");
+
+		stats->rx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_STUFF;
+	}
+	if (bdiag1 & (MCP25XXFD_REG_BDIAG1_DFORMERR |
+		      MCP25XXFD_REG_BDIAG1_NFORMERR)) {
+		netdev_dbg(priv->ndev, "Format error\n");
+
+		stats->rx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_FORM;
+	}
+
+	/* TX errors */
+	if (bdiag1 & MCP25XXFD_REG_BDIAG1_NACKERR) {
+		netdev_dbg(priv->ndev, "NACK error\n");
+
+		stats->tx_errors++;
+		if (cf) {
+			cf->can_id |= CAN_ERR_ACK;
+			cf->data[2] |= CAN_ERR_PROT_TX;
+		}
+	}
+	if (bdiag1 & (MCP25XXFD_REG_BDIAG1_DBIT1ERR |
+		      MCP25XXFD_REG_BDIAG1_NBIT1ERR)) {
+		netdev_dbg(priv->ndev, "Bit1 error\n");
+
+		stats->tx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT1;
+	}
+	if (bdiag1 & (MCP25XXFD_REG_BDIAG1_DBIT0ERR |
+		      MCP25XXFD_REG_BDIAG1_NBIT0ERR)) {
+		netdev_dbg(priv->ndev, "Bit0 error\n");
+
+		stats->tx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT0;
+	}
+
+	if (!cf)
+		return 0;
+
+	err = can_rx_offload_queue_sorted(&priv->offload, skb, timestamp);
+	if (err)
+		stats->rx_fifo_errors++;
+
+	return 0;
+}
+
+static int mcp25xxfd_handle_cerrif(struct mcp25xxfd_priv *priv)
+{
+	struct net_device_stats *stats = &priv->ndev->stats;
+	struct sk_buff *skb;
+	struct can_frame *cf = NULL;
+	enum can_state new_state, rx_state, tx_state;
+	u32 trec, timestamp;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_TREC, &trec);
+	if (err)
+		return err;
+
+	if (trec & MCP25XXFD_REG_TREC_TXBO)
+		tx_state = CAN_STATE_BUS_OFF;
+	else if (trec & MCP25XXFD_REG_TREC_TXBP)
+		tx_state = CAN_STATE_ERROR_PASSIVE;
+	else if (trec & MCP25XXFD_REG_TREC_TXWARN)
+		tx_state = CAN_STATE_ERROR_WARNING;
+	else
+		tx_state = CAN_STATE_ERROR_ACTIVE;
+
+	if (trec & MCP25XXFD_REG_TREC_RXBP)
+		rx_state = CAN_STATE_ERROR_PASSIVE;
+	else if (trec & MCP25XXFD_REG_TREC_RXWARN)
+		rx_state = CAN_STATE_ERROR_WARNING;
+	else
+		rx_state = CAN_STATE_ERROR_ACTIVE;
+
+	new_state = max(tx_state, rx_state);
+	if (new_state == priv->can.state)
+		return 0;
+
+	/* The skb allocation might fail, but can_change_state()
+	 * handles cf == NULL.
+	 */
+	skb = mcp25xxfd_alloc_can_err_skb(priv, &cf, &timestamp);
+	can_change_state(priv->ndev, cf, tx_state, rx_state);
+
+	if (new_state == CAN_STATE_BUS_OFF) {
+		/* As we're going to switch off the chip now, let's
+		 * save the error counters and return them to
+		 * userspace, if do_get_berr_counter() is called while
+		 * the chip is in Bus Off.
+		 */
+		err = __mcp25xxfd_get_berr_counter(priv->ndev, &priv->bec);
+		if (err)
+			return err;
+
+		mcp25xxfd_chip_stop(priv, CAN_STATE_BUS_OFF);
+		can_bus_off(priv->ndev);
+	}
+
+	if (!skb)
+		return 0;
+
+	if (new_state != CAN_STATE_BUS_OFF) {
+		struct can_berr_counter bec;
+
+		err = mcp25xxfd_get_berr_counter(priv->ndev, &bec);
+		if (err)
+			return err;
+		cf->data[6] = bec.txerr;
+		cf->data[7] = bec.rxerr;
+	}
+
+	err = can_rx_offload_queue_sorted(&priv->offload, skb, timestamp);
+	if (err)
+		stats->rx_fifo_errors++;
+
+	return 0;
+}
+
+static int
+mcp25xxfd_handle_modif(const struct mcp25xxfd_priv *priv, bool *set_normal_mode)
+{
+	const u8 mode_reference = mcp25xxfd_get_normal_mode(priv);
+	u8 mode;
+	int err;
+
+	err = mcp25xxfd_chip_get_mode(priv, &mode);
+	if (err)
+		return err;
+
+	if (mode == mode_reference) {
+		netdev_dbg(priv->ndev,
+			   "Controller changed into %s Mode (%u).\n",
+			   mcp25xxfd_get_mode_str(mode), mode);
+		return 0;
+	}
+
+	/* According to MCP2517FD errata DS80000792B 1., during a TX
+	 * MAB underflow, the controller will transition to Restricted
+	 * Operation Mode or Listen Only Mode (depending on SERR2LOM).
+	 *
+	 * However this is not always the case. If SERR2LOM is
+	 * configured for Restricted Operation Mode (SERR2LOM not set)
+	 * the MCP2517FD will sometimes transition to Listen Only Mode
+	 * first. When polling this bit we see that it will transition
+	 * to Restricted Operation Mode shortly after.
+	 */
+	if ((priv->devtype_data.quirks & MCP25XXFD_QUIRK_MAB_NO_WARN) &&
+	    (mode == MCP25XXFD_REG_CON_MODE_RESTRICTED ||
+	     mode == MCP25XXFD_REG_CON_MODE_LISTENONLY))
+		netdev_dbg(priv->ndev,
+			   "Controller changed into %s Mode (%u).\n",
+			   mcp25xxfd_get_mode_str(mode), mode);
+	else
+		netdev_err(priv->ndev,
+			   "Controller changed into %s Mode (%u).\n",
+			   mcp25xxfd_get_mode_str(mode), mode);
+
+	/* After the application requests Normal mode, the Controller
+	 * will automatically attempt to retransmit the message that
+	 * caused the TX MAB underflow.
+	 *
+	 * However, if there is an ECC error in the TX-RAM, we first
+	 * have to reload the tx-object before requesting Normal
+	 * mode. This is done later in mcp25xxfd_handle_eccif().
+	 */
+	if (priv->regs_status.intf & MCP25XXFD_REG_INT_ECCIF) {
+		*set_normal_mode = true;
+		return 0;
+	}
+
+	return mcp25xxfd_chip_set_normal_mode_nowait(priv);
+}
+
+static int mcp25xxfd_handle_serrif(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_ecc *ecc = &priv->ecc;
+	struct net_device_stats *stats = &priv->ndev->stats;
+	bool handled = false;
+
+	/* TX MAB underflow
+	 *
+	 * According to MCP2517FD Errata DS80000792B 1. a TX MAB
+	 * underflow is indicated by SERRIF and MODIF.
+	 *
+	 * In addition to the effects mentioned in the Errata, there
+	 * are Bus Errors due to the aborted CAN frame, so a IVMIF
+	 * will be seen as well.
+	 *
+	 * Sometimes there is an ECC error in the TX-RAM, which leads
+	 * to a TX MAB underflow.
+	 *
+	 * However, probably due to a race condition, there is no
+	 * associated MODIF pending.
+	 *
+	 * Further, there are situations, where the SERRIF is caused
+	 * by an ECC error in the TX-RAM, but not even the ECCIF is
+	 * set. This only seems to happen _after_ the first occurrence
+	 * of a ECCIF (which is tracked in ecc->cnt).
+	 *
+	 * Treat all as a known system errors..
+	 */
+	if ((priv->regs_status.intf & MCP25XXFD_REG_INT_MODIF &&
+	     priv->regs_status.intf & MCP25XXFD_REG_INT_IVMIF) ||
+	    priv->regs_status.intf & MCP25XXFD_REG_INT_ECCIF ||
+	    ecc->cnt) {
+		const char *msg;
+
+		if (priv->regs_status.intf & MCP25XXFD_REG_INT_ECCIF ||
+		    ecc->cnt)
+			msg = "TX MAB underflow due to ECC error detected.";
+		else
+			msg = "TX MAB underflow detected.";
+
+		if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_MAB_NO_WARN)
+			netdev_dbg(priv->ndev, "%s\n", msg);
+		else
+			netdev_info(priv->ndev, "%s\n", msg);
+
+		stats->tx_aborted_errors++;
+		stats->tx_errors++;
+		handled = true;
+	}
+
+	/* RX MAB overflow
+	 *
+	 * According to MCP2517FD Errata DS80000792B 1. a RX MAB
+	 * overflow is indicated by SERRIF.
+	 *
+	 * In addition to the effects mentioned in the Errata, (most
+	 * of the times) a RXOVIF is raised, if the FIFO that is being
+	 * received into has the RXOVIE activated (and we have enabled
+	 * RXOVIE on all FIFOs).
+	 *
+	 * Sometimes there is no RXOVIF just a RXIF is pending.
+	 *
+	 * Treat all as a known system errors..
+	 */
+	if (priv->regs_status.intf & MCP25XXFD_REG_INT_RXOVIF ||
+	    priv->regs_status.intf & MCP25XXFD_REG_INT_RXIF) {
+		stats->rx_dropped++;
+		handled = true;
+	}
+
+	if (!handled)
+		netdev_err(priv->ndev,
+			   "Unhandled System Error Interrupt (intf=0x%08x)!\n",
+			   priv->regs_status.intf);
+
+	return 0;
+}
+
+static int
+mcp25xxfd_handle_eccif_recover(struct mcp25xxfd_priv *priv, u8 nr)
+{
+	struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	struct mcp25xxfd_ecc *ecc = &priv->ecc;
+	struct mcp25xxfd_tx_obj *tx_obj;
+	u8 chip_tx_tail, tx_tail, offset;
+	u16 addr;
+	int err;
+
+	addr = FIELD_GET(MCP25XXFD_REG_ECCSTAT_ERRADDR_MASK, ecc->ecc_stat);
+
+	err = mcp25xxfd_tx_tail_get_from_chip(priv, &chip_tx_tail);
+	if (err)
+		return err;
+
+	tx_tail = mcp25xxfd_get_tx_tail(tx_ring);
+	offset = (nr - chip_tx_tail) & (tx_ring->obj_num - 1);
+
+	/* Bail out if one of the following is met:
+	 * - tx_tail information is inconsistent
+	 * - for mcp2517fd: offset not 0
+	 * - for mcp2518fd: offset not 0 or 1
+	 */
+	if (chip_tx_tail != tx_tail ||
+	    !(offset == 0 || (offset == 1 && mcp25xxfd_is_2518(priv)))) {
+		netdev_err(priv->ndev,
+			   "ECC Error information inconsistent (addr=0x%04x, nr=%d, tx_tail=0x%08x(%d), chip_tx_tail=%d, offset=%d).\n",
+			   addr, nr, tx_ring->tail, tx_tail, chip_tx_tail,
+			   offset);
+		return -EINVAL;
+	}
+
+	netdev_info(priv->ndev,
+		    "Recovering %s ECC Error at address 0x%04x (in TX-RAM, tx_obj=%d, tx_tail=0x%08x(%d), offset=%d).\n",
+		    ecc->ecc_stat & MCP25XXFD_REG_ECCSTAT_SECIF ?
+		    "Single" : "Double",
+		    addr, nr, tx_ring->tail, tx_tail, offset);
+
+	/* reload tx_obj into controller RAM ... */
+	tx_obj = &tx_ring->obj[nr];
+	err = spi_sync_transfer(priv->spi, tx_obj->xfer, 1);
+	if (err)
+		return err;
+
+	/* ... and trigger retransmit */
+	return mcp25xxfd_chip_set_normal_mode(priv);
+}
+
+static int
+mcp25xxfd_handle_eccif(struct mcp25xxfd_priv *priv, bool set_normal_mode)
+{
+	struct mcp25xxfd_ecc *ecc = &priv->ecc;
+	const char *msg;
+	bool in_tx_ram;
+	u32 ecc_stat;
+	u16 addr;
+	u8 nr;
+	int err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_ECCSTAT, &ecc_stat);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCSTAT,
+				 MCP25XXFD_REG_ECCSTAT_IF_MASK, ~ecc_stat);
+	if (err)
+		return err;
+
+	/* Check if ECC error occurred in TX-RAM */
+	addr = FIELD_GET(MCP25XXFD_REG_ECCSTAT_ERRADDR_MASK, ecc_stat);
+	err = mcp25xxfd_get_tx_nr_by_addr(priv->tx, &nr, addr);
+	if (!err)
+		in_tx_ram = true;
+	else if (err == -ENOENT)
+		in_tx_ram = false;
+	else
+		return err;
+
+	if (ecc_stat & MCP25XXFD_REG_ECCSTAT_SECIF)
+		msg = "Single ECC Error corrected at address";
+	else if (ecc_stat & MCP25XXFD_REG_ECCSTAT_DEDIF)
+		msg = "Double ECC Error detected at address";
+	else
+		return -EINVAL;
+
+	if (!in_tx_ram) {
+		ecc->ecc_stat = 0;
+
+		if (ecc_stat & MCP25XXFD_REG_ECCSTAT_SECIF)
+			netdev_info(priv->ndev, "%s 0x%04x.\n",
+				    msg, addr);
+		else
+			netdev_notice(priv->ndev, "%s 0x%04x.\n",
+				      msg, addr);
+	} else {
+		/* Re-occurring error? */
+		if (ecc->ecc_stat == ecc_stat) {
+			ecc->cnt++;
+		} else {
+			ecc->ecc_stat = ecc_stat;
+			ecc->cnt = 1;
+		}
+
+		netdev_info(priv->ndev,
+			    "%s 0x%04x (in TX-RAM, tx_obj=%d), occurred %d time%s.\n",
+			    msg, addr, nr, ecc->cnt, ecc->cnt > 1 ? "s" : "");
+
+		if (ecc->cnt >= MCP25XXFD_ECC_CNT_MAX)
+			return mcp25xxfd_handle_eccif_recover(priv, nr);
+	}
+
+	if (set_normal_mode)
+		return mcp25xxfd_chip_set_normal_mode_nowait(priv);
+
+	return 0;
+}
+
+static int mcp25xxfd_handle_spicrcif(struct mcp25xxfd_priv *priv)
+{
+	int err;
+	u32 crc;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_CRC, &crc);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_CRC,
+				 MCP25XXFD_REG_CRC_IF_MASK,
+				 ~crc);
+	if (err)
+		return err;
+
+	if (crc & MCP25XXFD_REG_CRC_FERRIF)
+		netdev_notice(priv->ndev, "CRC write command format error.\n");
+	else if (crc & MCP25XXFD_REG_CRC_CRCERRIF)
+		netdev_notice(priv->ndev,
+			      "CRC write error detected. CRC=0x%04lx.\n",
+			      FIELD_GET(MCP25XXFD_REG_CRC_MASK, crc));
+
+	return 0;
+}
+
+#define mcp25xxfd_handle(priv, irq, ...) \
+({ \
+	struct mcp25xxfd_priv *_priv = (priv); \
+	int err; \
+\
+	err = mcp25xxfd_handle_##irq(_priv, ## __VA_ARGS__); \
+	if (err) \
+		netdev_err(_priv->ndev, \
+			"IRQ handler mcp25xxfd_handle_%s() returned %d.\n", \
+			__stringify(irq), err); \
+	err; \
+})
+
+static irqreturn_t mcp25xxfd_irq(int irq, void *dev_id)
+{
+	struct mcp25xxfd_priv *priv = dev_id;
+	irqreturn_t handled = IRQ_NONE;
+	int err;
+
+	if (priv->rx_int)
+		do {
+			int rx_pending;
+
+			rx_pending = gpiod_get_value_cansleep(priv->rx_int);
+			if (!rx_pending)
+				break;
+
+			err = mcp25xxfd_handle(priv, rxif);
+			if (err)
+				goto out_fail;
+
+			handled = IRQ_HANDLED;
+		} while (1);
+
+	do {
+		u32 intf_pending, intf_pending_clearable;
+		bool set_normal_mode;
+
+		err = regmap_bulk_read(priv->map_reg, MCP25XXFD_REG_INT,
+				       &priv->regs_status,
+				       sizeof(priv->regs_status) /
+				       sizeof(u32));
+		if (err)
+			goto out_fail;
+
+		intf_pending = FIELD_GET(MCP25XXFD_REG_INT_IF_MASK,
+					 priv->regs_status.intf) &
+			FIELD_GET(MCP25XXFD_REG_INT_IE_MASK,
+				  priv->regs_status.intf);
+
+		if (!(intf_pending))
+			return handled;
+
+		/* Some interrupts must be ACKed in the
+		 * MCP25XXFD_REG_INT register.
+		 * - First ACK then handle, to avoid lost-IRQ race
+		 *   condition on fast re-occurring interrupts.
+		 * - Write "0" to clear active IRQs, "1" to all other,
+		 *   to avoid r/m/w race condition on the
+		 *   MCP25XXFD_REG_INT register.
+		 */
+		intf_pending_clearable = intf_pending &
+			MCP25XXFD_REG_INT_IF_CLEARABLE_MASK;
+		if (intf_pending_clearable) {
+			err = regmap_update_bits(priv->map_reg,
+						 MCP25XXFD_REG_INT,
+						 MCP25XXFD_REG_INT_IF_MASK,
+						 ~intf_pending_clearable);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_MODIF) {
+			err = mcp25xxfd_handle(priv, modif, &set_normal_mode);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_RXIF) {
+			err = mcp25xxfd_handle(priv, rxif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_TEFIF) {
+			err = mcp25xxfd_handle(priv, tefif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_RXOVIF) {
+			err = mcp25xxfd_handle(priv, rxovif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_TXATIF) {
+			err = mcp25xxfd_handle(priv, txatif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_IVMIF) {
+			err = mcp25xxfd_handle(priv, ivmif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_SERRIF) {
+			err = mcp25xxfd_handle(priv, serrif);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_ECCIF) {
+			err = mcp25xxfd_handle(priv, eccif, set_normal_mode);
+			if (err)
+				goto out_fail;
+		}
+
+		if (intf_pending & MCP25XXFD_REG_INT_SPICRCIF) {
+			err = mcp25xxfd_handle(priv, spicrcif);
+			if (err)
+				goto out_fail;
+		}
+
+		/* On the MCP2527FD and MCP2518FD, we don't get a
+		 * CERRIF IRQ on the transition TX ERROR_WARNING -> TX
+		 * ERROR_ACTIVE.
+		 */
+		if (intf_pending & MCP25XXFD_REG_INT_CERRIF ||
+		    priv->can.state > CAN_STATE_ERROR_ACTIVE) {
+			err = mcp25xxfd_handle(priv, cerrif);
+			if (err)
+				goto out_fail;
+
+			/* In Bus Off we completely shut down the
+			 * controller. Every subsequent register read
+			 * will read bogus data, and if
+			 * MCP25XXFD_QUIRK_CRC_REG is enabled the CRC
+			 * check will fail, too. So leave IRQ handler
+			 * directly.
+			 */
+			if (priv->can.state == CAN_STATE_BUS_OFF)
+				return IRQ_HANDLED;
+		}
+
+		handled = IRQ_HANDLED;
+	} while (1);
+
+ out_fail:
+	netdev_err(priv->ndev, "IRQ handler returned %d (intf=0x%08x).\n",
+		   err, priv->regs_status.intf);
+	mcp25xxfd_chip_interrupts_disable(priv);
+
+	return handled;
+}
+
+static inline struct
+mcp25xxfd_tx_obj *mcp25xxfd_get_tx_obj_next(struct mcp25xxfd_tx_ring *tx_ring)
+{
+	u8 tx_head;
+
+	tx_head = mcp25xxfd_get_tx_head(tx_ring);
+
+	return &tx_ring->obj[tx_head];
+}
+
+static void
+mcp25xxfd_tx_obj_from_skb(const struct mcp25xxfd_priv *priv,
+			  struct mcp25xxfd_tx_obj *tx_obj,
+			  const struct sk_buff *skb,
+			  unsigned int seq)
+{
+	const struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
+	struct mcp25xxfd_hw_tx_obj_raw *hw_tx_obj;
+	union mcp25xxfd_tx_obj_load_buf *load_buf;
+	u8 dlc;
+	u32 id, flags;
+	int offset, len;
+
+	if (cfd->can_id & CAN_EFF_FLAG) {
+		u32 sid, eid;
+
+		sid = FIELD_GET(MCP25XXFD_REG_FRAME_EFF_SID_MASK, cfd->can_id);
+		eid = FIELD_GET(MCP25XXFD_REG_FRAME_EFF_EID_MASK, cfd->can_id);
+
+		id = FIELD_PREP(MCP25XXFD_OBJ_ID_EID_MASK, eid) |
+			FIELD_PREP(MCP25XXFD_OBJ_ID_SID_MASK, sid);
+
+		flags = MCP25XXFD_OBJ_FLAGS_IDE;
+	} else {
+		id = FIELD_PREP(MCP25XXFD_OBJ_ID_SID_MASK, cfd->can_id);
+		flags = 0;
+	}
+
+	/* Use the MCP2518FD mask even on the MCP2517FD. It doesn't
+	 * harm, only the lower 7 bits will be transferred into the
+	 * TEF object.
+	 */
+	dlc = can_len2dlc(cfd->len);
+	flags |= FIELD_PREP(MCP25XXFD_OBJ_FLAGS_SEQ_MCP2518FD_MASK, seq) |
+		FIELD_PREP(MCP25XXFD_OBJ_FLAGS_DLC, dlc);
+
+	if (cfd->can_id & CAN_RTR_FLAG)
+		flags |= MCP25XXFD_OBJ_FLAGS_RTR;
+
+	/* CANFD */
+	if (can_is_canfd_skb(skb)) {
+		if (cfd->flags & CANFD_ESI)
+			flags |= MCP25XXFD_OBJ_FLAGS_ESI;
+
+		flags |= MCP25XXFD_OBJ_FLAGS_FDF;
+
+		if (cfd->flags & CANFD_BRS)
+			flags |= MCP25XXFD_OBJ_FLAGS_BRS;
+	}
+
+	load_buf = &tx_obj->buf;
+	if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_TX)
+		hw_tx_obj = &load_buf->crc.hw_tx_obj;
+	else
+		hw_tx_obj = &load_buf->nocrc.hw_tx_obj;
+
+	put_unaligned_le32(id, &hw_tx_obj->id);
+	put_unaligned_le32(flags, &hw_tx_obj->flags);
+
+	/* Clear data at end of CAN frame */
+	offset = round_down(cfd->len, sizeof(u32));
+	len = round_up(can_dlc2len(dlc), sizeof(u32)) - offset;
+	if (MCP25XXFD_SANITIZE_CAN && len)
+		memset(hw_tx_obj->data + offset, 0x0, len);
+	memcpy(hw_tx_obj->data, cfd->data, cfd->len);
+
+	/* Number of bytes to be written into the RAM of the controller */
+	len = sizeof(hw_tx_obj->id) + sizeof(hw_tx_obj->flags);
+	if (MCP25XXFD_SANITIZE_CAN)
+		len += round_up(can_dlc2len(dlc), sizeof(u32));
+	else
+		len += round_up(cfd->len, sizeof(u32));
+
+	if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_TX) {
+		u16 crc;
+
+		mcp25xxfd_spi_cmd_crc_set_len_in_ram(&load_buf->crc.cmd,
+						     len);
+		/* CRC */
+		len += sizeof(load_buf->crc.cmd);
+		crc = mcp25xxfd_crc16_compute(&load_buf->crc, len);
+		put_unaligned_be16(crc, (void *)load_buf + len);
+
+		/* Total length */
+		len += sizeof(load_buf->crc.crc);
+	} else {
+		len += sizeof(load_buf->nocrc.cmd);
+	}
+
+	tx_obj->xfer[0].len = len;
+}
+
+static int mcp25xxfd_tx_obj_write(const struct mcp25xxfd_priv *priv,
+				  struct mcp25xxfd_tx_obj *tx_obj)
+{
+	return spi_async(priv->spi, &tx_obj->msg);
+}
+
+static bool mcp25xxfd_tx_busy(const struct mcp25xxfd_priv *priv,
+			      struct mcp25xxfd_tx_ring *tx_ring)
+{
+	if (mcp25xxfd_get_tx_free(tx_ring) > 0)
+		return false;
+
+	netif_stop_queue(priv->ndev);
+
+	/* Memory barrier before checking tx_free (head and tail) */
+	smp_mb();
+
+	if (mcp25xxfd_get_tx_free(tx_ring) == 0) {
+		netdev_dbg(priv->ndev,
+			   "Stopping tx-queue (tx_head=0x%08x, tx_tail=0x%08x, len=%d).\n",
+			   tx_ring->head, tx_ring->tail,
+			   tx_ring->head - tx_ring->tail);
+
+		return true;
+	}
+
+	netif_start_queue(priv->ndev);
+
+	return false;
+}
+
+static netdev_tx_t mcp25xxfd_start_xmit(struct sk_buff *skb,
+					struct net_device *ndev)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+	struct mcp25xxfd_tx_ring *tx_ring = priv->tx;
+	struct mcp25xxfd_tx_obj *tx_obj;
+	u8 tx_head;
+	int err;
+
+	if (can_dropped_invalid_skb(ndev, skb))
+		return NETDEV_TX_OK;
+
+	if (mcp25xxfd_tx_busy(priv, tx_ring))
+		return NETDEV_TX_BUSY;
+
+	tx_obj = mcp25xxfd_get_tx_obj_next(tx_ring);
+	mcp25xxfd_tx_obj_from_skb(priv, tx_obj, skb, tx_ring->head);
+
+	/* Stop queue if we occupy the complete TX FIFO */
+	tx_head = mcp25xxfd_get_tx_head(tx_ring);
+	tx_ring->head++;
+	if (tx_ring->head - tx_ring->tail >= tx_ring->obj_num)
+		netif_stop_queue(ndev);
+
+	can_put_echo_skb(skb, ndev, tx_head);
+
+	err = mcp25xxfd_tx_obj_write(priv, tx_obj);
+	if (err)
+		goto out_err;
+
+	return NETDEV_TX_OK;
+
+ out_err:
+	netdev_err(priv->ndev, "ERROR in %s: %d\n", __func__, err);
+
+	return NETDEV_TX_OK;
+}
+
+static int mcp25xxfd_open(struct net_device *ndev)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+	const struct spi_device *spi = priv->spi;
+	int err;
+
+	err = pm_runtime_get_sync(ndev->dev.parent);
+	if (err < 0) {
+		pm_runtime_put_noidle(ndev->dev.parent);
+		return err;
+	}
+
+	err = open_candev(ndev);
+	if (err)
+		goto out_pm_runtime_put;
+
+	err = mcp25xxfd_ring_alloc(priv);
+	if (err)
+		goto out_close_candev;
+
+	err = mcp25xxfd_transceiver_enable(priv);
+	if (err)
+		goto out_mcp25xxfd_ring_free;
+
+	err = mcp25xxfd_chip_start(priv);
+	if (err)
+		goto out_transceiver_disable;
+
+	can_rx_offload_enable(&priv->offload);
+
+	err = request_threaded_irq(spi->irq, NULL, mcp25xxfd_irq,
+				   IRQF_ONESHOT, dev_name(&spi->dev),
+				   priv);
+	if (err)
+		goto out_can_rx_offload_disable;
+
+	err = mcp25xxfd_chip_interrupts_enable(priv);
+	if (err)
+		goto out_free_irq;
+
+	netif_start_queue(ndev);
+
+	return 0;
+
+ out_free_irq:
+	free_irq(spi->irq, priv);
+ out_can_rx_offload_disable:
+	can_rx_offload_disable(&priv->offload);
+ out_transceiver_disable:
+	mcp25xxfd_transceiver_disable(priv);
+ out_mcp25xxfd_ring_free:
+	mcp25xxfd_ring_free(priv);
+ out_close_candev:
+	close_candev(ndev);
+ out_pm_runtime_put:
+	mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED);
+	pm_runtime_put(ndev->dev.parent);
+
+	return err;
+}
+
+static int mcp25xxfd_stop(struct net_device *ndev)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+	mcp25xxfd_chip_interrupts_disable(priv);
+	free_irq(ndev->irq, priv);
+	can_rx_offload_disable(&priv->offload);
+	mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED);
+	mcp25xxfd_transceiver_disable(priv);
+	mcp25xxfd_ring_free(priv);
+	close_candev(ndev);
+
+	pm_runtime_put(ndev->dev.parent);
+
+	return 0;
+}
+
+static const struct net_device_ops mcp25xxfd_netdev_ops = {
+	.ndo_open = mcp25xxfd_open,
+	.ndo_stop = mcp25xxfd_stop,
+	.ndo_start_xmit	= mcp25xxfd_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static void
+mcp25xxfd_register_quirks(struct mcp25xxfd_priv *priv)
+{
+	const struct spi_device *spi = priv->spi;
+	const struct spi_controller *ctlr = spi->controller;
+
+	if (ctlr->flags & SPI_CONTROLLER_HALF_DUPLEX)
+		priv->devtype_data.quirks |= MCP25XXFD_QUIRK_HALF_DUPLEX;
+}
+
+static int mcp25xxfd_register_chip_detect(struct mcp25xxfd_priv *priv)
+{
+	const struct net_device *ndev = priv->ndev;
+	const struct mcp25xxfd_devtype_data *devtype_data;
+	u32 osc;
+	int err;
+
+	/* The OSC_LPMEN is only supported on MCP2518FD, so use it to
+	 * autodetect the model.
+	 */
+	err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_OSC,
+				 MCP25XXFD_REG_OSC_LPMEN,
+				 MCP25XXFD_REG_OSC_LPMEN);
+	if (err)
+		return err;
+
+	err = regmap_read(priv->map_reg, MCP25XXFD_REG_OSC, &osc);
+	if (err)
+		return err;
+
+	if (osc & MCP25XXFD_REG_OSC_LPMEN)
+		devtype_data = &mcp25xxfd_devtype_data_mcp2518fd;
+	else
+		devtype_data = &mcp25xxfd_devtype_data_mcp2517fd;
+
+	if (!mcp25xxfd_is_25XX(priv) &&
+	    priv->devtype_data.model != devtype_data->model) {
+		netdev_info(ndev,
+			    "Detected %s, but firmware specifies a %s. Fixing up.",
+			    __mcp25xxfd_get_model_str(devtype_data->model),
+			    mcp25xxfd_get_model_str(priv));
+	}
+	priv->devtype_data = *devtype_data;
+
+	/* We need to preserve the Half Duplex Quirk. */
+	mcp25xxfd_register_quirks(priv);
+
+	/* Re-init regmap with quirks of detected model. */
+	return mcp25xxfd_regmap_init(priv);
+}
+
+static int mcp25xxfd_register_check_rx_int(struct mcp25xxfd_priv *priv)
+{
+	int err, rx_pending;
+
+	if (!priv->rx_int)
+		return 0;
+
+	err = mcp25xxfd_chip_rx_int_enable(priv);
+	if (err)
+		return err;
+
+	/* Check if RX_INT is properly working. The RX_INT should not
+	 * be active after a softreset.
+	 */
+	rx_pending = gpiod_get_value_cansleep(priv->rx_int);
+
+	err = mcp25xxfd_chip_rx_int_disable(priv);
+	if (err)
+		return err;
+
+	if (!rx_pending)
+		return 0;
+
+	netdev_info(priv->ndev,
+		    "RX_INT active after softreset, disabling RX_INT support.");
+	devm_gpiod_put(&priv->spi->dev, priv->rx_int);
+	priv->rx_int = NULL;
+
+	return 0;
+}
+
+static int
+mcp25xxfd_register_get_dev_id(const struct mcp25xxfd_priv *priv,
+			      u32 *dev_id, u32 *effective_speed_hz)
+{
+	struct mcp25xxfd_map_buf_nocrc *buf_rx;
+	struct mcp25xxfd_map_buf_nocrc *buf_tx;
+	struct spi_transfer xfer[2] = { };
+	int err;
+
+	buf_rx = kzalloc(sizeof(*buf_rx), GFP_KERNEL);
+	if (!buf_rx)
+		return -ENOMEM;
+
+	buf_tx = kzalloc(sizeof(*buf_tx), GFP_KERNEL);
+	if (!buf_tx) {
+		err = -ENOMEM;
+		goto out_kfree_buf_rx;
+	}
+
+	xfer[0].tx_buf = buf_tx;
+	xfer[0].len = sizeof(buf_tx->cmd);
+	xfer[1].rx_buf = buf_rx->data;
+	xfer[1].len = sizeof(dev_id);
+
+	mcp25xxfd_spi_cmd_read_nocrc(&buf_tx->cmd, MCP25XXFD_REG_DEVID);
+	err = spi_sync_transfer(priv->spi, xfer, ARRAY_SIZE(xfer));
+	if (err)
+		goto out_kfree_buf_tx;
+
+	*dev_id = be32_to_cpup((__be32 *)buf_rx->data);
+	*effective_speed_hz = xfer->effective_speed_hz;
+
+ out_kfree_buf_tx:
+	kfree(buf_tx);
+ out_kfree_buf_rx:
+	kfree(buf_rx);
+
+	return 0;
+}
+
+#define MCP25XXFD_QUIRK_ACTIVE(quirk) \
+	(priv->devtype_data.quirks & MCP25XXFD_QUIRK_##quirk ? '+' : '-')
+
+static int
+mcp25xxfd_register_done(const struct mcp25xxfd_priv *priv)
+{
+	u32 dev_id, effective_speed_hz;
+	int err;
+
+	err = mcp25xxfd_register_get_dev_id(priv, &dev_id,
+					    &effective_speed_hz);
+	if (err)
+		return err;
+
+	netdev_info(priv->ndev,
+		    "%s rev%lu.%lu (%cRX_INT %cMAB_NO_WARN %cCRC_REG %cCRC_RX %cCRC_TX %cECC %cHD c:%u.%02uMHz m:%u.%02uMHz r:%u.%02uMHz e:%u.%02uMHz) successfully initialized.\n",
+		    mcp25xxfd_get_model_str(priv),
+		    FIELD_GET(MCP25XXFD_REG_DEVID_ID_MASK, dev_id),
+		    FIELD_GET(MCP25XXFD_REG_DEVID_REV_MASK, dev_id),
+		    priv->rx_int ? '+' : '-',
+		    MCP25XXFD_QUIRK_ACTIVE(MAB_NO_WARN),
+		    MCP25XXFD_QUIRK_ACTIVE(CRC_REG),
+		    MCP25XXFD_QUIRK_ACTIVE(CRC_RX),
+		    MCP25XXFD_QUIRK_ACTIVE(CRC_TX),
+		    MCP25XXFD_QUIRK_ACTIVE(ECC),
+		    MCP25XXFD_QUIRK_ACTIVE(HALF_DUPLEX),
+		    priv->can.clock.freq / 1000000,
+		    priv->can.clock.freq % 1000000 / 1000 / 10,
+		    priv->spi_max_speed_hz_orig / 1000000,
+		    priv->spi_max_speed_hz_orig % 1000000 / 1000 / 10,
+		    priv->spi->max_speed_hz / 1000000,
+		    priv->spi->max_speed_hz % 1000000 / 1000 / 10,
+		    effective_speed_hz / 1000000,
+		    effective_speed_hz % 1000000 / 1000 / 10);
+
+	return 0;
+}
+
+static int mcp25xxfd_register(struct mcp25xxfd_priv *priv)
+{
+	struct net_device *ndev = priv->ndev;
+	int err;
+
+	err = mcp25xxfd_clks_and_vdd_enable(priv);
+	if (err)
+		return err;
+
+	pm_runtime_get_noresume(ndev->dev.parent);
+	err = pm_runtime_set_active(ndev->dev.parent);
+	if (err)
+		goto out_runtime_put_noidle;
+	pm_runtime_enable(ndev->dev.parent);
+
+	mcp25xxfd_register_quirks(priv);
+
+	err = mcp25xxfd_chip_softreset(priv);
+	if (err == -ENODEV)
+		goto out_runtime_disable;
+	if (err)
+		goto out_chip_set_mode_sleep;
+
+	err = mcp25xxfd_register_chip_detect(priv);
+	if (err)
+		goto out_chip_set_mode_sleep;
+
+	err = mcp25xxfd_register_check_rx_int(priv);
+	if (err)
+		goto out_chip_set_mode_sleep;
+
+	err = register_candev(ndev);
+	if (err)
+		goto out_chip_set_mode_sleep;
+
+	err = mcp25xxfd_register_done(priv);
+	if (err)
+		goto out_unregister_candev;
+
+	/* Put controller into sleep mode and let pm_runtime_put()
+	 * disable the clocks and vdd. If CONFIG_PM is not enabled,
+	 * the clocks and vdd will stay powered.
+	 */
+	err = mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_SLEEP);
+	if (err)
+		goto out_unregister_candev;
+
+	pm_runtime_put(ndev->dev.parent);
+
+	return 0;
+
+ out_unregister_candev:
+	unregister_candev(ndev);
+ out_chip_set_mode_sleep:
+	mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_SLEEP);
+ out_runtime_disable:
+	pm_runtime_disable(ndev->dev.parent);
+ out_runtime_put_noidle:
+	pm_runtime_put_noidle(ndev->dev.parent);
+	mcp25xxfd_clks_and_vdd_disable(priv);
+
+	return err;
+}
+
+static inline void mcp25xxfd_unregister(struct mcp25xxfd_priv *priv)
+{
+	struct net_device *ndev	= priv->ndev;
+
+	unregister_candev(ndev);
+
+	pm_runtime_get_sync(ndev->dev.parent);
+	pm_runtime_put_noidle(ndev->dev.parent);
+	mcp25xxfd_clks_and_vdd_disable(priv);
+	pm_runtime_disable(ndev->dev.parent);
+}
+
+static const struct of_device_id mcp25xxfd_of_match[] = {
+	{
+		.compatible = "microchip,mcp2517fd",
+		.data = &mcp25xxfd_devtype_data_mcp2517fd,
+	}, {
+		.compatible = "microchip,mcp2518fd",
+		.data = &mcp25xxfd_devtype_data_mcp2518fd,
+	}, {
+		.compatible = "microchip,mcp25xxfd",
+		.data = &mcp25xxfd_devtype_data_mcp25xxfd,
+	}, {
+		/* sentinel */
+	},
+};
+MODULE_DEVICE_TABLE(of, mcp25xxfd_of_match);
+
+static const struct spi_device_id mcp25xxfd_id_table[] = {
+	{
+		.name = "mcp2517fd",
+		.driver_data = (kernel_ulong_t)&mcp25xxfd_devtype_data_mcp2517fd,
+	}, {
+		.name = "mcp2518fd",
+		.driver_data = (kernel_ulong_t)&mcp25xxfd_devtype_data_mcp2518fd,
+	}, {
+		.name = "mcp25xxfd",
+		.driver_data = (kernel_ulong_t)&mcp25xxfd_devtype_data_mcp25xxfd,
+	}, {
+		/* sentinel */
+	},
+};
+MODULE_DEVICE_TABLE(spi, mcp25xxfd_id_table);
+
+static int mcp25xxfd_probe(struct spi_device *spi)
+{
+	const void *match;
+	struct net_device *ndev;
+	struct mcp25xxfd_priv *priv;
+	struct gpio_desc *rx_int;
+	struct regulator *reg_vdd, *reg_xceiver;
+	struct clk *clk;
+	u32 freq;
+	int err;
+
+	rx_int = devm_gpiod_get_optional(&spi->dev, "microchip,rx-int",
+					 GPIOD_IN);
+	if (PTR_ERR(rx_int) == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	else if (IS_ERR(rx_int))
+		return PTR_ERR(rx_int);
+
+	reg_vdd = devm_regulator_get_optional(&spi->dev, "vdd");
+	if (PTR_ERR(reg_vdd) == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	else if (PTR_ERR(reg_vdd) == -ENODEV)
+		reg_vdd = NULL;
+	else if (IS_ERR(reg_vdd))
+		return PTR_ERR(reg_vdd);
+
+	reg_xceiver = devm_regulator_get_optional(&spi->dev, "xceiver");
+	if (PTR_ERR(reg_xceiver) == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	else if (PTR_ERR(reg_xceiver) == -ENODEV)
+		reg_xceiver = NULL;
+	else if (IS_ERR(reg_xceiver))
+		return PTR_ERR(reg_xceiver);
+
+	clk = devm_clk_get(&spi->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(&spi->dev, "No Oscillator (clock) defined.\n");
+		return PTR_ERR(clk);
+	}
+	freq = clk_get_rate(clk);
+
+	/* Sanity check */
+	if (freq < MCP25XXFD_SYSCLOCK_HZ_MIN ||
+	    freq > MCP25XXFD_SYSCLOCK_HZ_MAX) {
+		dev_err(&spi->dev,
+			"Oscillator frequency (%u Hz) is too low or high.\n",
+			freq);
+		return -ERANGE;
+	}
+
+	if (freq <= MCP25XXFD_SYSCLOCK_HZ_MAX / MCP25XXFD_OSC_PLL_MULTIPLIER) {
+		dev_err(&spi->dev,
+			"Oscillator frequency (%u Hz) is too low and PLL is not supported.\n",
+			freq);
+		return -ERANGE;
+	}
+
+	ndev = alloc_candev(sizeof(struct mcp25xxfd_priv),
+			    MCP25XXFD_TX_OBJ_NUM_MAX);
+	if (!ndev)
+		return -ENOMEM;
+
+	SET_NETDEV_DEV(ndev, &spi->dev);
+
+	ndev->netdev_ops = &mcp25xxfd_netdev_ops;
+	ndev->irq = spi->irq;
+	ndev->flags |= IFF_ECHO;
+
+	priv = netdev_priv(ndev);
+	spi_set_drvdata(spi, priv);
+	priv->can.clock.freq = freq;
+	priv->can.do_set_mode = mcp25xxfd_set_mode;
+	priv->can.do_get_berr_counter = mcp25xxfd_get_berr_counter;
+	priv->can.bittiming_const = &mcp25xxfd_bittiming_const;
+	priv->can.data_bittiming_const = &mcp25xxfd_data_bittiming_const;
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING |
+		CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
+	priv->ndev = ndev;
+	priv->spi = spi;
+	priv->rx_int = rx_int;
+	priv->clk = clk;
+	priv->reg_vdd = reg_vdd;
+	priv->reg_xceiver = reg_xceiver;
+
+	match = device_get_match_data(&spi->dev);
+	if (match)
+		priv->devtype_data = *(struct mcp25xxfd_devtype_data *)match;
+	else
+		priv->devtype_data = *(struct mcp25xxfd_devtype_data *)
+			spi_get_device_id(spi)->driver_data;
+
+	/* According to the datasheet the SPI clock must be less or
+	 * equal SYSCLOCK / 2.
+	 *
+	 * It turns out, that the Controller is not stable at this
+	 * rate. Known good and bad combinations are:
+	 *
+	 * MCP	ext-clk	SoC			SPI			SPI-clk		max-clk	parent-clk	Status	config
+	 *
+	 * 2518	20 MHz	allwinner,sun8i-h3	allwinner,sun8i-h3-spi	 8333333 Hz	 83.33%	600000000 Hz	good	assigned-clocks = <&ccu CLK_SPIx>
+	 * 2518	20 MHz	allwinner,sun8i-h3	allwinner,sun8i-h3-spi	 9375000 Hz	 93.75%	600000000 Hz	bad	assigned-clocks = <&ccu CLK_SPIx>
+	 * 2518	40 MHz	allwinner,sun8i-h3	allwinner,sun8i-h3-spi	16666667 Hz	 83.33%	600000000 Hz	good	assigned-clocks = <&ccu CLK_SPIx>
+	 * 2518	40 MHz	allwinner,sun8i-h3	allwinner,sun8i-h3-spi	18750000 Hz	 93.75%	600000000 Hz	bad	assigned-clocks = <&ccu CLK_SPIx>
+	 * 2517	20 MHz	fsl,imx8mm		fsl,imx51-ecspi		 8333333 Hz	 83.33%	 16666667 Hz	good	assigned-clocks = <&clk IMX8MM_CLK_ECSPIx_ROOT>
+	 * 2517	20 MHz	fsl,imx8mm		fsl,imx51-ecspi		 9523809 Hz	 95.34%	 28571429 Hz	bad	assigned-clocks = <&clk IMX8MM_CLK_ECSPIx_ROOT>
+	 * 2517 40 MHz	atmel,sama5d27		atmel,at91rm9200-spi	16400000 Hz	 82.00%	 82000000 Hz	good	default
+	 * 2518 40 MHz	atmel,sama5d27		atmel,at91rm9200-spi	16400000 Hz	 82.00%	 82000000 Hz	good	default
+	 *
+	 * Limit SPI clock to 85% of SYSCLOCK / 2 for now.
+	 */
+	priv->spi_max_speed_hz_orig = spi->max_speed_hz;
+	spi->max_speed_hz = min(spi->max_speed_hz, freq / 2 / 1000 * 850);
+	spi->bits_per_word = 8;
+	spi->rt = true;
+	err = spi_setup(spi);
+	if (err)
+		goto out_free_candev;
+
+	err = mcp25xxfd_regmap_init(priv);
+	if (err)
+		goto out_free_candev;
+
+	err = can_rx_offload_add_manual(ndev, &priv->offload,
+					MCP25XXFD_NAPI_WEIGHT);
+	if (err)
+		goto out_free_candev;
+
+	err = mcp25xxfd_register(priv);
+	if (err)
+		goto out_free_candev;
+
+	return 0;
+
+ out_free_candev:
+	spi->max_speed_hz = priv->spi_max_speed_hz_orig;
+
+	free_candev(ndev);
+
+	return err;
+}
+
+static int mcp25xxfd_remove(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct net_device *ndev = priv->ndev;
+
+	can_rx_offload_del(&priv->offload);
+	mcp25xxfd_unregister(priv);
+	spi->max_speed_hz = priv->spi_max_speed_hz_orig;
+	free_candev(ndev);
+
+	return 0;
+}
+
+static int __maybe_unused mcp25xxfd_runtime_suspend(struct device *device)
+{
+	const struct mcp25xxfd_priv *priv = dev_get_drvdata(device);
+
+	return mcp25xxfd_clks_and_vdd_disable(priv);
+}
+
+static int __maybe_unused mcp25xxfd_runtime_resume(struct device *device)
+{
+	const struct mcp25xxfd_priv *priv = dev_get_drvdata(device);
+
+	return mcp25xxfd_clks_and_vdd_enable(priv);
+}
+
+static const struct dev_pm_ops mcp25xxfd_pm_ops = {
+	SET_RUNTIME_PM_OPS(mcp25xxfd_runtime_suspend,
+			   mcp25xxfd_runtime_resume, NULL)
+};
+
+static struct spi_driver mcp25xxfd_driver = {
+	.driver = {
+		.name = DEVICE_NAME,
+		.pm = &mcp25xxfd_pm_ops,
+		.of_match_table = mcp25xxfd_of_match,
+	},
+	.probe = mcp25xxfd_probe,
+	.remove = mcp25xxfd_remove,
+	.id_table = mcp25xxfd_id_table,
+};
+module_spi_driver(mcp25xxfd_driver);
+
+MODULE_AUTHOR("Marc Kleine-Budde <mkl@pengutronix.de>");
+MODULE_DESCRIPTION("Microchip MCP25xxFD Family CAN controller driver");
+MODULE_LICENSE("GPL v2");
-- 
2.28.0


  parent reply	other threads:[~2020-09-18 17:25 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-18 17:25 [PATCH v53 1/6] mcp25xxfd: initial driver support Marc Kleine-Budde
2020-09-18 17:25 ` [PATCH v53 1/6] dt-binding: can: mcp25xxfd: document device tree bindings Marc Kleine-Budde
2020-09-18 17:25 ` [PATCH v53 2/6] can: mcp25xxfd: add regmap infrastructure Marc Kleine-Budde
2020-09-21 19:33   ` Mark Brown
2020-09-22  5:57     ` Marc Kleine-Budde
2020-09-22 12:13       ` Mark Brown
2020-09-22 13:56         ` Marc Kleine-Budde
2020-09-22 14:23           ` Mark Brown
2020-09-18 17:25 ` Marc Kleine-Budde [this message]
2020-09-18 17:25 ` [PATCH v53 4/6] can: mcp25xxfd: add listen-only mode Marc Kleine-Budde
2020-09-18 17:25 ` [PATCH v53 5/6] MAINTAINERS: Add entry for Microchip MCP25XXFD SPI-CAN network driver Marc Kleine-Budde
2020-09-18 17:25 ` [PATCH v53 6/6] MAINTAINERS: Add reviewer entry for microchip mcp25xxfd " Marc Kleine-Budde

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=20200918172536.2074504-4-mkl@pengutronix.de \
    --to=mkl@pengutronix.de \
    --cc=dev.kurt@vandijck-laurijssen.be \
    --cc=linux-can@vger.kernel.org \
    --cc=manivannan.sadhasivam@linaro.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.