[PATCH 3/6] can: mcp25xxfd: Add support for CAN reception
From: Manivannan Sadhasivam
Date: Wed Jun 10 2020 - 03:45:15 EST
From: Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
Add un-optimized CAN2.0 and CAN-FD reception support.
On a Rasperry pi 3 it is already able to process CAN2.0 Frames
with DLC=0 on a CAN bus with 1MHz without losing any packets
on the SPI side. Packets still get lost inside the network stack.
Signed-off-by: Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
[mani: misc cleanups for upstream]
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@xxxxxxxxxx>
---
drivers/net/can/spi/mcp25xxfd/Makefile | 3 +
.../net/can/spi/mcp25xxfd/mcp25xxfd_base.c | 6 +
drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.c | 395 ++++++++++-
drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.h | 35 +
.../can/spi/mcp25xxfd/mcp25xxfd_can_fifo.c | 228 ++++++
.../can/spi/mcp25xxfd/mcp25xxfd_can_fifo.h | 16 +
.../net/can/spi/mcp25xxfd/mcp25xxfd_can_id.h | 69 ++
.../net/can/spi/mcp25xxfd/mcp25xxfd_can_int.c | 655 ++++++++++++++++++
.../net/can/spi/mcp25xxfd/mcp25xxfd_can_int.h | 18 +
.../can/spi/mcp25xxfd/mcp25xxfd_can_priv.h | 131 ++++
.../net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.c | 233 +++++++
.../net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.h | 18 +
drivers/net/can/spi/mcp25xxfd/mcp25xxfd_int.c | 14 +-
.../net/can/spi/mcp25xxfd/mcp25xxfd_priv.h | 2 +
.../net/can/spi/mcp25xxfd/mcp25xxfd_regs.h | 5 +
15 files changed, 1823 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.c
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.h
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_id.h
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.c
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.h
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_priv.h
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.c
create mode 100644 drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.h
diff --git a/drivers/net/can/spi/mcp25xxfd/Makefile b/drivers/net/can/spi/mcp25xxfd/Makefile
index d8fdb76a9578..5787bdd57a9d 100644
--- a/drivers/net/can/spi/mcp25xxfd/Makefile
+++ b/drivers/net/can/spi/mcp25xxfd/Makefile
@@ -1,6 +1,9 @@
obj-$(CONFIG_CAN_MCP25XXFD) += mcp25xxfd.o
mcp25xxfd-objs := mcp25xxfd_base.o
mcp25xxfd-objs += mcp25xxfd_can.o
+mcp25xxfd-objs += mcp25xxfd_can_fifo.o
+mcp25xxfd-objs += mcp25xxfd_can_int.o
+mcp25xxfd-objs += mcp25xxfd_can_rx.o
mcp25xxfd-objs += mcp25xxfd_cmd.o
mcp25xxfd-objs += mcp25xxfd_crc.o
mcp25xxfd-objs += mcp25xxfd_ecc.o
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c
index 4be456df0998..c6b67c54a3cd 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c
@@ -123,6 +123,11 @@ static int mcp25xxfd_base_probe(struct spi_device *spi)
if (ret)
goto out_power;
+ /* setting up CAN */
+ ret = mcp25xxfd_can_setup(priv);
+ if (ret)
+ goto out_power;
+
dev_info(&spi->dev,
"MCP%04x successfully initialized.\n", model);
return 0;
@@ -140,6 +145,7 @@ static int mcp25xxfd_base_remove(struct spi_device *spi)
{
struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+ mcp25xxfd_can_remove(priv);
mcp25xxfd_base_power_enable(priv->power, 0);
clk_disable_unprepare(priv->clk);
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.c
index 41a5ab508582..2ac78024c171 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.c
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.c
@@ -5,15 +5,119 @@
* Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
*/
+#include <linux/bitfield.h>
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
#include <linux/device.h>
+#include <linux/interrupt.h>
#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include "mcp25xxfd_base.h"
+#include "mcp25xxfd_can_fifo.h"
+#include "mcp25xxfd_can_int.h"
+#include "mcp25xxfd_can_priv.h"
#include "mcp25xxfd_can.h"
#include "mcp25xxfd_cmd.h"
+#include "mcp25xxfd_int.h"
#include "mcp25xxfd_priv.h"
#include "mcp25xxfd_regs.h"
+#include <uapi/linux/can/netlink.h>
+
+/* everything related to bit timing */
+static
+const struct can_bittiming_const mcp25xxfd_can_nominal_bittiming_const = {
+ .name = DEVICE_NAME,
+ .tseg1_min = 2,
+ .tseg1_max = BIT(MCP25XXFD_CAN_NBTCFG_TSEG1_BITS),
+ .tseg2_min = 1,
+ .tseg2_max = BIT(MCP25XXFD_CAN_NBTCFG_TSEG2_BITS),
+ .sjw_max = BIT(MCP25XXFD_CAN_NBTCFG_SJW_BITS),
+ .brp_min = 1,
+ .brp_max = BIT(MCP25XXFD_CAN_NBTCFG_BRP_BITS),
+ .brp_inc = 1,
+};
+
+static
+const struct can_bittiming_const mcp25xxfd_can_data_bittiming_const = {
+ .name = DEVICE_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = BIT(MCP25XXFD_CAN_DBTCFG_TSEG1_BITS),
+ .tseg2_min = 1,
+ .tseg2_max = BIT(MCP25XXFD_CAN_DBTCFG_TSEG2_BITS),
+ .sjw_max = BIT(MCP25XXFD_CAN_DBTCFG_SJW_BITS),
+ .brp_min = 1,
+ .brp_max = BIT(MCP25XXFD_CAN_DBTCFG_BRP_BITS),
+ .brp_inc = 1,
+};
+
+static int mcp25xxfd_can_do_set_nominal_bittiming(struct net_device *net)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+ struct can_bittiming *bt = &cpriv->can.bittiming;
+ int sjw = bt->sjw;
+ int pseg2 = bt->phase_seg2;
+ int pseg1 = bt->phase_seg1;
+ int propseg = bt->prop_seg;
+ int brp = bt->brp;
+ int tseg1 = propseg + pseg1;
+ int tseg2 = pseg2;
+
+ /* calculate nominal bit timing */
+ cpriv->regs.nbtcfg = ((sjw - 1) << MCP25XXFD_CAN_NBTCFG_SJW_SHIFT) |
+ ((tseg2 - 1) << MCP25XXFD_CAN_NBTCFG_TSEG2_SHIFT) |
+ ((tseg1 - 1) << MCP25XXFD_CAN_NBTCFG_TSEG1_SHIFT) |
+ ((brp - 1) << MCP25XXFD_CAN_NBTCFG_BRP_SHIFT);
+
+ return mcp25xxfd_cmd_write(cpriv->priv->spi, MCP25XXFD_CAN_NBTCFG,
+ cpriv->regs.nbtcfg);
+}
+
+static int mcp25xxfd_can_do_set_data_bittiming(struct net_device *net)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+ struct mcp25xxfd_priv *priv = cpriv->priv;
+ struct can_bittiming *bt = &cpriv->can.data_bittiming;
+ struct spi_device *spi = priv->spi;
+ int sjw = bt->sjw;
+ int pseg2 = bt->phase_seg2;
+ int pseg1 = bt->phase_seg1;
+ int propseg = bt->prop_seg;
+ int brp = bt->brp;
+ int tseg1 = propseg + pseg1;
+ int tseg2 = pseg2;
+ int tdco;
+ int ret;
+
+ /* set up Transmitter delay compensation */
+ cpriv->regs.tdc = FIELD_PREP(MCP25XXFD_CAN_TDC_TDCMOD_MASK,
+ MCP25XXFD_CAN_TDC_TDCMOD_AUTO);
+
+ /* configure TDC offsets */
+ tdco = clamp_t(int, bt->brp * tseg1, -64, 63);
+ cpriv->regs.tdc &= ~MCP25XXFD_CAN_TDC_TDCO_MASK;
+ cpriv->regs.tdc |= FIELD_PREP(MCP25XXFD_CAN_TDC_TDCO_MASK, tdco);
+
+ /* set TDC */
+ ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_CAN_TDC, cpriv->regs.tdc);
+ if (ret)
+ return ret;
+
+ /* calculate data bit timing */
+ cpriv->regs.dbtcfg =
+ FIELD_PREP(MCP25XXFD_CAN_DBTCFG_SJW_MASK, (sjw - 1)) |
+ FIELD_PREP(MCP25XXFD_CAN_DBTCFG_TSEG2_MASK, (tseg2 - 1)) |
+ FIELD_PREP(MCP25XXFD_CAN_DBTCFG_TSEG1_MASK, (tseg1 - 1)) |
+ FIELD_PREP(MCP25XXFD_CAN_DBTCFG_BRP_MASK, (brp - 1));
+
+ return mcp25xxfd_cmd_write(spi, MCP25XXFD_CAN_DBTCFG,
+ cpriv->regs.dbtcfg);
+}
+
int mcp25xxfd_can_get_mode(struct mcp25xxfd_priv *priv, u32 *reg)
{
int ret;
@@ -25,10 +129,10 @@ int mcp25xxfd_can_get_mode(struct mcp25xxfd_priv *priv, u32 *reg)
return FIELD_GET(MCP25XXFD_CAN_CON_OPMOD_MASK, *reg);
}
-static int mcp25xxfd_can_switch_mode(struct mcp25xxfd_priv *priv,
- u32 *reg, int mode)
+int mcp25xxfd_can_switch_mode_no_wait(struct mcp25xxfd_priv *priv,
+ u32 *reg, int mode)
{
- int ret, i;
+ int ret;
ret = mcp25xxfd_can_get_mode(priv, reg);
if (ret < 0)
@@ -41,7 +145,15 @@ static int mcp25xxfd_can_switch_mode(struct mcp25xxfd_priv *priv,
FIELD_PREP(MCP25XXFD_CAN_CON_OPMOD_MASK, mode);
/* Request the mode switch */
- ret = mcp25xxfd_cmd_write(priv->spi, MCP25XXFD_CAN_CON, *reg);
+ return mcp25xxfd_cmd_write(priv->spi, MCP25XXFD_CAN_CON, *reg);
+}
+
+int mcp25xxfd_can_switch_mode(struct mcp25xxfd_priv *priv, u32 *reg, int mode)
+{
+ int ret, i;
+
+ /* trigger the mode switch itself */
+ ret = mcp25xxfd_can_switch_mode_no_wait(priv, reg, mode);
if (ret)
return ret;
@@ -139,3 +251,278 @@ int mcp25xxfd_can_probe(struct mcp25xxfd_priv *priv)
/* Finally check if modeswitch is really working */
return mcp25xxfd_can_probe_modeswitch(priv);
}
+
+static int mcp25xxfd_can_config(struct net_device *net)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+ struct mcp25xxfd_priv *priv = cpriv->priv;
+ struct spi_device *spi = priv->spi;
+ int ret;
+
+ /* setup value of con_register */
+ cpriv->regs.con = MCP25XXFD_CAN_CON_STEF; /* enable TEF, disable TXQ */
+
+ /* non iso FD mode */
+ if (!(cpriv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO))
+ cpriv->regs.con |= MCP25XXFD_CAN_CON_ISOCRCEN;
+
+ /* one shot */
+ if (cpriv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+ cpriv->regs.con |= MCP25XXFD_CAN_CON_RTXAT;
+
+ /* apply it now together with a mode switch */
+ ret = mcp25xxfd_can_switch_mode(cpriv->priv, &cpriv->regs.con,
+ MCP25XXFD_CAN_CON_MODE_CONFIG);
+ if (ret)
+ return 0;
+
+ /* time stamp control register - 1ns resolution */
+ cpriv->regs.tscon = 0;
+ ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_CAN_TBC, 0);
+ if (ret)
+ return ret;
+
+ cpriv->regs.tscon = MCP25XXFD_CAN_TSCON_TBCEN |
+ FIELD_PREP(MCP25XXFD_CAN_TSCON_TBCPRE_MASK,
+ ((cpriv->can.clock.freq / 1000000)));
+ ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_CAN_TSCON, cpriv->regs.tscon);
+ if (ret)
+ return ret;
+
+ /* setup fifos */
+ ret = mcp25xxfd_can_fifo_setup(cpriv);
+ if (ret)
+ return ret;
+
+ /* setup can bittiming now - the do_set_bittiming methods
+ * are not used as they get called before open
+ */
+ ret = mcp25xxfd_can_do_set_nominal_bittiming(net);
+ if (ret)
+ return ret;
+
+ ret = mcp25xxfd_can_do_set_data_bittiming(net);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/* mode setting */
+static int mcp25xxfd_can_do_set_mode(struct net_device *net,
+ enum can_mode mode)
+{
+ switch (mode) {
+ case CAN_MODE_START:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/* binary error counters */
+static int mcp25xxfd_can_get_berr_counter(const struct net_device *net,
+ struct can_berr_counter *bec)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+
+ bec->txerr = FIELD_PREP(MCP25XXFD_CAN_TREC_TEC_MASK,
+ cpriv->status.trec);
+ bec->rxerr = FIELD_PREP(MCP25XXFD_CAN_TREC_REC_MASK,
+ cpriv->status.trec);
+
+ return 0;
+}
+
+static int mcp25xxfd_can_open(struct net_device *net)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+ struct spi_device *spi = cpriv->priv->spi;
+ int ret, mode;
+
+ ret = open_candev(net);
+ if (ret) {
+ netdev_err(net, "unable to set initial baudrate!\n");
+ return ret;
+ }
+
+ /* request an IRQ but keep disabled for now */
+ ret = request_threaded_irq(spi->irq, NULL,
+ mcp25xxfd_can_int,
+ IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ cpriv->priv->device_name, cpriv);
+ if (ret) {
+ dev_err(&spi->dev, "failed to acquire irq %d - %i\n",
+ spi->irq, ret);
+ goto out_candev;
+ }
+
+ disable_irq(spi->irq);
+ cpriv->irq.allocated = true;
+ cpriv->irq.enabled = false;
+
+ /* enable power to the transceiver */
+ ret = mcp25xxfd_base_power_enable(cpriv->transceiver, 1);
+ if (ret)
+ goto out_irq;
+
+ /* configure controller for reception */
+ ret = mcp25xxfd_can_config(net);
+ if (ret)
+ goto out_power;
+
+ /* setting up state */
+ cpriv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ /* enable interrupts */
+ ret = mcp25xxfd_int_enable(cpriv->priv, true);
+ if (ret)
+ goto out_canconfig;
+
+ if (cpriv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ mode = MCP25XXFD_CAN_CON_MODE_EXT_LOOPBACK;
+ else if (cpriv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ mode = MCP25XXFD_CAN_CON_MODE_LISTENONLY;
+ else if (cpriv->can.ctrlmode & CAN_CTRLMODE_FD)
+ mode = MCP25XXFD_CAN_CON_MODE_MIXED;
+ else
+ mode = MCP25XXFD_CAN_CON_MODE_CAN2_0;
+
+ /* switch to active mode */
+ ret = mcp25xxfd_can_switch_mode(cpriv->priv, &cpriv->regs.con, mode);
+ if (ret)
+ goto out_int;
+
+ return 0;
+
+out_int:
+ mcp25xxfd_int_enable(cpriv->priv, false);
+out_canconfig:
+ mcp25xxfd_can_fifo_release(cpriv);
+out_power:
+ mcp25xxfd_base_power_enable(cpriv->transceiver, 0);
+out_irq:
+ free_irq(spi->irq, cpriv);
+ cpriv->irq.allocated = false;
+ cpriv->irq.enabled = false;
+out_candev:
+ close_candev(net);
+ return ret;
+}
+
+static void mcp25xxfd_can_shutdown(struct mcp25xxfd_can_priv *cpriv)
+{
+ /* switch us to CONFIG mode - this disables the controller */
+ mcp25xxfd_can_switch_mode(cpriv->priv, &cpriv->regs.con,
+ MCP25XXFD_CAN_CON_MODE_CONFIG);
+}
+
+static int mcp25xxfd_can_stop(struct net_device *net)
+{
+ struct mcp25xxfd_can_priv *cpriv = netdev_priv(net);
+ struct mcp25xxfd_priv *priv = cpriv->priv;
+ struct spi_device *spi = priv->spi;
+
+ /* shutdown the can controller */
+ mcp25xxfd_can_shutdown(cpriv);
+
+ /* disable inerrupts on controller */
+ mcp25xxfd_int_enable(cpriv->priv, false);
+
+ /* disable the transceiver */
+ mcp25xxfd_base_power_enable(cpriv->transceiver, 0);
+
+ /* disable interrupt on host */
+ free_irq(spi->irq, cpriv);
+ cpriv->irq.allocated = false;
+ cpriv->irq.enabled = false;
+
+ /* close the can_decice */
+ close_candev(net);
+
+ return 0;
+}
+
+static const struct net_device_ops mcp25xxfd_netdev_ops = {
+ .ndo_open = mcp25xxfd_can_open,
+ .ndo_stop = mcp25xxfd_can_stop,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+/* probe and remove */
+int mcp25xxfd_can_setup(struct mcp25xxfd_priv *priv)
+{
+ struct spi_device *spi = priv->spi;
+ struct mcp25xxfd_can_priv *cpriv;
+ struct net_device *net;
+ struct regulator *transceiver;
+ int ret;
+
+ /* get transceiver power regulator*/
+ transceiver = devm_regulator_get(&spi->dev, "xceiver");
+ if (PTR_ERR(transceiver) == -EPROBE_DEFER)
+ return PTR_ERR(transceiver);
+
+ /* allocate can device */
+ net = alloc_candev(sizeof(*cpriv), TX_ECHO_SKB_MAX);
+ if (!net)
+ return -ENOMEM;
+
+ cpriv = netdev_priv(net);
+ cpriv->priv = priv;
+ priv->cpriv = cpriv;
+
+ /* setup network */
+ SET_NETDEV_DEV(net, &spi->dev);
+ net->netdev_ops = &mcp25xxfd_netdev_ops;
+ net->flags |= IFF_ECHO;
+
+ cpriv->transceiver = transceiver;
+
+ cpriv->can.clock.freq = priv->clock_freq;
+ cpriv->can.bittiming_const =
+ &mcp25xxfd_can_nominal_bittiming_const;
+ cpriv->can.data_bittiming_const =
+ &mcp25xxfd_can_data_bittiming_const;
+
+ /* we are not setting bit-timing methods here as they get called by
+ * the framework before open. So the controller would be still in sleep
+ * mode, which does not help as things are configured in open instead.
+ */
+ cpriv->can.do_set_mode =
+ mcp25xxfd_can_do_set_mode;
+ cpriv->can.do_get_berr_counter =
+ mcp25xxfd_can_get_berr_counter;
+ cpriv->can.ctrlmode_supported =
+ CAN_CTRLMODE_FD |
+ CAN_CTRLMODE_FD_NON_ISO |
+ CAN_CTRLMODE_LOOPBACK |
+ CAN_CTRLMODE_LISTENONLY |
+ CAN_CTRLMODE_BERR_REPORTING |
+ CAN_CTRLMODE_ONE_SHOT;
+
+ ret = register_candev(net);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to register can device\n");
+ goto out;
+ }
+
+ return 0;
+
+out:
+ free_candev(net);
+ priv->cpriv = NULL;
+
+ return ret;
+}
+
+void mcp25xxfd_can_remove(struct mcp25xxfd_priv *priv)
+{
+ if (priv->cpriv) {
+ unregister_candev(priv->cpriv->can.dev);
+ free_candev(priv->cpriv->can.dev);
+ priv->cpriv = NULL;
+ }
+}
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.h
index f54c716735fb..4b18b5bb3d45 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.h
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can.h
@@ -8,9 +8,44 @@
#ifndef __MCP25XXFD_CAN_H
#define __MCP25XXFD_CAN_H
+#include "mcp25xxfd_can_priv.h"
#include "mcp25xxfd_priv.h"
+#include "mcp25xxfd_regs.h"
+
+/* get the optimal controller target mode */
+static inline
+int mcp25xxfd_can_targetmode(struct mcp25xxfd_can_priv *cpriv)
+{
+ return (cpriv->can.dev->mtu == CAN_MTU) ?
+ MCP25XXFD_CAN_CON_MODE_CAN2_0 : MCP25XXFD_CAN_CON_MODE_MIXED;
+}
+
+static inline
+void mcp25xxfd_can_queue_frame(struct mcp25xxfd_can_priv *cpriv,
+ s32 fifo, u16 ts)
+{
+ int idx = cpriv->fifos.submit_queue_count;
+
+ cpriv->fifos.submit_queue[idx].fifo = fifo;
+ cpriv->fifos.submit_queue[idx].ts = ts;
+
+ cpriv->fifos.submit_queue_count++;
+}
+
+/* get the current controller mode */
+int mcp25xxfd_can_get_mode(struct mcp25xxfd_priv *priv, u32 *reg);
+
+/* switch controller mode */
+int mcp25xxfd_can_switch_mode_no_wait(struct mcp25xxfd_priv *priv,
+ u32 *reg, int mode);
+int mcp25xxfd_can_switch_mode(struct mcp25xxfd_priv *priv,
+ u32 *reg, int mode);
/* probe the can controller */
int mcp25xxfd_can_probe(struct mcp25xxfd_priv *priv);
+/* setup and the can controller net interface */
+int mcp25xxfd_can_setup(struct mcp25xxfd_priv *priv);
+void mcp25xxfd_can_remove(struct mcp25xxfd_priv *priv);
+
#endif /* __MCP25XXFD_CAN_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.c
new file mode 100644
index 000000000000..4bd776772d2d
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+/* here we define and configure the fifo layout */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "mcp25xxfd_can.h"
+#include "mcp25xxfd_can_fifo.h"
+#include "mcp25xxfd_can_priv.h"
+#include "mcp25xxfd_cmd.h"
+
+static int mcp25xxfd_can_fifo_get_address(struct mcp25xxfd_can_priv *cpriv)
+{
+ int fifo, ret;
+
+ /* we need to move out of config mode to force address computation */
+ ret = mcp25xxfd_can_switch_mode(cpriv->priv, &cpriv->regs.con,
+ MCP25XXFD_CAN_CON_MODE_INT_LOOPBACK);
+ if (ret)
+ return ret;
+
+ /* and get back into config mode */
+ ret = mcp25xxfd_can_switch_mode(cpriv->priv, &cpriv->regs.con,
+ MCP25XXFD_CAN_CON_MODE_CONFIG);
+ if (ret)
+ return ret;
+
+ /* read address and config back in */
+ for (fifo = 1; fifo < 32; fifo++) {
+ ret = mcp25xxfd_cmd_read(cpriv->priv->spi,
+ MCP25XXFD_CAN_FIFOUA(fifo),
+ &cpriv->fifos.info[fifo].offset);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcp25xxfd_can_fifo_setup_config(struct mcp25xxfd_can_priv *cpriv,
+ struct mcp25xxfd_fifo *desc,
+ u32 flags, u32 flags_last)
+{
+ u32 val;
+ int i, p, f, c, ret;
+
+ for (i = 0, f = desc->start, c = desc->count, p = 31;
+ c > 0; i++, f++, p--, c--) {
+ val = (c > 1) ? flags : flags_last;
+
+ /* write the config to the controller in one go */
+ ret = mcp25xxfd_cmd_write(cpriv->priv->spi,
+ MCP25XXFD_CAN_FIFOCON(f), val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcp25xxfd_can_fifo_setup_rx(struct mcp25xxfd_can_priv *cpriv)
+{
+ u32 rx_flags = MCP25XXFD_CAN_FIFOCON_FRESET | /* reset FIFO */
+ MCP25XXFD_CAN_FIFOCON_RXTSEN | /* RX timestamps */
+ MCP25XXFD_CAN_FIFOCON_TFERFFIE | /* FIFO Full */
+ MCP25XXFD_CAN_FIFOCON_TFHRFHIE | /* FIFO Half Full*/
+ MCP25XXFD_CAN_FIFOCON_TFNRFNIE | /* FIFO not empty */
+ (cpriv->fifos.payload_mode <<
+ MCP25XXFD_CAN_FIFOCON_PLSIZE_SHIFT) |
+ (0 << MCP25XXFD_CAN_FIFOCON_FSIZE_SHIFT); /* 1 FIFO deep */
+ /* enable overflow int on last fifo */
+ u32 rx_flags_last = rx_flags | MCP25XXFD_CAN_FIFOCON_RXOVIE;
+
+ return mcp25xxfd_can_fifo_setup_config(cpriv, &cpriv->fifos.rx,
+ rx_flags, rx_flags_last);
+}
+
+static int mcp25xxfd_can_fifo_setup_rxfilter(struct mcp25xxfd_can_priv *cpriv)
+{
+ u8 filter_con[32];
+ int c, f;
+
+ /* clear the filters and filter mappings for all filters */
+ memset(filter_con, 0, sizeof(filter_con));
+
+ /* and now set up the rx filters */
+ for (c = 0, f = cpriv->fifos.rx.start; c < cpriv->fifos.rx.count;
+ c++, f++) {
+ /* set up filter config - we can use the mask of filter 0 */
+ filter_con[c] = MCP25XXFD_CAN_FIFOCON_FLTEN(0) |
+ (f << MCP25XXFD_CAN_FILCON_SHIFT(0));
+ }
+
+ /* and set up filter control */
+ return mcp25xxfd_cmd_write_regs(cpriv->priv->spi,
+ MCP25XXFD_CAN_FLTCON(0),
+ (u32 *)filter_con, sizeof(filter_con));
+}
+
+static int mcp25xxfd_can_fifo_compute(struct mcp25xxfd_can_priv *cpriv)
+{
+ int rx_memory_available;
+
+ switch (cpriv->can.dev->mtu) {
+ case CAN_MTU:
+ /* MTU is 8 */
+ cpriv->fifos.payload_size = 8;
+ cpriv->fifos.payload_mode = MCP25XXFD_CAN_TXQCON_PLSIZE_8;
+
+ break;
+ case CANFD_MTU:
+ /* MTU is 64 */
+ cpriv->fifos.payload_size = 64;
+ cpriv->fifos.payload_mode = MCP25XXFD_CAN_TXQCON_PLSIZE_64;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* compute effective sizes */
+ cpriv->fifos.rx.size = sizeof(struct mcp25xxfd_can_obj_rx) +
+ cpriv->fifos.payload_size;
+
+ /* calculate evailable memory for RX_fifos */
+ rx_memory_available = MCP25XXFD_SRAM_SIZE;
+
+ /* calculate possible amount of RX fifos */
+ cpriv->fifos.rx.count = rx_memory_available / cpriv->fifos.rx.size;
+
+ /* now calculate effective number of rx-fifos. There are only 31 fifos
+ * available in total, so we need to limit ourselves
+ */
+ if (cpriv->fifos.rx.count > 31)
+ cpriv->fifos.rx.count = 31;
+
+ cpriv->fifos.rx.start = 1;
+
+ return 0;
+}
+
+static int mcp25xxfd_can_fifo_clear_regs(struct mcp25xxfd_can_priv *cpriv,
+ u32 start, u32 end)
+{
+ size_t len = end - start;
+ u8 *data;
+ int ret;
+
+ data = kzalloc(len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = mcp25xxfd_cmd_write_regs(cpriv->priv->spi,
+ start, (u32 *)data, len);
+
+ kfree(data);
+
+ return ret;
+}
+
+static int mcp25xxfd_can_fifo_clear(struct mcp25xxfd_can_priv *cpriv)
+{
+ int ret;
+
+ memset(&cpriv->fifos.info, 0, sizeof(cpriv->fifos.info));
+ memset(&cpriv->fifos.rx, 0, sizeof(cpriv->fifos.rx));
+
+ /* clear FIFO config */
+ ret = mcp25xxfd_can_fifo_clear_regs(cpriv, MCP25XXFD_CAN_FIFOCON(1),
+ MCP25XXFD_CAN_FIFOCON(32));
+ if (ret)
+ return ret;
+
+ /* clear the filter mask - match any frame with every filter */
+ return mcp25xxfd_can_fifo_clear_regs(cpriv, MCP25XXFD_CAN_FLTCON(0),
+ MCP25XXFD_CAN_FLTCON(32));
+}
+
+int mcp25xxfd_can_fifo_setup(struct mcp25xxfd_can_priv *cpriv)
+{
+ int ret;
+
+ /* clear fifo config */
+ ret = mcp25xxfd_can_fifo_clear(cpriv);
+ if (ret)
+ return ret;
+
+ ret = mcp25xxfd_can_fifo_compute(cpriv);
+ if (ret)
+ return ret;
+
+ cpriv->regs.tefcon = 0;
+ ret = mcp25xxfd_cmd_write(cpriv->priv->spi, MCP25XXFD_CAN_TEFCON,
+ cpriv->regs.tefcon);
+ if (ret)
+ return ret;
+
+ ret = mcp25xxfd_cmd_write(cpriv->priv->spi, MCP25XXFD_CAN_TXQCON, 0);
+ if (ret)
+ return ret;
+
+ ret = mcp25xxfd_can_fifo_setup_rx(cpriv);
+ if (ret)
+ return ret;
+
+ ret = mcp25xxfd_can_fifo_setup_rxfilter(cpriv);
+ if (ret)
+ return ret;
+
+ /* get fifo addresses */
+ ret = mcp25xxfd_can_fifo_get_address(cpriv);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+void mcp25xxfd_can_fifo_release(struct mcp25xxfd_can_priv *cpriv)
+{
+ mcp25xxfd_can_fifo_clear(cpriv);
+}
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.h
new file mode 100644
index 000000000000..ed2daa05220a
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_fifo.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+#ifndef __MCP25XXFD_CAN_FIFO_H
+#define __MCP25XXFD_CAN_FIFO_H
+
+#include "mcp25xxfd_can_priv.h"
+
+int mcp25xxfd_can_fifo_setup(struct mcp25xxfd_can_priv *cpriv);
+void mcp25xxfd_can_fifo_release(struct mcp25xxfd_can_priv *cpriv);
+
+#endif /* __MCP25XXFD_CAN_FIFO_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_id.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_id.h
new file mode 100644
index 000000000000..00a6c6639bd5
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_id.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+#ifndef __MCP25XXFD_CAN_IF_H
+#define __MCP25XXFD_CAN_IF_H
+
+#include <uapi/linux/can.h>
+
+#include "mcp25xxfd_can_id.h"
+#include "mcp25xxfd_regs.h"
+
+/* ideally these would be defined in uapi/linux/can.h */
+#define MCP25XXFD_CAN_EFF_SID_SHIFT (CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)
+#define MCP25XXFD_CAN_EFF_SID_BITS CAN_SFF_ID_BITS
+#define MCP25XXFD_CAN_EFF_SID_MASK \
+ GENMASK(MCP25XXFD_CAN_EFF_SID_SHIFT + MCP25XXFD_CAN_EFF_SID_BITS - 1, \
+ MCP25XXFD_CAN_EFF_SID_SHIFT)
+#define MCP25XXFD_CAN_EFF_EID_SHIFT 0
+#define MCP25XXFD_CAN_EFF_EID_BITS MCP25XXFD_CAN_EFF_SID_SHIFT
+#define MCP25XXFD_CAN_EFF_EID_MASK \
+ GENMASK(MCP25XXFD_CAN_EFF_EID_SHIFT + MCP25XXFD_CAN_EFF_EID_BITS - 1, \
+ MCP25XXFD_CAN_EFF_EID_SHIFT)
+
+static inline
+void mcp25xxfd_can_id_from_mcp25xxfd(u32 mcp_id, u32 mcp_flags, u32 *can_id)
+{
+ u32 sid = (mcp_id & MCP25XXFD_CAN_OBJ_ID_SID_MASK) >>
+ MCP25XXFD_CAN_OBJ_ID_SID_SHIFT;
+ u32 eid = (mcp_id & MCP25XXFD_CAN_OBJ_ID_EID_MASK) >>
+ MCP25XXFD_CAN_OBJ_ID_EID_SHIFT;
+
+ /* select normal or extended ids */
+ if (mcp_flags & MCP25XXFD_CAN_OBJ_FLAGS_IDE) {
+ *can_id = (eid << MCP25XXFD_CAN_EFF_EID_SHIFT) |
+ (sid << MCP25XXFD_CAN_EFF_SID_SHIFT) |
+ CAN_EFF_FLAG;
+ } else {
+ *can_id = sid << MCP25XXFD_CAN_EFF_EID_SHIFT;
+ }
+ /* handle rtr */
+ *can_id |= (mcp_flags & MCP25XXFD_CAN_OBJ_FLAGS_RTR) ? CAN_RTR_FLAG : 0;
+}
+
+static inline
+void mcp25xxfd_can_id_to_mcp25xxfd(u32 can_id, u32 *id, u32 *flags)
+{
+ /* depending on can_id flag compute extended or standard ids */
+ if (can_id & CAN_EFF_FLAG) {
+ int sid = (can_id & MCP25XXFD_CAN_EFF_SID_MASK) >>
+ MCP25XXFD_CAN_EFF_SID_SHIFT;
+ int eid = (can_id & MCP25XXFD_CAN_EFF_EID_MASK) >>
+ MCP25XXFD_CAN_EFF_EID_SHIFT;
+ *id = (eid << MCP25XXFD_CAN_OBJ_ID_EID_SHIFT) |
+ (sid << MCP25XXFD_CAN_OBJ_ID_SID_SHIFT);
+ *flags = MCP25XXFD_CAN_OBJ_FLAGS_IDE;
+ } else {
+ *id = can_id & CAN_SFF_MASK;
+ *flags = 0;
+ }
+
+ /* Handle RTR */
+ *flags |= (can_id & CAN_RTR_FLAG) ? MCP25XXFD_CAN_OBJ_FLAGS_RTR : 0;
+}
+
+#endif /* __MCP25XXFD_CAN_IF_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.c
new file mode 100644
index 000000000000..83656b2604df
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+
+#include "mcp25xxfd_regs.h"
+#include "mcp25xxfd_can.h"
+#include "mcp25xxfd_can_int.h"
+#include "mcp25xxfd_can_priv.h"
+#include "mcp25xxfd_can_rx.h"
+#include "mcp25xxfd_cmd.h"
+#include "mcp25xxfd_ecc.h"
+
+#define MCP25XXFD_RESCHEDULE_TIMES 4
+
+static void mcp25xxfd_can_int_send_error_skb(struct mcp25xxfd_can_priv *cpriv)
+{
+ struct net_device *net = cpriv->can.dev;
+ struct sk_buff *skb;
+ struct can_frame *frame;
+
+ /* allocate error frame */
+ skb = alloc_can_err_skb(net, &frame);
+ if (!skb) {
+ netdev_err(net, "cannot allocate error skb\n");
+ return;
+ }
+
+ /* setup can error frame data */
+ frame->can_id |= cpriv->error_frame.id;
+ memcpy(frame->data, cpriv->error_frame.data, sizeof(frame->data));
+
+ /* and submit it */
+ netif_receive_skb(skb);
+}
+
+static int mcp25xxfd_can_int_compare_obj_ts(const void *a, const void *b)
+{
+ s32 ats = ((struct mcp25xxfd_obj_ts *)a)->ts;
+ s32 bts = ((struct mcp25xxfd_obj_ts *)b)->ts;
+
+ if (ats < bts)
+ return -1;
+ if (ats > bts)
+ return 1;
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_submit_frames(struct mcp25xxfd_can_priv *cpriv)
+{
+ struct mcp25xxfd_obj_ts *queue = cpriv->fifos.submit_queue;
+ int count = cpriv->fifos.submit_queue_count;
+ int i, fifo;
+ int ret;
+
+ /* skip processing if the queue count is 0 */
+ if (count == 0)
+ goto out;
+
+ /* sort the fifos (rx and tx - actually TEF) by receive timestamp */
+ sort(queue, count, sizeof(*queue),
+ mcp25xxfd_can_int_compare_obj_ts, NULL);
+
+ /* now submit the fifos */
+ for (i = 0; i < count; i++) {
+ fifo = queue[i].fifo;
+ ret = mcp25xxfd_can_rx_submit_frame(cpriv, fifo);
+ if (ret)
+ return ret;
+ }
+
+ /* if we have received or transmitted something and the IVMIE is
+ * disabled, then enable it. This is mostly to avoid unnecessary
+ * interrupts when CAN bus is disconnected.
+ */
+ if (!(cpriv->status.intf | MCP25XXFD_CAN_INT_IVMIE)) {
+ cpriv->status.intf |= MCP25XXFD_CAN_INT_IVMIE;
+ ret = mcp25xxfd_cmd_write_mask(cpriv->priv->spi,
+ MCP25XXFD_CAN_INT,
+ cpriv->status.intf,
+ MCP25XXFD_CAN_INT_IVMIE);
+ if (ret)
+ return ret;
+ }
+
+out:
+ return 0;
+}
+
+static int mcp25xxfd_can_int_clear_int_flags(struct mcp25xxfd_can_priv *cpriv)
+{
+ u32 clearable_irq_active = cpriv->status.intf &
+ MCP25XXFD_CAN_INT_IF_CLEAR_MASK;
+ u32 clear_irq = cpriv->status.intf & (~MCP25XXFD_CAN_INT_IF_CLEAR_MASK);
+
+ if (!clearable_irq_active)
+ return 0;
+
+ return mcp25xxfd_cmd_write_mask(cpriv->priv->spi, MCP25XXFD_CAN_INT,
+ clear_irq, clearable_irq_active);
+}
+
+static
+int mcp25xxfd_can_int_handle_serrif_txmab(struct mcp25xxfd_can_priv *cpriv)
+{
+ int mode = mcp25xxfd_can_targetmode(cpriv);
+
+ cpriv->can.dev->stats.tx_fifo_errors++;
+ cpriv->can.dev->stats.tx_errors++;
+
+ /* data7 contains custom mcp25xxfd error flags */
+ cpriv->error_frame.data[7] |= MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_SERR_TX;
+
+ /* and switch back into the correct mode */
+ return mcp25xxfd_can_switch_mode_no_wait(cpriv->priv,
+ &cpriv->regs.con, mode);
+}
+
+static
+int mcp25xxfd_can_int_handle_serrif_rxmab(struct mcp25xxfd_can_priv *cpriv)
+{
+ cpriv->can.dev->stats.rx_dropped++;
+ cpriv->can.dev->stats.rx_errors++;
+
+ /* data7 contains custom mcp25xxfd error flags */
+ cpriv->error_frame.data[7] |= MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_SERR_RX;
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_handle_serrif(struct mcp25xxfd_can_priv *cpriv)
+{
+ if (!(cpriv->status.intf & MCP25XXFD_CAN_INT_SERRIF))
+ return 0;
+
+ /* Errors here are:
+ * * Bus Bandwidth Error: when a RX Message Assembly Buffer
+ * is still full when the next message has already arrived
+ * the recived message shall be ignored
+ * * TX MAB Underflow: when a TX Message is invalid
+ * due to ECC errors or TXMAB underflow
+ * in this situatioon the system will transition to
+ * Restricted or Listen Only mode
+ */
+
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_UNSPEC;
+
+ /* mode change + invalid message would indicate TX MAB Underflow */
+ if ((cpriv->status.intf & MCP25XXFD_CAN_INT_MODIF) &&
+ (cpriv->status.intf & MCP25XXFD_CAN_INT_IVMIF)) {
+ return mcp25xxfd_can_int_handle_serrif_txmab(cpriv);
+ }
+
+ /* for RX there is only the RXIF an indicator - surprizingly RX-MAB
+ * does not change mode or anything
+ */
+ if (cpriv->status.intf & MCP25XXFD_CAN_INT_RXIF)
+ return mcp25xxfd_can_int_handle_serrif_rxmab(cpriv);
+
+ dev_warn_ratelimited(&cpriv->priv->spi->dev,
+ "unidentified system interrupt - intf = %08x\n",
+ cpriv->status.intf);
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_handle_modif(struct mcp25xxfd_can_priv *cpriv)
+{
+ struct spi_device *spi = cpriv->priv->spi;
+ int mode;
+ int ret;
+
+ /* Note that this irq does not get triggered in all situations
+ * for example SERRIF will move to RESTICTED or LISTENONLY but MODIF
+ * will not be raised!
+ */
+ if (!(cpriv->status.intf & MCP25XXFD_CAN_INT_MODIF))
+ return 0;
+
+ /* get the current mode */
+ ret = mcp25xxfd_can_get_mode(cpriv->priv, &mode);
+ if (ret)
+ return ret;
+
+ mode = ret;
+
+ /* switches to the same mode as before are ignored
+ * - this typically happens if the driver is shortly
+ * switching to a different mode and then returning to the
+ * original mode
+ */
+ if (mode == cpriv->mode)
+ return 0;
+
+ /* if we are restricted, then return to "normal" mode */
+ if (mode == MCP25XXFD_CAN_CON_MODE_RESTRICTED) {
+ cpriv->mode = mode;
+ mode = mcp25xxfd_can_targetmode(cpriv);
+ return mcp25xxfd_can_switch_mode_no_wait(cpriv->priv,
+ &cpriv->regs.con,
+ mode);
+ }
+
+ /* the controller itself will transition to sleep, so we ignore it */
+ if (mode == MCP25XXFD_CAN_CON_MODE_SLEEP) {
+ cpriv->mode = mode;
+ return 0;
+ }
+
+ dev_warn(&spi->dev,
+ "Controller unexpectedly switched from mode %u to %u\n",
+ cpriv->mode, mode);
+
+ /* assign the mode as current */
+ cpriv->mode = mode;
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_handle_eccif(struct mcp25xxfd_can_priv *cpriv)
+{
+ if (!(cpriv->status.intf & MCP25XXFD_CAN_INT_ECCIF))
+ return 0;
+
+ /* and prepare ERROR FRAME */
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_UNSPEC;
+ /* data7 contains custom mcp25xxfd error flags */
+ cpriv->error_frame.data[7] |= MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_ECC;
+
+ /* delegate to interrupt cleaning */
+ return mcp25xxfd_ecc_clear_int(cpriv->priv);
+}
+
+static void mcp25xxfd_can_int_handle_ivmif_tx(struct mcp25xxfd_can_priv *cpriv,
+ u32 *mask)
+{
+ /* check if it is really a known tx error */
+ if ((cpriv->bus.bdiag[1] &
+ (MCP25XXFD_CAN_BDIAG1_DBIT1ERR |
+ MCP25XXFD_CAN_BDIAG1_DBIT0ERR |
+ MCP25XXFD_CAN_BDIAG1_NACKERR |
+ MCP25XXFD_CAN_BDIAG1_NBIT1ERR |
+ MCP25XXFD_CAN_BDIAG1_NBIT0ERR
+ )) == 0)
+ return;
+
+ /* mark it as a protocol error */
+ cpriv->error_frame.id |= CAN_ERR_PROT;
+
+ /* and update statistics */
+ cpriv->can.dev->stats.tx_errors++;
+
+ /* and handle all the known cases */
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NACKERR) {
+ /* TX-Frame not acknowledged - connected to CAN-bus? */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NACKERR;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_TX;
+ cpriv->can.dev->stats.tx_aborted_errors++;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NBIT1ERR) {
+ /* TX-Frame CAN-BUS Level is unexpectedly dominant */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NBIT1ERR;
+ cpriv->can.dev->stats.tx_carrier_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_BIT1;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NBIT0ERR) {
+ /* TX-Frame CAN-BUS Level is unexpectedly recessive */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NBIT0ERR;
+ cpriv->can.dev->stats.tx_carrier_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_BIT0;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_DBIT1ERR) {
+ /* TX-Frame CAN-BUS Level is unexpectedly dominant
+ * during data phase
+ */
+ *mask |= MCP25XXFD_CAN_BDIAG1_DBIT1ERR;
+ cpriv->can.dev->stats.tx_carrier_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_BIT1;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_DBIT0ERR) {
+ /* TX-Frame CAN-BUS Level is unexpectedly recessive
+ * during data phase
+ */
+ *mask |= MCP25XXFD_CAN_BDIAG1_DBIT0ERR;
+ cpriv->can.dev->stats.tx_carrier_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_BIT0;
+ }
+}
+
+static void mcp25xxfd_can_int_handle_ivmif_rx(struct mcp25xxfd_can_priv *cpriv,
+ u32 *mask)
+{
+ /* check if it is really a known tx error */
+ if ((cpriv->bus.bdiag[1] &
+ (MCP25XXFD_CAN_BDIAG1_DCRCERR |
+ MCP25XXFD_CAN_BDIAG1_DSTUFERR |
+ MCP25XXFD_CAN_BDIAG1_DFORMERR |
+ MCP25XXFD_CAN_BDIAG1_NCRCERR |
+ MCP25XXFD_CAN_BDIAG1_NSTUFERR |
+ MCP25XXFD_CAN_BDIAG1_NFORMERR
+ )) == 0)
+ return;
+
+ /* mark it as a protocol error */
+ cpriv->error_frame.id |= CAN_ERR_PROT;
+
+ /* and update statistics */
+ cpriv->can.dev->stats.rx_errors++;
+
+ /* handle the cases */
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_DCRCERR) {
+ /* RX-Frame with bad CRC during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_DCRCERR;
+ cpriv->can.dev->stats.rx_crc_errors++;
+ cpriv->error_frame.data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_DSTUFERR) {
+ /* RX-Frame with bad stuffing during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_DSTUFERR;
+ cpriv->can.dev->stats.rx_frame_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_STUFF;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_DFORMERR) {
+ /* RX-Frame with bad format during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_DFORMERR;
+ cpriv->can.dev->stats.rx_frame_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_FORM;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NCRCERR) {
+ /* RX-Frame with bad CRC during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NCRCERR;
+ cpriv->can.dev->stats.rx_crc_errors++;
+ cpriv->error_frame.data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NSTUFERR) {
+ /* RX-Frame with bad stuffing during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NSTUFERR;
+ cpriv->can.dev->stats.rx_frame_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_STUFF;
+ }
+
+ if (cpriv->bus.bdiag[1] & MCP25XXFD_CAN_BDIAG1_NFORMERR) {
+ /* RX-Frame with bad format during data phase */
+ *mask |= MCP25XXFD_CAN_BDIAG1_NFORMERR;
+ cpriv->can.dev->stats.rx_frame_errors++;
+ cpriv->error_frame.data[2] |= CAN_ERR_PROT_FORM;
+ }
+}
+
+static int mcp25xxfd_can_int_handle_ivmif(struct mcp25xxfd_can_priv *cpriv)
+{
+ struct spi_device *spi = cpriv->priv->spi;
+ u32 mask, bdiag1;
+ int ret;
+
+ if (!(cpriv->status.intf & MCP25XXFD_CAN_INT_IVMIF))
+ return 0;
+
+ if (cpriv->status.intf & MCP25XXFD_CAN_INT_SERRIF)
+ return 0;
+
+ ret = mcp25xxfd_cmd_read_regs(spi, MCP25XXFD_CAN_BDIAG0,
+ cpriv->bus.bdiag,
+ sizeof(cpriv->bus.bdiag));
+ if (ret)
+ return ret;
+
+ mask = 0;
+
+ /* check rx and tx errors */
+ mcp25xxfd_can_int_handle_ivmif_tx(cpriv, &mask);
+ mcp25xxfd_can_int_handle_ivmif_rx(cpriv, &mask);
+
+ /* clear flags if we have bits masked */
+ if (!mask) {
+ dev_warn_once(&spi->dev,
+ "found IVMIF situation not supported by driver - bdiag = [0x%08x, 0x%08x]",
+ cpriv->bus.bdiag[0], cpriv->bus.bdiag[1]);
+ return -EINVAL;
+ }
+
+ bdiag1 = cpriv->bus.bdiag[1] & (~mask);
+ ret = mcp25xxfd_cmd_write_mask(spi, MCP25XXFD_CAN_BDIAG1, bdiag1, mask);
+ if (ret)
+ return ret;
+
+ /* clear the interrupt flag until we have received or transmited */
+ cpriv->status.intf &= ~(MCP25XXFD_CAN_INT_IVMIE);
+ return mcp25xxfd_cmd_write_mask(spi, MCP25XXFD_CAN_INT,
+ cpriv->status.intf,
+ MCP25XXFD_CAN_INT_IVMIE);
+}
+
+static int mcp25xxfd_can_int_handle_cerrif(struct mcp25xxfd_can_priv *cpriv)
+{
+ if (!(cpriv->status.intf & MCP25XXFD_CAN_INT_CERRIF))
+ return 0;
+
+ /* this interrupt exists primarilly to counter possible bus off
+ * situations. More detailed information can be found and controlled in
+ * the TREC register
+ */
+
+ netdev_warn(cpriv->can.dev, "CAN Bus error experienced");
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_error_counters(struct mcp25xxfd_can_priv *cpriv)
+{
+ if (cpriv->status.trec & MCP25XXFD_CAN_TREC_TXWARN) {
+ cpriv->bus.new_state = CAN_STATE_ERROR_WARNING;
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_TX_WARNING;
+ }
+
+ if (cpriv->status.trec & MCP25XXFD_CAN_TREC_RXWARN) {
+ cpriv->bus.new_state = CAN_STATE_ERROR_WARNING;
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_RX_WARNING;
+ }
+
+ if (cpriv->status.trec & MCP25XXFD_CAN_TREC_TXBP) {
+ cpriv->bus.new_state = CAN_STATE_ERROR_PASSIVE;
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+ }
+
+ if (cpriv->status.trec & MCP25XXFD_CAN_TREC_RXBP) {
+ cpriv->bus.new_state = CAN_STATE_ERROR_PASSIVE;
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+ }
+
+ if (cpriv->status.trec & MCP25XXFD_CAN_TREC_TXBO) {
+ cpriv->bus.new_state = CAN_STATE_BUS_OFF;
+ cpriv->error_frame.id |= CAN_ERR_BUSOFF;
+ }
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_error_handling(struct mcp25xxfd_can_priv *cpriv)
+{
+ /* based on the last state state check the new state */
+ switch (cpriv->can.state) {
+ case CAN_STATE_ERROR_ACTIVE:
+ if (cpriv->bus.new_state >= CAN_STATE_ERROR_WARNING &&
+ cpriv->bus.new_state <= CAN_STATE_BUS_OFF)
+ cpriv->can.can_stats.error_warning++;
+ fallthrough;
+ case CAN_STATE_ERROR_WARNING:
+ if (cpriv->bus.new_state >= CAN_STATE_ERROR_PASSIVE &&
+ cpriv->bus.new_state <= CAN_STATE_BUS_OFF)
+ cpriv->can.can_stats.error_passive++;
+ break;
+ default:
+ break;
+ }
+
+ cpriv->can.state = cpriv->bus.new_state;
+
+ /* send error packet */
+ if (cpriv->error_frame.id)
+ mcp25xxfd_can_int_send_error_skb(cpriv);
+
+ /* handle BUS OFF */
+ if (cpriv->can.state == CAN_STATE_BUS_OFF) {
+ if (cpriv->can.restart_ms == 0) {
+ cpriv->can.can_stats.bus_off++;
+ can_bus_off(cpriv->can.dev);
+ }
+ }
+
+ return 0;
+}
+
+static int mcp25xxfd_can_int_handle_status(struct mcp25xxfd_can_priv *cpriv)
+{
+ int ret;
+
+ ret = mcp25xxfd_can_int_clear_int_flags(cpriv);
+ if (ret)
+ return ret;
+
+ /* set up new state and error frame for this loop */
+ cpriv->bus.new_state = cpriv->bus.state;
+ memset(&cpriv->error_frame, 0, sizeof(cpriv->error_frame));
+
+ /* setup the process queue by clearing the counter */
+ cpriv->fifos.submit_queue_count = 0;
+
+ /* system error interrupt needs to get handled first
+ * to get us out of restricted mode
+ */
+ ret = mcp25xxfd_can_int_handle_serrif(cpriv);
+ if (ret)
+ return ret;
+
+ /* mode change interrupt */
+ ret = mcp25xxfd_can_int_handle_modif(cpriv);
+ if (ret)
+ return ret;
+
+ /* handle the rx */
+ ret = mcp25xxfd_can_rx_handle_int_rxif(cpriv);
+ if (ret)
+ return ret;
+
+ /* handle error interrupt flags */
+ ret = mcp25xxfd_can_rx_handle_int_rxovif(cpriv);
+ if (ret)
+ return ret;
+
+ /* sram ECC error interrupt */
+ ret = mcp25xxfd_can_int_handle_eccif(cpriv);
+ if (ret)
+ return ret;
+
+ /* message format interrupt */
+ ret = mcp25xxfd_can_int_handle_ivmif(cpriv);
+ if (ret)
+ return ret;
+
+ /* handle bus errors in more detail */
+ ret = mcp25xxfd_can_int_handle_cerrif(cpriv);
+ if (ret)
+ return ret;
+
+ /* error counter handling */
+ ret = mcp25xxfd_can_int_error_counters(cpriv);
+ if (ret)
+ return ret;
+
+ /* error counter handling */
+ ret = mcp25xxfd_can_int_error_handling(cpriv);
+ if (ret)
+ return ret;
+
+ /* and submit can frames to network stack */
+ ret = mcp25xxfd_can_int_submit_frames(cpriv);
+
+ return ret;
+}
+
+irqreturn_t mcp25xxfd_can_int(int irq, void *dev_id)
+{
+ struct mcp25xxfd_can_priv *cpriv = dev_id;
+ int loops, ret;
+
+ /* loop forever unless we need to exit */
+ for (loops = 0; true; loops++) {
+ /* read interrupt status flags in bulk */
+ ret = mcp25xxfd_cmd_read_regs(cpriv->priv->spi,
+ MCP25XXFD_CAN_INT,
+ &cpriv->status.intf,
+ sizeof(cpriv->status));
+ if (ret)
+ return ret;
+
+ /* only act if the IE mask configured has active IF bits
+ * otherwise the Interrupt line should be deasserted already
+ * so we can exit the loop
+ */
+ if (((cpriv->status.intf >> MCP25XXFD_CAN_INT_IE_SHIFT) &
+ cpriv->status.intf) == 0)
+ break;
+
+ /* handle the status */
+ ret = mcp25xxfd_can_int_handle_status(cpriv);
+ if (ret)
+ return ret;
+
+ /* allow voluntarily rescheduling every so often to avoid
+ * long CS lows at the end of a transfer on low power CPUs
+ * avoiding SERR happening
+ */
+ if (loops % MCP25XXFD_RESCHEDULE_TIMES == 0)
+ cond_resched();
+ }
+
+ return IRQ_HANDLED;
+}
+
+int mcp25xxfd_can_int_clear(struct mcp25xxfd_priv *priv)
+{
+ return mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_CAN_INT, 0,
+ MCP25XXFD_CAN_INT_IF_MASK);
+}
+
+int mcp25xxfd_can_int_enable(struct mcp25xxfd_priv *priv, bool enable)
+{
+ struct mcp25xxfd_can_priv *cpriv = priv->cpriv;
+ const u32 mask = MCP25XXFD_CAN_INT_TEFIE |
+ MCP25XXFD_CAN_INT_RXIE |
+ MCP25XXFD_CAN_INT_MODIE |
+ MCP25XXFD_CAN_INT_SERRIE |
+ MCP25XXFD_CAN_INT_IVMIE |
+ MCP25XXFD_CAN_INT_CERRIE |
+ MCP25XXFD_CAN_INT_RXOVIE |
+ MCP25XXFD_CAN_INT_ECCIE;
+ u32 value = cpriv ? cpriv->status.intf : 0;
+ int ret;
+
+ value &= ~(MCP25XXFD_CAN_INT_IE_MASK);
+ if (enable)
+ value |= mask;
+
+ ret = mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_CAN_INT,
+ value, mask);
+ if (ret)
+ return ret;
+
+ if (!cpriv)
+ return 0;
+
+ cpriv->status.intf = value;
+ if (cpriv->irq.allocated) {
+ if (enable && !cpriv->irq.enabled)
+ enable_irq(cpriv->priv->spi->irq);
+ if (!enable && cpriv->irq.enabled)
+ disable_irq(cpriv->priv->spi->irq);
+ cpriv->irq.enabled = enable;
+ } else {
+ cpriv->irq.enabled = false;
+ }
+
+ return 0;
+}
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.h
new file mode 100644
index 000000000000..aa67a5da9271
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_int.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+#ifndef __MCP25XXFD_CAN_INT_H
+#define __MCP25XXFD_CAN_INT_H
+
+#include "mcp25xxfd_priv.h"
+#include <linux/irqreturn.h>
+
+int mcp25xxfd_can_int_clear(struct mcp25xxfd_priv *priv);
+int mcp25xxfd_can_int_enable(struct mcp25xxfd_priv *priv, bool enable);
+
+irqreturn_t mcp25xxfd_can_int(int irq, void *dev_id);
+
+#endif /* __MCP25XXFD_CAN_INT_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_priv.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_priv.h
new file mode 100644
index 000000000000..e043b262a868
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_priv.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+#ifndef __MCP25XXFD_CAN_PRIV_H
+#define __MCP25XXFD_CAN_PRIV_H
+
+#include <linux/can/dev.h>
+#include <linux/dcache.h>
+
+#include "mcp25xxfd_priv.h"
+
+#define TX_ECHO_SKB_MAX 32
+
+/* information on each fifo type */
+struct mcp25xxfd_fifo {
+ u32 count;
+ u32 start;
+ u32 size;
+};
+
+/* used for sorting incoming messages */
+struct mcp25xxfd_obj_ts {
+ s32 ts; /* using signed to handle rollover correctly when sorting */
+ u16 fifo;
+};
+
+/* general info on each fifo */
+struct mcp25xxfd_fifo_info {
+ u32 offset;
+ u32 priority;
+};
+
+struct mcp25xxfd_can_priv {
+ /* can_priv has to be the first one to be usable with alloc_candev
+ * which expects struct can_priv to be right at the start of the
+ * priv structure
+ */
+ struct can_priv can;
+ struct mcp25xxfd_priv *priv;
+ struct regulator *transceiver;
+
+ /* the can mode currently active */
+ int mode;
+
+ /* interrupt state */
+ struct {
+ int enabled;
+ int allocated;
+ } irq;
+
+ /* can config registers */
+ struct {
+ u32 con;
+ u32 tdc;
+ u32 tscon;
+ u32 tefcon;
+ u32 nbtcfg;
+ u32 dbtcfg;
+ } regs;
+
+ /* can status registers (mostly) - read in one go
+ * bdiag0 and bdiag1 are optional, but when
+ * berr counters are requested on a regular basis
+ * during high CAN-bus load this would trigger the fact
+ * that spi_sync would get queued for execution in the
+ * spi thread and the spi handler would not get
+ * called inline in the interrupt thread without any
+ * context switches or wakeups...
+ */
+ struct {
+ u32 intf;
+ /* ASSERT(CAN_INT + 4 == CAN_RXIF) */
+ u32 rxif;
+ /* ASSERT(CAN_RXIF + 4 == CAN_TXIF) */
+ u32 txif;
+ /* ASSERT(CAN_TXIF + 4 == CAN_RXOVIF) */
+ u32 rxovif;
+ /* ASSERT(CAN_RXOVIF + 4 == CAN_TXATIF) */
+ u32 txatif;
+ /* ASSERT(CAN_TXATIF + 4 == CAN_TXREQ) */
+ u32 txreq;
+ /* ASSERT(CAN_TXREQ + 4 == CAN_TREC) */
+ u32 trec;
+ } status;
+
+ /* information of fifo setup */
+ struct {
+ /* define payload size and mode */
+ u32 payload_size;
+ u32 payload_mode;
+
+ /* infos on fifo layout */
+
+ /* info on each fifo */
+ struct mcp25xxfd_fifo_info info[32];
+
+ /* extra info on rx fifo groups */
+ struct mcp25xxfd_fifo rx;
+
+ /* queue of can frames that need to get submitted
+ * to the network stack during an interrupt loop in one go
+ * (this gets sorted by timestamp before submission
+ * and contains both rx frames as well tx frames that have
+ * gone over the CAN bus successfully
+ */
+ struct mcp25xxfd_obj_ts submit_queue[32];
+ int submit_queue_count;
+ } fifos;
+
+ /* bus state */
+ struct {
+ u32 state;
+ u32 new_state;
+ u32 bdiag[2];
+ } bus;
+
+ /* can error messages */
+ struct {
+ u32 id;
+ u8 data[8];
+ } error_frame;
+
+ /* a copy of mcp25xxfd-sram in ram */
+ u8 sram[MCP25XXFD_SRAM_SIZE];
+};
+
+#endif /* __MCP25XXFD_CAN_PRIV_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.c
new file mode 100644
index 000000000000..5e3f706e7a3f
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ *
+ * Based on Microchip MCP251x CAN controller driver written by
+ * David Vrabel, Copyright 2006 Arcom Control Systems Ltd.
+ */
+
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+#include "mcp25xxfd_cmd.h"
+#include "mcp25xxfd_can.h"
+#include "mcp25xxfd_can_id.h"
+#include "mcp25xxfd_can_priv.h"
+#include "mcp25xxfd_can_rx.h"
+
+static struct sk_buff *
+mcp25xxfd_can_rx_submit_normal_frame(struct mcp25xxfd_can_priv *cpriv,
+ u32 id, u32 dlc, u8 **data)
+{
+ struct can_frame *frame;
+ struct sk_buff *skb;
+
+ /* allocate frame */
+ skb = alloc_can_skb(cpriv->can.dev, &frame);
+ if (!skb)
+ return NULL;
+
+ /* set id, dlc and flags */
+ frame->can_id = id;
+ frame->can_dlc = dlc;
+
+ /* and set the pointer to data */
+ *data = frame->data;
+
+ return skb;
+}
+
+/* it is almost identical except for the type of the frame... */
+static struct sk_buff *
+mcp25xxfd_can_rx_submit_fd_frame(struct mcp25xxfd_can_priv *cpriv,
+ u32 id, u32 flags, u32 len, u8 **data)
+{
+ struct canfd_frame *frame;
+ struct sk_buff *skb;
+
+ /* allocate frame */
+ skb = alloc_canfd_skb(cpriv->can.dev, &frame);
+ if (!skb)
+ return NULL;
+
+ /* set id, dlc and flags */
+ frame->can_id = id;
+ frame->len = len;
+ frame->flags |= flags;
+
+ /* and set the pointer to data */
+ *data = frame->data;
+
+ return skb;
+}
+
+int mcp25xxfd_can_rx_submit_frame(struct mcp25xxfd_can_priv *cpriv, int fifo)
+{
+ struct net_device *net = cpriv->can.dev;
+ int addr = cpriv->fifos.info[fifo].offset;
+ struct mcp25xxfd_can_obj_rx *rx =
+ (struct mcp25xxfd_can_obj_rx *)(cpriv->sram + addr);
+ u8 *data = NULL;
+ struct sk_buff *skb;
+ u32 id, dlc, len, flags;
+
+ /* compute the can_id */
+ mcp25xxfd_can_id_from_mcp25xxfd(rx->id, rx->flags, &id);
+
+ /* and dlc */
+ dlc = (rx->flags & MCP25XXFD_CAN_OBJ_FLAGS_DLC_MASK) >>
+ MCP25XXFD_CAN_OBJ_FLAGS_DLC_SHIFT;
+ len = can_dlc2len(dlc);
+
+ /* update stats */
+ net->stats.rx_packets++;
+ net->stats.rx_bytes += len;
+
+ /* allocate the skb buffer */
+ if (rx->flags & MCP25XXFD_CAN_OBJ_FLAGS_FDF) {
+ flags = 0;
+ flags |= (rx->flags & MCP25XXFD_CAN_OBJ_FLAGS_BRS) ?
+ CANFD_BRS : 0;
+ flags |= (rx->flags & MCP25XXFD_CAN_OBJ_FLAGS_ESI) ?
+ CANFD_ESI : 0;
+ skb = mcp25xxfd_can_rx_submit_fd_frame(cpriv, id, flags,
+ len, &data);
+ } else {
+ skb = mcp25xxfd_can_rx_submit_normal_frame(cpriv, id,
+ len, &data);
+ }
+ if (!skb) {
+ netdev_err(net, "cannot allocate RX skb\n");
+ net->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+
+ /* copy the payload data */
+ memcpy(data, rx->data, len);
+
+ /* and submit the frame */
+ netif_rx_ni(skb);
+
+ return 0;
+}
+
+static int mcp25xxfd_can_rx_read_frame(struct mcp25xxfd_can_priv *cpriv,
+ int fifo, int prefetch_bytes)
+{
+ struct spi_device *spi = cpriv->priv->spi;
+ struct net_device *net = cpriv->can.dev;
+ int addr = cpriv->fifos.info[fifo].offset;
+ struct mcp25xxfd_can_obj_rx *rx =
+ (struct mcp25xxfd_can_obj_rx *)(cpriv->sram + addr);
+ int dlc;
+ int len, ret;
+
+ /* we read the header plus prefetch_bytes */
+ ret = mcp25xxfd_cmd_read_multi(spi, MCP25XXFD_SRAM_ADDR(addr),
+ rx, sizeof(*rx) + prefetch_bytes);
+ if (ret)
+ return ret;
+
+ /* transpose the headers to CPU format*/
+ rx->id = le32_to_cpu(rx->id);
+ rx->flags = le32_to_cpu(rx->flags);
+ rx->ts = le32_to_cpu(rx->ts);
+
+ /* compute len */
+ dlc = (rx->flags & MCP25XXFD_CAN_OBJ_FLAGS_DLC_MASK) >>
+ MCP25XXFD_CAN_OBJ_FLAGS_DLC_SHIFT;
+ len = can_dlc2len(min_t(int, dlc, (net->mtu == CANFD_MTU) ? 15 : 8));
+
+ /* read the remaining data for canfd frames */
+ if (len > prefetch_bytes) {
+ /* here the extra portion reading data after prefetch */
+ ret = mcp25xxfd_cmd_read_multi(spi,
+ MCP25XXFD_SRAM_ADDR(addr) +
+ sizeof(*rx) + prefetch_bytes,
+ &rx->data[prefetch_bytes],
+ len - prefetch_bytes);
+ if (ret)
+ return ret;
+ }
+
+ /* clear the rest of the buffer - just to be safe */
+ memset(rx->data + len, 0, ((net->mtu == CANFD_MTU) ? 64 : 8) - len);
+
+ /* add the fifo to the process queues */
+ mcp25xxfd_can_queue_frame(cpriv, fifo, rx->ts);
+
+ /* and clear the interrupt flag for that fifo */
+ return mcp25xxfd_cmd_write_mask(spi, MCP25XXFD_CAN_FIFOCON(fifo),
+ MCP25XXFD_CAN_FIFOCON_FRESET,
+ MCP25XXFD_CAN_FIFOCON_FRESET);
+}
+
+static int mcp25xxfd_can_rx_read_frames(struct mcp25xxfd_can_priv *cpriv)
+{
+ int i, f, prefetch;
+ int ret;
+
+ prefetch = 8;
+ /* TODO: Optimize this */
+ for (i = 0, f = cpriv->fifos.rx.start; i < cpriv->fifos.rx.count;
+ i++, f++) {
+ if (cpriv->status.rxif & BIT(f)) {
+ /* read the frame */
+ ret = mcp25xxfd_can_rx_read_frame(cpriv, f, prefetch);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int mcp25xxfd_can_rx_handle_int_rxif(struct mcp25xxfd_can_priv *cpriv)
+{
+ if (!cpriv->status.rxif)
+ return 0;
+
+ /* read all the fifos */
+ return mcp25xxfd_can_rx_read_frames(cpriv);
+}
+
+int mcp25xxfd_can_rx_handle_int_rxovif(struct mcp25xxfd_can_priv *cpriv)
+{
+ u32 mask = MCP25XXFD_CAN_FIFOSTA_RXOVIF;
+ int ret, i, reg;
+
+ if (!cpriv->status.rxovif)
+ return 0;
+
+ /* clear all fifos that have an overflow bit set */
+ for (i = 0; i < 32; i++) {
+ if (cpriv->status.rxovif & BIT(i)) {
+ /* clear fifo status */
+ reg = MCP25XXFD_CAN_FIFOSTA(i);
+ ret = mcp25xxfd_cmd_write_mask(cpriv->priv->spi,
+ reg, 0, mask);
+ if (ret)
+ return ret;
+
+ /* update statistics */
+ cpriv->can.dev->stats.rx_over_errors++;
+ cpriv->can.dev->stats.rx_errors++;
+
+ /* and prepare ERROR FRAME */
+ cpriv->error_frame.id |= CAN_ERR_CRTL;
+ cpriv->error_frame.data[1] |=
+ CAN_ERR_CRTL_RX_OVERFLOW;
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.h
new file mode 100644
index 000000000000..71953e2f3615
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_can_rx.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@xxxxxxxxxxxxxxxx>
+ */
+
+#ifndef __MCP25XXFD_CAN_RX_H
+#define __MCP25XXFD_CAN_RX_H
+
+#include "mcp25xxfd_priv.h"
+
+int mcp25xxfd_can_rx_submit_frame(struct mcp25xxfd_can_priv *cpriv, int fifo);
+
+int mcp25xxfd_can_rx_handle_int_rxif(struct mcp25xxfd_can_priv *cpriv);
+int mcp25xxfd_can_rx_handle_int_rxovif(struct mcp25xxfd_can_priv *cpriv);
+
+#endif /* __MCP25XXFD_CAN_RX_H */
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_int.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_int.c
index 5e274d452646..182172b6c59c 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_int.c
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_int.c
@@ -8,6 +8,7 @@
#include <linux/kernel.h>
#include <linux/spi/spi.h>
+#include "mcp25xxfd_can_int.h"
#include "mcp25xxfd_crc.h"
#include "mcp25xxfd_ecc.h"
#include "mcp25xxfd_int.h"
@@ -21,7 +22,11 @@ int mcp25xxfd_int_clear(struct mcp25xxfd_priv *priv)
if (ret)
return ret;
- return mcp25xxfd_crc_clear_int(priv);
+ ret = mcp25xxfd_crc_clear_int(priv);
+ if (ret)
+ return ret;
+
+ return mcp25xxfd_can_int_clear(priv);
}
int mcp25xxfd_int_enable(struct mcp25xxfd_priv *priv, bool enable)
@@ -47,12 +52,19 @@ int mcp25xxfd_int_enable(struct mcp25xxfd_priv *priv, bool enable)
if (ret)
goto out_crc;
+ ret = mcp25xxfd_can_int_enable(priv, enable);
+ if (ret)
+ goto out_ecc;
+
/* If we disable interrupts, then clear interrupt flags last */
if (!enable)
mcp25xxfd_int_clear(priv);
return 0;
+out_ecc:
+ mcp25xxfd_ecc_enable_int(priv, false);
+
out_crc:
mcp25xxfd_crc_enable_int(priv, false);
return ret;
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h
index 8bc7a599224c..85c27a7f6785 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h
@@ -23,9 +23,11 @@ enum mcp25xxfd_model {
CAN_MCP2517FD = 0x2517,
};
+struct mcp25xxfd_can_priv;
struct mcp25xxfd_priv {
struct spi_device *spi;
struct clk *clk;
+ struct mcp25xxfd_can_priv *cpriv;
/* actual model of the mcp25xxfd */
enum mcp25xxfd_model model;
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_regs.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_regs.h
index b500cb46b9a4..222527439c70 100644
--- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_regs.h
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_regs.h
@@ -653,4 +653,9 @@ struct mcp25xxfd_can_obj_tef {
MCP25XXFD_CAN_FLAGS_FILHIT_BITS - 1, \
MCP25XXFD_CAN_FLAGS_FILHIT_SHIFT)
+/* custom status error */
+#define MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_SERR_RX BIT(0)
+#define MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_SERR_TX BIT(1)
+#define MCP25XXFD_CAN_ERR_DATA7_MCP25XXFD_ECC BIT(2)
+
#endif /* __MCP25XXFD_REGS_H */
--
2.17.1