can: ifi: Add IFI CANFD IP support
authorMarek Vasut <marex@denx.de>
Wed, 20 Jan 2016 14:33:39 +0000 (15:33 +0100)
committerMarc Kleine-Budde <mkl@pengutronix.de>
Sat, 20 Feb 2016 13:56:15 +0000 (14:56 +0100)
The patch adds support for IFI CAN/FD controller [1]. This driver
currently supports sending and receiving both standard CAN and new
CAN/FD frames. Both ISO and BOSCH variant of CAN/FD is supported.

[1] http://www.ifi-pld.de/IP/CANFD/canfd.html

Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Marc Kleine-Budde <mkl@pengutronix.de>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Oliver Hartkopp <socketcan@hartkopp.net>
Cc: Wolfgang Grandegger <wg@grandegger.com>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
drivers/net/can/Kconfig
drivers/net/can/Makefile
drivers/net/can/ifi_canfd/Kconfig [new file with mode: 0644]
drivers/net/can/ifi_canfd/Makefile [new file with mode: 0644]
drivers/net/can/ifi_canfd/ifi_canfd.c [new file with mode: 0644]

index b77ef32..164ccde 100644 (file)
@@ -149,6 +149,7 @@ config PCH_CAN
 
 source "drivers/net/can/c_can/Kconfig"
 source "drivers/net/can/cc770/Kconfig"
+source "drivers/net/can/ifi_canfd/Kconfig"
 source "drivers/net/can/m_can/Kconfig"
 source "drivers/net/can/mscan/Kconfig"
 source "drivers/net/can/sja1000/Kconfig"
index 4f85c2b..e3db0c8 100644 (file)
@@ -20,6 +20,7 @@ obj-$(CONFIG_CAN_CC770)               += cc770/
 obj-$(CONFIG_CAN_C_CAN)                += c_can/
 obj-$(CONFIG_CAN_FLEXCAN)      += flexcan.o
 obj-$(CONFIG_CAN_GRCAN)                += grcan.o
+obj-$(CONFIG_CAN_IFI_CANFD)    += ifi_canfd/
 obj-$(CONFIG_CAN_JANZ_ICAN3)   += janz-ican3.o
 obj-$(CONFIG_CAN_MSCAN)                += mscan/
 obj-$(CONFIG_CAN_M_CAN)                += m_can/
diff --git a/drivers/net/can/ifi_canfd/Kconfig b/drivers/net/can/ifi_canfd/Kconfig
new file mode 100644 (file)
index 0000000..9e8934f
--- /dev/null
@@ -0,0 +1,8 @@
+config CAN_IFI_CANFD
+       depends on HAS_IOMEM
+       tristate "IFI CAN_FD IP"
+       ---help---
+         This driver adds support for the I/F/I CAN_FD soft IP block
+         connected to the "platform bus" (Linux abstraction for directly
+         to the processor attached devices). The CAN_FD is most often
+         synthesised into an FPGA or CPLD.
diff --git a/drivers/net/can/ifi_canfd/Makefile b/drivers/net/can/ifi_canfd/Makefile
new file mode 100644 (file)
index 0000000..b229960
--- /dev/null
@@ -0,0 +1,5 @@
+#
+#  Makefile for the IFI CANFD controller driver.
+#
+
+obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd.o
diff --git a/drivers/net/can/ifi_canfd/ifi_canfd.c b/drivers/net/can/ifi_canfd/ifi_canfd.c
new file mode 100644 (file)
index 0000000..0d1c164
--- /dev/null
@@ -0,0 +1,917 @@
+/*
+ * CAN bus driver for IFI CANFD controller
+ *
+ * Copyright (C) 2016 Marek Vasut <marex@denx.de>
+ *
+ * Details about this controller can be found at
+ * http://www.ifi-pld.de/IP/CANFD/canfd.html
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/can/dev.h>
+
+#define IFI_CANFD_STCMD                                0x0
+#define IFI_CANFD_STCMD_HARDRESET              0xDEADCAFD
+#define IFI_CANFD_STCMD_ENABLE                 BIT(0)
+#define IFI_CANFD_STCMD_ERROR_ACTIVE           BIT(2)
+#define IFI_CANFD_STCMD_ERROR_PASSIVE          BIT(3)
+#define IFI_CANFD_STCMD_BUSOFF                 BIT(4)
+#define IFI_CANFD_STCMD_BUSMONITOR             BIT(16)
+#define IFI_CANFD_STCMD_LOOPBACK               BIT(18)
+#define IFI_CANFD_STCMD_DISABLE_CANFD          BIT(24)
+#define IFI_CANFD_STCMD_ENABLE_ISO             BIT(25)
+#define IFI_CANFD_STCMD_NORMAL_MODE            ((u32)BIT(31))
+
+#define IFI_CANFD_RXSTCMD                      0x4
+#define IFI_CANFD_RXSTCMD_REMOVE_MSG           BIT(0)
+#define IFI_CANFD_RXSTCMD_RESET                        BIT(7)
+#define IFI_CANFD_RXSTCMD_EMPTY                        BIT(8)
+#define IFI_CANFD_RXSTCMD_OVERFLOW             BIT(13)
+
+#define IFI_CANFD_TXSTCMD                      0x8
+#define IFI_CANFD_TXSTCMD_ADD_MSG              BIT(0)
+#define IFI_CANFD_TXSTCMD_HIGH_PRIO            BIT(1)
+#define IFI_CANFD_TXSTCMD_RESET                        BIT(7)
+#define IFI_CANFD_TXSTCMD_EMPTY                        BIT(8)
+#define IFI_CANFD_TXSTCMD_FULL                 BIT(12)
+#define IFI_CANFD_TXSTCMD_OVERFLOW             BIT(13)
+
+#define IFI_CANFD_INTERRUPT                    0xc
+#define IFI_CANFD_INTERRUPT_ERROR_WARNING      ((u32)BIT(1))
+#define IFI_CANFD_INTERRUPT_TXFIFO_EMPTY       BIT(16)
+#define IFI_CANFD_INTERRUPT_TXFIFO_REMOVE      BIT(22)
+#define IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY      BIT(24)
+#define IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER  BIT(25)
+#define IFI_CANFD_INTERRUPT_SET_IRQ            ((u32)BIT(31))
+
+#define IFI_CANFD_IRQMASK                      0x10
+#define IFI_CANFD_IRQMASK_SET_ERR              BIT(7)
+#define IFI_CANFD_IRQMASK_SET_TS               BIT(15)
+#define IFI_CANFD_IRQMASK_TXFIFO_EMPTY         BIT(16)
+#define IFI_CANFD_IRQMASK_SET_TX               BIT(23)
+#define IFI_CANFD_IRQMASK_RXFIFO_NEMPTY                BIT(24)
+#define IFI_CANFD_IRQMASK_SET_RX               ((u32)BIT(31))
+
+#define IFI_CANFD_TIME                         0x14
+#define IFI_CANFD_FTIME                                0x18
+#define IFI_CANFD_TIME_TIMEB_OFF               0
+#define IFI_CANFD_TIME_TIMEA_OFF               8
+#define IFI_CANFD_TIME_PRESCALE_OFF            16
+#define IFI_CANFD_TIME_SJW_OFF_ISO             25
+#define IFI_CANFD_TIME_SJW_OFF_BOSCH           28
+#define IFI_CANFD_TIME_SET_SJW_BOSCH           BIT(6)
+#define IFI_CANFD_TIME_SET_TIMEB_BOSCH         BIT(7)
+#define IFI_CANFD_TIME_SET_PRESC_BOSCH         BIT(14)
+#define IFI_CANFD_TIME_SET_TIMEA_BOSCH         BIT(15)
+
+#define IFI_CANFD_TDELAY                       0x1c
+
+#define IFI_CANFD_ERROR                                0x20
+#define IFI_CANFD_ERROR_TX_OFFSET              0
+#define IFI_CANFD_ERROR_TX_MASK                        0xff
+#define IFI_CANFD_ERROR_RX_OFFSET              16
+#define IFI_CANFD_ERROR_RX_MASK                        0xff
+
+#define IFI_CANFD_ERRCNT                       0x24
+
+#define IFI_CANFD_SUSPEND                      0x28
+
+#define IFI_CANFD_REPEAT                       0x2c
+
+#define IFI_CANFD_TRAFFIC                      0x30
+
+#define IFI_CANFD_TSCONTROL                    0x34
+
+#define IFI_CANFD_TSC                          0x38
+
+#define IFI_CANFD_TST                          0x3c
+
+#define IFI_CANFD_RES1                         0x40
+
+#define IFI_CANFD_RES2                         0x44
+
+#define IFI_CANFD_PAR                          0x48
+
+#define IFI_CANFD_CANCLOCK                     0x4c
+
+#define IFI_CANFD_SYSCLOCK                     0x50
+
+#define IFI_CANFD_VER                          0x54
+
+#define IFI_CANFD_IP_ID                                0x58
+#define IFI_CANFD_IP_ID_VALUE                  0xD073CAFD
+
+#define IFI_CANFD_TEST                         0x5c
+
+#define IFI_CANFD_RXFIFO_TS_63_32              0x60
+
+#define IFI_CANFD_RXFIFO_TS_31_0               0x64
+
+#define IFI_CANFD_RXFIFO_DLC                   0x68
+#define IFI_CANFD_RXFIFO_DLC_DLC_OFFSET                0
+#define IFI_CANFD_RXFIFO_DLC_DLC_MASK          0xf
+#define IFI_CANFD_RXFIFO_DLC_RTR               BIT(4)
+#define IFI_CANFD_RXFIFO_DLC_EDL               BIT(5)
+#define IFI_CANFD_RXFIFO_DLC_BRS               BIT(6)
+#define IFI_CANFD_RXFIFO_DLC_ESI               BIT(7)
+#define IFI_CANFD_RXFIFO_DLC_OBJ_OFFSET                8
+#define IFI_CANFD_RXFIFO_DLC_OBJ_MASK          0x1ff
+#define IFI_CANFD_RXFIFO_DLC_FNR_OFFSET                24
+#define IFI_CANFD_RXFIFO_DLC_FNR_MASK          0xff
+
+#define IFI_CANFD_RXFIFO_ID                    0x6c
+#define IFI_CANFD_RXFIFO_ID_ID_OFFSET          0
+#define IFI_CANFD_RXFIFO_ID_ID_STD_MASK                0x3ff
+#define IFI_CANFD_RXFIFO_ID_ID_XTD_MASK                0x1fffffff
+#define IFI_CANFD_RXFIFO_ID_IDE                        BIT(29)
+
+#define IFI_CANFD_RXFIFO_DATA                  0x70    /* 0x70..0xac */
+
+#define IFI_CANFD_TXFIFO_SUSPEND_US            0xb0
+
+#define IFI_CANFD_TXFIFO_REPEATCOUNT           0xb4
+
+#define IFI_CANFD_TXFIFO_DLC                   0xb8
+#define IFI_CANFD_TXFIFO_DLC_DLC_OFFSET                0
+#define IFI_CANFD_TXFIFO_DLC_DLC_MASK          0xf
+#define IFI_CANFD_TXFIFO_DLC_RTR               BIT(4)
+#define IFI_CANFD_TXFIFO_DLC_EDL               BIT(5)
+#define IFI_CANFD_TXFIFO_DLC_BRS               BIT(6)
+#define IFI_CANFD_TXFIFO_DLC_FNR_OFFSET                24
+#define IFI_CANFD_TXFIFO_DLC_FNR_MASK          0xff
+
+#define IFI_CANFD_TXFIFO_ID                    0xbc
+#define IFI_CANFD_TXFIFO_ID_ID_OFFSET          0
+#define IFI_CANFD_TXFIFO_ID_ID_STD_MASK                0x3ff
+#define IFI_CANFD_TXFIFO_ID_ID_XTD_MASK                0x1fffffff
+#define IFI_CANFD_TXFIFO_ID_IDE                        BIT(29)
+
+#define IFI_CANFD_TXFIFO_DATA                  0xc0    /* 0xb0..0xfc */
+
+#define IFI_CANFD_FILTER_MASK(n)               (0x800 + ((n) * 8) + 0)
+#define IFI_CANFD_FILTER_MASK_EXT              BIT(29)
+#define IFI_CANFD_FILTER_MASK_EDL              BIT(30)
+#define IFI_CANFD_FILTER_MASK_VALID            ((u32)BIT(31))
+
+#define IFI_CANFD_FILTER_IDENT(n)              (0x800 + ((n) * 8) + 4)
+#define IFI_CANFD_FILTER_IDENT_IDE             BIT(29)
+#define IFI_CANFD_FILTER_IDENT_CANFD           BIT(30)
+#define IFI_CANFD_FILTER_IDENT_VALID           ((u32)BIT(31))
+
+/* IFI CANFD private data structure */
+struct ifi_canfd_priv {
+       struct can_priv         can;    /* must be the first member */
+       struct napi_struct      napi;
+       struct net_device       *ndev;
+       void __iomem            *base;
+};
+
+static void ifi_canfd_irq_enable(struct net_device *ndev, bool enable)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       u32 enirq = 0;
+
+       if (enable) {
+               enirq = IFI_CANFD_IRQMASK_TXFIFO_EMPTY |
+                       IFI_CANFD_IRQMASK_RXFIFO_NEMPTY;
+       }
+
+       writel(IFI_CANFD_IRQMASK_SET_ERR |
+              IFI_CANFD_IRQMASK_SET_TS |
+              IFI_CANFD_IRQMASK_SET_TX |
+              IFI_CANFD_IRQMASK_SET_RX | enirq,
+              priv->base + IFI_CANFD_IRQMASK);
+}
+
+static void ifi_canfd_read_fifo(struct net_device *ndev)
+{
+       struct net_device_stats *stats = &ndev->stats;
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       struct canfd_frame *cf;
+       struct sk_buff *skb;
+       const u32 rx_irq_mask = IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY |
+                               IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER;
+       u32 rxdlc, rxid;
+       u32 dlc, id;
+       int i;
+
+       rxdlc = readl(priv->base + IFI_CANFD_RXFIFO_DLC);
+       if (rxdlc & IFI_CANFD_RXFIFO_DLC_EDL)
+               skb = alloc_canfd_skb(ndev, &cf);
+       else
+               skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+       if (!skb) {
+               stats->rx_dropped++;
+               return;
+       }
+
+       dlc = (rxdlc >> IFI_CANFD_RXFIFO_DLC_DLC_OFFSET) &
+             IFI_CANFD_RXFIFO_DLC_DLC_MASK;
+       if (rxdlc & IFI_CANFD_RXFIFO_DLC_EDL)
+               cf->len = can_dlc2len(dlc);
+       else
+               cf->len = get_can_dlc(dlc);
+
+       rxid = readl(priv->base + IFI_CANFD_RXFIFO_ID);
+       id = (rxid >> IFI_CANFD_RXFIFO_ID_ID_OFFSET);
+       if (id & IFI_CANFD_RXFIFO_ID_IDE)
+               id &= IFI_CANFD_RXFIFO_ID_ID_XTD_MASK;
+       else
+               id &= IFI_CANFD_RXFIFO_ID_ID_STD_MASK;
+       cf->can_id = id;
+
+       if (rxdlc & IFI_CANFD_RXFIFO_DLC_ESI) {
+               cf->flags |= CANFD_ESI;
+               netdev_dbg(ndev, "ESI Error\n");
+       }
+
+       if (!(rxdlc & IFI_CANFD_RXFIFO_DLC_EDL) &&
+           (rxdlc & IFI_CANFD_RXFIFO_DLC_RTR)) {
+               cf->can_id |= CAN_RTR_FLAG;
+       } else {
+               if (rxdlc & IFI_CANFD_RXFIFO_DLC_BRS)
+                       cf->flags |= CANFD_BRS;
+
+               for (i = 0; i < cf->len; i += 4) {
+                       *(u32 *)(cf->data + i) =
+                               readl(priv->base + IFI_CANFD_RXFIFO_DATA + i);
+               }
+       }
+
+       /* Remove the packet from FIFO */
+       writel(IFI_CANFD_RXSTCMD_REMOVE_MSG, priv->base + IFI_CANFD_RXSTCMD);
+       writel(rx_irq_mask, priv->base + IFI_CANFD_INTERRUPT);
+
+       stats->rx_packets++;
+       stats->rx_bytes += cf->len;
+
+       netif_receive_skb(skb);
+}
+
+static int ifi_canfd_do_rx_poll(struct net_device *ndev, int quota)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       u32 pkts = 0;
+       u32 rxst;
+
+       rxst = readl(priv->base + IFI_CANFD_RXSTCMD);
+       if (rxst & IFI_CANFD_RXSTCMD_EMPTY) {
+               netdev_dbg(ndev, "No messages in RX FIFO\n");
+               return 0;
+       }
+
+       for (;;) {
+               if (rxst & IFI_CANFD_RXSTCMD_EMPTY)
+                       break;
+               if (quota <= 0)
+                       break;
+
+               ifi_canfd_read_fifo(ndev);
+               quota--;
+               pkts++;
+               rxst = readl(priv->base + IFI_CANFD_RXSTCMD);
+       }
+
+       if (pkts)
+               can_led_event(ndev, CAN_LED_EVENT_RX);
+
+       return pkts;
+}
+
+static int ifi_canfd_handle_lost_msg(struct net_device *ndev)
+{
+       struct net_device_stats *stats = &ndev->stats;
+       struct sk_buff *skb;
+       struct can_frame *frame;
+
+       netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+       stats->rx_errors++;
+       stats->rx_over_errors++;
+
+       skb = alloc_can_err_skb(ndev, &frame);
+       if (unlikely(!skb))
+               return 0;
+
+       frame->can_id |= CAN_ERR_CRTL;
+       frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+       netif_receive_skb(skb);
+
+       return 1;
+}
+
+static int ifi_canfd_get_berr_counter(const struct net_device *ndev,
+                                     struct can_berr_counter *bec)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       u32 err;
+
+       err = readl(priv->base + IFI_CANFD_ERROR);
+       bec->rxerr = (err >> IFI_CANFD_ERROR_RX_OFFSET) &
+                    IFI_CANFD_ERROR_RX_MASK;
+       bec->txerr = (err >> IFI_CANFD_ERROR_TX_OFFSET) &
+                    IFI_CANFD_ERROR_TX_MASK;
+
+       return 0;
+}
+
+static int ifi_canfd_handle_state_change(struct net_device *ndev,
+                                        enum can_state new_state)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       struct net_device_stats *stats = &ndev->stats;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+       struct can_berr_counter bec;
+
+       switch (new_state) {
+       case CAN_STATE_ERROR_ACTIVE:
+               /* error warning state */
+               priv->can.can_stats.error_warning++;
+               priv->can.state = CAN_STATE_ERROR_WARNING;
+               break;
+       case CAN_STATE_ERROR_PASSIVE:
+               /* error passive state */
+               priv->can.can_stats.error_passive++;
+               priv->can.state = CAN_STATE_ERROR_PASSIVE;
+               break;
+       case CAN_STATE_BUS_OFF:
+               /* bus-off state */
+               priv->can.state = CAN_STATE_BUS_OFF;
+               ifi_canfd_irq_enable(ndev, 0);
+               priv->can.can_stats.bus_off++;
+               can_bus_off(ndev);
+               break;
+       default:
+               break;
+       }
+
+       /* propagate the error condition to the CAN stack */
+       skb = alloc_can_err_skb(ndev, &cf);
+       if (unlikely(!skb))
+               return 0;
+
+       ifi_canfd_get_berr_counter(ndev, &bec);
+
+       switch (new_state) {
+       case CAN_STATE_ERROR_ACTIVE:
+               /* error warning state */
+               cf->can_id |= CAN_ERR_CRTL;
+               cf->data[1] = (bec.txerr > bec.rxerr) ?
+                       CAN_ERR_CRTL_TX_WARNING :
+                       CAN_ERR_CRTL_RX_WARNING;
+               cf->data[6] = bec.txerr;
+               cf->data[7] = bec.rxerr;
+               break;
+       case CAN_STATE_ERROR_PASSIVE:
+               /* error passive state */
+               cf->can_id |= CAN_ERR_CRTL;
+               cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+               if (bec.txerr > 127)
+                       cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+               cf->data[6] = bec.txerr;
+               cf->data[7] = bec.rxerr;
+               break;
+       case CAN_STATE_BUS_OFF:
+               /* bus-off state */
+               cf->can_id |= CAN_ERR_BUSOFF;
+               break;
+       default:
+               break;
+       }
+
+       stats->rx_packets++;
+       stats->rx_bytes += cf->can_dlc;
+       netif_receive_skb(skb);
+
+       return 1;
+}
+
+static int ifi_canfd_handle_state_errors(struct net_device *ndev, u32 stcmd)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       int work_done = 0;
+       u32 isr;
+
+       /*
+        * The ErrWarn condition is a little special, since the bit is
+        * located in the INTERRUPT register instead of STCMD register.
+        */
+       isr = readl(priv->base + IFI_CANFD_INTERRUPT);
+       if ((isr & IFI_CANFD_INTERRUPT_ERROR_WARNING) &&
+           (priv->can.state != CAN_STATE_ERROR_WARNING)) {
+               /* Clear the interrupt */
+               writel(IFI_CANFD_INTERRUPT_ERROR_WARNING,
+                      priv->base + IFI_CANFD_INTERRUPT);
+               netdev_dbg(ndev, "Error, entered warning state\n");
+               work_done += ifi_canfd_handle_state_change(ndev,
+                                               CAN_STATE_ERROR_WARNING);
+       }
+
+       if ((stcmd & IFI_CANFD_STCMD_ERROR_PASSIVE) &&
+           (priv->can.state != CAN_STATE_ERROR_PASSIVE)) {
+               netdev_dbg(ndev, "Error, entered passive state\n");
+               work_done += ifi_canfd_handle_state_change(ndev,
+                                               CAN_STATE_ERROR_PASSIVE);
+       }
+
+       if ((stcmd & IFI_CANFD_STCMD_BUSOFF) &&
+           (priv->can.state != CAN_STATE_BUS_OFF)) {
+               netdev_dbg(ndev, "Error, entered bus-off state\n");
+               work_done += ifi_canfd_handle_state_change(ndev,
+                                               CAN_STATE_BUS_OFF);
+       }
+
+       return work_done;
+}
+
+static int ifi_canfd_poll(struct napi_struct *napi, int quota)
+{
+       struct net_device *ndev = napi->dev;
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       const u32 stcmd_state_mask = IFI_CANFD_STCMD_ERROR_PASSIVE |
+                                    IFI_CANFD_STCMD_BUSOFF;
+       int work_done = 0;
+
+       u32 stcmd = readl(priv->base + IFI_CANFD_STCMD);
+       u32 rxstcmd = readl(priv->base + IFI_CANFD_STCMD);
+
+       /* Handle bus state changes */
+       if ((stcmd & stcmd_state_mask) ||
+           ((stcmd & IFI_CANFD_STCMD_ERROR_ACTIVE) == 0))
+               work_done += ifi_canfd_handle_state_errors(ndev, stcmd);
+
+       /* Handle lost messages on RX */
+       if (rxstcmd & IFI_CANFD_RXSTCMD_OVERFLOW)
+               work_done += ifi_canfd_handle_lost_msg(ndev);
+
+       /* Handle normal messages on RX */
+       if (!(rxstcmd & IFI_CANFD_RXSTCMD_EMPTY))
+               work_done += ifi_canfd_do_rx_poll(ndev, quota - work_done);
+
+       if (work_done < quota) {
+               napi_complete(napi);
+               ifi_canfd_irq_enable(ndev, 1);
+       }
+
+       return work_done;
+}
+
+static irqreturn_t ifi_canfd_isr(int irq, void *dev_id)
+{
+       struct net_device *ndev = (struct net_device *)dev_id;
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       struct net_device_stats *stats = &ndev->stats;
+       const u32 rx_irq_mask = IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY |
+                               IFI_CANFD_INTERRUPT_RXFIFO_NEMPTY_PER;
+       const u32 tx_irq_mask = IFI_CANFD_INTERRUPT_TXFIFO_EMPTY |
+                               IFI_CANFD_INTERRUPT_TXFIFO_REMOVE;
+       const u32 clr_irq_mask = ~(IFI_CANFD_INTERRUPT_SET_IRQ |
+                                  IFI_CANFD_INTERRUPT_ERROR_WARNING);
+       u32 isr;
+
+       isr = readl(priv->base + IFI_CANFD_INTERRUPT);
+
+       /* No interrupt */
+       if (isr == 0)
+               return IRQ_NONE;
+
+       /* Clear all pending interrupts but ErrWarn */
+       writel(clr_irq_mask, priv->base + IFI_CANFD_INTERRUPT);
+
+       /* RX IRQ, start NAPI */
+       if (isr & rx_irq_mask) {
+               ifi_canfd_irq_enable(ndev, 0);
+               napi_schedule(&priv->napi);
+       }
+
+       /* TX IRQ */
+       if (isr & tx_irq_mask) {
+               stats->tx_bytes += can_get_echo_skb(ndev, 0);
+               stats->tx_packets++;
+               can_led_event(ndev, CAN_LED_EVENT_TX);
+               netif_wake_queue(ndev);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static const struct can_bittiming_const ifi_canfd_bittiming_const = {
+       .name           = KBUILD_MODNAME,
+       .tseg1_min      = 2,    /* Time segment 1 = prop_seg + phase_seg1 */
+       .tseg1_max      = 64,
+       .tseg2_min      = 1,    /* Time segment 2 = phase_seg2 */
+       .tseg2_max      = 16,
+       .sjw_max        = 16,
+       .brp_min        = 1,
+       .brp_max        = 1024,
+       .brp_inc        = 1,
+};
+
+static const struct can_bittiming_const ifi_canfd_data_bittiming_const = {
+       .name           = KBUILD_MODNAME,
+       .tseg1_min      = 2,    /* Time segment 1 = prop_seg + phase_seg1 */
+       .tseg1_max      = 16,
+       .tseg2_min      = 1,    /* Time segment 2 = phase_seg2 */
+       .tseg2_max      = 8,
+       .sjw_max        = 4,
+       .brp_min        = 1,
+       .brp_max        = 32,
+       .brp_inc        = 1,
+};
+
+static void ifi_canfd_set_bittiming(struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       const struct can_bittiming *bt = &priv->can.bittiming;
+       const struct can_bittiming *dbt = &priv->can.data_bittiming;
+       u16 brp, sjw, tseg1, tseg2;
+       u32 noniso_arg = 0;
+       u32 time_off;
+
+       if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) {
+               noniso_arg = IFI_CANFD_TIME_SET_TIMEB_BOSCH |
+                            IFI_CANFD_TIME_SET_TIMEA_BOSCH |
+                            IFI_CANFD_TIME_SET_PRESC_BOSCH |
+                            IFI_CANFD_TIME_SET_SJW_BOSCH;
+               time_off = IFI_CANFD_TIME_SJW_OFF_BOSCH;
+       } else {
+               time_off = IFI_CANFD_TIME_SJW_OFF_ISO;
+       }
+
+       /* Configure bit timing */
+       brp = bt->brp - 1;
+       sjw = bt->sjw - 1;
+       tseg1 = bt->prop_seg + bt->phase_seg1 - 1;
+       tseg2 = bt->phase_seg2 - 1;
+       writel((tseg2 << IFI_CANFD_TIME_TIMEB_OFF) |
+              (tseg1 << IFI_CANFD_TIME_TIMEA_OFF) |
+              (brp << IFI_CANFD_TIME_PRESCALE_OFF) |
+              (sjw << time_off),
+              priv->base + IFI_CANFD_TIME);
+
+       /* Configure data bit timing */
+       brp = dbt->brp - 1;
+       sjw = dbt->sjw - 1;
+       tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1;
+       tseg2 = dbt->phase_seg2 - 1;
+       writel((tseg2 << IFI_CANFD_TIME_TIMEB_OFF) |
+              (tseg1 << IFI_CANFD_TIME_TIMEA_OFF) |
+              (brp << IFI_CANFD_TIME_PRESCALE_OFF) |
+              (sjw << time_off) |
+              noniso_arg,
+              priv->base + IFI_CANFD_FTIME);
+}
+
+static void ifi_canfd_set_filter(struct net_device *ndev, const u32 id,
+                                const u32 mask, const u32 ident)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+
+       writel(mask, priv->base + IFI_CANFD_FILTER_MASK(id));
+       writel(ident, priv->base + IFI_CANFD_FILTER_IDENT(id));
+}
+
+static void ifi_canfd_set_filters(struct net_device *ndev)
+{
+       /* Receive all CAN frames (standard ID) */
+       ifi_canfd_set_filter(ndev, 0,
+                            IFI_CANFD_FILTER_MASK_VALID |
+                            IFI_CANFD_FILTER_MASK_EXT,
+                            IFI_CANFD_FILTER_IDENT_VALID);
+
+       /* Receive all CAN frames (extended ID) */
+       ifi_canfd_set_filter(ndev, 1,
+                            IFI_CANFD_FILTER_MASK_VALID |
+                            IFI_CANFD_FILTER_MASK_EXT,
+                            IFI_CANFD_FILTER_IDENT_VALID |
+                            IFI_CANFD_FILTER_IDENT_IDE);
+
+       /* Receive all CANFD frames */
+       ifi_canfd_set_filter(ndev, 2,
+                            IFI_CANFD_FILTER_MASK_VALID |
+                            IFI_CANFD_FILTER_MASK_EDL |
+                            IFI_CANFD_FILTER_MASK_EXT,
+                            IFI_CANFD_FILTER_IDENT_VALID |
+                            IFI_CANFD_FILTER_IDENT_CANFD |
+                            IFI_CANFD_FILTER_IDENT_IDE);
+}
+
+static void ifi_canfd_start(struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       u32 stcmd;
+
+       /* Reset the IP */
+       writel(IFI_CANFD_STCMD_HARDRESET, priv->base + IFI_CANFD_STCMD);
+       writel(0, priv->base + IFI_CANFD_STCMD);
+
+       ifi_canfd_set_bittiming(ndev);
+       ifi_canfd_set_filters(ndev);
+
+       /* Reset FIFOs */
+       writel(IFI_CANFD_RXSTCMD_RESET, priv->base + IFI_CANFD_RXSTCMD);
+       writel(0, priv->base + IFI_CANFD_RXSTCMD);
+       writel(IFI_CANFD_TXSTCMD_RESET, priv->base + IFI_CANFD_TXSTCMD);
+       writel(0, priv->base + IFI_CANFD_TXSTCMD);
+
+       /* Repeat transmission until successful */
+       writel(0, priv->base + IFI_CANFD_REPEAT);
+       writel(0, priv->base + IFI_CANFD_SUSPEND);
+
+       /* Clear all pending interrupts */
+       writel((u32)(~IFI_CANFD_INTERRUPT_SET_IRQ),
+              priv->base + IFI_CANFD_INTERRUPT);
+
+       stcmd = IFI_CANFD_STCMD_ENABLE | IFI_CANFD_STCMD_NORMAL_MODE;
+
+       if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+               stcmd |= IFI_CANFD_STCMD_BUSMONITOR;
+
+       if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+               stcmd |= IFI_CANFD_STCMD_LOOPBACK;
+
+       if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+               stcmd |= IFI_CANFD_STCMD_ENABLE_ISO;
+
+       if (!(priv->can.ctrlmode & (CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO)))
+               stcmd |= IFI_CANFD_STCMD_DISABLE_CANFD;
+
+       priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+       ifi_canfd_irq_enable(ndev, 1);
+
+       /* Enable controller */
+       writel(stcmd, priv->base + IFI_CANFD_STCMD);
+}
+
+static void ifi_canfd_stop(struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+
+       /* Reset the IP */
+       writel(IFI_CANFD_STCMD_HARDRESET, priv->base + IFI_CANFD_STCMD);
+
+       /* Mask all interrupts */
+       writel(~0, priv->base + IFI_CANFD_IRQMASK);
+
+       /* Clear all pending interrupts */
+       writel((u32)(~IFI_CANFD_INTERRUPT_SET_IRQ),
+              priv->base + IFI_CANFD_INTERRUPT);
+
+       /* Set the state as STOPPED */
+       priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int ifi_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+       switch (mode) {
+       case CAN_MODE_START:
+               ifi_canfd_start(ndev);
+               netif_wake_queue(ndev);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int ifi_canfd_open(struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       int ret;
+
+       ret = open_candev(ndev);
+       if (ret) {
+               netdev_err(ndev, "Failed to open CAN device\n");
+               return ret;
+       }
+
+       /* Register interrupt handler */
+       ret = request_irq(ndev->irq, ifi_canfd_isr, IRQF_SHARED,
+                         ndev->name, ndev);
+       if (ret < 0) {
+               netdev_err(ndev, "Failed to request interrupt\n");
+               goto err_irq;
+       }
+
+       ifi_canfd_start(ndev);
+
+       can_led_event(ndev, CAN_LED_EVENT_OPEN);
+       napi_enable(&priv->napi);
+       netif_start_queue(ndev);
+
+       return 0;
+err_irq:
+       close_candev(ndev);
+       return ret;
+}
+
+static int ifi_canfd_close(struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+
+       netif_stop_queue(ndev);
+       napi_disable(&priv->napi);
+
+       ifi_canfd_stop(ndev);
+
+       free_irq(ndev->irq, ndev);
+
+       close_candev(ndev);
+
+       can_led_event(ndev, CAN_LED_EVENT_STOP);
+
+       return 0;
+}
+
+static netdev_tx_t ifi_canfd_start_xmit(struct sk_buff *skb,
+                                       struct net_device *ndev)
+{
+       struct ifi_canfd_priv *priv = netdev_priv(ndev);
+       struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+       u32 txst, txid;
+       u32 txdlc = 0;
+       int i;
+
+       if (can_dropped_invalid_skb(ndev, skb))
+               return NETDEV_TX_OK;
+
+       /* Check if the TX buffer is full */
+       txst = readl(priv->base + IFI_CANFD_TXSTCMD);
+       if (txst & IFI_CANFD_TXSTCMD_FULL) {
+               netif_stop_queue(ndev);
+               netdev_err(ndev, "BUG! TX FIFO full when queue awake!\n");
+               return NETDEV_TX_BUSY;
+       }
+
+       netif_stop_queue(ndev);
+
+       if (cf->can_id & CAN_EFF_FLAG) {
+               txid = cf->can_id & CAN_EFF_MASK;
+               txid |= IFI_CANFD_TXFIFO_ID_IDE;
+       } else {
+               txid = cf->can_id & CAN_SFF_MASK;
+       }
+
+       if (priv->can.ctrlmode & (CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO)) {
+               if (can_is_canfd_skb(skb)) {
+                       txdlc |= IFI_CANFD_TXFIFO_DLC_EDL;
+                       if (cf->flags & CANFD_BRS)
+                               txdlc |= IFI_CANFD_TXFIFO_DLC_BRS;
+               }
+       }
+
+       if (cf->can_id & CAN_RTR_FLAG)
+               txdlc |= IFI_CANFD_TXFIFO_DLC_RTR;
+
+       /* message ram configuration */
+       writel(txid, priv->base + IFI_CANFD_TXFIFO_ID);
+       writel(txdlc, priv->base + IFI_CANFD_TXFIFO_DLC);
+
+       for (i = 0; i < cf->len; i += 4) {
+               writel(*(u32 *)(cf->data + i),
+                      priv->base + IFI_CANFD_TXFIFO_DATA + i);
+       }
+
+       writel(0, priv->base + IFI_CANFD_TXFIFO_REPEATCOUNT);
+       writel(0, priv->base + IFI_CANFD_TXFIFO_SUSPEND_US);
+
+       can_put_echo_skb(skb, ndev, 0);
+
+       /* Start the transmission */
+       writel(IFI_CANFD_TXSTCMD_ADD_MSG, priv->base + IFI_CANFD_TXSTCMD);
+
+       return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops ifi_canfd_netdev_ops = {
+       .ndo_open       = ifi_canfd_open,
+       .ndo_stop       = ifi_canfd_close,
+       .ndo_start_xmit = ifi_canfd_start_xmit,
+       .ndo_change_mtu = can_change_mtu,
+};
+
+static int ifi_canfd_plat_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct net_device *ndev;
+       struct ifi_canfd_priv *priv;
+       struct resource *res;
+       void __iomem *addr;
+       int irq, ret;
+       u32 id;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       addr = devm_ioremap_resource(dev, res);
+       irq = platform_get_irq(pdev, 0);
+       if (IS_ERR(addr) || irq < 0)
+               return -EINVAL;
+
+       id = readl(addr + IFI_CANFD_IP_ID);
+       if (id != IFI_CANFD_IP_ID_VALUE) {
+               dev_err(dev, "This block is not IFI CANFD, id=%08x\n", id);
+               return -EINVAL;
+       }
+
+       ndev = alloc_candev(sizeof(*priv), 1);
+       if (!ndev)
+               return -ENOMEM;
+
+       ndev->irq = irq;
+       ndev->flags |= IFF_ECHO;        /* we support local echo */
+       ndev->netdev_ops = &ifi_canfd_netdev_ops;
+
+       priv = netdev_priv(ndev);
+       priv->ndev = ndev;
+       priv->base = addr;
+
+       netif_napi_add(ndev, &priv->napi, ifi_canfd_poll, 64);
+
+       priv->can.state = CAN_STATE_STOPPED;
+
+       priv->can.clock.freq = readl(addr + IFI_CANFD_SYSCLOCK);
+
+       priv->can.bittiming_const       = &ifi_canfd_bittiming_const;
+       priv->can.data_bittiming_const  = &ifi_canfd_data_bittiming_const;
+       priv->can.do_set_mode           = ifi_canfd_set_mode;
+       priv->can.do_get_berr_counter   = ifi_canfd_get_berr_counter;
+
+       /* IFI CANFD can do both Bosch FD and ISO FD */
+       priv->can.ctrlmode = CAN_CTRLMODE_FD;
+
+       /* IFI CANFD can do both Bosch FD and ISO FD */
+       priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+                                      CAN_CTRLMODE_LISTENONLY |
+                                      CAN_CTRLMODE_FD |
+                                      CAN_CTRLMODE_FD_NON_ISO;
+
+       platform_set_drvdata(pdev, ndev);
+       SET_NETDEV_DEV(ndev, dev);
+
+       ret = register_candev(ndev);
+       if (ret) {
+               dev_err(dev, "Failed to register (ret=%d)\n", ret);
+               goto err_reg;
+       }
+
+       devm_can_led_init(ndev);
+
+       dev_info(dev, "Driver registered: regs=%p, irq=%d, clock=%d\n",
+                priv->base, ndev->irq, priv->can.clock.freq);
+
+       return 0;
+
+err_reg:
+       free_candev(ndev);
+       return ret;
+}
+
+static int ifi_canfd_plat_remove(struct platform_device *pdev)
+{
+       struct net_device *ndev = platform_get_drvdata(pdev);
+
+       unregister_candev(ndev);
+       platform_set_drvdata(pdev, NULL);
+       free_candev(ndev);
+
+       return 0;
+}
+
+static const struct of_device_id ifi_canfd_of_table[] = {
+       { .compatible = "ifi,canfd-1.0", .data = NULL },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ifi_canfd_of_table);
+
+static struct platform_driver ifi_canfd_plat_driver = {
+       .driver = {
+               .name           = KBUILD_MODNAME,
+               .of_match_table = ifi_canfd_of_table,
+       },
+       .probe  = ifi_canfd_plat_probe,
+       .remove = ifi_canfd_plat_remove,
+};
+
+module_platform_driver(ifi_canfd_plat_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CAN bus driver for IFI CANFD controller");