Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH 01/10] genirq: Export irq_set_msi_desc()
From: Thomas Gleixner @ 2017-11-24 23:55 UTC (permalink / raw)
  To: Manikanta Maddireddy
  Cc: thierry.reding, jonathanh, robh+dt, frowand.list, bhelgaas, rjw,
	vidyas, kthota, linux-tegra, devicetree, linux-pci, linux-pm,
	Marc Zyngier
In-Reply-To: <1511538800-8275-2-git-send-email-mmaddireddy@nvidia.com>

On Fri, 24 Nov 2017, Manikanta Maddireddy wrote:

Please CC the proper mailing list for irq related changes.

> PCI bus support MSI interrupts, allow PCI host driver to set MSI descriptor
> data for an irq.

This is not really an explanation why this export is needed.

Thanks,

	tglx

> 
> Signed-off-by: Manikanta Maddireddy <mmaddireddy@nvidia.com>
> ---
>  kernel/irq/chip.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c
> index 5a2ef92c2782..bfbd17386bc4 100644
> --- a/kernel/irq/chip.c
> +++ b/kernel/irq/chip.c
> @@ -136,6 +136,7 @@ int irq_set_msi_desc(unsigned int irq, struct msi_desc *entry)
>  {
>  	return irq_set_msi_desc_off(irq, 0, entry);
>  }
> +EXPORT_SYMBOL(irq_set_msi_desc);
>  
>  /**
>   *	irq_set_chip_data - set irq chip data for an irq
> -- 
> 2.1.4
> 
> 

^ permalink raw reply

* [PATCH 2/2] arm64: dts: renesas: v3msk: add EtherAVB support
From: Sergei Shtylyov @ 2017-11-24 20:59 UTC (permalink / raw)
  To: Simon Horman, Rob Herring, Catalin Marinas, Will Deacon,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Magnus Damm, Mark Rutland,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Vladimir Barinov, Sergei Shtylyov

[-- Attachment #1: arm64-dts-renesas-v3msk-add-EtherAVB-support.patch --]
[-- Type: text/plain, Size: 1152 bytes --]

Define the V3M Starter Kit board dependent part of the EtherAVB
device node.

Based on the original (and large) patch by Vladimir Barinov.

Signed-off-by: Vladimir Barinov <vladimir.barinov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8@public.gmane.org>
Signed-off-by: Sergei Shtylyov <sergei.shtylyov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8@public.gmane.org>

---
 arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts |   11 +++++++++++
 1 file changed, 11 insertions(+)

Index: renesas/arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts
===================================================================
--- renesas.orig/arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts
+++ renesas/arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts
@@ -31,6 +31,17 @@
 	};
 };
 
+&avb {
+	renesas,no-ether-link;
+	phy-handle = <&phy0>;
+	status = "okay";
+
+	phy0: ethernet-phy@0 {
+		rxc-skew-ps = <1500>;
+		reg = <0>;
+	};
+};
+
 &extal_clk {
 	clock-frequency = <16666666>;
 };

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH 1/2] arm64: dts: renesas: initial V3MSK board device tree
From: Sergei Shtylyov @ 2017-11-24 20:59 UTC (permalink / raw)
  To: Simon Horman, Rob Herring, Catalin Marinas, Will Deacon,
	linux-renesas-soc, devicetree
  Cc: Magnus Damm, Mark Rutland, linux-arm-kernel, Vladimir Barinov,
	Sergei Shtylyov

[-- Attachment #1: arm64-dts-renesas-initial-V3MSK-board-device-tree.patch --]
[-- Type: text/plain, Size: 2313 bytes --]

Add the initial device  tree for the V3M Starter Kit board.
The board has 1 debug serial port (SCIF0); include support for it,
so that the serial console can work.

Based on the original (and large) patch by Vladimir Barinov.

Signed-off-by: Vladimir Barinov <vladimir.barinov@cogentembedded.com>
Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>

---
 arch/arm64/boot/dts/renesas/Makefile           |    2 -
 arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts |   44 +++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

Index: renesas/arch/arm64/boot/dts/renesas/Makefile
===================================================================
--- renesas.orig/arch/arm64/boot/dts/renesas/Makefile
+++ renesas/arch/arm64/boot/dts/renesas/Makefile
@@ -7,7 +7,7 @@ dtb-$(CONFIG_ARCH_R8A7795) += r8a7795-es
 dtb-$(CONFIG_ARCH_R8A7796) += r8a7796-salvator-x.dtb r8a7796-m3ulcb.dtb
 dtb-$(CONFIG_ARCH_R8A7796) += r8a7796-m3ulcb-kf.dtb
 dtb-$(CONFIG_ARCH_R8A7796) += r8a7796-salvator-xs.dtb
-dtb-$(CONFIG_ARCH_R8A77970) += r8a77970-eagle.dtb
+dtb-$(CONFIG_ARCH_R8A77970) += r8a77970-eagle.dtb r8a77970-v3msk.dtb
 dtb-$(CONFIG_ARCH_R8A77995) += r8a77995-draak.dtb
 
 always		:= $(dtb-y)
Index: renesas/arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts
===================================================================
--- /dev/null
+++ renesas/arch/arm64/boot/dts/renesas/r8a77970-v3msk.dts
@@ -0,0 +1,44 @@
+/*
+ * Device Tree Source for the V3M Starter Kit board
+ *
+ * Copyright (C) 2017 Renesas Electronics Corp.
+ * Copyright (C) 2017 Cogent Embedded, Inc.
+ *
+ * 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.
+ */
+
+/dts-v1/;
+#include "r8a77970.dtsi"
+
+/ {
+	model = "Renesas V3M Starter Kit board";
+	compatible = "renesas,v3msk", "renesas,r8a77970";
+
+	aliases {
+		serial0 = &scif0;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+
+	memory@48000000 {
+		device_type = "memory";
+		/* first 128MB is reserved for secure area. */
+		reg = <0x0 0x48000000 0x0 0x38000000>;
+	};
+};
+
+&extal_clk {
+	clock-frequency = <16666666>;
+};
+
+&extalr_clk {
+	clock-frequency = <32768>;
+};
+
+&scif0 {
+	status = "okay";
+};

^ permalink raw reply

* [PATCH 0/2] Add V3M Starter Kit board support
From: Sergei Shtylyov @ 2017-11-24 20:59 UTC (permalink / raw)
  To: Simon Horman, Rob Herring, Catalin Marinas, Will Deacon,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Magnus Damm, Mark Rutland,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Sergei Shtylyov

Hello.

Here's the set of 2 patches against Simon Horman's 'renesas.git' repo's
'renesas-devel-20171124v2-v4.14' tag. I'm adding the device tree support for
the R8A77970-based V3M Starter Kit board.

[1/2] arm64: dts: renesas: initial V3MSK board device tree
[2/2] arm64: dts: renesas: v3msk: add EtherAVB support

WBR, Sergei
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH 0/2] Add V3M Starter Kit board support
From: Sergei Shtylyov @ 2017-11-24 20:59 UTC (permalink / raw)
  To: Simon Horman, Rob Herring, Catalin Marinas, Will Deacon,
	linux-renesas-soc, devicetree
  Cc: Magnus Damm, Mark Rutland, linux-arm-kernel, Sergei Shtylyov

Hello.

Here's the set of 2 patches against Simon Horman's 'renesas.git' repo's
'renesas-devel-20171124v2-v4.14' tag. I'm adding the device tree support for
the R8A77970-based V3M Starter Kit board.

[1/2] arm64: dts: renesas: initial V3MSK board device tree
[2/2] arm64: dts: renesas: v3msk: add EtherAVB support

WBR, Sergei

^ permalink raw reply

* Re: [PATCH] ARM: dts: add minimal device tree for compute model 3
From: Stefan Wahren @ 2017-11-24 20:57 UTC (permalink / raw)
  To: Russell King, kernel, eric, Rob Herring, Catalin Marinas,
	Mark Rutland, Florian Fainelli, linux-arm-kernel,
	linux-rpi-kernel, devicetree
In-Reply-To: <20171124121813.11318-1-kernel@martin.sperl.org>

Hi Martin,

thanks for working on this.

> kernel@martin.sperl.org hat am 24. November 2017 um 13:18 geschrieben:
> 
> 
> From: Martin Sperl <kernel@martin.sperl.org>
> 
> Add a minimal working device tree for the compute model 3
> for both arm and arm64.

i think it's worth to provide more context:

The Raspberry Compute Module 3 is a SoM which contains a BCM2837 processor, 1 GB RAM and a 4 GB eMMC. There is also a carrier board which is called Compute Module IO Board V3.

Datasheet:
https://www.raspberrypi.org/documentation/hardware/computemodule/RPI-CM-DATASHEET-V1_0.pdf

> 
> Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
> ---
>  arch/arm/boot/dts/Makefile                       |  1 +
>  arch/arm/boot/dts/bcm2837-rpi-cm3.dts            | 38 ++++++++++++++++++++++++
>  arch/arm64/boot/dts/broadcom/Makefile            |  1 +
>  arch/arm64/boot/dts/broadcom/bcm2837-rpi-cm3.dts |  2 ++
>  4 files changed, 42 insertions(+)
>  create mode 100644 arch/arm/boot/dts/bcm2837-rpi-cm3.dts
>  create mode 100644 arch/arm64/boot/dts/broadcom/bcm2837-rpi-cm3.dts
> 
> diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
> index eff87a344566..b24a682c450d 100644
> --- a/arch/arm/boot/dts/Makefile
> +++ b/arch/arm/boot/dts/Makefile
> @@ -75,6 +75,7 @@ dtb-$(CONFIG_ARCH_BCM2835) += \
>  	bcm2835-rpi-a-plus.dtb \
>  	bcm2836-rpi-2-b.dtb \
>  	bcm2837-rpi-3-b.dtb \
> +	bcm2837-rpi-cm3.dtb \

Since we need always 2 boards (SoM + carrier), i like to see two separate DT files. A DTSI file for the SoM (describe RAM, eMMC) and a DTS file for the carrier board (please look at arm/boot/dts/imx6ul-tx6ul.dtsi and arm/boot/dts/imx6ul-tx6ul-mainboard.dts as an example).

Suggested include hierarchy:

  bcm2837.dtsi
    bcm2837-rpi-cm3.dtsi
      bcm2837-rpi-cm3-io3.dts

>  	bcm2835-rpi-zero.dtb \
>  	bcm2835-rpi-zero-w.dtb
>  dtb-$(CONFIG_ARCH_BCM_5301X) += \
> diff --git a/arch/arm/boot/dts/bcm2837-rpi-cm3.dts b/arch/arm/boot/dts/bcm2837-rpi-cm3.dts
> new file mode 100644
> index 000000000000..f387bd8bd707
> --- /dev/null
> +++ b/arch/arm/boot/dts/bcm2837-rpi-cm3.dts
> @@ -0,0 +1,38 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/dts-v1/;
> +#include "bcm2837.dtsi"
> +#include "bcm2835-rpi.dtsi"
> +#include "bcm283x-rpi-usb-host.dtsi"
> +
> +/ {
> +	compatible = "raspberrypi,3-compute-model", "brcm,bcm2837";

s/compute-model/compute-module/

> +	model = "Raspberry Pi Compute Module 3";

Raspberry Pi Compute Module 3 on a IO board V3

> +
> +	memory {
> +		reg = <0 0x40000000>;
> +	};
> +
> +	leds {
> +		act {
> +			gpios = <&gpio 47 0>;

Please use GPIO_ACTIVE_HIGH here

> +		};
> +	};
> +};
> +
> +&hdmi {
> +	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
> +};
> +
> +&uart0 {
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&uart0_gpio14>;
> +	status = "okay";
> +};
> +
> +/* SDHOST is used to drive the SD card */

Since this is an eMMC please drop this misleading comment.

> +&sdhost {
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&sdhost_gpio48>;
> +	status = "okay";
> +	bus-width = <4>;

non-removable;

> +};
> diff --git a/arch/arm64/boot/dts/broadcom/Makefile b/arch/arm64/boot/dts/broadcom/Makefile
> index 3df2db7f8878..aecf58f503ae 100644
> --- a/arch/arm64/boot/dts/broadcom/Makefile
> +++ b/arch/arm64/boot/dts/broadcom/Makefile
> @@ -1,5 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0
>  dtb-$(CONFIG_ARCH_BCM2835) += bcm2837-rpi-3-b.dtb
> +dtb-$(CONFIG_ARCH_BCM2835) += bcm2837-rpi-cm3.dtb
>  
>  dts-dirs	+= northstar2
>  dts-dirs	+= stingray
> diff --git a/arch/arm64/boot/dts/broadcom/bcm2837-rpi-cm3.dts b/arch/arm64/boot/dts/broadcom/bcm2837-rpi-cm3.dts
> new file mode 100644
> index 000000000000..a1bba71462ed
> --- /dev/null
> +++ b/arch/arm64/boot/dts/broadcom/bcm2837-rpi-cm3.dts
> @@ -0,0 +1,2 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include "arm/bcm2837-rpi-cm3.dts"
> -- 
> 2.11.0
> 
> 
> _______________________________________________
> linux-rpi-kernel mailing list
> linux-rpi-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rpi-kernel

^ permalink raw reply

* [PATCH] arm64: renesas: document V3MSK board bindings
From: Sergei Shtylyov @ 2017-11-24 20:47 UTC (permalink / raw)
  To: Rob Herring, devicetree, Simon Horman, linux-renesas-soc
  Cc: Mark Rutland, Magnus Damm, linux-arm-kernel, Sergei Shtylyov

[-- Attachment #1: arm64-renesas-document-V3MSK-board-bindings.patch --]
[-- Type: text/plain, Size: 909 bytes --]

Document the V3M Starter Kit device tree bindings, listing it as
a supported board.

This allows to use checkpatch.pl to validate .dts files referring to
the V3MSK board.

Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>

---
 Documentation/devicetree/bindings/arm/shmobile.txt |    2 ++
 1 file changed, 2 insertions(+)

Index: renesas/Documentation/devicetree/bindings/arm/shmobile.txt
===================================================================
--- renesas.orig/Documentation/devicetree/bindings/arm/shmobile.txt
+++ renesas/Documentation/devicetree/bindings/arm/shmobile.txt
@@ -112,6 +112,8 @@ Boards:
     compatible = "renesas,sk-rzg1e", "renesas,r8a7745"
   - SK-RZG1M (YR8A77430S000BE)
     compatible = "renesas,sk-rzg1m", "renesas,r8a7743"
+  - V3MSK
+    compatible = "renesas,v3msk", "renesas,r8a77970"
   - Wheat
     compatible = "renesas,wheat", "renesas,r8a7792"
 

^ permalink raw reply

* Re: [PATCH 03/10] PM / QoS: Fix device resume latency for non PM QoS devices
From: Thierry Reding @ 2017-11-24 20:39 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Manikanta Maddireddy, Jon Hunter, Rob Herring, Frank Rowand,
	Bjorn Helgaas, Rafael J. Wysocki, Thomas Gleixner,
	vidyas-DDmLM1+adcrQT0dZR+AlfA, kthota-DDmLM1+adcrQT0dZR+AlfA,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Linux PCI,
	Linux PM
In-Reply-To: <CAJZ5v0ghcwSHdrbZd_=XA=tHz7=aTUVd66dHkMYRNyXpnqP84w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

[-- Attachment #1: Type: text/plain, Size: 1385 bytes --]

On Fri, Nov 24, 2017 at 05:50:42PM +0100, Rafael J. Wysocki wrote:
> On Fri, Nov 24, 2017 at 4:53 PM, Manikanta Maddireddy
> <mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> wrote:
> > In 'commit 0cc2b4e5a020 ("PM / QoS: Fix device resume latency PM QoS")'
> > PM QoS resume latency modified 0 as "no latency at all". However
> > dev_pm_qos_raw_read_value() returns 0 for devices which doesn't have
> > PM QoS constraints. This is blocking runtime suspend for these devices
> > in rpm_check_suspend_allowed(). Return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT
> > when PM QoS constraints are not available for a particular device.
> >
> > Fixes: 0cc2b4e5a020 ("PM / QoS: Fix device resume latency PM QoS")
> 
> That commit has been reverted, so this patch is not applicable and
> therefore the whole series isn't.
> 
> What kernel is it based off?

It looks like this might have crept in via commit 0759e80b84e3 ("PM /
QoS: Fix device resume latency framework"). But checking more closely,
that commit actually incorporates this change already.

According to the git log the correct commit for this showed up in
linux-next only today, which is probably why Manikanta missed it.

Manikanta: can you try rebasing your series on top of next-20171124?
git should notice that this particular change is already part of that
and drop it during the rebase.

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* [PATCH 2/2] can: mcp2517fd: Add Microchip mcp2517fd CAN FD driver
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2017-11-24 18:35 UTC (permalink / raw)
  To: Wolfgang Grandegger, Marc Kleine-Budde, Rob Herring, Mark Rutland,
	linux-can-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Martin Sperl
In-Reply-To: <20171124183509.12810-1-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>

From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>

This patch adds support for the Microchip mcp2517fd CAN-FD controller.
The mcp2517fd is capable of transmitting and receiving standard data
frames, extended data frames, remote frames and Can-FD frames.
The mcp2517fd interfaces with the host over SPI.

Datasheet:
* http://ww1.microchip.com/downloads/en/DeviceDoc/20005688A.pdf
Reference manual:
* http://ww1.microchip.com/downloads/en/DeviceDoc/20005678A.pdf

Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
 drivers/net/can/spi/Kconfig     |    6 +
 drivers/net/can/spi/Makefile    |    1 +
 drivers/net/can/spi/mcp2517fd.c | 3733 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 3740 insertions(+)
 create mode 100644 drivers/net/can/spi/mcp2517fd.c

diff --git a/drivers/net/can/spi/Kconfig b/drivers/net/can/spi/Kconfig
index 8f2e0dd7b756..03c0176ccfbf 100644
--- a/drivers/net/can/spi/Kconfig
+++ b/drivers/net/can/spi/Kconfig
@@ -13,4 +13,10 @@ config CAN_MCP251X
 	---help---
 	  Driver for the Microchip MCP251x SPI CAN controllers.

+config CAN_MCP2517FD
+	tristate "Microchip MCP2517FD SPI CAN controllers"
+	depends on HAS_DMA
+	---help---
+	  Driver for the Microchip MCP2517FD SPI CAN controllers.
+
 endmenu
diff --git a/drivers/net/can/spi/Makefile b/drivers/net/can/spi/Makefile
index f59fa3731073..7a4ed1998661 100644
--- a/drivers/net/can/spi/Makefile
+++ b/drivers/net/can/spi/Makefile
@@ -5,3 +5,4 @@

 obj-$(CONFIG_CAN_HI311X)	+= hi311x.o
 obj-$(CONFIG_CAN_MCP251X)	+= mcp251x.o
+obj-$(CONFIG_CAN_MCP2517FD)	+= mcp2517fd.o
diff --git a/drivers/net/can/spi/mcp2517fd.c b/drivers/net/can/spi/mcp2517fd.c
new file mode 100644
index 000000000000..11a3f7e7daa8
--- /dev/null
+++ b/drivers/net/can/spi/mcp2517fd.c
@@ -0,0 +1,3733 @@
+/*
+ * CAN bus driver for Microchip 2517FD CAN Controller with SPI Interface
+ *
+ * Copyright 2017 Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * 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/can/led.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/freezer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.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/slab.h>
+#include <linux/sort.h>
+#include <linux/spi/spi.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
+
+#define DEVICE_NAME "mcp2517fd"
+
+/* device description and rational:
+ *
+ * the mcp2517fd is a CanFD controller that also supports can2.0 only
+ * modes.
+ * It is connected via spi to the host and requires at minimum a single
+ * irq line in addition to the SPI lines - it is not mentioned explicitly
+ * in the documentation but in principle SPI 3-wire should be possible.
+ *
+ * The clock connected is typically 4MHz, 20MHz or 40MHz.
+ * For the 4MHz clock the controller contains 10x PLL circuitry.
+ *
+ * The controller itself has 2KB or ECC-SRAM for data.
+ * It also has 32 FIFOs (of up to 32 CAN-frames).
+ * There are 4 Fifo types which can get configured:
+ * * TEF - Transmission Event Fifo - which consumes FIFO 0
+ *   even if it is not configured
+ * * Tansmission Queue - for up to 32 Frames.
+ *   this queue reorders CAN frames to get transmitted following the
+ *   typical CAN dominant/recessive rules on the can bus itself.
+ *   This FIFO is optional.
+ * * TX FIFO: generic TX fifos that can contain arbitrary data
+ *   and which come with a configurable priority for transmission
+ *   It is also possible to have the Controller automatically trigger
+ *   a transfer when a Filter Rule for a RTR frame matches.
+ *   Each of these fifos in principle can get configured for distinct
+ *   dlc sizes (8 thru 64 bytes)
+ * * RX FIFO: generic RX fifo which is filled via filter-rules.
+ *   Each of these fifos in principle can get configured for distinct
+ *   dlc sizes (8 thru 64 bytes)
+ *   Unfortunately there is no filter rule that would allow triggering
+ *   on different frame sizes, so for all practical purposes the
+ *   RX fifos have to be of the same size (unless one wants to experience
+ *   lost data).
+ * When a Can Frame is transmitted fromthe TX Queue or an individual
+ * TX FIFO then a small TEF Frame can get added to the TEF FIFO queue
+ * to log the Transmission of the frame - this includes ID, Flags
+ * (including a custom identifier/index) .
+ *
+ * The controller provides an optional free running counter with a divider
+ * for timestamping of RX frames as well as for TEF entries.
+ *
+ * Driver Implementation details and rational:
+ * * The whole driver has been designed to give best performance
+ *   and as little packet loss as possible with 1MHZ Can frames with DLC=0
+ *   on small/slow devices like the Raspberry Pi 1
+ * * This means that some optimizations for full duplex communication
+ *   have been implemented to avoid CPU introduced latencies
+ *   (especially for spi_write_then_read cases) - this only applies to
+ *   4 wire SPI busses.
+ * * Due to the fact that the TXQ does reorder Can-Frames we do not make
+ *   use of it to avoid unexpected behaviour (say when running a
+ *   firmware upgrade via Can)
+ * * this means we use individual TX-fifos with a given priority and
+ *   we have to wait until all the TX fifos have been transmitted before
+ *   we can restart the networking queue to avoid reordering the frames on
+ *   the Can bus itself.
+ *   Still we can transmit a transmit only Duty-cycle of 66% to 90% on the
+ *   Can bus (at 1MHz).
+ *   The scaling factors here are:
+ *   * Can bus speed - lower Speeds increase Duty-cycle
+ *   * SPI Clock Rate - higher speeds increase duty-cycle
+ *   * CPU speed + SPI implementation - reduces latencies between transfers
+ * * There is a module parameter that allows the modification of the
+ *   number of tx_fifos, which is by default 7.
+ * * The driver offers some module parameters that allow to control the use
+ *   of some optimizations (prefer reading more data than necessary instead
+ *   of multiple SPI transfers - the idea here is that this way we may
+ *   allow the SPI-controller to use DMA instead of programmed IO to
+ *   limit latencies/number of interrupts)
+ *   When we have to read multiple RX frames in CanFD mode:
+ *   * we allow reading all 64 bytes of payload even if DLC <=8
+ *     this mode is used in Can2.0 only mode by default and can not get
+ *     disabled (SRAM Reads have to be a multiple of 4 bytes anyway)
+ *   * Releasing/freeing the RX queue requires writing of 1 byte per fifo.
+ *     unfortunately these 32-bit registers are not ajacent to each other,
+ *     so that for 2 consecutive RX Frames instead of writing 1 byte per
+ *     fifo (with protocol overhead of 2 bytes - so a total of 6 bytes in
+ *     2 transfers) we transmit 13 bytes (with a protocol overhead of 2 -
+ *     so a total of 15 bytes)
+ *     This optimization is only enabled by a module parameter.
+ * * we use TEF + time stamping to record the transmitted frames
+ *   including their timestamp - we use this to order TX and RX frames
+ *   when submitting them to the network stack.
+ * * due to the inability to "filter" based on DLC sizes we have to use
+ *   a common FIFO size. This is 8 bytes for Can2.0 and 64 bytes for CanFD.
+ * * the driver tries to detect the Controller only by reading registers,
+ *   but there are circumstances (e.g. after a crashed driver) where we
+ *   have to "blindly" configure the clock rate to get the controller to
+ *   respond correctly.
+ * * There is one situation where the controller will require a full POR
+ *   (total power off) to recover from a bad Clock configuration.
+ *   This happens when the wrong clock is configured in the device tree
+ *   (say 4MHz are configured, while 20 or 40MHz are used)
+ *   in such a situation the driver tries to enable the PLL, which will
+ *   never synchronize and the controller becomes unresponsive to further
+ *   spi requests until a POR.
+ */
+
+#define MCP2517FD_OST_DELAY_MS		3
+#define MCP2517FD_MIN_CLOCK_FREQUENCY	1000000
+#define MCP2517FD_MAX_CLOCK_FREQUENCY	40000000
+#define MCP2517FD_PLL_MULTIPLIER	10
+#define MCP2517FD_AUTO_PLL_MAX_CLOCK_FREQUENCY				\
+	(MCP2517FD_MAX_CLOCK_FREQUENCY / MCP2517FD_PLL_MULTIPLIER)
+#define MCP2517FD_SCLK_DIVIDER		2
+
+#define MCP2517FD_OSC_POLLING_JIFFIES	(HZ / 2)
+
+#define TX_ECHO_SKB_MAX	32
+
+#define INSTRUCTION_RESET		0x0000
+#define INSTRUCTION_READ		0x3000
+#define INSTRUCTION_WRITE		0x2000
+#define INSTRUCTION_READ_CRC		0xB000
+#define INSTRUCTION_WRITE_CRC		0xA000
+#define INSTRUCTION_WRITE_SAVE		0xC000
+
+#define ADDRESS_MASK			0x0fff
+
+#define MCP2517FD_SFR_BASE(x)		(0xE00 + (x))
+#define MCP2517FD_OSC			MCP2517FD_SFR_BASE(0x00)
+#  define MCP2517FD_OSC_PLLEN		BIT(0)
+#  define MCP2517FD_OSC_OSCDIS		BIT(2)
+#  define MCP2517FD_OSC_SCLKDIV		BIT(4)
+#  define MCP2517FD_OSC_CLKODIV_BITS	2
+#  define MCP2517FD_OSC_CLKODIV_SHIFT	5
+#  define MCP2517FD_OSC_CLKODIV_MASK			\
+	GENMASK(MCP2517FD_OSC_CLKODIV_SHIFT		\
+		+ MCP2517FD_OSC_CLKODIV_BITS - 1,	\
+		MCP2517FD_OSC_CLKODIV_SHIFT)
+#  define MCP2517FD_OSC_CLKODIV_10	3
+#  define MCP2517FD_OSC_CLKODIV_4	2
+#  define MCP2517FD_OSC_CLKODIV_2	1
+#  define MCP2517FD_OSC_CLKODIV_1	0
+#  define MCP2517FD_OSC_PLLRDY		BIT(8)
+#  define MCP2517FD_OSC_OSCRDY		BIT(10)
+#  define MCP2517FD_OSC_SCLKRDY		BIT(12)
+#define MCP2517FD_IOCON			MCP2517FD_SFR_BASE(0x04)
+#  define MCP2517FD_IOCON_TRIS0		BIT(0)
+#  define MCP2517FD_IOCON_TRIS1		BIT(1)
+#  define MCP2517FD_IOCON_XSTBYEN	BIT(6)
+#  define MCP2517FD_IOCON_LAT0		BIT(8)
+#  define MCP2517FD_IOCON_LAT1		BIT(9)
+#  define MCP2517FD_IOCON_GPIO0		BIT(16)
+#  define MCP2517FD_IOCON_GPIO1		BIT(17)
+#  define MCP2517FD_IOCON_PM0		BIT(24)
+#  define MCP2517FD_IOCON_PM1		BIT(25)
+#  define MCP2517FD_IOCON_TXCANOD	BIT(28)
+#  define MCP2517FD_IOCON_SOF		BIT(29)
+#  define MCP2517FD_IOCON_INTOD		BIT(29)
+#define MCP2517FD_CRC			MCP2517FD_SFR_BASE(0x08)
+#  define MCP2517FD_CRC_MASK		GENMASK(15, 0)
+#  define MCP2517FD_CRC_CRCERRIE	BIT(16)
+#  define MCP2517FD_CRC_FERRIE		BIT(17)
+#  define MCP2517FD_CRC_CRCERRIF	BIT(24)
+#  define MCP2517FD_CRC_FERRIF		BIT(25)
+#define MCP2517FD_ECCCON		MCP2517FD_SFR_BASE(0x0C)
+#  define MCP2517FD_ECCCON_ECCEN	BIT(0)
+#  define MCP2517FD_ECCCON_SECIE	BIT(1)
+#  define MCP2517FD_ECCCON_DEDIE	BIT(2)
+#  define MCP2517FD_ECCCON_PARITY_BITS 6
+#  define MCP2517FD_ECCCON_PARITY_SHIFT 8
+#  define MCP2517FD_ECCCON_PARITY_MASK			\
+	GENMASK(MCP2517FD_ECCCON_PARITY_SHIFT		\
+		+ MCP2517FD_ECCCON_PARITY_BITS - 1,	\
+		MCP2517FD_ECCCON_PARITY_SHIFT)
+#define MCP2517FD_ECCSTAT		MCP2517FD_SFR_BASE(0x10)
+#  define MCP2517FD_ECCSTAT_SECIF	BIT(1)
+#  define MCP2517FD_ECCSTAT_DEDIF	BIT(2)
+#  define MCP2517FD_ECCSTAT_ERRADDR_SHIFT 8
+#  define MCP2517FD_ECCSTAT_ERRADDR_MASK	      \
+	GENMASK(MCP2517FD_ECCSTAT_ERRADDR_SHIFT + 11, \
+		MCP2517FD_ECCSTAT_ERRADDR_SHIFT)
+
+#define CAN_SFR_BASE(x)			(0x000 + (x))
+#define CAN_CON				CAN_SFR_BASE(0x00)
+#  define CAN_CON_DNCNT_BITS		5
+#  define CAN_CON_DNCNT_SHIFT		0
+#  define CAN_CON_DNCNT_MASK					\
+	GENMASK(CAN_CON_DNCNT_SHIFT + CAN_CON_DNCNT_BITS - 1,	\
+		CAN_CON_DNCNT_SHIFT)
+#  define CAN_CON_ISOCRCEN		BIT(5)
+#  define CAN_CON_PXEDIS		BIT(6)
+#  define CAN_CON_WAKFIL		BIT(8)
+#  define CAN_CON_WFT_BITS		2
+#  define CAN_CON_WFT_SHIFT		9
+#  define CAN_CON_WFT_MASK					\
+	GENMASK(CAN_CON_WFT_SHIFT + CAN_CON_WFT_BITS - 1,	\
+		CAN_CON_WFT_SHIFT)
+#  define CAN_CON_BUSY			BIT(11)
+#  define CAN_CON_BRSDIS		BIT(12)
+#  define CAN_CON_RTXAT			BIT(16)
+#  define CAN_CON_ESIGM			BIT(17)
+#  define CAN_CON_SERR2LOM		BIT(18)
+#  define CAN_CON_STEF			BIT(19)
+#  define CAN_CON_TXQEN			BIT(20)
+#  define CAN_CON_OPMODE_BITS		3
+#  define CAN_CON_OPMOD_SHIFT		21
+#  define CAN_CON_OPMOD_MASK					\
+	GENMASK(CAN_CON_OPMOD_SHIFT + CAN_CON_OPMODE_BITS - 1,	\
+		CAN_CON_OPMOD_SHIFT)
+#  define CAN_CON_REQOP_BITS		3
+#  define CAN_CON_REQOP_SHIFT		24
+#  define CAN_CON_REQOP_MASK					\
+	GENMASK(CAN_CON_REQOP_SHIFT + CAN_CON_REQOP_BITS - 1,	\
+		CAN_CON_REQOP_SHIFT)
+#    define CAN_CON_MODE_MIXED			0
+#    define CAN_CON_MODE_SLEEP			1
+#    define CAN_CON_MODE_INTERNAL_LOOPBACK	2
+#    define CAN_CON_MODE_LISTENONLY		3
+#    define CAN_CON_MODE_CONFIG			4
+#    define CAN_CON_MODE_EXTERNAL_LOOPBACK	5
+#    define CAN_CON_MODE_CAN2_0			6
+#    define CAN_CON_MODE_RESTRICTED		7
+#  define CAN_CON_ABAT			BIT(27)
+#  define CAN_CON_TXBWS_BITS		3
+#  define CAN_CON_TXBWS_SHIFT		28
+#  define CAN_CON_TXBWS_MASK					\
+	GENMASK(CAN_CON_TXBWS_SHIFT + CAN_CON_TXBWS_BITS - 1,	\
+		CAN_CON_TXBWS_SHIFT)
+#  define CAN_CON_DEFAULT				\
+	(CAN_CON_ISOCRCEN |				\
+	 CAN_CON_PXEDIS |				\
+	 CAN_CON_WAKFIL |				\
+	 (3 << CAN_CON_WFT_SHIFT) |			\
+	 CAN_CON_STEF |					\
+	 CAN_CON_TXQEN |				\
+	 (CAN_CON_MODE_CONFIG << CAN_CON_OPMOD_SHIFT) |	\
+	 (CAN_CON_MODE_CONFIG << CAN_CON_REQOP_SHIFT))
+#  define CAN_CON_DEFAULT_MASK	\
+	(CAN_CON_DNCNT_MASK |	\
+	 CAN_CON_ISOCRCEN |	\
+	 CAN_CON_PXEDIS |	\
+	 CAN_CON_WAKFIL |	\
+	 CAN_CON_WFT_MASK |	\
+	 CAN_CON_BRSDIS |	\
+	 CAN_CON_RTXAT |	\
+	 CAN_CON_ESIGM |	\
+	 CAN_CON_SERR2LOM |	\
+	 CAN_CON_STEF |		\
+	 CAN_CON_TXQEN |	\
+	 CAN_CON_OPMOD_MASK |	\
+	 CAN_CON_REQOP_MASK |	\
+	 CAN_CON_ABAT |		\
+	 CAN_CON_TXBWS_MASK)
+#define CAN_NBTCFG			CAN_SFR_BASE(0x04)
+#  define CAN_NBTCFG_SJW_BITS		7
+#  define CAN_NBTCFG_SJW_SHIFT		0
+#  define CAN_NBTCFG_SJW_MASK					\
+	GENMASK(CAN_NBTCFG_SJW_SHIFT + CAN_NBTCFG_SJW_BITS - 1, \
+		CAN_NBTCFG_SJW_SHIFT)
+#  define CAN_NBTCFG_TSEG2_BITS		7
+#  define CAN_NBTCFG_TSEG2_SHIFT	8
+#  define CAN_NBTCFG_TSEG2_MASK					    \
+	GENMASK(CAN_NBTCFG_TSEG2_SHIFT + CAN_NBTCFG_TSEG2_BITS - 1, \
+		CAN_NBTCFG_TSEG2_SHIFT)
+#  define CAN_NBTCFG_TSEG1_BITS		8
+#  define CAN_NBTCFG_TSEG1_SHIFT	16
+#  define CAN_NBTCFG_TSEG1_MASK					    \
+	GENMASK(CAN_NBTCFG_TSEG1_SHIFT + CAN_NBTCFG_TSEG1_BITS - 1, \
+		CAN_NBTCFG_TSEG1_SHIFT)
+#  define CAN_NBTCFG_BRP_BITS		8
+#  define CAN_NBTCFG_BRP_SHIFT		24
+#  define CAN_NBTCFG_BRP_MASK					\
+	GENMASK(CAN_NBTCFG_BRP_SHIFT + CAN_NBTCFG_BRP_BITS - 1, \
+		CAN_NBTCFG_BRP_SHIFT)
+#define CAN_DBTCFG			CAN_SFR_BASE(0x08)
+#  define CAN_DBTCFG_SJW_BITS		4
+#  define CAN_DBTCFG_SJW_SHIFT		0
+#  define CAN_DBTCFG_SJW_MASK					\
+	GENMASK(CAN_DBTCFG_SJW_SHIFT + CAN_DBTCFG_SJW_BITS - 1, \
+		CAN_DBTCFG_SJW_SHIFT)
+#  define CAN_DBTCFG_TSEG2_BITS		4
+#  define CAN_DBTCFG_TSEG2_SHIFT	8
+#  define CAN_DBTCFG_TSEG2_MASK					    \
+	GENMASK(CAN_DBTCFG_TSEG2_SHIFT + CAN_DBTCFG_TSEG2_BITS - 1, \
+		CAN_DBTCFG_TSEG2_SHIFT)
+#  define CAN_DBTCFG_TSEG1_BITS		5
+#  define CAN_DBTCFG_TSEG1_SHIFT	16
+#  define CAN_DBTCFG_TSEG1_MASK					    \
+	GENMASK(CAN_DBTCFG_TSEG1_SHIFT + CAN_DBTCFG_TSEG1_BITS - 1, \
+		CAN_DBTCFG_TSEG1_SHIFT)
+#  define CAN_DBTCFG_BRP_BITS		8
+#  define CAN_DBTCFG_BRP_SHIFT		24
+#  define CAN_DBTCFG_BRP_MASK					\
+	GENMASK(CAN_DBTCFG_BRP_SHIFT + CAN_DBTCFG_BRP_BITS - 1, \
+		CAN_DBTCFG_BRP_SHIFT)
+#define CAN_TDC				CAN_SFR_BASE(0x0C)
+#  define CAN_TDC_TDCV_BITS		5
+#  define CAN_TDC_TDCV_SHIFT		0
+#  define CAN_TDC_TDCV_MASK					\
+	GENMASK(CAN_TDC_TDCV_SHIFT + CAN_TDC_TDCV_BITS - 1, \
+		CAN_TDC_TDCV_SHIFT)
+#  define CAN_TDC_TDCO_BITS		5
+#  define CAN_TDC_TDCO_SHIFT		8
+#  define CAN_TDC_TDCO_MASK					\
+	GENMASK(CAN_TDC_TDCO_SHIFT + CAN_TDC_TDCO_BITS - 1, \
+		CAN_TDC_TDCO_SHIFT)
+#  define CAN_TDC_TDCMOD_BITS		2
+#  define CAN_TDC_TDCMOD_SHIFT		16
+#  define CAN_TDC_TDCMOD_MASK					\
+	GENMASK(CAN_TDC_TDCMOD_SHIFT + CAN_TDC_TDCMOD_BITS - 1, \
+		CAN_TDC_TDCMOD_SHIFT)
+#  define CAN_TDC_SID11EN		BIT(24)
+#  define CAN_TDC_EDGFLTEN		BIT(25)
+#define CAN_TBC				CAN_SFR_BASE(0x10)
+#define CAN_TSCON			CAN_SFR_BASE(0x14)
+#  define CAN_TSCON_TBCPRE_BITS		10
+#  define CAN_TSCON_TBCPRE_SHIFT	0
+#  define CAN_TSCON_TBCPRE_MASK					    \
+	GENMASK(CAN_TSCON_TBCPRE_SHIFT + CAN_TSCON_TBCPRE_BITS - 1, \
+		CAN_TSCON_TBCPRE_SHIFT)
+#  define CAN_TSCON_TBCEN		BIT(16)
+#  define CAN_TSCON_TSEOF		BIT(17)
+#  define CAN_TSCON_TSRES		BIT(18)
+#define CAN_VEC				CAN_SFR_BASE(0x18)
+#  define CAN_VEC_ICODE_BITS		7
+#  define CAN_VEC_ICODE_SHIFT		0
+#  define CAN_VEC_ICODE_MASK					    \
+	GENMASK(CAN_VEC_ICODE_SHIFT + CAN_VEC_ICODE_BITS - 1,	    \
+		CAN_VEC_ICODE_SHIFT)
+#  define CAN_VEC_FILHIT_BITS		5
+#  define CAN_VEC_FILHIT_SHIFT		8
+#  define CAN_VEC_FILHIT_MASK					\
+	GENMASK(CAN_VEC_FILHIT_SHIFT + CAN_VEC_FILHIT_BITS - 1, \
+		CAN_VEC_FILHIT_SHIFT)
+#  define CAN_VEC_TXCODE_BITS		7
+#  define CAN_VEC_TXCODE_SHIFT		16
+#  define CAN_VEC_TXCODE_MASK					\
+	GENMASK(CAN_VEC_TXCODE_SHIFT + CAN_VEC_TXCODE_BITS - 1, \
+		CAN_VEC_TXCODE_SHIFT)
+#  define CAN_VEC_RXCODE_BITS		7
+#  define CAN_VEC_RXCODE_SHIFT		24
+#  define CAN_VEC_RXCODE_MASK					\
+	GENMASK(CAN_VEC_RXCODE_SHIFT + CAN_VEC_RXCODE_BITS - 1, \
+		CAN_VEC_RXCODE_SHIFT)
+#define CAN_INT				CAN_SFR_BASE(0x1C)
+#  define CAN_INT_IF_SHIFT		0
+#  define CAN_INT_TXIF			BIT(0)
+#  define CAN_INT_RXIF			BIT(1)
+#  define CAN_INT_TBCIF			BIT(2)
+#  define CAN_INT_MODIF			BIT(3)
+#  define CAN_INT_TEFIF			BIT(4)
+#  define CAN_INT_ECCIF			BIT(8)
+#  define CAN_INT_SPICRCIF		BIT(9)
+#  define CAN_INT_TXATIF		BIT(10)
+#  define CAN_INT_RXOVIF		BIT(11)
+#  define CAN_INT_SERRIF		BIT(12)
+#  define CAN_INT_CERRIF		BIT(13)
+#  define CAN_INT_WAKIF			BIT(14)
+#  define CAN_INT_IVMIF			BIT(15)
+#  define CAN_INT_IF_MASK		\
+	(CAN_INT_TXIF |			\
+	 CAN_INT_RXIF |			\
+	 CAN_INT_TBCIF	|		\
+	 CAN_INT_MODIF	|		\
+	 CAN_INT_TEFIF	|		\
+	 CAN_INT_ECCIF	|		\
+	 CAN_INT_SPICRCIF |		\
+	 CAN_INT_TXATIF |		\
+	 CAN_INT_RXOVIF |		\
+	 CAN_INT_CERRIF |		\
+	 CAN_INT_SERRIF |		\
+	 CAN_INT_WAKEIF |		\
+	 CAN_INT_IVMIF)
+#  define CAN_INT_IE_SHIFT		16
+#  define CAN_INT_TXIE			(CAN_INT_TXIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_RXIE			(CAN_INT_RXIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TBCIE			(CAN_INT_TBCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_MODIE			(CAN_INT_MODIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TEFIE			(CAN_INT_TEFIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_ECCIE			(CAN_INT_ECCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_SPICRCIE		\
+	(CAN_INT_SPICRCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TXATIE		(CAN_INT_TXATIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_RXOVIE		(CAN_INT_RXOVIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_CERRIE		(CAN_INT_CERRIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_SERRIE		(CAN_INT_SERRIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_WAKIE			(CAN_INT_WAKIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_IVMIE			(CAN_INT_IVMIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_IE_MASK		\
+	(CAN_INT_TXIE |			\
+	 CAN_INT_RXIE |			\
+	 CAN_INT_TBCIE	|		\
+	 CAN_INT_MODIE	|		\
+	 CAN_INT_TEFIE	|		\
+	 CAN_INT_ECCIE	|		\
+	 CAN_INT_SPICRCIE |		\
+	 CAN_INT_TXATIE |		\
+	 CAN_INT_RXOVIE |		\
+	 CAN_INT_CERRIE |		\
+	 CAN_INT_SERRIE |		\
+	 CAN_INT_WAKEIE |		\
+	 CAN_INT_IVMIE)
+#define CAN_RXIF			CAN_SFR_BASE(0x20)
+#define CAN_TXIF			CAN_SFR_BASE(0x24)
+#define CAN_RXOVIF			CAN_SFR_BASE(0x28)
+#define CAN_TXATIF			CAN_SFR_BASE(0x2C)
+#define CAN_TXREQ			CAN_SFR_BASE(0x30)
+#define CAN_TREC			CAN_SFR_BASE(0x34)
+#  define CAN_TREC_REC_BITS		8
+#  define CAN_TREC_REC_SHIFT		0
+#  define CAN_TREC_REC_MASK				    \
+	GENMASK(CAN_TREC_REC_SHIFT + CAN_TREC_REC_BITS - 1, \
+		CAN_TREC_REC_SHIFT)
+#  define CAN_TREC_TEC_BITS		8
+#  define CAN_TREC_TEC_SHIFT		8
+#  define CAN_TREC_TEC_MASK				    \
+	GENMASK(CAN_TREC_TEC_SHIFT + CAN_TREC_TEC_BITS - 1, \
+		CAN_TREC_TEC_SHIFT)
+#  define CAN_TREC_EWARN		BIT(16)
+#  define CAN_TREC_RXWARN		BIT(17)
+#  define CAN_TREC_TXWARN		BIT(18)
+#  define CAN_TREC_RXBP			BIT(19)
+#  define CAN_TREC_TXBP			BIT(20)
+#  define CAN_TREC_TXBO			BIT(21)
+#define CAN_BDIAG0			CAN_SFR_BASE(0x38)
+#  define CAN_BDIAG0_NRERRCNT_BITS	8
+#  define CAN_BDIAG0_NRERRCNT_SHIFT	0
+#  define CAN_BDIAG0_NRERRCNT_MASK				\
+	GENMASK(CAN_BDIAG0_NRERRCNT_SHIFT + CAN_BDIAG0_NRERRCNT_BITS - 1, \
+		CAN_BDIAG0_NRERRCNT_SHIFT)
+#  define CAN_BDIAG0_NTERRCNT_BITS	8
+#  define CAN_BDIAG0_NTERRCNT_SHIFT	8
+#  define CAN_BDIAG0_NTERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_NTERRCNT_SHIFT + CAN_BDIAG0_NTERRCNT_BITS - 1, \
+		CAN_BDIAG0_NTERRCNT_SHIFT)
+#  define CAN_BDIAG0_DRERRCNT_BITS	8
+#  define CAN_BDIAG0_DRERRCNT_SHIFT	16
+#  define CAN_BDIAG0_DRERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_DRERRCNT_SHIFT + CAN_BDIAG0_DRERRCNT_BITS - 1, \
+		CAN_BDIAG0_DRERRCNT_SHIFT)
+#  define CAN_BDIAG0_DTERRCNT_BITS	8
+#  define CAN_BDIAG0_DTERRCNT_SHIFT	24
+#  define CAN_BDIAG0_DTERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_DTERRCNT_SHIFT + CAN_BDIAG0_DTERRCNT_BITS - 1, \
+		CAN_BDIAG0_DTERRCNT_SHIFT)
+#define CAN_BDIAG1			CAN_SFR_BASE(0x3C)
+#  define CAN_BDIAG1_EFMSGCNT_BITS	16
+#  define CAN_BDIAG1_EFMSGCNT_SHIFT	0
+#  define CAN_BDIAG1_EFMSGCNT_MASK					\
+	GENMASK(CAN_BDIAG1_EFMSGCNT_SHIFT + CAN_BDIAG1_EFMSGCNT_BITS - 1, \
+		CAN_BDIAG1_EFMSGCNT_SHIFT)
+#  define CAN_BDIAG1_NBIT0ERR		BIT(16)
+#  define CAN_BDIAG1_NBIT1ERR		BIT(17)
+#  define CAN_BDIAG1_NACKERR		BIT(18)
+#  define CAN_BDIAG1_NSTUFERR		BIT(19)
+#  define CAN_BDIAG1_NFORMERR		BIT(20)
+#  define CAN_BDIAG1_NCRCERR		BIT(21)
+#  define CAN_BDIAG1_TXBOERR		BIT(23)
+#  define CAN_BDIAG1_DBIT0ERR		BIT(24)
+#  define CAN_BDIAG1_DBIT1ERR		BIT(25)
+#  define CAN_BDIAG1_DFORMERR		BIT(27)
+#  define CAN_BDIAG1_DSTUFERR		BIT(28)
+#  define CAN_BDIAG1_DCRCERR		BIT(29)
+#  define CAN_BDIAG1_ESI		BIT(30)
+#  define CAN_BDIAG1_DLCMM		BIT(31)
+#define CAN_TEFCON			CAN_SFR_BASE(0x40)
+#  define CAN_TEFCON_TEFNEIE		BIT(0)
+#  define CAN_TEFCON_TEFHIE		BIT(1)
+#  define CAN_TEFCON_TEFFIE		BIT(2)
+#  define CAN_TEFCON_TEFOVIE		BIT(3)
+#  define CAN_TEFCON_TEFTSEN		BIT(5)
+#  define CAN_TEFCON_UINC		BIT(8)
+#  define CAN_TEFCON_FRESET		BIT(10)
+#  define CAN_TEFCON_FSIZE_BITS		5
+#  define CAN_TEFCON_FSIZE_SHIFT	24
+#  define CAN_TEFCON_FSIZE_MASK					    \
+	GENMASK(CAN_TEFCON_FSIZE_SHIFT + CAN_TEFCON_FSIZE_BITS - 1, \
+		CAN_TEFCON_FSIZE_SHIFT)
+#define CAN_TEFSTA			CAN_SFR_BASE(0x44)
+#  define CAN_TEFSTA_TEFNEIF		BIT(0)
+#  define CAN_TEFSTA_TEFHIF		BIT(1)
+#  define CAN_TEFSTA_TEFFIF		BIT(2)
+#  define CAN_TEFSTA_TEVOVIF		BIT(3)
+#define CAN_TEFUA			CAN_SFR_BASE(0x48)
+#define CAN_RESERVED			CAN_SFR_BASE(0x4C)
+#define CAN_TXQCON			CAN_SFR_BASE(0x50)
+#  define CAN_TXQCON_TXQNIE		BIT(0)
+#  define CAN_TXQCON_TXQEIE		BIT(2)
+#  define CAN_TXQCON_TXATIE		BIT(4)
+#  define CAN_TXQCON_TXEN		BIT(7)
+#  define CAN_TXQCON_UINC		BIT(8)
+#  define CAN_TXQCON_TXREQ		BIT(9)
+#  define CAN_TXQCON_FRESET		BIT(10)
+#  define CAN_TXQCON_TXPRI_BITS		5
+#  define CAN_TXQCON_TXPRI_SHIFT	16
+#  define CAN_TXQCON_TXPRI_MASK					    \
+	GENMASK(CAN_TXQCON_TXPRI_SHIFT + CAN_TXQCON_TXPRI_BITS - 1, \
+		CAN_TXQCON_TXPRI_SHIFT)
+#  define CAN_TXQCON_TXAT_BITS		2
+#  define CAN_TXQCON_TXAT_SHIFT		21
+#  define CAN_TXQCON_TXAT_MASK					    \
+	GENMASK(CAN_TXQCON_TXAT_SHIFT + CAN_TXQCON_TXAT_BITS - 1, \
+		CAN_TXQCON_TXAT_SHIFT)
+#  define CAN_TXQCON_FSIZE_BITS		5
+#  define CAN_TXQCON_FSIZE_SHIFT	24
+#  define CAN_TXQCON_FSIZE_MASK					    \
+	GENMASK(CAN_TXQCON_FSIZE_SHIFT + CAN_TXQCON_FSIZE_BITS - 1, \
+		CAN_TXQCON_FSIZE_SHIFT)
+#  define CAN_TXQCON_PLSIZE_BITS	3
+#  define CAN_TXQCON_PLSIZE_SHIFT	29
+#  define CAN_TXQCON_PLSIZE_MASK				      \
+	GENMASK(CAN_TXQCON_PLSIZE_SHIFT + CAN_TXQCON_PLSIZE_BITS - 1, \
+		CAN_TXQCON_PLSIZE_SHIFT)
+#    define CAN_TXQCON_PLSIZE_8		0
+#    define CAN_TXQCON_PLSIZE_12	1
+#    define CAN_TXQCON_PLSIZE_16	2
+#    define CAN_TXQCON_PLSIZE_20	3
+#    define CAN_TXQCON_PLSIZE_24	4
+#    define CAN_TXQCON_PLSIZE_32	5
+#    define CAN_TXQCON_PLSIZE_48	6
+#    define CAN_TXQCON_PLSIZE_64	7
+
+#define CAN_TXQSTA			CAN_SFR_BASE(0x54)
+#  define CAN_TXQSTA_TXQNIF		BIT(0)
+#  define CAN_TXQSTA_TXQEIF		BIT(2)
+#  define CAN_TXQSTA_TXATIF		BIT(4)
+#  define CAN_TXQSTA_TXERR		BIT(5)
+#  define CAN_TXQSTA_TXLARB		BIT(6)
+#  define CAN_TXQSTA_TXABT		BIT(7)
+#  define CAN_TXQSTA_TXQCI_BITS		5
+#  define CAN_TXQSTA_TXQCI_SHIFT	8
+#  define CAN_TXQSTA_TXQCI_MASK					    \
+	GENMASK(CAN_TXQSTA_TXQCI_SHIFT + CAN_TXQSTA_TXQCI_BITS - 1, \
+		CAN_TXQSTA_TXQCI_SHIFT)
+
+#define CAN_TXQUA			CAN_SFR_BASE(0x58)
+#define CAN_FIFOCON(x)			CAN_SFR_BASE(0x5C + 12 * (x - 1))
+#define CAN_FIFOCON_TFNRFNIE		BIT(0)
+#define CAN_FIFOCON_TFHRFHIE		BIT(1)
+#define CAN_FIFOCON_TFERFFIE		BIT(2)
+#define CAN_FIFOCON_RXOVIE		BIT(3)
+#define CAN_FIFOCON_TXATIE		BIT(4)
+#define CAN_FIFOCON_RXTSEN		BIT(5)
+#define CAN_FIFOCON_RTREN		BIT(6)
+#define CAN_FIFOCON_TXEN		BIT(7)
+#define CAN_FIFOCON_UINC		BIT(8)
+#define CAN_FIFOCON_TXREQ		BIT(9)
+#define CAN_FIFOCON_FRESET		BIT(10)
+#  define CAN_FIFOCON_TXPRI_BITS	5
+#  define CAN_FIFOCON_TXPRI_SHIFT	16
+#  define CAN_FIFOCON_TXPRI_MASK					\
+	GENMASK(CAN_FIFOCON_TXPRI_SHIFT + CAN_FIFOCON_TXPRI_BITS - 1,	\
+		CAN_FIFOCON_TXPRI_SHIFT)
+#  define CAN_FIFOCON_TXAT_BITS		2
+#  define CAN_FIFOCON_TXAT_SHIFT	21
+#  define CAN_FIFOCON_TXAT_MASK					    \
+	GENMASK(CAN_FIFOCON_TXAT_SHIFT + CAN_FIFOCON_TXAT_BITS - 1, \
+		CAN_FIFOCON_TXAT_SHIFT)
+#  define CAN_FIFOCON_TXAT_ONE_SHOT	0
+#  define CAN_FIFOCON_TXAT_THREE_SHOT	1
+#  define CAN_FIFOCON_TXAT_UNLIMITED	2
+#  define CAN_FIFOCON_FSIZE_BITS	5
+#  define CAN_FIFOCON_FSIZE_SHIFT	24
+#  define CAN_FIFOCON_FSIZE_MASK					\
+	GENMASK(CAN_FIFOCON_FSIZE_SHIFT + CAN_FIFOCON_FSIZE_BITS - 1,	\
+		CAN_FIFOCON_FSIZE_SHIFT)
+#  define CAN_FIFOCON_PLSIZE_BITS	3
+#  define CAN_FIFOCON_PLSIZE_SHIFT	29
+#  define CAN_FIFOCON_PLSIZE_MASK					\
+	GENMASK(CAN_FIFOCON_PLSIZE_SHIFT + CAN_FIFOCON_PLSIZE_BITS - 1, \
+		CAN_FIFOCON_PLSIZE_SHIFT)
+#define CAN_FIFOSTA(x)			CAN_SFR_BASE(0x60 + 12 * (x - 1))
+#  define CAN_FIFOSTA_TFNRFNIF		BIT(0)
+#  define CAN_FIFOSTA_TFHRFHIF		BIT(1)
+#  define CAN_FIFOSTA_TFERFFIF		BIT(2)
+#  define CAN_FIFOSTA_RXOVIF		BIT(3)
+#  define CAN_FIFOSTA_TXATIF		BIT(4)
+#  define CAN_FIFOSTA_RXTSEN		BIT(5)
+#  define CAN_FIFOSTA_RTREN		BIT(6)
+#  define CAN_FIFOSTA_TXEN		BIT(7)
+#  define CAN_FIFOSTA_FIFOCI_BITS	5
+#  define CAN_FIFOSTA_FIFOCI_SHIFT	8
+#  define CAN_FIFOSTA_FIFOCI_MASK					\
+	GENMASK(CAN_FIFOSTA_FIFOCI_SHIFT + CAN_FIFOSTA_FIFOCI_BITS - 1, \
+		CAN_FIFOSTA_FIFOCI_SHIFT)
+#define CAN_FIFOUA(x)			CAN_SFR_BASE(0x64 + 12 * (x - 1))
+#define CAN_FLTCON(x)			CAN_SFR_BASE(0x1D0 + (x & 0x1c))
+#  define CAN_FILCON_SHIFT(x)		((x & 3) * 8)
+#  define CAN_FILCON_BITS(x)		4
+#  define CAN_FILCON_MASK(x)					\
+	GENMASK(CAN_FILCON_SHIFT(x) + CAN_FILCON_BITS(x) - 1,	\
+		CAN_FILCON_SHIFT(x))
+#  define CAN_FIFOCON_FLTEN(x)		BIT(7 + CAN_FILCON_SHIFT(x))
+#define CAN_FLTOBJ(x)			CAN_SFR_BASE(0x1F0 + 8 * x)
+#  define CAN_FILOBJ_SID_BITS		11
+#  define CAN_FILOBJ_SID_SHIFT		0
+#  define CAN_FILOBJ_SID_MASK					\
+	GENMASK(CAN_FILOBJ_SID_SHIFT + CAN_FILOBJ_SID_BITS - 1, \
+		CAN_FILOBJ_SID_SHIFT)
+#  define CAN_FILOBJ_EID_BITS		18
+#  define CAN_FILOBJ_EID_SHIFT		12
+#  define CAN_FILOBJ_EID_MASK					\
+	GENMASK(CAN_FILOBJ_EID_SHIFT + CAN_FILOBJ_EID_BITS - 1, \
+		CAN_FILOBJ_EID_SHIFT)
+#  define CAN_FILOBJ_SID11		BIT(29)
+#  define CAN_FILOBJ_EXIDE		BIT(30)
+#define CAN_FLTMASK(x)			CAN_SFR_BASE(0x1F4 + 8 * x)
+#  define CAN_FILMASK_MSID_BITS		11
+#  define CAN_FILMASK_MSID_SHIFT	0
+#  define CAN_FILMASK_MSID_MASK					\
+	GENMASK(CAN_FILMASK_MSID_SHIFT + CAN_FILMASK_MSID_BITS - 1, \
+		CAN_FILMASK_MSID_SHIFT)
+#  define CAN_FILMASK_MEID_BITS		18
+#  define CAN_FILMASK_MEID_SHIFT	12
+#  define CAN_FILMASK_MEID_MASK					\
+	GENMASK(CAN_FILMASK_MEID_SHIFT + CAN_FILMASK_MEID_BITS - 1, \
+		CAN_FILMASK_MEID_SHIFT)
+#  define CAN_FILMASK_MSID11		BIT(29)
+#  define CAN_FILMASK_MIDE		BIT(30)
+
+#define CAN_OBJ_ID_SID_BITS		11
+#define CAN_OBJ_ID_SID_SHIFT		0
+#define CAN_OBJ_ID_SID_MASK					\
+	GENMASK(CAN_OBJ_ID_SID_SHIFT + CAN_OBJ_ID_SID_BITS - 1, \
+		CAN_OBJ_ID_SID_SHIFT)
+#define CAN_OBJ_ID_EID_BITS		18
+#define CAN_OBJ_ID_EID_SHIFT		11
+#define CAN_OBJ_ID_EID_MASK					\
+	GENMASK(CAN_OBJ_ID_EID_SHIFT + CAN_OBJ_ID_EID_BITS - 1, \
+		CAN_OBJ_ID_EID_SHIFT)
+#define CAN_OBJ_ID_SID_BIT11		BIT(29)
+
+#define CAN_OBJ_FLAGS_DLC_BITS		4
+#define CAN_OBJ_FLAGS_DLC_SHIFT		0
+#define CAN_OBJ_FLAGS_DLC_MASK					      \
+	GENMASK(CAN_OBJ_FLAGS_DLC_SHIFT + CAN_OBJ_FLAGS_DLC_BITS - 1, \
+		CAN_OBJ_FLAGS_DLC_SHIFT)
+#define CAN_OBJ_FLAGS_IDE		BIT(4)
+#define CAN_OBJ_FLAGS_RTR		BIT(5)
+#define CAN_OBJ_FLAGS_BRS		BIT(6)
+#define CAN_OBJ_FLAGS_FDF		BIT(7)
+#define CAN_OBJ_FLAGS_ESI		BIT(8)
+#define CAN_OBJ_FLAGS_SEQ_BITS		7
+#define CAN_OBJ_FLAGS_SEQ_SHIFT		9
+#define CAN_OBJ_FLAGS_SEQ_MASK					      \
+	GENMASK(CAN_OBJ_FLAGS_SEQ_SHIFT + CAN_OBJ_FLAGS_SEQ_BITS - 1, \
+		CAN_OBJ_FLAGS_SEQ_SHIFT)
+#define CAN_OBJ_FLAGS_FILHIT_BITS	11
+#define CAN_OBJ_FLAGS_FILHIT_SHIFT	5
+#define CAN_OBJ_FLAGS_FILHIT_MASK				      \
+	GENMASK(CAN_FLAGS_FILHIT_SHIFT + CAN_FLAGS_FILHIT_BITS - 1, \
+		CAN_FLAGS_FILHIT_SHIFT)
+
+#define CAN_OBJ_FLAGS_CUSTOM_ISTEF	BIT(31)
+
+#define MCP2517FD_BUFFER_TXRX_SIZE 2048
+
+static const char * const mcp2517fd_mode_names[] = {
+	[CAN_CON_MODE_MIXED] = "can2.0+canfd",
+	[CAN_CON_MODE_SLEEP] = "sleep",
+	[CAN_CON_MODE_INTERNAL_LOOPBACK] = "internal loopback",
+	[CAN_CON_MODE_LISTENONLY] = "listen only",
+	[CAN_CON_MODE_CONFIG] = "config",
+	[CAN_CON_MODE_EXTERNAL_LOOPBACK] = "external loopback",
+	[CAN_CON_MODE_CAN2_0] = "can2.0",
+	[CAN_CON_MODE_RESTRICTED] = "restricted"
+};
+
+struct mcp2517fd_obj {
+	u32 id;
+	u32 flags;
+};
+
+struct mcp2517fd_obj_tx {
+	struct mcp2517fd_obj header;
+	u32 data[];
+};
+
+static void mcp2517fd_obj_to_le(struct mcp2517fd_obj *obj)
+{
+	obj->id = cpu_to_le32(obj->id);
+	obj->flags = cpu_to_le32(obj->flags);
+}
+
+struct mcp2517fd_obj_ts {
+	u32 id;
+	u32 flags;
+	u32 ts;
+};
+
+struct mcp2517fd_obj_tef {
+	struct mcp2517fd_obj_ts header;
+};
+
+struct mcp2517fd_obj_rx {
+	struct mcp2517fd_obj_ts header;
+	u8 data[];
+};
+
+static void mcp2517fd_obj_ts_from_le(struct mcp2517fd_obj_ts *obj)
+{
+	obj->id = le32_to_cpu(obj->id);
+	obj->flags = le32_to_cpu(obj->flags);
+	obj->ts = le32_to_cpu(obj->ts);
+}
+
+#define FIFO_DATA(x)			(0x400 + (x))
+#define FIFO_DATA_SIZE			0x800
+
+static const struct can_bittiming_const mcp2517fd_nominal_bittiming_const = {
+	.name		= DEVICE_NAME,
+	.tseg1_min	= 2,
+	.tseg1_max	= BIT(CAN_NBTCFG_TSEG1_BITS),
+	.tseg2_min	= 1,
+	.tseg2_max	= BIT(CAN_NBTCFG_TSEG2_BITS),
+	.sjw_max	= BIT(CAN_NBTCFG_SJW_BITS),
+	.brp_min	= 1,
+	.brp_max	= BIT(CAN_NBTCFG_BRP_BITS),
+	.brp_inc	= 1,
+};
+
+static const struct can_bittiming_const mcp2517fd_data_bittiming_const = {
+	.name		= DEVICE_NAME,
+	.tseg1_min	= 1,
+	.tseg1_max	= BIT(CAN_DBTCFG_TSEG1_BITS),
+	.tseg2_min	= 1,
+	.tseg2_max	= BIT(CAN_DBTCFG_TSEG2_BITS),
+	.sjw_max	= BIT(CAN_DBTCFG_SJW_BITS),
+	.brp_min	= 1,
+	.brp_max	= BIT(CAN_DBTCFG_BRP_BITS),
+	.brp_inc	= 1,
+};
+
+enum mcp2517fd_model {
+	CAN_MCP2517FD	= 0x2517,
+};
+
+enum mcp2517fd_gpio_mode {
+	gpio_mode_int		= 0,
+	gpio_mode_standby	= MCP2517FD_IOCON_XSTBYEN,
+	gpio_mode_out_low	= MCP2517FD_IOCON_PM0,
+	gpio_mode_out_high	= MCP2517FD_IOCON_PM0 | MCP2517FD_IOCON_LAT0,
+	gpio_mode_in		= MCP2517FD_IOCON_PM0 | MCP2517FD_IOCON_TRIS0
+};
+
+struct mcp2517fd_trigger_tx_message {
+	struct spi_message msg;
+	struct spi_transfer fill_xfer;
+	struct spi_transfer trigger_xfer;
+	int fifo;
+	char fill_cmd[2];
+	char fill_obj[sizeof(struct mcp2517fd_obj_tx)];
+	char fill_data[64];
+	char trigger_cmd[2];
+	char trigger_data;
+};
+
+struct mcp2517fd_read_fifo_info {
+	struct mcp2517fd_obj_ts *rxb[32];
+	int rx_count;
+	u32 tsmin;
+	u32 tsmax;
+};
+
+struct mcp2517fd_priv {
+	struct can_priv	   can;
+	struct net_device *net;
+	struct spi_device *spi;
+	struct regulator *power;
+	struct regulator *transceiver;
+	struct clk *clk;
+
+	struct dentry *debugfs_dir;
+
+	/* the actual model of the mcp2517fd */
+	enum mcp2517fd_model model;
+
+	struct {
+		/* clock configuration */
+		bool clock_pll;
+		bool clock_div2;
+		int  clock_odiv;
+
+		/* GPIO configuration */
+		enum mcp2517fd_gpio_mode  gpio0_mode;
+		enum mcp2517fd_gpio_mode  gpio1_mode;
+		bool gpio_opendrain;
+		bool txcan_opendrain;
+		bool int_opendrain;
+	} config;
+
+	/* the distinct spi_speeds to use for spi communication */
+	u32 spi_setup_speed_hz;
+	u32 spi_speed_hz;
+
+	/* fifo info */
+	struct {
+		/* define payload size and mode */
+		int payload_size;
+		u32 payload_mode;
+
+		/* TEF addresses - start, end and current */
+		u32 tef_address_start;
+		u32 tef_address_end;
+		u32 tef_address;
+
+		/* address in mcp2517fd-Fifo RAM of each fifo */
+		u32 fifo_address[32];
+
+		/* infos on tx-fifos */
+		u32 tx_fifos;
+		u32 tx_fifo_start;
+		u32 tx_fifo_mask; /* bitmask of which fifo is a tx fifo */
+		u32 tx_submitted_mask;
+		u32 tx_pending_mask;
+		u32 tx_processed_mask;
+
+		/* info on rx_fifos */
+		u32 rx_fifos;
+		u32 rx_fifo_depth;
+		u32 rx_fifo_start;
+		u32 rx_fifo_mask;  /* bitmask of which fifo is a rx fifo */
+
+		/* memory image of FIFO RAM on mcp2517fd */
+		u8 fifo_data[MCP2517FD_BUFFER_TXRX_SIZE];
+
+	} fifos;
+
+	/* structure with active fifos that need to get fed to the system */
+	struct mcp2517fd_read_fifo_info queued_fifos;
+
+	/* statistics */
+	struct {
+		/* number of calls to the irq handler */
+		u64 irq_calls;
+		/* number of loops inside the irq handler */
+		u64 irq_loops;
+
+		/* interrupt handler state and statistics */
+		u32 irq_state;
+#define IRQ_STATE_NEVER_RUN 0
+#define IRQ_STATE_RUNNING 1
+#define IRQ_STATE_HANDLED 2
+		/* stats on number of rx overflows */
+		u64 rx_overflow;
+		/* statistics of FIFO usage */
+		u64 fifo_usage[32];
+	} stats;
+
+	/* the current status of the mcp2517fd */
+	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;
+		/* ASSERT(CAN_TREC + 4 == CAN_BDIAG0) */
+		u32 bdiag0;
+		/* ASSERT(CAN_BDIAG0 + 4 == CAN_BDIAG1) */
+		u32 bdiag1;
+	} status;
+
+	/* configuration registers */
+	struct {
+		u32 osc;
+		u32 ecccon;
+		u32 con;
+		u32 iocon;
+		u32 tdc;
+		u32 tscon;
+		u32 tefcon;
+		u32 nbtcfg;
+		u32 dbtcfg;
+	} regs;
+
+	/* interrupt handler signaling */
+	int force_quit;
+	int after_suspend;
+#define AFTER_SUSPEND_UP 1
+#define AFTER_SUSPEND_DOWN 2
+#define AFTER_SUSPEND_POWER 4
+#define AFTER_SUSPEND_RESTART 8
+	int restart_tx;
+
+	/* interrupt flags during irq handling */
+	u32 int_clear_mask;
+	u32 int_clear_value;
+	u32 bdiag1_clear_mask;
+	u32 bdiag1_clear_value;
+
+	/* composit error id and dataduring irq handling */
+	u32 can_err_id;
+	u32 can_err_data[8];
+
+	/* the current mode */
+	u32 active_can_mode;
+	u32 new_state;
+
+	/* status of the tx_queue enabled/disabled */
+	u32 tx_queue_status;
+
+	/* spi-tx/rx buffers for efficient transfers
+	 * used during setup and irq
+	 */
+	u8 spi_tx[MCP2517FD_BUFFER_TXRX_SIZE];
+	u8 spi_rx[MCP2517FD_BUFFER_TXRX_SIZE];
+
+	/* structure for transmit fifo spi_messages */
+	struct mcp2517fd_trigger_tx_message *spi_transmit_fifos;
+};
+
+/* module parameters */
+bool use_bulk_release_fifos;
+module_param(use_bulk_release_fifos, bool, 0664);
+MODULE_PARM_DESC(use_bulk_release_fifos,
+		 "Use code that favours longer spi transfers over multiple transfers");
+bool use_complete_fdfifo_read;
+module_param(use_complete_fdfifo_read, bool, 0664);
+MODULE_PARM_DESC(use_complete_fdfifo_read,
+		 "Use code that favours longer spi transfers over multiple transfers for fd can");
+unsigned int tx_fifos;
+module_param(tx_fifos, uint, 0664);
+MODULE_PARM_DESC(tx_fifos,
+		 "Number of tx-fifos to configure\n");
+unsigned int bw_sharing_log2bits;
+module_param(bw_sharing_log2bits, uint, 0664);
+MODULE_PARM_DESC(bw_sharing_log2bits,
+		 "Delay between 2 transmissions in number of arbitration bit times\n");
+
+/* spi sync helper */
+
+/* wrapper arround spi_sync, that sets speed_hz */
+static int mcp2517fd_sync_transfer(struct spi_device *spi,
+				   struct spi_transfer *xfer,
+				   unsigned int xfers,
+				   int speed_hz)
+{
+	int i;
+
+	for (i = 0; i < xfers; i++)
+		xfer[i].speed_hz = speed_hz;
+
+	return spi_sync_transfer(spi, xfer, xfers);
+}
+
+/* an optimization of spi_write_then_read that merges the transfers */
+static int mcp2517fd_write_then_read(struct spi_device *spi,
+				     const void *tx_buf,
+				     unsigned int tx_len,
+				     void *rx_buf,
+				     unsigned int rx_len,
+				     int speed_hz)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct spi_transfer xfer[2];
+	int ret;
+
+	memset(xfer, 0, sizeof(xfer));
+
+	/* when using a halfduplex controller or to big for buffer */
+	if ((spi->master->flags & SPI_MASTER_HALF_DUPLEX) ||
+	    (tx_len + rx_len > sizeof(priv->spi_tx))) {
+		xfer[0].tx_buf = tx_buf;
+		xfer[0].len = tx_len;
+
+		xfer[1].rx_buf = rx_buf;
+		xfer[1].len = rx_len;
+
+		return mcp2517fd_sync_transfer(spi, xfer, 2, speed_hz);
+	}
+
+	/* full duplex optimization */
+	xfer[0].tx_buf = priv->spi_tx;
+	xfer[0].rx_buf = priv->spi_rx;
+	xfer[0].len = tx_len + rx_len;
+
+	/* copy and clean */
+	memcpy(priv->spi_tx, tx_buf, tx_len);
+	memset(priv->spi_tx + tx_len, 0, rx_len);
+
+	ret = mcp2517fd_sync_transfer(spi, xfer, 1, speed_hz);
+	if (ret)
+		return ret;
+
+	memcpy(rx_buf, xfer[0].rx_buf + tx_len, rx_len);
+
+	return 0;
+}
+
+/* simple spi_write wrapper with speed_hz */
+static int mcp2517fd_write(struct spi_device *spi,
+			   const void *tx_buf,
+			   unsigned int tx_len,
+			   int speed_hz)
+{
+	struct spi_transfer xfer;
+
+	memset(&xfer, 0, sizeof(xfer));
+	xfer.tx_buf = tx_buf;
+	xfer.len = tx_len;
+
+	return mcp2517fd_sync_transfer(spi, &xfer, 1, speed_hz);
+}
+
+/* spi_sync wrapper similar to spi_write_then_read that optimizes transfers */
+static int mcp2517fd_write_then_write(struct spi_device *spi,
+				      const void *tx_buf,
+				      unsigned int tx_len,
+				      const void *tx2_buf,
+				      unsigned int tx2_len,
+				      int speed_hz)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct spi_transfer xfer;
+
+	if (tx_len + tx2_len > MCP2517FD_BUFFER_TXRX_SIZE)
+		return -EINVAL;
+
+	memset(&xfer, 0, sizeof(xfer));
+	xfer.len = tx_len + tx2_len;
+	xfer.tx_buf = priv->spi_tx;
+
+	memcpy(priv->spi_tx, tx_buf, tx_len);
+	memcpy(priv->spi_tx + tx_len, tx2_buf, tx2_len);
+
+	return mcp2517fd_sync_transfer(spi, &xfer, 1, speed_hz);
+}
+
+/* mcp2517fd spi command/protocol helper */
+
+static void mcp2517fd_calc_cmd_addr(u16 cmd, u16 addr, u8 *data)
+{
+	cmd = cmd | (addr & ADDRESS_MASK);
+
+	data[0] = (cmd >> 8) & 0xff;
+	data[1] = (cmd >> 0) & 0xff;
+}
+
+static int mcp2517fd_cmd_reset(struct spi_device *spi, u32 speed_hz)
+{
+	u8 cmd[2];
+
+	mcp2517fd_calc_cmd_addr(INSTRUCTION_RESET, 0, cmd);
+
+	/* write the reset command */
+	return mcp2517fd_write(spi, cmd, 2, speed_hz);
+}
+
+/* read multiple bytes, transform some registers */
+static int mcp2517fd_cmd_readn(struct spi_device *spi, u32 reg,
+			       void *data, int n, u32 speed_hz)
+{
+	u8 cmd[2];
+	int ret;
+
+	mcp2517fd_calc_cmd_addr(INSTRUCTION_READ, reg, cmd);
+
+	ret = mcp2517fd_write_then_read(spi, &cmd, 2, data, n, speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mcp2517fd_convert_to_cpu(u32 *data, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		data[i] = le32_to_cpu(data[i]);
+
+	return 0;
+}
+
+/* read a register, but we are only interrested in a few bytes */
+static int mcp2517fd_cmd_read_mask(struct spi_device *spi, u32 reg,
+				   u32 *data, u32 mask, u32 speed_hz)
+{
+	int first_byte, last_byte, len_byte;
+	int ret;
+
+	/* check that at least one bit is set */
+	if (!mask)
+		return -EINVAL;
+
+	/* calculate first and last byte used */
+	first_byte = (ffs(mask) - 1) >> 3;
+	last_byte = (fls(mask) - 1) >> 3;
+	len_byte = last_byte - first_byte + 1;
+
+	/* do a partial read */
+	*data = 0;
+	ret = mcp2517fd_cmd_readn(spi, reg,
+				  ((void *)data + first_byte), len_byte,
+				  speed_hz);
+	if (ret)
+		return ret;
+
+	return mcp2517fd_convert_to_cpu(data, 1);
+}
+
+static int mcp2517fd_cmd_read(struct spi_device *spi, u32 reg, u32 *data,
+			      u32 speed_hz)
+{
+	return mcp2517fd_cmd_read_mask(spi, reg, data, -1, speed_hz);
+}
+
+/* read a register, but we are only interrested in a few bytes */
+static int mcp2517fd_cmd_write_mask(struct spi_device *spi, u32 reg,
+				    u32 data, u32 mask, u32 speed_hz)
+{
+	int first_byte, last_byte, len_byte;
+	u8 cmd[2];
+
+	/* check that at least one bit is set */
+	if (!mask)
+		return -EINVAL;
+
+	/* calculate first and last byte used */
+	first_byte = (ffs(mask) - 1)  >> 3;
+	last_byte = (fls(mask) - 1)  >> 3;
+	len_byte = last_byte - first_byte + 1;
+
+	/* prepare buffer */
+	mcp2517fd_calc_cmd_addr(INSTRUCTION_WRITE, reg + first_byte, cmd);
+	data = cpu_to_le32(data);
+
+	return mcp2517fd_write_then_write(spi,
+					  cmd, sizeof(cmd),
+					  ((void *)&data + first_byte),
+					  len_byte,
+					  speed_hz);
+}
+
+static int mcp2517fd_cmd_write(struct spi_device *spi, u32 reg, u32 data,
+			       u32 speed_hz)
+{
+	return mcp2517fd_cmd_write_mask(spi, reg, data, -1, speed_hz);
+}
+
+static int mcp2517fd_cmd_writen(struct spi_device *spi, u32 reg,
+				void *data, int n, u32 speed_hz)
+{
+	u8 cmd[2];
+	int ret;
+
+	mcp2517fd_calc_cmd_addr(INSTRUCTION_WRITE, reg, cmd);
+
+	ret = mcp2517fd_write_then_write(spi, &cmd, 2, data, n, speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* ideally these would be defined in uapi/linux/can.h */
+#define CAN_EFF_SID_SHIFT		(CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)
+#define CAN_EFF_SID_BITS		CAN_SFF_ID_BITS
+#define CAN_EFF_SID_MASK				      \
+	GENMASK(CAN_EFF_SID_SHIFT + CAN_EFF_SID_BITS - 1,     \
+		CAN_EFF_SID_SHIFT)
+#define CAN_EFF_EID_SHIFT		0
+#define CAN_EFF_EID_BITS		CAN_EFF_SID_SHIFT
+#define CAN_EFF_EID_MASK				      \
+	GENMASK(CAN_EFF_EID_SHIFT + CAN_EFF_EID_BITS - 1,     \
+		CAN_EFF_EID_SHIFT)
+
+static void mcp2517fd_canid_to_mcpid(u32 can_id, u32 *id, u32 *flags)
+{
+	if (can_id & CAN_EFF_FLAG) {
+		int sid = (can_id & CAN_EFF_SID_MASK) >> CAN_EFF_SID_SHIFT;
+		int eid = (can_id & CAN_EFF_EID_MASK) >> CAN_EFF_EID_SHIFT;
+		*id = (eid << CAN_OBJ_ID_EID_SHIFT) |
+			(sid << CAN_OBJ_ID_SID_SHIFT);
+		*flags = CAN_OBJ_FLAGS_IDE;
+	} else {
+		*id = can_id & CAN_SFF_MASK;
+		*flags = 0;
+	}
+
+	*flags |= (can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+}
+
+static void mcp2517fd_mcpid_to_canid(u32 mcpid, u32 mcpflags, u32 *id)
+{
+	u32 sid = (mcpid & CAN_OBJ_ID_SID_MASK) >> CAN_OBJ_ID_SID_SHIFT;
+	u32 eid = (mcpid & CAN_OBJ_ID_EID_MASK) >> CAN_OBJ_ID_EID_SHIFT;
+
+	if (mcpflags & CAN_OBJ_FLAGS_IDE) {
+		*id = (eid << CAN_EFF_EID_SHIFT) |
+			(sid << CAN_EFF_SID_SHIFT) |
+			CAN_EFF_FLAG;
+	} else {
+		*id = sid;
+	}
+
+	*id |= (mcpflags & CAN_OBJ_FLAGS_RTR) ? CAN_RTR_FLAG : 0;
+}
+
+/* CAN transmit related*/
+
+static void mcp2517fd_mark_tx_pending(void *context)
+{
+	struct mcp2517fd_trigger_tx_message *txm = context;
+	struct spi_device *spi = txm->msg.spi;
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	/* only here or in the irq handler this value is changed,
+	 * so there is no race condition and it does not require locking
+	 * serialization happens via spi_pump_message
+	 */
+	priv->fifos.tx_pending_mask |= BIT(txm->fifo);
+}
+
+static int mcp2517fd_fill_spi_transmit_fifos(struct mcp2517fd_priv *priv)
+{
+	struct mcp2517fd_trigger_tx_message *txm;
+	int i, fifo;
+	const u32 trigger = CAN_FIFOCON_TXREQ | CAN_FIFOCON_UINC;
+	const int first_byte = (ffs(trigger) - 1)  >> 3;
+
+	priv->spi_transmit_fifos = kcalloc(
+		priv->fifos.tx_fifos,
+		sizeof(*priv->spi_transmit_fifos),
+		GFP_KERNEL | GFP_DMA);
+	if (!priv->spi_transmit_fifos)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		txm = &priv->spi_transmit_fifos[i];
+		/* prepare the message */
+		spi_message_init(&txm->msg);
+		txm->msg.complete = mcp2517fd_mark_tx_pending;
+		txm->msg.context = txm;
+		txm->fifo = fifo;
+		/* the payload itself */
+		txm->fill_xfer.speed_hz = priv->spi_speed_hz;
+		txm->fill_xfer.tx_buf = txm->fill_cmd;
+		txm->fill_xfer.len = 2;
+		txm->fill_xfer.cs_change = true;
+		mcp2517fd_calc_cmd_addr(
+			INSTRUCTION_WRITE,
+			FIFO_DATA(priv->fifos.fifo_address[fifo]),
+			txm->fill_cmd);
+		spi_message_add_tail(&txm->fill_xfer, &txm->msg);
+		/* the trigger command */
+		txm->trigger_xfer.speed_hz = priv->spi_speed_hz;
+		txm->trigger_xfer.tx_buf = txm->trigger_cmd;
+		txm->trigger_xfer.len = 3;
+		mcp2517fd_calc_cmd_addr(INSTRUCTION_WRITE,
+					CAN_FIFOCON(fifo) + first_byte,
+					txm->trigger_cmd);
+		txm->trigger_data = trigger >> (8 * first_byte);
+		spi_message_add_tail(&txm->trigger_xfer, &txm->msg);
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_transmit_message_common(
+	struct spi_device *spi, int fifo,
+	struct mcp2517fd_obj_tx *obj, int len, u8 *data)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct mcp2517fd_trigger_tx_message *txm =
+		&priv->spi_transmit_fifos[fifo - priv->fifos.tx_fifo_start];
+	int ret;
+
+	/* add fifo as seq */
+	obj->header.flags |= fifo << CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+	/* transform to le32 */
+	mcp2517fd_obj_to_le(&obj->header);
+
+	/* fill in details */
+	memcpy(txm->fill_obj, obj, sizeof(struct mcp2517fd_obj_tx));
+	memset(txm->fill_data, 0, priv->fifos.payload_size);
+	memcpy(txm->fill_data, data, len);
+
+	/* transfers to FIFO RAM has to be multiple of 4 */
+	txm->fill_xfer.len =
+		2 + sizeof(struct mcp2517fd_obj_tx) + ALIGN(len, 4);
+
+	/* and transmit asyncroniously */
+	ret = spi_async(spi, &txm->msg);
+	if (ret)
+		return NETDEV_TX_BUSY;
+
+	return NETDEV_TX_OK;
+}
+
+static int mcp2517fd_transmit_fdmessage(struct spi_device *spi, int fifo,
+					struct canfd_frame *frame)
+{
+	struct mcp2517fd_obj_tx obj;
+	int dlc = can_len2dlc(frame->len);
+	u32 flags;
+
+	frame->len = can_dlc2len(dlc);
+
+	mcp2517fd_canid_to_mcpid(frame->can_id, &obj.header.id, &flags);
+
+	flags |= dlc << CAN_OBJ_FLAGS_DLC_SHIFT;
+	flags |= (frame->can_id & CAN_EFF_FLAG) ? CAN_OBJ_FLAGS_IDE : 0;
+	flags |= (frame->can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+	flags |= (frame->flags & CANFD_BRS) ? CAN_OBJ_FLAGS_BRS : 0;
+	flags |= (frame->flags & CANFD_ESI) ? CAN_OBJ_FLAGS_ESI : 0;
+	flags |= CAN_OBJ_FLAGS_FDF;
+
+	obj.header.flags = flags;
+
+	return mcp2517fd_transmit_message_common(
+		spi, fifo, &obj, frame->len, frame->data);
+}
+
+static int mcp2517fd_transmit_message(struct spi_device *spi, int fifo,
+				      struct can_frame *frame)
+{
+	struct mcp2517fd_obj_tx obj;
+	u32 flags;
+
+	if (frame->can_dlc > 8)
+		frame->can_dlc = 8;
+
+	mcp2517fd_canid_to_mcpid(frame->can_id, &obj.header.id, &flags);
+
+	flags |= frame->can_dlc << CAN_OBJ_FLAGS_DLC_SHIFT;
+	flags |= (frame->can_id & CAN_EFF_FLAG) ? CAN_OBJ_FLAGS_IDE : 0;
+	flags |= (frame->can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+
+	obj.header.flags = flags;
+
+	return mcp2517fd_transmit_message_common(
+		spi, fifo, &obj, frame->can_dlc, frame->data);
+}
+
+static netdev_tx_t mcp2517fd_start_xmit(struct sk_buff *skb,
+					struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+	u32 pending_mask;
+	int fifo;
+	int ret;
+
+	if (can_dropped_invalid_skb(net, skb))
+		return NETDEV_TX_OK;
+
+	if (priv->can.state == CAN_STATE_BUS_OFF) {
+		priv->tx_queue_status = 0;
+		netif_stop_queue(priv->net);
+		return NETDEV_TX_BUSY;
+	}
+
+	/* get effective mask */
+	pending_mask = priv->fifos.tx_pending_mask |
+		priv->fifos.tx_submitted_mask;
+
+	/* decide on fifo to assign */
+	if (pending_mask)
+		fifo = ffs(pending_mask) - 2;
+	else
+		fifo = priv->fifos.tx_fifo_start + priv->fifos.tx_fifos - 1;
+
+	/* handle error - this should not happen... */
+	if (fifo < priv->fifos.tx_fifo_start) {
+		dev_err(&spi->dev,
+			"reached tx-fifo %i, which is not valid\n",
+			fifo);
+		return NETDEV_TX_BUSY;
+	}
+
+	/* if we are the last one, then stop the queue */
+	if (fifo == priv->fifos.tx_fifo_start) {
+		priv->tx_queue_status = 0;
+		netif_stop_queue(priv->net);
+	}
+
+	/* mark as submitted */
+	priv->fifos.tx_submitted_mask |= BIT(fifo);
+	priv->stats.fifo_usage[fifo]++;
+
+	/* now process it for real */
+	if (can_is_canfd_skb(skb))
+		ret = mcp2517fd_transmit_fdmessage(
+			spi, fifo, (struct canfd_frame *)skb->data);
+	else
+		ret = mcp2517fd_transmit_message(
+			spi, fifo, (struct can_frame *)skb->data);
+
+	/* keep it for reference until the message really got transmitted */
+	if (ret == NETDEV_TX_OK)
+		can_put_echo_skb(skb, priv->net, fifo);
+
+	return ret;
+}
+
+/* CAN RX Related */
+
+static int mcp2517fd_can_transform_rx_fd(struct spi_device *spi,
+					 struct mcp2517fd_obj_rx *rx)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct canfd_frame *frame;
+	struct sk_buff *skb;
+	u32 flags = rx->header.flags;
+
+	/* allocate the skb buffer */
+	skb = alloc_canfd_skb(priv->net, &frame);
+	if (!skb) {
+		dev_err(&spi->dev, "cannot allocate RX skb\n");
+		priv->net->stats.rx_dropped++;
+		return -ENOMEM;
+	}
+
+	mcp2517fd_mcpid_to_canid(rx->header.id, flags, &frame->can_id);
+	frame->flags |= (flags & CAN_OBJ_FLAGS_BRS) ? CANFD_BRS : 0;
+	frame->flags |= (flags & CAN_OBJ_FLAGS_ESI) ? CANFD_ESI : 0;
+
+	frame->len = can_dlc2len((flags & CAN_OBJ_FLAGS_DLC_MASK)
+				 >> CAN_OBJ_FLAGS_DLC_SHIFT);
+
+	memcpy(frame->data, rx->data, frame->len);
+
+	priv->net->stats.rx_packets++;
+	priv->net->stats.rx_bytes += frame->len;
+
+	can_led_event(priv->net, CAN_LED_EVENT_RX);
+
+	netif_rx_ni(skb);
+
+	return 0;
+}
+
+static int mcp2517fd_can_transform_rx_normal(struct spi_device *spi,
+					     struct mcp2517fd_obj_rx *rx)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct sk_buff *skb;
+	struct can_frame *frame;
+	u32 flags = rx->header.flags;
+	int len;
+
+	/* allocate the skb buffer */
+	skb = alloc_can_skb(priv->net, &frame);
+	if (!skb) {
+		dev_err(&spi->dev, "cannot allocate RX skb\n");
+		priv->net->stats.rx_dropped++;
+		return -ENOMEM;
+	}
+
+	mcp2517fd_mcpid_to_canid(rx->header.id, flags, &frame->can_id);
+
+	frame->can_dlc = (flags & CAN_OBJ_FLAGS_DLC_MASK)
+		>> CAN_OBJ_FLAGS_DLC_SHIFT;
+
+	len = can_dlc2len(frame->can_dlc);
+
+	memcpy(frame->data, rx->data, len);
+
+	priv->net->stats.rx_packets++;
+	priv->net->stats.rx_bytes += len;
+
+	can_led_event(priv->net, CAN_LED_EVENT_RX);
+
+	netif_rx_ni(skb);
+
+	return 0;
+}
+
+static int mcp2517fd_process_queued_rx(struct spi_device *spi,
+				       struct mcp2517fd_obj_ts *obj)
+{
+	struct mcp2517fd_obj_rx *rx = container_of(
+		obj, struct mcp2517fd_obj_rx, header);
+
+	if (obj->flags & CAN_OBJ_FLAGS_FDF)
+		return mcp2517fd_can_transform_rx_fd(
+			spi, rx);
+	else
+		return mcp2517fd_can_transform_rx_normal(
+			spi, rx);
+}
+
+static int mcp2517fd_normal_release_fifos(struct spi_device *spi,
+					  int start, int end)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	/* release each fifo in a separate transfer */
+	for (; start < end ; start++) {
+		ret = mcp2517fd_cmd_write_mask(
+			spi, CAN_FIFOCON(start),
+			CAN_FIFOCON_UINC,
+			CAN_FIFOCON_UINC,
+			priv->spi_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* unfortunately the CAN_FIFOCON are not directly consecutive
+ * so the optimization of "clearing all in one spi_transfer"
+ * would produce an overhead of 11 unnecessary bytes/fifo
+ * - transferring 14 (2 cmd + 12 data) bytes
+ * instead of just 3 (2 + 1).
+ * On some slower systems this may still be beneficial,
+ * but it is not good enough for the generic case.
+ * On a Raspberry Pi CM the timings for clearing 3 fifos
+ * (at 12.5MHz SPI clock speed) are:
+ * * normal:
+ *   * 3 spi transfers
+ *   * 9 bytes total
+ *   * 36.74us from first CS low to last CS high
+ *   * individual CS: 9.14us, 5.74us and 5.16us
+ *   * 77.02us from CS up of fifo transfer to last release CS up
+ * * bulk:
+ *   * 1 spi transfer
+ *   * 27 bytes total
+ *   * 29.06us CS Low
+ *   * 78.28us from CS up of fifo transfer to last release CS up
+ * this obviously varies with SPI_clock speed
+ * - the slower the clock the less efficient the optimization.
+ * similarly the faster the CPU (and bigger the code cache) the
+ * less effcient the optimization - the above case is border line.
+ */
+
+#define FIFOCON_SPACING (CAN_FIFOCON(1) - CAN_FIFOCON(0))
+#define FIFOCON_SPACINGW (FIFOCON_SPACING / sizeof(u32))
+
+static int mcp2517fd_bulk_release_fifos(struct spi_device *spi,
+					int start, int end)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int i;
+	int ret;
+
+	/* calculate start address and length */
+	int fifos = end - start;
+	int first_byte = (ffs(CAN_FIFOCON_UINC) - 1)  >> 3;
+	int addr = CAN_FIFOCON(start);
+	int len = 1 + (fifos - 1) * FIFOCON_SPACING;
+
+	/* the worsted case buffer */
+	u32 buf[32 * FIFOCON_SPACINGW], base;
+
+	base = (priv->fifos.payload_mode << CAN_FIFOCON_PLSIZE_SHIFT) |
+		((priv->fifos.rx_fifo_depth - 1) << CAN_FIFOCON_FSIZE_SHIFT) |
+		CAN_FIFOCON_RXTSEN | /* RX timestamps */
+		CAN_FIFOCON_UINC |
+		CAN_FIFOCON_TFERFFIE | /* FIFO Full */
+		CAN_FIFOCON_TFHRFHIE | /* FIFO Half Full*/
+		CAN_FIFOCON_TFNRFNIE; /* FIFO not empty */
+
+	memset(buf, 0, sizeof(buf));
+	for (i = 0; i < end - start ; i++) {
+		if (i == priv->fifos.rx_fifos - 1)
+			base |= CAN_FIFOCON_RXOVIE;
+		buf[FIFOCON_SPACINGW * i] = cpu_to_le32(base);
+	}
+
+	ret = mcp2517fd_cmd_writen(spi, addr + first_byte,
+				   (u8 *)buf + first_byte,
+				   len,
+				   priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* queued FIFO handling for release to system */
+
+static void mcp2517fd_clear_queued_fifos(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	/* prepare rfi - mostly used for sorting */
+	priv->queued_fifos.tsmin = -1;
+	priv->queued_fifos.tsmax = 0;
+	priv->queued_fifos.rx_count = 0;
+}
+
+static void mcp2517fd_addto_queued_fifos(struct spi_device *spi,
+					 struct mcp2517fd_obj_ts *obj)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct mcp2517fd_read_fifo_info *rfi = &priv->queued_fifos;
+
+	/* add pointer to queued array-list */
+	rfi->rxb[rfi->rx_count] = obj;
+	rfi->rx_count++;
+
+	/* and get tsmin/tsmax */
+	if (rfi->tsmin > obj->ts)
+		rfi->tsmin = obj->ts;
+	if (rfi->tsmax > obj->ts)
+		rfi->tsmax = obj->ts;
+}
+
+static int mcp2517fd_process_queued_tef(struct spi_device *spi,
+					struct mcp2517fd_obj_ts *obj)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct mcp2517fd_obj_tef *tef = container_of(
+		obj, struct mcp2517fd_obj_tef, header);
+	int dlc = (obj->flags & CAN_OBJ_FLAGS_DLC_MASK)
+		>> CAN_OBJ_FLAGS_DLC_SHIFT;
+	int fifo = (tef->header.flags & CAN_OBJ_FLAGS_SEQ_MASK) >>
+		CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+	/* update counters */
+	priv->net->stats.tx_packets++;
+	priv->net->stats.tx_bytes += can_dlc2len(dlc);
+
+	/* release it */
+	can_get_echo_skb(priv->net, fifo);
+
+	can_led_event(priv->net, CAN_LED_EVENT_TX);
+
+	return 0;
+}
+
+static int mcp2517fd_compare_obj_ts(const void *a, const void *b)
+{
+	const struct mcp2517fd_obj_ts * const *rxa = a;
+	const struct mcp2517fd_obj_ts * const *rxb = b;
+	/* using signed here to handle rollover correctly */
+	s32 ats = (*rxa)->ts;
+	s32 bts = (*rxb)->ts;
+
+	if (ats < bts)
+		return -1;
+	if (ats > bts)
+		return 1;
+	return 0;
+}
+
+static int mcp2517fd_process_queued_fifos(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct mcp2517fd_read_fifo_info *rfi = &priv->queued_fifos;
+	int i;
+	int ret;
+
+	/* sort the fifos (rx and TEF) by receive timestamp */
+	sort(rfi->rxb, rfi->rx_count, sizeof(struct mcp2517fd_obj_ts *),
+	     mcp2517fd_compare_obj_ts, NULL);
+
+	/* process the recived fifos */
+	for (i = 0; i < rfi->rx_count ; i++) {
+		if (rfi->rxb[i]->flags & CAN_OBJ_FLAGS_CUSTOM_ISTEF) {
+			ret = mcp2517fd_process_queued_tef(
+				spi, rfi->rxb[i]);
+		} else {
+			ret = mcp2517fd_process_queued_rx(
+				spi, rfi->rxb[i]);
+		}
+		if (ret)
+			return ret;
+	}
+
+	/* clear queued fifos */
+	mcp2517fd_clear_queued_fifos(spi);
+
+	return 0;
+}
+
+static int mcp2517fd_transform_rx(struct spi_device *spi,
+				  struct mcp2517fd_obj_rx *rx)
+{
+	int dlc;
+
+	/* transform the data to system byte order */
+	mcp2517fd_obj_ts_from_le(&rx->header);
+
+	/* add the object to the list */
+	mcp2517fd_addto_queued_fifos(spi, &rx->header);
+
+	/* calc length and return it */
+	dlc = (rx->header.flags & CAN_OBJ_FLAGS_DLC_MASK)
+		>> CAN_OBJ_FLAGS_DLC_SHIFT;
+	return can_dlc2len(dlc);
+}
+
+/* read_fifo implementations
+ *
+ * read_fifos is a simple implementation, that:
+ *   * loops all fifos
+ *     * read header + some data-bytes (8)
+ *     * read rest of data-bytes bytes
+ *     * release fifo
+ *   for 3 can frames dlc<=8 to read here we have:
+ *     * 6 spi transfers
+ *     * 75 bytes (= 3 * (2 + 12 + 8) bytes + 3 * 3 bytes)
+ *   for 3 canfd frames dlc>8 to read here we have:
+ *     * 9 spi transfers
+ *     * 81 (= 3 * (2 + 12 + 8 + 2) bytes + 3 * 3 bytes) + 3 * extra payload
+ *     this only transfers the required size of bytes on the spi bus.
+ *
+ * bulk_read_fifos is an optimization that is most practical for
+ * Can2.0 busses, but may also be practical for CanFD busses that
+ * have a high average payload data size.
+ *
+ * It will read all of the fifo data in a single spi_transfer:
+ *   * read all fifos in one go (as long as these are ajacent to each other)
+ *   * loop all fifos
+ *     * release fifo
+ *   for 3 can2.0 frames to read here we have:
+ *     * 4 spi transfers
+ *     * 71 bytes (= 2 + 3 * (12 + 8) bytes + 3 * 3 bytes)
+ *   for 3 canfd frames to read here we have:
+ *     * 4 spi transfers
+ *     * 230 bytes (= 2 + 3 * (12 + 64) bytes)
+ *     obviously this reads way too many bytes for framesizes <=32 bytes,
+ *     but it avoids the overhead on the CPU side and may even trigger
+ *     DMA transfers due to the high byte count, which release CPU cycles.
+ *
+ * This optimization will also be efficient for cases where a high
+ * percentage of canFD frames has a dlc-size > 8.
+ * This mode is used for Can2.0 configured busses.
+ *
+ * For now this option can get forced for CanFD via a module parameter.
+ * In the future there may be some heuristics that could trigger a usage
+ * of this mode as well in some circumstances.
+ *
+ * Note: there is a second optimization for release fifo as well,
+ *       but it is not as efficient as this optimization for the
+ *       non-CanFD case - see mcp2517fd_bulk_release_fifos
+ */
+
+static int mcp2517fd_read_fifos(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int fifo_header_size = sizeof(struct mcp2517fd_obj_rx);
+	int fifo_min_payload_size = 8;
+	int fifo_min_size = fifo_header_size + fifo_min_payload_size;
+	int fifo_max_payload_size =
+		((priv->can.ctrlmode & CAN_CTRLMODE_FD) ? 64 : 8);
+	u32 mask = priv->status.rxif;
+	struct mcp2517fd_obj_rx *rx;
+	int i, len;
+	int ret;
+
+	/* read all the "open" segments in big chunks */
+	for (i = priv->fifos.rx_fifo_start;
+	     i < priv->fifos.rx_fifo_start + priv->fifos.rx_fifos;
+	     i++) {
+		if (!(mask & BIT(i)))
+			continue;
+		/* the fifo to fill */
+		rx = (struct mcp2517fd_obj_rx *)
+			(priv->fifos.fifo_data + priv->fifos.fifo_address[i]);
+		/* read the minimal payload */
+		ret = mcp2517fd_cmd_readn(
+			spi, FIFO_DATA(priv->fifos.fifo_address[i]),
+			rx,
+			fifo_min_size,
+			priv->spi_speed_hz);
+		if (ret)
+			return ret;
+		/* process fifo stats and get length */
+		len = min_t(int, mcp2517fd_transform_rx(spi, rx),
+			    fifo_max_payload_size);
+
+		/* read extra payload if needed */
+		if (len > fifo_min_payload_size) {
+			ret = mcp2517fd_cmd_readn(
+				spi,
+				FIFO_DATA(priv->fifos.fifo_address[i] +
+					  fifo_min_size),
+				&rx->data[fifo_min_payload_size],
+				len - fifo_min_payload_size,
+				priv->spi_speed_hz);
+			if (ret)
+				return ret;
+		}
+		/* release fifo */
+		ret = mcp2517fd_normal_release_fifos(spi, i, i + 1);
+		if (ret)
+			return ret;
+		/* increment fifo_usage */
+		priv->stats.fifo_usage[i]++;
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_bulk_read_fifos(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int fifo_header_size = sizeof(struct mcp2517fd_obj_rx);
+	int fifo_max_payload_size = priv->fifos.payload_size;
+	int fifo_max_size = fifo_header_size + fifo_max_payload_size;
+	u32 mask = priv->status.rxif;
+	u32 rx_fifo_end = priv->fifos.rx_fifo_start +
+		priv->fifos.rx_fifos;
+	struct mcp2517fd_obj_rx *rx;
+	int i, j;
+	int ret;
+
+	/* read all the "open" segments in big chunks */
+	for (i = priv->fifos.rx_fifo_start; i < rx_fifo_end; i++) {
+		if (mask & BIT(i)) {
+			/* find the last set bit in sequence */
+			for (j = i;
+			     (j < rx_fifo_end) && (mask & BIT(j));
+			     j++) {
+				/* clear the mask */
+				mask &= ~BIT(j);
+			}
+
+			/* now we got start and end, so read the range */
+			ret = mcp2517fd_cmd_readn(
+				spi,
+				FIFO_DATA(priv->fifos.fifo_address[i]),
+				priv->fifos.fifo_data +
+				priv->fifos.fifo_address[i],
+				(j - i) * fifo_max_size,
+				priv->spi_speed_hz);
+			if (ret)
+				return ret;
+
+			/* clear all the fifos in range */
+			if (use_bulk_release_fifos)
+				ret = mcp2517fd_bulk_release_fifos(spi,
+								   i, j);
+			else
+				ret = mcp2517fd_normal_release_fifos(spi,
+								     i, j);
+			if (ret)
+				return ret;
+
+			/* preprocess data */
+			for (; i < j ; i++) {
+				/* store the fifo to process */
+				rx = (struct mcp2517fd_obj_rx *)(
+					priv->fifos.fifo_data +
+					priv->fifos.fifo_address[i]);
+				/* process fifo stats */
+				mcp2517fd_transform_rx(spi, rx);
+				/* increment usage */
+				priv->stats.fifo_usage[i]++;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_can_ist_handle_rxif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	u32 mask = priv->status.rxif;
+	int ret;
+
+	if (!mask)
+		return 0;
+
+	/* read all the fifos - for non-fd case use bulk read optimization */
+	if (((priv->can.ctrlmode & CAN_CTRLMODE_FD) == 0) ||
+	    use_complete_fdfifo_read)
+		ret = mcp2517fd_bulk_read_fifos(spi);
+	else
+		ret = mcp2517fd_read_fifos(spi);
+
+	return 0;
+}
+
+static int mcp2517fd_can_ist_handle_tefif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct mcp2517fd_obj_tef *tef;
+	u32 pending = priv->fifos.tx_pending_mask &
+		(~priv->fifos.tx_processed_mask);
+	int i, count, fifo;
+	int ret;
+
+	/* calculate the number of fifos that have been processed */
+	count = hweight_long(pending);
+	count -= hweight_long(priv->status.txreq);
+	if (count <= 0) {
+		dev_err(&spi->dev,
+			"handle_tefif: unexpected count = %i\n",
+			count);
+		return -EINVAL;
+	}
+
+	/* now clear TEF for each */
+	/* TODO: optimize for BULK reads, as we (hopefully) know COUNT */
+	for (i = 0; i < count; i++) {
+		/* calc address in address space */
+		tef = (struct mcp2517fd_obj_tef *)(priv->fifos.fifo_data +
+						   priv->fifos.tef_address);
+		/* read all the object data */
+		ret = mcp2517fd_cmd_readn(spi,
+					  FIFO_DATA(priv->fifos.tef_address),
+					  tef, sizeof(*tef),
+					  priv->spi_speed_hz);
+
+		/* increment the counter to read next */
+		ret = mcp2517fd_cmd_write_mask(spi,
+					       CAN_TEFCON,
+					       CAN_TEFCON_UINC,
+					       CAN_TEFCON_UINC,
+					       priv->spi_speed_hz);
+
+		/* transform the data to system byte order */
+		mcp2517fd_obj_ts_from_le(&tef->header);
+
+		fifo = (tef->header.flags & CAN_OBJ_FLAGS_SEQ_MASK) >>
+			CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+		/* submit to queue */
+		tef->header.flags |= CAN_OBJ_FLAGS_CUSTOM_ISTEF;
+		mcp2517fd_addto_queued_fifos(spi, &tef->header);
+
+		/* increment tef */
+		priv->fifos.tef_address += sizeof(*tef);
+		if (priv->fifos.tef_address > priv->fifos.tef_address_end)
+			priv->fifos.tef_address = priv->fifos.tef_address_start;
+
+		/* and set mask */
+		priv->fifos.tx_processed_mask |= BIT(fifo);
+
+		if (fifo == priv->fifos.tx_fifo_start)
+			priv->tx_queue_status = 2;
+	}
+
+	return 0;
+}
+
+static void mcp2517fd_error_skb(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct sk_buff *skb;
+	struct can_frame *frame;
+
+	skb = alloc_can_err_skb(net, &frame);
+	if (skb) {
+		frame->can_id = priv->can_err_id;
+		memcpy(frame->data, priv->can_err_data, 8);
+		netif_rx_ni(skb);
+	} else {
+		netdev_err(net, "cannot allocate error skb\n");
+	}
+}
+
+static int mcp2517fd_can_ist_handle_rxovif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	u32 mask = priv->status.rxovif;
+	int i;
+	int ret;
+
+	/* clear all fifos that have an overflow bit set */
+	for (i = 0; i < 32; i++) {
+		if (mask & BIT(i)) {
+			ret = mcp2517fd_cmd_write_mask(spi,
+						       CAN_FIFOSTA(i),
+						       0,
+						       CAN_FIFOSTA_RXOVIF,
+						       priv->spi_speed_hz);
+			if (ret)
+				return ret;
+			/* update statistics */
+			priv->net->stats.rx_over_errors++;
+			priv->net->stats.rx_errors++;
+			priv->stats.rx_overflow++;
+			priv->can_err_id |= CAN_ERR_CRTL;
+			priv->can_err_data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+		}
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_can_ist_handle_modif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int mode;
+	int ret;
+
+	/* mask interrupt for clearing */
+	priv->int_clear_mask |= CAN_INT_MODIF;
+
+	/* read the mode bit */
+	ret = mcp2517fd_cmd_read_mask(spi,
+				      CAN_CON,
+				      &priv->regs.con,
+				      CAN_CON_OPMOD_MASK,
+				      priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	mode = (priv->regs.con & CAN_CON_OPMOD_MASK) >>
+		CAN_CON_OPMOD_SHIFT;
+
+	/* this mostly happens on initialization */
+	if (mode == priv->active_can_mode) {
+		dev_err(&spi->dev,
+			"Controller switched to already active mode: %s(%u)\n",
+			mcp2517fd_mode_names[mode], mode);
+		return 0;
+	}
+
+	/* these we need to handle correctly */
+	dev_err(&spi->dev,
+		"Controller switched from mode %s(%u) to %s(%u)\n",
+		mcp2517fd_mode_names[priv->active_can_mode],
+		priv->active_can_mode,
+		mcp2517fd_mode_names[mode], mode);
+
+	/* finally assign the mode as currently active */
+	priv->active_can_mode = mode;
+	return 0;
+}
+
+static int mcp2517fd_can_ist_handle_cerrif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	/* in principle we could also delay reading bdiag registers
+	 * until we get here - it would add some extra delay in the
+	 * error case, but be slightly faster in the "normal" case.
+	 * slightly faster would be saving 8 bytes of spi transfer.
+	 */
+
+	dev_err_ratelimited(&spi->dev, "CAN Bus error\n");
+	priv->can_err_id |= CAN_ERR_BUSERROR;
+	priv->int_clear_mask |= CAN_INT_CERRIF;
+
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DBIT0ERR | CAN_BDIAG1_NBIT0ERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_BIT0;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DBIT0ERR |
+			CAN_BDIAG1_NBIT0ERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DBIT1ERR | CAN_BDIAG1_NBIT1ERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_BIT1;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DBIT1ERR |
+			CAN_BDIAG1_NBIT1ERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DSTUFERR | CAN_BDIAG1_NSTUFERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_STUFF;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DSTUFERR |
+			CAN_BDIAG1_NSTUFERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DFORMERR | CAN_BDIAG1_NFORMERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_FORM;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DFORMERR |
+			CAN_BDIAG1_NFORMERR;
+	}
+	if (priv->status.bdiag1 & CAN_BDIAG1_NACKERR) {
+		priv->can_err_id |= CAN_ERR_ACK;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_NACKERR;
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_can_ist_handle_eccif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+	u32 val;
+	u32 addr;
+
+	priv->can_err_id |= CAN_ERR_CRTL;
+	priv->can_err_data[1] |= CAN_ERR_CRTL_UNSPEC;
+	priv->int_clear_mask |= CAN_INT_ECCIF;
+
+	/* read ECC status register */
+	ret = mcp2517fd_cmd_read(spi, MCP2517FD_ECCSTAT, &val,
+				 priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	addr = (val & MCP2517FD_ECCSTAT_ERRADDR_MASK) >>
+		MCP2517FD_ECCSTAT_ERRADDR_SHIFT;
+
+	dev_err_ratelimited(&spi->dev,
+			    "ECC %s bit error at %03x\n",
+			    (val & MCP2517FD_ECCSTAT_DEDIF) ?
+			    "double" : "single",
+			    addr);
+
+	return mcp2517fd_cmd_write(spi, MCP2517FD_ECCSTAT, 0,
+				 priv->spi_speed_hz);
+}
+
+static int mcp2517fd_can_ist_handle_serrif(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	/* 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
+	 */
+
+	priv->can_err_id |= CAN_ERR_CRTL;
+	priv->can_err_data[1] |= CAN_ERR_CRTL_UNSPEC;
+	priv->int_clear_mask |= CAN_INT_SERRIF;
+
+	/* a mode change or ecc error would indicate TX MAB Undeflow */
+	if (priv->status.intf & (CAN_INT_MODIF | CAN_INT_ECCIF)) {
+		dev_warn_ratelimited(&spi->dev, "TX MAB underflow\n");
+		priv->net->stats.tx_fifo_errors++;
+		priv->net->stats.tx_errors++;
+	} else {
+		dev_warn_ratelimited(&spi->dev, "RX MAB overflow\n");
+		priv->net->stats.rx_dropped++;
+		priv->net->stats.rx_errors++;
+	}
+	return 0;
+}
+
+static int mcp2517fd_disable_interrupts(struct spi_device *spi,
+					u32 speed_hz)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	priv->status.intf = 0;
+	return mcp2517fd_cmd_write(spi, CAN_INT, 0, speed_hz);
+}
+
+static int mcp2517fd_enable_interrupts(struct spi_device *spi,
+				       u32 speed_hz)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	priv->status.intf = CAN_INT_TEFIE |
+		CAN_INT_RXIE |
+		CAN_INT_MODIE |
+		CAN_INT_SERRIE |
+		CAN_INT_IVMIE |
+		CAN_INT_CERRIE |
+		CAN_INT_ECCIE;
+	return mcp2517fd_cmd_write(spi, CAN_INT,
+				   priv->status.intf,
+				   speed_hz);
+}
+
+static int mcp2517fd_hw_wake(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	u32 waitfor = MCP2517FD_OSC_OSCRDY;
+	u32 mask = waitfor | MCP2517FD_OSC_OSCDIS;
+	unsigned long timeout;
+	int ret;
+
+	if (priv->active_can_mode != CAN_CON_MODE_SLEEP)
+		return 0;
+
+	/* write clock */
+	ret = mcp2517fd_cmd_write(
+		spi, MCP2517FD_OSC, priv->regs.osc,
+		priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* wait for synced pll/osc/sclk */
+	timeout = jiffies + MCP2517FD_OSC_POLLING_JIFFIES;
+	while (time_before_eq(jiffies, timeout)) {
+		ret = mcp2517fd_cmd_read(spi, MCP2517FD_OSC,
+					 &priv->regs.osc,
+					 priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		if ((priv->regs.osc & mask) == waitfor) {
+			priv->active_can_mode = CAN_CON_MODE_CONFIG;
+			return 0;
+		}
+	}
+
+	dev_err(&spi->dev,
+		"Clock did not enable within the timeout period\n");
+	return -ETIMEDOUT;
+}
+
+static void mcp2517fd_hw_sleep(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	/* disable interrupts */
+	mcp2517fd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+
+	priv->active_can_mode = CAN_CON_MODE_SLEEP;
+	priv->regs.con = (priv->regs.con & ~CAN_CON_REQOP_MASK) |
+		(priv->active_can_mode << CAN_CON_REQOP_SHIFT);
+	mcp2517fd_cmd_write(spi, CAN_CON,
+			    priv->regs.con,
+			    priv->spi_setup_speed_hz);
+}
+
+static int mcp2517fd_can_ist_handle_status(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	/* interrupt clearing info */
+	priv->int_clear_value = 0;
+	priv->int_clear_mask = 0;
+	priv->bdiag1_clear_value = 0;
+	priv->bdiag1_clear_mask = 0;
+	priv->can_err_id = 0;
+	memset(priv->can_err_data, 0, 8);
+
+	/* state changes */
+	priv->new_state = priv->can.state;
+
+	/* clear queued fifos */
+	mcp2517fd_clear_queued_fifos(spi);
+
+	/* handle the rx */
+	if (priv->status.intf & CAN_INT_RXIF) {
+		ret = mcp2517fd_can_ist_handle_rxif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* handle the tef */
+	if (priv->status.intf & CAN_INT_TEFIF) {
+		ret = mcp2517fd_can_ist_handle_tefif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* process the queued fifos */
+	ret = mcp2517fd_process_queued_fifos(spi);
+
+	/* restart the tx queue if needed */
+	if (priv->tx_queue_status == 2) {
+		/* nothing should be left pending /in flight now... */
+		priv->fifos.tx_pending_mask = 0;
+		priv->fifos.tx_submitted_mask = 0;
+		priv->fifos.tx_processed_mask = 0;
+		priv->tx_queue_status = 1;
+		/* wake queue now */
+		netif_wake_queue(priv->net);
+	}
+
+	/* handle error interrupt flags */
+	if (priv->status.rxovif) {
+		ret = mcp2517fd_can_ist_handle_rxovif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* mode change erros */
+	if (priv->status.intf & CAN_INT_MODIF) {
+		ret = mcp2517fd_can_ist_handle_modif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* sram ECC error interrupt */
+	if (priv->status.intf & CAN_INT_ECCIF) {
+		ret = mcp2517fd_can_ist_handle_eccif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* system error interrupt*/
+	if (priv->status.intf & CAN_INT_SERRIF) {
+		ret = mcp2517fd_can_ist_handle_serrif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* message format interrupt */
+	if (priv->status.intf & CAN_INT_IVMIF) {
+		priv->can_err_id |= CAN_ERR_PROT;
+		priv->can_err_data[2] |= CAN_ERR_PROT_FORM;
+		priv->int_clear_mask |= CAN_INT_IVMIF;
+		priv->net->stats.rx_frame_errors++;
+		priv->net->stats.rx_errors++;
+	}
+
+	/* handle bus errors in more detail */
+	if (priv->status.intf & CAN_INT_CERRIF) {
+		ret = mcp2517fd_can_ist_handle_cerrif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* Error counter handling */
+	if (priv->status.trec & CAN_TREC_TXWARN) {
+		priv->new_state = CAN_STATE_ERROR_WARNING;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_TX_WARNING;
+	}
+	if (priv->status.trec & CAN_TREC_RXWARN) {
+		priv->new_state = CAN_STATE_ERROR_WARNING;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_RX_WARNING;
+	}
+	if (priv->status.trec & CAN_TREC_TXBP) {
+		priv->new_state = CAN_STATE_ERROR_PASSIVE;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+	}
+	if (priv->status.trec & CAN_TREC_RXBP) {
+		priv->new_state = CAN_STATE_ERROR_PASSIVE;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+	}
+	if (priv->status.trec & CAN_TREC_TXBO) {
+		priv->new_state = CAN_STATE_BUS_OFF;
+		priv->can_err_id |= CAN_ERR_BUSOFF;
+	}
+
+	/* based on the last state state check the new state */
+	switch (priv->can.state) {
+	case CAN_STATE_ERROR_ACTIVE:
+		if (priv->new_state >= CAN_STATE_ERROR_WARNING &&
+		    priv->new_state <= CAN_STATE_BUS_OFF)
+			priv->can.can_stats.error_warning++;
+		/* fallthrough */
+	case CAN_STATE_ERROR_WARNING:
+		if (priv->new_state >= CAN_STATE_ERROR_PASSIVE &&
+		    priv->new_state <= CAN_STATE_BUS_OFF)
+			priv->can.can_stats.error_passive++;
+		break;
+	default:
+		break;
+	}
+	priv->can.state = priv->new_state;
+
+	/* and send error packet */
+	if (priv->can_err_id)
+		mcp2517fd_error_skb(priv->net);
+
+	/* handle BUS OFF */
+	if (priv->can.state == CAN_STATE_BUS_OFF) {
+		if (priv->can.restart_ms == 0) {
+			netif_stop_queue(priv->net);
+			priv->force_quit = 1;
+			priv->can.can_stats.bus_off++;
+			can_bus_off(priv->net);
+			mcp2517fd_hw_sleep(spi);
+		}
+	}
+
+	/* clear int flags */
+	if (priv->int_clear_mask) {
+		ret = mcp2517fd_cmd_write_mask(spi,
+					       CAN_INT,
+					       priv->int_clear_value,
+					       priv->int_clear_mask,
+					       priv->spi_speed_hz);
+		if (ret)
+			return ret;
+	}
+	if (priv->bdiag1_clear_mask) {
+		ret = mcp2517fd_cmd_write_mask(spi,
+					       CAN_BDIAG1,
+					       priv->bdiag1_clear_value,
+					       priv->bdiag1_clear_mask,
+					       priv->spi_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t mcp2517fd_can_ist(int irq, void *dev_id)
+{
+	struct mcp2517fd_priv *priv = dev_id;
+	struct spi_device *spi = priv->spi;
+	int ret;
+
+	priv->stats.irq_calls++;
+	priv->stats.irq_state = IRQ_STATE_RUNNING;
+
+	while (!priv->force_quit) {
+		/* count irq loops */
+		priv->stats.irq_loops++;
+
+		/* read interrupt status flags */
+		ret = mcp2517fd_cmd_readn(spi, CAN_INT,
+					  &priv->status,
+					  sizeof(priv->status),
+					  priv->spi_speed_hz);
+		if (ret)
+			return ret;
+
+		/* only act if the mask is applied */
+		if ((priv->status.intf &
+		     (priv->status.intf >> CAN_INT_IE_SHIFT)) == 0)
+			break;
+
+		/* handle the status */
+		ret = mcp2517fd_can_ist_handle_status(spi);
+		if (ret)
+			return ret;
+	}
+
+	priv->stats.irq_state = IRQ_STATE_HANDLED;
+
+	return IRQ_HANDLED;
+}
+
+static int mcp2517fd_get_berr_counter(const struct net_device *net,
+				      struct can_berr_counter *bec)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+
+	bec->txerr = (priv->status.trec & CAN_TREC_TEC_MASK) >>
+		CAN_TREC_TEC_SHIFT;
+	bec->rxerr = (priv->status.trec & CAN_TREC_REC_MASK) >>
+		CAN_TREC_REC_SHIFT;
+
+	return 0;
+}
+
+static int mcp2517fd_power_enable(struct regulator *reg, int enable)
+{
+	if (IS_ERR_OR_NULL(reg))
+		return 0;
+
+	if (enable)
+		return regulator_enable(reg);
+	else
+		return regulator_disable(reg);
+}
+
+static int mcp2517fd_do_set_mode(struct net_device *net, enum can_mode mode)
+{
+	switch (mode) {
+	case CAN_MODE_START:
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int mcp2517fd_do_set_nominal_bittiming(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct can_bittiming *bt = &priv->can.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;
+
+	/* calculate nominal bit timing */
+	priv->regs.nbtcfg = ((sjw - 1) << CAN_NBTCFG_SJW_SHIFT) |
+		((tseg2 - 1) << CAN_NBTCFG_TSEG2_SHIFT) |
+		((tseg1 - 1) << CAN_NBTCFG_TSEG1_SHIFT) |
+		((brp - 1) << CAN_NBTCFG_BRP_SHIFT);
+
+	return mcp2517fd_cmd_write(spi, CAN_NBTCFG,
+				   priv->regs.nbtcfg,
+				   priv->spi_setup_speed_hz);
+}
+
+static int mcp2517fd_do_set_data_bittiming(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct can_bittiming *bt = &priv->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;
+
+	/* calculate nominal bit timing */
+	priv->regs.dbtcfg = ((sjw - 1) << CAN_DBTCFG_SJW_SHIFT) |
+		((tseg2 - 1) << CAN_DBTCFG_TSEG2_SHIFT) |
+		((tseg1 - 1) << CAN_DBTCFG_TSEG1_SHIFT) |
+		((brp - 1) << CAN_DBTCFG_BRP_SHIFT);
+
+	return mcp2517fd_cmd_write(spi, CAN_DBTCFG,
+				   priv->regs.dbtcfg,
+				   priv->spi_setup_speed_hz);
+}
+
+static int mcp2517fd_hw_probe(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	u32 val;
+	int ret;
+
+	/* Wait for oscillator startup timer after power up */
+	mdelay(MCP2517FD_OST_DELAY_MS);
+
+	/* send a "blind" reset, hoping we are in Config mode */
+	mcp2517fd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+	/* Wait for oscillator startup again */
+	mdelay(MCP2517FD_OST_DELAY_MS);
+
+	/* check clock register that the clock is ready or disabled */
+	ret = mcp2517fd_cmd_read(spi, MCP2517FD_OSC, &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* there can only be one... */
+	switch (val & (MCP2517FD_OSC_OSCRDY | MCP2517FD_OSC_OSCDIS)) {
+	case MCP2517FD_OSC_OSCRDY: /* either the clock is ready */
+		break;
+	case MCP2517FD_OSC_OSCDIS: /* or the clock is disabled */
+		/* wakeup sleeping system */
+		ret = mcp2517fd_hw_wake(spi);
+		if (ret)
+			return ret;
+		/* send a reset, hoping we are now in Config mode */
+		mcp2517fd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+		/* Wait for oscillator startup again */
+		mdelay(MCP2517FD_OST_DELAY_MS);
+		break;
+	default:
+		/* otherwise there is no valid device (or in strange state)
+		 *
+		 * if PLL is enabled but not ready, then there may be
+		 * something "fishy"
+		 * this happened during driver development
+		 * (enabling pll, when when on wrong clock), so best warn
+		 * about such a possibility
+		 */
+		if ((val & (MCP2517FD_OSC_PLLEN | MCP2517FD_OSC_PLLRDY))
+		    == MCP2517FD_OSC_PLLEN)
+			dev_err(&spi->dev,
+				"mcp2517fd may be in a strange state - a power disconnect may be required\n");
+
+		return -ENODEV;
+	}
+
+	/* check if we are in config mode already*/
+
+	/* read CON register and match */
+	ret = mcp2517fd_cmd_read(spi, CAN_CON, &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* apply mask and check */
+	if ((val & CAN_CON_DEFAULT_MASK) == CAN_CON_DEFAULT)
+		return 0;
+
+	/* as per datasheet a reset only works in Config Mode
+	 * so as we have in principle no knowledge of the current
+	 * mode that the controller is in we have no safe way
+	 * to detect the device correctly
+	 * hence we need to "blindly" put the controller into
+	 * config mode.
+	 * on the "save" side, the OSC reg has to be valid already,
+	 * so there is a chance we got the controller...
+	 */
+
+	/* blindly force it into config mode */
+	ret = mcp2517fd_cmd_write(spi, CAN_CON, CAN_CON_DEFAULT,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* delay some time */
+	mdelay(MCP2517FD_OST_DELAY_MS);
+
+	/* reset can controller */
+	mcp2517fd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+	/* delay some time */
+	mdelay(MCP2517FD_OST_DELAY_MS);
+
+	/* read CON register and match a final time */
+	ret = mcp2517fd_cmd_read(spi, CAN_CON, &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* just allow dumping the register if we ever get here */
+	dev_dbg(&spi->dev, "read CAN_CON = 0x%08x\n", val);
+
+	/* apply mask and check */
+	if ((val & CAN_CON_DEFAULT_MASK) != CAN_CON_DEFAULT)
+		return -ENODEV;
+
+	/* just in case: disable interrupts on controller */
+	return mcp2517fd_disable_interrupts(spi,
+					    priv->spi_setup_speed_hz);
+}
+
+static int mcp2517fd_set_normal_mode(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+		priv->active_can_mode = CAN_CON_MODE_EXTERNAL_LOOPBACK;
+	else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		priv->active_can_mode = CAN_CON_MODE_LISTENONLY;
+	else if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+		priv->active_can_mode = CAN_CON_MODE_MIXED;
+	else
+		priv->active_can_mode = CAN_CON_MODE_CAN2_0;
+
+	/* set mode to normal */
+	priv->regs.con = (priv->regs.con & ~CAN_CON_REQOP_MASK) |
+		(priv->active_can_mode << CAN_CON_REQOP_SHIFT);
+
+	ret = mcp2517fd_cmd_write(spi, CAN_CON,
+				  priv->regs.con,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	return 0;
+}
+
+static int mcp2517fd_setup_osc(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	int val = ((priv->config.clock_pll) ? MCP2517FD_OSC_PLLEN : 0)
+		| ((priv->config.clock_div2) ? MCP2517FD_OSC_SCLKDIV : 0);
+	int waitfor = ((priv->config.clock_pll) ? MCP2517FD_OSC_PLLRDY : 0)
+		| ((priv->config.clock_div2) ? MCP2517FD_OSC_SCLKRDY : 0)
+		| MCP2517FD_OSC_OSCRDY;
+	int ret;
+	unsigned long timeout;
+
+	/* manage clock_out divider */
+	switch (priv->config.clock_odiv) {
+	case 10:
+		val |= (MCP2517FD_OSC_CLKODIV_10)
+			<< MCP2517FD_OSC_CLKODIV_SHIFT;
+		break;
+	case 4:
+		val |= (MCP2517FD_OSC_CLKODIV_4)
+			<< MCP2517FD_OSC_CLKODIV_SHIFT;
+		break;
+	case 2:
+		val |= (MCP2517FD_OSC_CLKODIV_2)
+			<< MCP2517FD_OSC_CLKODIV_SHIFT;
+		break;
+	case 1:
+		val |= (MCP2517FD_OSC_CLKODIV_1)
+			<< MCP2517FD_OSC_CLKODIV_SHIFT;
+		break;
+	case 0:
+		/* this means implicitly SOF output */
+		val |= (MCP2517FD_OSC_CLKODIV_10)
+			<< MCP2517FD_OSC_CLKODIV_SHIFT;
+		break;
+	default:
+		dev_err(&spi->dev,
+			"Unsupported output clock divider %i\n",
+			priv->config.clock_odiv);
+		return -EINVAL;
+	}
+
+	/* write clock */
+	ret = mcp2517fd_cmd_write(spi, MCP2517FD_OSC, val,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* wait for synced pll/osc/sclk */
+	timeout = jiffies + MCP2517FD_OSC_POLLING_JIFFIES;
+	while (time_before_eq(jiffies, timeout)) {
+		ret = mcp2517fd_cmd_read(spi, MCP2517FD_OSC,
+					 &priv->regs.osc,
+					 priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		if ((priv->regs.osc & waitfor) == waitfor)
+			return 0;
+	}
+
+	dev_err(&spi->dev,
+		"Clock did not lock within the timeout period\n");
+
+	/* we timed out */
+	return -ENODEV;
+}
+
+static int mcp2517fd_setup_fifo(struct net_device *net,
+				struct mcp2517fd_priv *priv,
+				struct spi_device *spi)
+{
+	u32 con_val = priv->regs.con & (~CAN_CON_REQOP_MASK);
+	u32 val, available_memory, tx_memory_used;
+	int ret;
+	int i, fifo;
+
+	/* clear all filter */
+	for (i = 0; i < 32; i++) {
+		ret = mcp2517fd_cmd_write(spi, CAN_FLTOBJ(i), 0,
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		ret = mcp2517fd_cmd_write(spi, CAN_FLTMASK(i), 0,
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		ret = mcp2517fd_cmd_write_mask(
+			spi, CAN_FLTCON(i), 0,
+			CAN_FILCON_MASK(i),
+			priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	/* decide on TEF, tx and rx FIFOS */
+	switch (net->mtu) {
+	case CAN_MTU:
+		/* note: if we have INT1 connected to a GPIO
+		 * then we could handle this differently and more
+		 * efficiently
+		 */
+
+		/* mtu is 8 */
+		priv->fifos.payload_size = 8;
+		priv->fifos.payload_mode = CAN_TXQCON_PLSIZE_8;
+
+		/* 7 tx fifos starting at fifo 1 */
+		priv->fifos.tx_fifos = 7;
+
+		/* 24 rx fifos with 1 buffers/fifo */
+		priv->fifos.rx_fifo_depth = 1;
+
+		break;
+	case CANFD_MTU:
+		/* wish there was a way to have hw filters
+		 * that can separate based on length ...
+		 */
+		/* MTU is 64 */
+		priv->fifos.payload_size = 64;
+		priv->fifos.payload_mode = CAN_TXQCON_PLSIZE_64;
+
+		/* 7 tx fifos */
+		priv->fifos.tx_fifos = 7;
+
+		/* 19 rx fifos with 1 buffer/fifo */
+		priv->fifos.rx_fifo_depth = 1;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* if defined as a module modify the number of tx_fifos */
+	if (tx_fifos) {
+		dev_info(&spi->dev,
+			 "Using %i tx-fifos as per module parameter\n",
+			 tx_fifos);
+		priv->fifos.tx_fifos = tx_fifos;
+	}
+
+	/* check range - we need 1 RX-fifo and one tef-fifo, hence 30 */
+	if (priv->fifos.tx_fifos > 30) {
+		dev_err(&spi->dev,
+			"There is an absolute maximum of 30 tx-fifos\n");
+		return -EINVAL;
+	}
+
+	tx_memory_used = priv->fifos.tx_fifos * (
+		sizeof(struct mcp2517fd_obj_tef) +
+		sizeof(struct mcp2517fd_obj_tx) +
+		priv->fifos.payload_size
+		);
+	/* check that we are not exceeding memory limits with 1 RX buffer */
+	if (tx_memory_used + (sizeof(struct mcp2517fd_obj_rx) +
+		   priv->fifos.payload_size) > MCP2517FD_BUFFER_TXRX_SIZE) {
+		dev_err(&spi->dev,
+			"Configured %i tx-fifos exceeds available memory already\n",
+			priv->fifos.tx_fifos);
+		return -EINVAL;
+	}
+
+	/* calculate possible amount of RX fifos */
+	available_memory = MCP2517FD_BUFFER_TXRX_SIZE - tx_memory_used;
+
+	priv->fifos.rx_fifos = available_memory /
+		(sizeof(struct mcp2517fd_obj_rx) +
+		 priv->fifos.payload_size) /
+		priv->fifos.rx_fifo_depth;
+
+	/* we only support 31 FIFOS in total (TEF = FIFO0),
+	 * so modify rx accordingly
+	 */
+	if (priv->fifos.tx_fifos + priv->fifos.rx_fifos > 31)
+		priv->fifos.rx_fifos = 31 - priv->fifos.tx_fifos;
+
+	/* calculate rx/tx fifo start */
+	priv->fifos.rx_fifo_start = 1;
+	priv->fifos.tx_fifo_start =
+		priv->fifos.rx_fifo_start + priv->fifos.rx_fifos;
+
+	/* set up TEF SIZE to the number of tx_fifos and IRQ */
+	priv->regs.tefcon = CAN_TEFCON_FRESET |
+		CAN_TEFCON_TEFNEIE |
+		CAN_TEFCON_TEFTSEN |
+		((priv->fifos.tx_fifos - 1) << CAN_TEFCON_FSIZE_SHIFT),
+
+	ret = mcp2517fd_cmd_write(
+		spi, CAN_TEFCON,
+		priv->regs.tefcon,
+		priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* set up tx fifos */
+	val = CAN_FIFOCON_TXEN |
+		CAN_FIFOCON_FRESET | /* reset FIFO */
+		(priv->fifos.payload_mode << CAN_FIFOCON_PLSIZE_SHIFT) |
+		(0 << CAN_FIFOCON_FSIZE_SHIFT); /* 1 FIFO only */
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+		val |= CAN_FIFOCON_TXAT_ONE_SHOT <<
+			CAN_FIFOCON_TXAT_SHIFT;
+	else
+		val |= CAN_FIFOCON_TXAT_UNLIMITED <<
+			CAN_FIFOCON_TXAT_SHIFT;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		ret = mcp2517fd_cmd_write(
+			spi, CAN_FIFOCON(fifo),
+			val | (fifo << CAN_FIFOCON_TXPRI_SHIFT),
+			priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		priv->fifos.tx_fifo_mask |= BIT(fifo);
+	}
+
+	/* now set up RX FIFO */
+	for (i = 0; i < priv->fifos.rx_fifos; i++) {
+		fifo = priv->fifos.rx_fifo_start + i;
+		/* prepare the fifo itself */
+		ret = mcp2517fd_cmd_write(
+			spi, CAN_FIFOCON(fifo),
+			(priv->fifos.payload_mode <<
+			 CAN_FIFOCON_PLSIZE_SHIFT) |
+			((priv->fifos.rx_fifo_depth - 1) <<
+			 CAN_FIFOCON_FSIZE_SHIFT) |
+			CAN_FIFOCON_RXTSEN | /* RX timestamps */
+			CAN_FIFOCON_FRESET | /* reset FIFO */
+			CAN_FIFOCON_TFERFFIE | /* FIFO Full */
+			CAN_FIFOCON_TFHRFHIE | /* FIFO Half Full*/
+			CAN_FIFOCON_TFNRFNIE | /* FIFO not empty */
+			/* on the last fifo add overflow flag */
+			((i == priv->fifos.rx_fifos - 1) ?
+			 CAN_FIFOCON_RXOVIE : 0),
+			priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		/* prepare the rx filter config: filter i directs to fifo
+		 * FLTMSK and FLTOBJ are 0 already, so they match everything
+		 */
+		ret = mcp2517fd_cmd_write_mask(
+			spi, CAN_FLTCON(i),
+			CAN_FIFOCON_FLTEN(i) | (fifo << CAN_FILCON_SHIFT(i)),
+			CAN_FIFOCON_FLTEN(i) | CAN_FILCON_MASK(i),
+			priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+
+		priv->fifos.rx_fifo_mask |= BIT(fifo);
+	}
+
+	/* we need to move out of CONFIG mode shortly to get the addresses */
+	ret = mcp2517fd_cmd_write(
+		spi, CAN_CON, con_val |
+		(CAN_CON_MODE_INTERNAL_LOOPBACK << CAN_CON_REQOP_SHIFT),
+		priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* for the TEF fifo */
+	ret = mcp2517fd_cmd_read(spi, CAN_TEFUA, &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+	priv->fifos.tef_address = val;
+	priv->fifos.tef_address_start = val;
+	priv->fifos.tef_address_end = priv->fifos.tef_address_start +
+		(priv->fifos.tx_fifos) * sizeof(struct mcp2517fd_obj_tef) -
+		1;
+
+	/* get all the relevant addresses for the transmit fifos */
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		ret = mcp2517fd_cmd_read(spi, CAN_FIFOUA(fifo),
+					 &val, priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		priv->fifos.fifo_address[fifo] = val;
+	}
+
+	/* and prepare the spi_messages */
+	ret = mcp2517fd_fill_spi_transmit_fifos(priv);
+	if (ret)
+		return ret;
+
+	/* get all the relevant addresses for the rx fifos */
+	for (i = 0; i < priv->fifos.rx_fifos; i++) {
+		fifo = priv->fifos.rx_fifo_start + i;
+		ret = mcp2517fd_cmd_read(spi, CAN_FIFOUA(fifo),
+					 &val, priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+	       priv->fifos.fifo_address[fifo] = val;
+	}
+
+	/* now get back into config mode */
+	ret = mcp2517fd_cmd_write(
+		spi, CAN_CON, con_val |
+		(CAN_CON_MODE_CONFIG << CAN_CON_REQOP_SHIFT),
+		priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mcp2517fd_setup(struct net_device *net,
+			   struct mcp2517fd_priv *priv,
+			   struct spi_device *spi)
+{
+	u32 val;
+	int ret;
+
+	/* set up pll/clock if required */
+	ret = mcp2517fd_setup_osc(spi);
+	if (ret)
+		return ret;
+
+	/* set up RAM ECC (but for now without interrupts) */
+	priv->regs.ecccon = MCP2517FD_ECCCON_ECCEN;
+	ret = mcp2517fd_cmd_write(spi, MCP2517FD_ECCCON,
+				  priv->regs.ecccon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* GPIO handling - could expose this as gpios*/
+	val = 0; /* PUSHPULL INT , TXCAN PUSH/PULL, no Standby */
+
+	/* SOF/CLOCKOUT pin 3 */
+	if (priv->config.clock_odiv < 0)
+		val |= MCP2517FD_IOCON_SOF;
+	/* GPIO0 - pin 9 */
+	switch (priv->config.gpio0_mode) {
+	case gpio_mode_standby:
+	case gpio_mode_int: /* asserted low on TXIF */
+	case gpio_mode_out_low:
+	case gpio_mode_out_high:
+	case gpio_mode_in:
+		val |= priv->config.gpio0_mode;
+		break;
+	default: /* GPIO IN */
+		dev_err(&spi->dev,
+			"GPIO1 does not support mode %08x\n",
+			priv->config.gpio0_mode);
+		return -EINVAL;
+	}
+	/* GPIO1 - pin 8 */
+	switch (priv->config.gpio1_mode) {
+	case gpio_mode_standby:
+		dev_err(&spi->dev,
+			"GPIO1 does not support transceiver standby\n");
+		return -EINVAL;
+	case gpio_mode_int: /* asserted low on RXIF */
+	case gpio_mode_out_low:
+	case gpio_mode_out_high:
+	case gpio_mode_in:
+		val |= priv->config.gpio1_mode << 1;
+		break;
+	default:
+		dev_err(&spi->dev,
+			"GPIO1 does not support mode %08x\n",
+			priv->config.gpio0_mode);
+		return -EINVAL;
+	}
+	/* INT/GPIO pins as open drain */
+	if (priv->config.gpio_opendrain)
+		val |= MCP2517FD_IOCON_INTOD;
+	if (priv->config.txcan_opendrain)
+		val |= MCP2517FD_IOCON_TXCANOD; /* OpenDrain TXCAN */
+	if (priv->config.int_opendrain)
+		val |= MCP2517FD_IOCON_INTOD; /* OpenDrain INT pins */
+
+	priv->regs.iocon = val;
+	ret = mcp2517fd_cmd_write(spi, MCP2517FD_IOCON, val,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* set up Transmitter Delay compensation */
+	priv->regs.tdc = CAN_TDC_EDGFLTEN;
+	ret = mcp2517fd_cmd_write(spi, CAN_TDC, priv->regs.tdc,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* time stamp control register - 1ns resolution, but disabled */
+	ret = mcp2517fd_cmd_write(spi, CAN_TBC, 0,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+	priv->regs.tscon = CAN_TSCON_TBCEN |
+		((priv->can.clock.freq / 1000000)
+		 << CAN_TSCON_TBCPRE_SHIFT);
+	ret = mcp2517fd_cmd_write(spi, CAN_TSCON,
+				  priv->regs.tscon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* setup value of con_register */
+	priv->regs.con = CAN_CON_STEF /* enable TEF */;
+	/* transmissin bandwidth sharing bits */
+	if (bw_sharing_log2bits < 12)
+		bw_sharing_log2bits = 12;
+	priv->regs.con |= bw_sharing_log2bits << CAN_CON_TXBWS_SHIFT;
+	/* non iso FD mode */
+	if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO))
+		priv->regs.con |= CAN_CON_ISOCRCEN;
+	/* one shot */
+	if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+		priv->regs.con |= CAN_CON_RTXAT;
+
+	/* setup fifos - this also puts the system into sleep mode */
+	return mcp2517fd_setup_fifo(net, priv, spi);
+}
+
+static int mcp2517fd_open(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+	int ret;
+
+	ret = open_candev(net);
+	if (ret) {
+		dev_err(&spi->dev, "unable to set initial baudrate!\n");
+		return ret;
+	}
+
+	mcp2517fd_power_enable(priv->transceiver, 1);
+
+	priv->force_quit = 0;
+
+	priv->stats.irq_state = 0;
+	priv->stats.irq_calls = 0;
+	priv->stats.irq_loops = 0;
+
+	priv->force_quit = 0;
+	ret = request_threaded_irq(spi->irq, NULL,
+				   mcp2517fd_can_ist,
+				   IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+				   DEVICE_NAME, priv);
+	if (ret) {
+		dev_err(&spi->dev, "failed to acquire irq %d - %i\n",
+			spi->irq, ret);
+		mcp2517fd_power_enable(priv->transceiver, 0);
+		close_candev(net);
+		return ret;
+	}
+
+	/* wake from sleep if necessary */
+	ret = mcp2517fd_hw_wake(spi);
+	if (ret)
+		goto open_clean;
+
+	ret = mcp2517fd_hw_probe(spi);
+	if (ret) {
+		dev_err(&spi->dev,
+			"HW Probe failed, but was working earlier!\n");
+		goto open_clean;
+	}
+
+	ret = mcp2517fd_setup(net, priv, spi);
+	if (ret)
+		goto open_clean;
+
+	mcp2517fd_do_set_nominal_bittiming(net);
+	mcp2517fd_do_set_data_bittiming(net);
+
+	ret = mcp2517fd_set_normal_mode(spi);
+	if (ret)
+		goto open_clean;
+
+	/* only now enable the interrupt on the controller */
+	ret =  mcp2517fd_enable_interrupts(spi,
+					   priv->spi_setup_speed_hz);
+	if (ret)
+		goto open_clean;
+
+	can_led_event(net, CAN_LED_EVENT_OPEN);
+
+	priv->tx_queue_status = 1;
+	netif_wake_queue(net);
+
+	return 0;
+
+open_clean:
+	mcp2517fd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+	free_irq(spi->irq, priv);
+	mcp2517fd_hw_sleep(spi);
+	mcp2517fd_power_enable(priv->transceiver, 0);
+	close_candev(net);
+
+	return ret;
+}
+
+static void mcp2517fd_clean(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	int i;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		if (priv->fifos.tx_pending_mask & BIT(i)) {
+			can_free_echo_skb(priv->net, 0);
+			priv->net->stats.tx_errors++;
+		}
+	}
+
+	priv->fifos.tx_pending_mask = 0;
+}
+
+static int mcp2517fd_stop(struct net_device *net)
+{
+	struct mcp2517fd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+
+	close_candev(net);
+
+	kfree(priv->spi_transmit_fifos);
+	priv->spi_transmit_fifos = NULL;
+
+	priv->force_quit = 1;
+	free_irq(spi->irq, priv);
+
+	/* Disable and clear pending interrupts */
+	mcp2517fd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+
+	mcp2517fd_clean(net);
+
+	mcp2517fd_hw_sleep(spi);
+
+	mcp2517fd_power_enable(priv->transceiver, 0);
+
+	priv->can.state = CAN_STATE_STOPPED;
+
+	can_led_event(net, CAN_LED_EVENT_STOP);
+
+	return 0;
+}
+
+static const struct net_device_ops mcp2517fd_netdev_ops = {
+	.ndo_open = mcp2517fd_open,
+	.ndo_stop = mcp2517fd_stop,
+	.ndo_start_xmit = mcp2517fd_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static const struct of_device_id mcp2517fd_of_match[] = {
+	{
+		.compatible	= "microchip,mcp2517fd",
+		.data		= (void *)CAN_MCP2517FD,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mcp2517fd_of_match);
+
+static const struct spi_device_id mcp2517fd_id_table[] = {
+	{
+		.name		= "mcp2517fd",
+		.driver_data	= (kernel_ulong_t)CAN_MCP2517FD,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, mcp2517fd_id_table);
+
+static void mcp2517fd_debugfs_add(struct mcp2517fd_priv *priv)
+{
+#if defined(CONFIG_DEBUG_FS)
+	struct dentry *root, *fifousage, *fifoaddr, *rx, *tx, *status, *regs;
+	char name[32];
+	int i;
+
+	/* create the net device name */
+	snprintf(name, sizeof(name), DEVICE_NAME "-%s", priv->net->name);
+	priv->debugfs_dir = debugfs_create_dir(name, NULL);
+	root = priv->debugfs_dir;
+
+	rx = debugfs_create_dir("rx", root);
+	tx = debugfs_create_dir("tx", root);
+	fifousage = debugfs_create_dir("fifo_usage", root);
+	fifoaddr = debugfs_create_dir("fifo_address", root);
+	status = debugfs_create_dir("status", root);
+	regs = debugfs_create_dir("regs", root);
+
+	/* add spi speed info */
+	debugfs_create_u32("spi_setup_speed_hz", 0444, root,
+			   &priv->spi_setup_speed_hz);
+	debugfs_create_u32("spi_speed_hz", 0444, root,
+			   &priv->spi_speed_hz);
+
+	/* add irq state info */
+	debugfs_create_u64("irq_calls", 0444, root, &priv->stats.irq_calls);
+	debugfs_create_u64("irq_loops", 0444, root, &priv->stats.irq_loops);
+	debugfs_create_u32("irq_state", 0444, root, &priv->stats.irq_state);
+
+	/* export the status structure */
+	debugfs_create_x32("intf", 0444, status, &priv->status.intf);
+	debugfs_create_x32("rx_if", 0444, status, &priv->status.rxif);
+	debugfs_create_x32("tx_if", 0444, status, &priv->status.txif);
+	debugfs_create_x32("rx_ovif", 0444, status, &priv->status.rxovif);
+	debugfs_create_x32("tx_atif", 0444, status, &priv->status.txatif);
+	debugfs_create_x32("tx_req", 0444, status, &priv->status.txreq);
+	debugfs_create_x32("trec", 0444, status, &priv->status.trec);
+	debugfs_create_x32("bdiag0", 0444, status, &priv->status.bdiag0);
+	debugfs_create_x32("bdiag1", 0444, status, &priv->status.bdiag1);
+
+	/* some configuration registers */
+	debugfs_create_x32("con", 0444, regs, &priv->regs.con);
+	debugfs_create_x32("ecccon", 0444, regs, &priv->regs.ecccon);
+	debugfs_create_x32("osc", 0444, regs, &priv->regs.osc);
+	debugfs_create_x32("iocon", 0444, regs, &priv->regs.iocon);
+	debugfs_create_x32("tdc", 0444, regs, &priv->regs.tdc);
+	debugfs_create_x32("tscon", 0444, regs, &priv->regs.tscon);
+	debugfs_create_x32("nbtcfg", 0444, regs, &priv->regs.nbtcfg);
+	debugfs_create_x32("dbtcfg", 0444, regs, &priv->regs.dbtcfg);
+
+	/* information on fifos */
+	debugfs_create_u32("fifo_start", 0444, rx,
+			   &priv->fifos.rx_fifo_start);
+	debugfs_create_u32("fifo_count", 0444, rx,
+			   &priv->fifos.rx_fifos);
+	debugfs_create_x32("fifo_mask", 0444, rx,
+			   &priv->fifos.rx_fifo_mask);
+	debugfs_create_u64("rx_overflow", 0444, rx,
+			   &priv->stats.rx_overflow);
+
+	debugfs_create_u32("fifo_start", 0444, tx,
+			   &priv->fifos.tx_fifo_start);
+	debugfs_create_u32("fifo_count", 0444, tx,
+			   &priv->fifos.tx_fifos);
+	debugfs_create_x32("fifo_mask", 0444, tx,
+			   &priv->fifos.tx_fifo_mask);
+	debugfs_create_x32("fifo_pending", 0444, tx,
+			   &priv->fifos.tx_pending_mask);
+	debugfs_create_x32("fifo_submitted", 0444, tx,
+			   &priv->fifos.tx_submitted_mask);
+	debugfs_create_x32("fifo_processed", 0444, tx,
+			   &priv->fifos.tx_processed_mask);
+	debugfs_create_u32("queue_status", 0444, tx,
+			   &priv->tx_queue_status);
+
+	debugfs_create_u32("fifo_max_payload_size", 0444, root,
+			   &priv->fifos.payload_size);
+
+	/* statistics on fifo buffer usage */
+	for (i = 1; i < 32; i++) {
+		snprintf(name, sizeof(name), "%02i", i);
+		debugfs_create_u64(name, 0444, fifousage,
+				   &priv->stats.fifo_usage[i]);
+		debugfs_create_u32(name, 0444, fifoaddr,
+				   &priv->fifos.fifo_address[i]);
+	}
+
+#endif
+}
+
+static void mcp2517fd_debugfs_remove(struct mcp2517fd_priv *priv)
+{
+#if defined(CONFIG_DEBUG_FS)
+	debugfs_remove_recursive(priv->debugfs_dir);
+#endif
+}
+
+int mcp2517fd_of_parse(struct mcp2517fd_priv *priv)
+{
+#ifdef CONFIG_OF_DYNAMIC
+	struct spi_device *spi = priv->spi;
+	const struct device_node *np = spi->dev.of_node;
+	u32 val;
+	int ret;
+
+	ret = of_property_read_u32_index(np, "microchip,clock_div",
+					 0, &val);
+	if (!ret) {
+		switch (val) {
+		case 1:
+			priv->config.clock_div2 = false;
+			break;
+		case 2:
+			priv->config.clock_div2 = true;
+			break;
+		default:
+			dev_err(&spi->dev,
+				"Invalid value in device tree for microchip,clock_div: %u - valid_values: 1, 2\n",
+				val);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32_index(np, "microchip,clock_out_div",
+					 0, &val);
+	if (!ret) {
+		switch (val) {
+		case 0:
+		case 1:
+		case 2:
+		case 4:
+		case 10:
+			priv->config.clock_odiv = val;
+			break;
+		default:
+			dev_err(&spi->dev,
+				"Invalid value in device tree for microchip,clock_out_div: %u - valid values: 0, 1, 2, 4, 10\n",
+				val);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32_index(np, "microchip,gpio0_mode",
+					 0, &val);
+	if (!ret) {
+		switch (val) {
+		case 0:
+			priv->config.gpio0_mode = gpio_mode_in;
+			break;
+		case 1:
+			priv->config.gpio0_mode = gpio_mode_int;
+			break;
+		case 2:
+			priv->config.gpio0_mode = gpio_mode_out_low;
+			break;
+		case 3:
+			priv->config.gpio0_mode = gpio_mode_out_high;
+			break;
+		case 4:
+			priv->config.gpio0_mode = gpio_mode_standby;
+			break;
+		default:
+			dev_err(&spi->dev,
+				"Invalid value in device tree for microchip,gpio0_mode: %u - valid values: 0, 1, 2, 3, 4\n",
+				val);
+			return -EINVAL;
+		}
+	} else {
+		priv->config.gpio0_mode = gpio_mode_in;
+	}
+
+	ret = of_property_read_u32_index(np, "microchip,gpio1_mode",
+					 0, &val);
+	if (!ret) {
+		switch (val) {
+		case 0:
+			priv->config.gpio1_mode = gpio_mode_in;
+			break;
+		case 1:
+			priv->config.gpio1_mode = gpio_mode_int;
+			break;
+		case 2:
+			priv->config.gpio1_mode = gpio_mode_out_low;
+			break;
+		case 3:
+			priv->config.gpio1_mode = gpio_mode_out_high;
+			break;
+		default:
+			dev_err(&spi->dev,
+				"Invalid value in device tree for microchip,gpio1_mode: %u - valif_values: 0, 1, 2, 3, 4\n",
+				val);
+			return -EINVAL;
+		}
+	} else {
+		priv->config.gpio1_mode = gpio_mode_in;
+	}
+
+	priv->config.gpio_opendrain = of_property_read_bool(
+		np, "microchip,gpio_opendrain");
+
+	priv->config.txcan_opendrain = of_property_read_bool(
+		np, "microchip,txcan_opendrain");
+
+	priv->config.int_opendrain = of_property_read_bool(
+		np, "microchip,int_opendrain");
+#endif
+	return 0;
+}
+
+static int mcp2517fd_can_probe(struct spi_device *spi)
+{
+	const struct of_device_id *of_id =
+		of_match_device(mcp2517fd_of_match, &spi->dev);
+	struct net_device *net;
+	struct mcp2517fd_priv *priv;
+	struct clk *clk;
+	int ret, freq;
+
+	/* as irq_create_fwspec_mapping() can return 0, check for it */
+	if (spi->irq <= 0) {
+		dev_err(&spi->dev, "no valid irq line defined: irq = %i\n",
+			spi->irq);
+		return -EINVAL;
+	}
+
+	clk = devm_clk_get(&spi->dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+	freq = clk_get_rate(clk);
+
+	if (freq < MCP2517FD_MIN_CLOCK_FREQUENCY ||
+	    freq > MCP2517FD_MAX_CLOCK_FREQUENCY) {
+		dev_err(&spi->dev,
+			"Clock frequency %i is not in range\n", freq);
+		return -ERANGE;
+	}
+
+	/* Allocate can/net device */
+	net = alloc_candev(sizeof(*priv), TX_ECHO_SKB_MAX);
+	if (!net)
+		return -ENOMEM;
+
+	if (!IS_ERR(clk)) {
+		ret = clk_prepare_enable(clk);
+		if (ret)
+			goto out_free;
+	}
+
+	net->netdev_ops = &mcp2517fd_netdev_ops;
+	net->flags |= IFF_ECHO;
+
+	priv = netdev_priv(net);
+	priv->can.bittiming_const = &mcp2517fd_nominal_bittiming_const;
+	priv->can.do_set_bittiming = &mcp2517fd_do_set_nominal_bittiming;
+	priv->can.data_bittiming_const = &mcp2517fd_data_bittiming_const;
+	priv->can.do_set_data_bittiming = &mcp2517fd_do_set_data_bittiming;
+	priv->can.do_set_mode = mcp2517fd_do_set_mode;
+	priv->can.do_get_berr_counter = mcp2517fd_get_berr_counter;
+
+	priv->can.ctrlmode_supported =
+		CAN_CTRLMODE_FD |
+		CAN_CTRLMODE_LOOPBACK |
+		CAN_CTRLMODE_LISTENONLY |
+		CAN_CTRLMODE_BERR_REPORTING |
+		CAN_CTRLMODE_FD_NON_ISO |
+		CAN_CTRLMODE_ONE_SHOT;
+
+	if (of_id)
+		priv->model = (enum mcp2517fd_model)of_id->data;
+	else
+		priv->model = spi_get_device_id(spi)->driver_data;
+
+	priv->spi = spi;
+	priv->net = net;
+	priv->clk = clk;
+
+	spi_set_drvdata(spi, priv);
+
+	/* set up gpio modes as GPIO INT */
+	priv->config.gpio0_mode = gpio_mode_int;
+	priv->config.gpio1_mode = gpio_mode_int;
+	/* all by default as push/pull */
+	priv->config.gpio_opendrain = false;
+	priv->config.txcan_opendrain = false;
+	priv->config.int_opendrain = false;
+	/* do not use the SCK clock divider of 2 */
+	priv->config.clock_div2 = false;
+	/* clock output is divided by 10 */
+	priv->config.clock_odiv = 10;
+
+	/* as a first guess we assume we are in CAN_CON_MODE_SLEEP
+	 * this is how we leave the controller when removing ourselves
+	 */
+	priv->active_can_mode = CAN_CON_MODE_SLEEP;
+
+	/* if we have a clock that is smaller then 4MHz, then enable the pll */
+	priv->config.clock_pll =
+		(freq <= MCP2517FD_AUTO_PLL_MAX_CLOCK_FREQUENCY);
+
+	/* check in device tree for overrrides */
+	ret = mcp2517fd_of_parse(priv);
+	if (ret)
+		return ret;
+
+	/* decide on real can clock rate */
+	priv->can.clock.freq = freq;
+	if (priv->config.clock_pll) {
+		priv->can.clock.freq *= MCP2517FD_PLL_MULTIPLIER;
+		if (priv->can.clock.freq > MCP2517FD_MAX_CLOCK_FREQUENCY) {
+			dev_err(&spi->dev,
+				"PLL clock frequency %i would exceed limit\n",
+				priv->can.clock.freq
+				);
+			return -EINVAL;
+		}
+	}
+	if (priv->config.clock_div2)
+		priv->can.clock.freq /= MCP2517FD_SCLK_DIVIDER;
+
+	/* calclculate the clock frequencies to use */
+	priv->spi_setup_speed_hz = freq / 2;
+	priv->spi_speed_hz = priv->can.clock.freq / 2;
+	if (priv->config.clock_div2) {
+		priv->spi_setup_speed_hz /= MCP2517FD_SCLK_DIVIDER;
+		priv->spi_speed_hz /= MCP2517FD_SCLK_DIVIDER;
+	}
+
+	if (spi->max_speed_hz) {
+		priv->spi_setup_speed_hz = min_t(int,
+						 priv->spi_setup_speed_hz,
+						 spi->max_speed_hz);
+		priv->spi_speed_hz = min_t(int,
+					   priv->spi_speed_hz,
+					   spi->max_speed_hz);
+	}
+
+	/* Configure the SPI bus */
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret)
+		goto out_clk;
+
+	priv->power = devm_regulator_get_optional(&spi->dev, "vdd");
+	priv->transceiver = devm_regulator_get_optional(&spi->dev, "xceiver");
+	if ((PTR_ERR(priv->power) == -EPROBE_DEFER) ||
+	    (PTR_ERR(priv->transceiver) == -EPROBE_DEFER)) {
+		ret = -EPROBE_DEFER;
+		goto out_clk;
+	}
+
+	ret = mcp2517fd_power_enable(priv->power, 1);
+	if (ret)
+		goto out_clk;
+
+	SET_NETDEV_DEV(net, &spi->dev);
+
+	ret = mcp2517fd_hw_probe(spi);
+	/* on error retry a second time */
+	if (ret == -ENODEV) {
+		ret = mcp2517fd_hw_probe(spi);
+		if (!ret)
+			dev_info(&spi->dev,
+				 "found device only during retry\n");
+	}
+	if (ret) {
+		if (ret == -ENODEV)
+			dev_err(&spi->dev,
+				"Cannot initialize MCP%x. Wrong wiring?\n",
+				priv->model);
+		goto error_probe;
+	}
+
+	mcp2517fd_hw_sleep(spi);
+
+	ret = register_candev(net);
+	if (ret)
+		goto error_probe;
+
+	/* register debugfs */
+	mcp2517fd_debugfs_add(priv);
+
+	devm_can_led_init(net);
+
+	netdev_info(net, "MCP%x successfully initialized.\n", priv->model);
+	return 0;
+
+error_probe:
+	mcp2517fd_power_enable(priv->power, 0);
+
+out_clk:
+	if (!IS_ERR(clk))
+		clk_disable_unprepare(clk);
+
+out_free:
+	free_candev(net);
+	dev_err(&spi->dev, "Probe failed, err=%d\n", -ret);
+	return ret;
+}
+
+static int mcp2517fd_can_remove(struct spi_device *spi)
+{
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct net_device *net = priv->net;
+
+	mcp2517fd_debugfs_remove(priv);
+
+	unregister_candev(net);
+
+	mcp2517fd_power_enable(priv->power, 0);
+
+	if (!IS_ERR(priv->clk))
+		clk_disable_unprepare(priv->clk);
+
+	free_candev(net);
+
+	return 0;
+}
+
+static int __maybe_unused mcp2517fd_can_suspend(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+	struct net_device *net = priv->net;
+
+	priv->force_quit = 1;
+	disable_irq(spi->irq);
+
+	if (netif_running(net)) {
+		netif_device_detach(net);
+
+		mcp2517fd_hw_sleep(spi);
+		mcp2517fd_power_enable(priv->transceiver, 0);
+		priv->after_suspend = AFTER_SUSPEND_UP;
+	} else {
+		priv->after_suspend = AFTER_SUSPEND_DOWN;
+	}
+
+	if (!IS_ERR_OR_NULL(priv->power)) {
+		regulator_disable(priv->power);
+		priv->after_suspend |= AFTER_SUSPEND_POWER;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused mcp2517fd_can_resume(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct mcp2517fd_priv *priv = spi_get_drvdata(spi);
+
+	if (priv->after_suspend & AFTER_SUSPEND_POWER)
+		mcp2517fd_power_enable(priv->power, 1);
+
+	if (priv->after_suspend & AFTER_SUSPEND_UP)
+		mcp2517fd_power_enable(priv->transceiver, 1);
+	else
+		priv->after_suspend = 0;
+
+	priv->force_quit = 0;
+
+	enable_irq(spi->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mcp2517fd_can_pm_ops, mcp2517fd_can_suspend,
+	mcp2517fd_can_resume);
+
+static struct spi_driver mcp2517fd_can_driver = {
+	.driver = {
+		.name = DEVICE_NAME,
+		.of_match_table = mcp2517fd_of_match,
+		.pm = &mcp2517fd_can_pm_ops,
+	},
+	.id_table = mcp2517fd_id_table,
+	.probe = mcp2517fd_can_probe,
+	.remove = mcp2517fd_can_remove,
+};
+module_spi_driver(mcp2517fd_can_driver);
+
+MODULE_AUTHOR("Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>");
+MODULE_DESCRIPTION("Microchip 2517FD CAN driver");
+MODULE_LICENSE("GPL v2");
--
2.11.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 1/2] dt-binding: can: mcp2517fd: document device tree bindings
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2017-11-24 18:35 UTC (permalink / raw)
  To: Wolfgang Grandegger, Marc Kleine-Budde, Rob Herring, Mark Rutland,
	linux-can-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Martin Sperl
In-Reply-To: <20171124183509.12810-1-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>

From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>

Add device-tree bindings for Microcip CanFD Controller mcp2517fd

Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
 .../bindings/net/can/microchip,mcp2517fd.txt       | 47 ++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/can/microchip,mcp2517fd.txt

diff --git a/Documentation/devicetree/bindings/net/can/microchip,mcp2517fd.txt b/Documentation/devicetree/bindings/net/can/microchip,mcp2517fd.txt
new file mode 100644
index 000000000000..96cbf0c96895
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/can/microchip,mcp2517fd.txt
@@ -0,0 +1,47 @@
+* Microchip MCP2517 stand-alone CAN controller device tree bindings
+
+Required properties:
+ - compatible: Should be one of the following:
+   - "microchip,mcp2517fd" for MCP2517fd.
+ - reg: SPI chip select.
+ - clocks: The clock feeding the CAN controller.
+ - interrupt-parent: The parent interrupt controller.
+ - interrupts: Should contain IRQ line for the CAN controller.
+
+Optional properties:
+ - vdd-supply: Regulator that powers the CAN controller.
+ - xceiver-supply: Regulator that powers the CAN transceiver.
+ - microchip,clock_out_div = <0|1|2|4|10>: Clock output pin divider
+					   0 = Start of Frame output
+					   default: 10
+ - microchip,clock_div = <1|2>: internal clock divider - default 1
+ - microchip,gpio_opendrain: gpio (int0,1) in open drain mode
+			     instead of default push/pull
+ - microchip,int_opendrain: int pin in open drain mode
+			    instead of default push/pull
+ - microchip,txcan_opendrain: txcan pin in open drain mode
+			      instead of default push/pull
+ - microchip,gpio0_mode : gpio mode functionality
+			  0 = input
+			  1 = TX interrupt output - default
+			  2 = output default low
+			  3 = output default high
+			  4 = (tx) transceiver standby
+ - microchip,gpio1_mode : gpio mode functionality
+			  0 = input - default
+			  1 = RX interrupt output - default
+			  2 = output default low
+			  3 = output default high
+
+Example:
+	can0: can@1 {
+		compatible = "microchip,mcp2515";
+		reg = <1>;
+		clocks = <&clk24m>;
+		interrupt-parent = <&gpio4>;
+		interrupts = <13 0x8>;
+		vdd-supply = <&reg5v0>;
+		xceiver-supply = <&reg5v0>;
+		microchip,gpio0_mode = <4>;
+		microchip,gpio0_mode = <1>;
+	};
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 0/2] Microchip mcp2517fd can controller driver
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2017-11-24 18:35 UTC (permalink / raw)
  To: Wolfgang Grandegger, Marc Kleine-Budde, Rob Herring, Mark Rutland,
	linux-can-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Martin Sperl

From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>

This patchset adds a driver for the mcp2517fd CanFD controller.

Most of the features of the controller are supported by this driver release.

The controller includes a few features that the current core Can(FD)
implementation does not support:
* Transmit Bandwidth Sharing bits
  - this waits for a configurable number of syncronization bits before
    atempting to transmit the next frame - for the moment controllable
    via a module parameter
* SID11 with CanFD frames
  - at this moment not supported by driver
* 3 transmit attempts in addition to ONE_SHOT
* transmitter delay compensation configurations
* micro second exact transmission and reception timestamps
  - used internally by driver
* variable number of tx-fifos
  - exposed via module parameter at this moment

The driver has been heavily optimized so that it can handle
a 100% utilized 1MHz Can-bus (with 11 bit can frames with DLC=0)
even on less powerfull SOCs like the raspberry pi 1 without
dropping frames due to driver/spi latencies (still dropps
are observed in the can/network stack).

To achive this the driver includes several code optimization
Two of which prefers continuous long spi transfers over multiple
small individual spi transfers of variable length.
This is an optimization that would favour spi dma transfers instead
of more expensive PIO in typical SPI controllers.
Both of them can get explicitly enabled by the use of a module parameter.
One of those is automatically used in the case of Can2.0 only bus
configurations.

Finer details of the implementation and rational is included with
the driver code it self for future reference.

The driver has also been tested for basic CanFD transfers with
BRS up to 5.7Mhz data bitrates - the available transceiver did
not allow for faster rates...

The driver itself exposes lots of internal data/statistics via
debugfs.

The driver implements a lock-less design for transmissions
making use instead of prepared spi messages submitted via spi_async
for transmission in the start_xmit_start code without requireing
an extra workqueue and the corresponding latencies.

Martin Sperl (2):
  dt-binding: can: mcp2517fd: document device tree bindings
  can: mcp2517fd: Add Microchip mcp2517fd CAN FD driver

 .../bindings/net/can/microchip,mcp2517fd.txt       |   47 +
 drivers/net/can/spi/Kconfig                        |    6 +
 drivers/net/can/spi/Makefile                       |    1 +
 drivers/net/can/spi/mcp2517fd.c                    | 3733 ++++++++++++++++++++
 4 files changed, 3787 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/can/microchip,mcp2517fd.txt
 create mode 100644 drivers/net/can/spi/mcp2517fd.c

--
2.11.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [RFC PATCH 1/3] dt-bindings: pinctrl: sunxi: document new generic binding
From: Andre Przywara @ 2017-11-24 17:19 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Linus Walleij, Maxime Ripard, Chen-Yu Tsai,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Mark Rutland,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Linux ARM, Arnd Bergmann,
	Icenowy Zheng,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
In-Reply-To: <20171124133123.GA15999@ulmo>

Hi Thierry,

thanks for the reply and having this discussion!

On 24/11/17 13:31, Thierry Reding wrote:
> On Fri, Nov 24, 2017 at 12:04:12PM +0000, Andre Przywara wrote:
>> Hi,
>>
>> (adding linux-sunxi, which I forgot at the initial post).
>>
>> On 24/11/17 10:52, Thierry Reding wrote:
>>> On Fri, Nov 24, 2017 at 11:19:52AM +0100, Linus Walleij wrote:
>>>> On Mon, Nov 13, 2017 at 2:25 AM, Andre Przywara <andre.przywara-5wv7dgnIgG8@public.gmane.org> wrote:
>>>>
>>>>> So far all the Allwinner pinctrl drivers provided a table in the
>>>>> kernel to describe all the pins and the link between the pinctrl functions
>>>>> names (strings) and their respective mux values (register values).
>>>>>
>>>>> Extend the binding to put those mappings in the DT, so that any SoC can
>>>>> describe its pinctrl and GPIO data fully there instead of relying on
>>>>> tables.
>>>>> This uses a generic compatible name, to be prepended with an SoC
>>>>> specific name in the node.
>>>
>>> This seems backwards to me. I'm not sure if Rob has any hard rules on
>>> this, but in the past I've seen a lot of drivers stick this kind of data
>>> into drivers. I personally also prefer that approach because the data is
>>> completely static and there's no way for any specific board to customize
>>> it. So the tables are in fact implied completely by the SoC compatible
>>> string.
>>
>> But this is just *data*, and I believe it is actually package specific.
>> We need the DT to describe the relation between devices and pins anyway,
>> it's just that we use arbitrary strings today instead of the actual
>> register value. This is what the generic pinmux property fixes.
> 
> Register values don't belong in a device tree.

I don't think that's true in a general way. This "pinmux" is already an
accepted property and used for exactly that purpose in other pinctrl
drivers:
$ git grep -l '[^,]pinmux = ' arch/arm{,64}/boot/dts
Plus the fsl,pinmux-ids property, which seems to serve the same purpose.

> And it's totally fine to
> have data in the kernel, too. We do it all the time.

I know, but that does not mean that I need to like it or should avoid
finding better solutions.

>>> Moving all of this data into device tree has a number of disadvantages:
>>>
>>>   * Existing boards already use the static tables in the driver, and the
>>>     device trees don't contain any data, so you can't get rid of any of
>>>     the existing tables because it would break ABI.
>>
>> Yes, my DeLorean is in the garage, so I can't really change this anymore
>> ;-) But that doesn't mean we have to go on with this forever, I think.
> 
> ABI stability means that, yes, you have to keep maintaining the existing
> tables forever, else old DTBs will stop working if you rip out the
> static tables that drivers depend on.

I think you got me wrong, I am totally not arguing ABI stability and its
consequences. That's why this binding is mostly for newer SoCs, but also
designed to stay compatible with the old binding - that's for other DT
users. Of course we need to keep the tables in the kernel.

>>>   * Moving the table into the DT doesn't actually solve anything because
>>>     the driver would have to validate the DT description to make sure it
>>>     contains valid data. And in order to validate DT content, the driver
>>>     would need a copy of the table anyway.
>>
>> I don't get what the driver would need to validate? We rely on DT
>> information to be correct anyway, otherwise your board just won't work.
>> If the DT is wrong, you have much bigger problems.
> 
> Given that DT is an ABI you should treat it the same way as other ABIs.
> You can't rely on the DT being correct.

But it's not a *user* ABI, where unprivileged users can supply input data.
What else can you rely on then if not on the DT?
By definition you can't be able to validate every bit of it. How would
you know that the reg address of that SATA chip is correct and not
actually pointing to the "set-board-on-fire" device?
And what about those regulators, where it gives you the allowed voltage
range?

> Rather you have to make sure to
> validate it before you hand the content to hardware. If you allow direct
> register access to your hardware via DT and don't validate, it becomes
> really easy for people to exploit it.

If you can change the DT, you already have full control over the system.
Ideally the DT comes as part of the firmware, so if you control this,
there are far less complicated ways to burn down the system. And even if
you supply the DT with your kernel, you must have control and could
trigger the set-fire device via /dev/mem or the like.

> This is not the same as saying we need to be able to fully validate all
> aspects of device tree. We can't, because some information simply does
> not exist outside of DT. However, I think it's a big mistake to trust a
> user to fully know about all intricacies of a pinmux and not make any
> mistake when writing the device tree.

When does a *user* write a device tree? What would a clueless Joe
Average expect from doing so? The device tree is written by either the
board/SoC vendor or by some kernel developers. It is not meant to be
changed by the user, apart from carefully crafted overlays, maybe. You
can have the same control by just changing the kernel (binary patching
the image file or re-compiling with
writel(MATCHES, base_addr + SET_FIRE)).

> What if one of the settings causes the board to go up in flames?

Then you better not play with it. But I don't think this is a valid
argument, really. What if gravity reverses tomorrow?
Are there any known issues with writing mux values to pinctrl registers?
I don't think the consequences would be different from putting low or
high to a GPIO, which you can do already from userland.

>> Actually we gain something, because we only commit information that can
>> actually be tested. Right now we have a lot of information which is
>> copied from the manual, and nobody knows if pin H24 on the A10 is really
>> PATA-CS1 or not. Plus we have bugs when creating the table, plus
>> copy&paste bugs. I found some while grep-ing for patterns - will send
>> fixes ASAP.
> 
> That's a different matter. If you've got bugs in the tables, then go fix
> the tables. However the assumption here is that you've done at least a
> minimum of testing and your driver didn't cause your board to go up in
> flames.

Yes, but at the moment we can't test all of the settings we put in the
kernel table. Some pins are for devices for which we don't have drivers
in the kernel (yet), for some we probably never will (JTAG).
>From writing this table for the A64 before I know it's tedious and even
with review there are bugs left. If we can decrease this risk, I am all
for it.

> When patches were posted, people had the opportunity to review
> the tables for correctness. However, if you put all of the flexibility
> into DT, you also put all of the risk there. People may just make some
> stupid mistake and cause physical damage to their hardware.

I don't see how a DT is more risky. Conceptually I consider the DT being
part of the firmware, so one trust level above the kernel. I understand
that some people have different experiences with "firmware", but frankly
I believe this discussion is pointless. If you get the DT wrong, your
board won't work. If the system easily allows you to destroy it by means
of software, you might want to fix it in general, for instance by
telling firmware to not allow access to it from the kernel. Or by
designing hardware in a way that you don't need to expose critical
settings to software. That how x86 solves most of it and what makes it
hard to physically kill a rented root server.
Hiding stuff from the DT and putting molly guards in the kernel does not
really solve this problem.

>> In the moment all the table gives us is a mapping between a *string* and
>> the respective mux register value (per pin), plus the number of pins in
>> each bank. This can *easily* be put in the DT and should belong there.
> 
> Why? This is data that is implied by the compatible string and static
> per SoC. There is no way you can change the mapping in DT. What does
> need to go into DT is the configuration of the pinmux, that is, what
> function is used for each pin on a given board.

Exactly, and that does not change on the *board* level, because there we
just refer to the pinctrl subnode phandles, defined in <soc>.dtsi.
And it's just there that I want to add the right pinmux value.

>> Actually I believe that the current binding is not correct, because it
>> makes those mux strings a part of the binding, though this is not
>> documented anywhere. A developer cannot take the binding and write a
>> working driver or even a DT without looking at the code.
>> Plus we already changed those names in the past (for instance commit
>> bc0f566a98c4), basically breaking compatibility.
> 
> If you haven't documented the strings your binding is not complete.
> That's a bug and should be fixed. Also, it is occasionally acceptable to
> break compatibility (it's technically only breaking if somebody notices)
> and fixing bugs in bindings has in the past been one of the exceptions
> where breaking ABI was specifically allowed.
> 
> However, the kind of breakage we're talking about here is total. If you
> rip out the static tables from your driver, you don't have any data to
> replace the missing information and none of the driver will work. This
> is different from the driver erroring out trying to configure a pin for
> the NAND function because it couldn't match the name.

I think this is a misunderstanding. As mentioned above I never proposed
to remove the tables from the kernel. I think I said this explicitly in
several places:
PATCH 0/3:
> Of course we can't remove the existing tables, to keep compatiblity
> with older DTs, but at least we don't need to add more tables in the
> future.
PATCH 1/3:
> +The binding described above can be extended in this manner to be
supported
> +by *both* an existing driver and some generic driver. Existing
drivers will
> +ignore the new properties and revert to their internal table instead.

I totally understand the concept of compatibility, ask Maxime, he hates
me for that ;-)

> Also, device tree bindings are not documentation for how to write a
> driver. They are not a replacement for hardware documentation. Nobody
> should be expected to be able to write an OS driver solely based on a
> device tree binding. Device tree bindings are more of a configuration
> interface specification for OS drivers.

Yes, but together with the hardware docs you should be able to write a
driver. And here you can't, because you are missing the strings. So a
BSD developer has to look at Linux code.
Sure you can go ahead and add those strings to the binding, but I wonder
how complicated it has to get until we just go with the obviously
simplest way and just add this innocent mux value to the DT.

>>> I don't think you're going to do yourself any favours by pushing this. I
>>> also don't see the commit description give any reason why you want to
>>> move the table into device tree. Do you see any advantages in doing so?
>>
>> We stop adding tables with SoC specific *data* in the kernel *code*
>> base. With being boolean Kconfig options, this gets added to every
>> single-image kernel.
> 
> The kernel is full of data:
> 
> 	$ objdump -h build/arm64/vmlinux
> 	[...]
> 	  1 .text         009c99c0  ffff000008081000  ffff000008081000  00011000  2**11
> 	                  CONTENTS, ALLOC, LOAD, READONLY, CODE
> 	  2 .rodata       00403bf8  ffff000008a50000  ffff000008a50000  009e0000  2**12
> 	                  CONTENTS, ALLOC, LOAD, DATA
> 	[...]
> 
> So that's about 40% of the kernel image. Code really is no good without
> data to process.

But how much of this is SoC specific configuration data? How much is it
in x86? Yes, historically we had and have a lot of configuration data in
ARM kernels. But that doesn't mean that we have to continue with this or
even increase the share.

>> More important: those tables help Linux, but other DT consumers (*BSD,
>> U-Boot) have to replicate them, which is just wrong, IMHO.
> 
> Yeah, I've heard this before. To be honest, I think these tables are the
> kind of data that you should generate, and once you do that it becomes
> extremely cheap to add the data to other DT consumers.

I don't get what's the point of duplicating this information everywhere.
We already have places in the DT to put this information:
	function = "uart1";
So we add "pinmux = <2>" *once* to *the* DT, and everyone gets this for
free. If U-Boot wants to enable MMC0, it just follows the phandle from
pinctrl-0 and writes the value(s) from "pinmux" to the registers derived
from the member of the "pins" string list. No SoC knowledge required.

> And let's face it: the really difficult part of adding pinmux support is
> to write the driver (or subsystem if you don't have one yet). Adding the
> data is really the easy part.

I know, I did this before. But currently you have to write both the DT
part *and* the kernel table. And check patch 3/3, that's what the table
gets reduced to. So you avoid writing 601 lines, instead add 23 lines to
the DT.

>> I believe the kernel is a nice collection of really good code for
>> complicated file systems, high performance network protocols and
>> sophisticated memory management, among others. It shouldn't be a dumping
>> ground for arbitrary, very SoC specific information. Cf. LinusT 2011.
>> DT is out there to fix this, so we should do so.
> 
> Every driver is very SoC specific information. There's never been an
> objection to having SoC specific drivers in the kernel. And back at the
> time the discussion was as much about the development process and code
> structure than it was about board files.
> 
> The majority of the improvements over the years have been achieved by
> moving drivers out of arch/arm and moving board files to DT. The goal
> was never to get rid of all data.

Sure, not all data. But if we have the relatively easy opportunity to
avoid further addition of data, we should do it, I believe.
This significantly reduces the amount of kernel code we need to add to
support new SoCs.

Cheers,
Andre.

^ permalink raw reply

* [PATCH v2] ARM: dts: imx6qdl-nitrogen6x: Add SPI NOR partitions
From: Otavio Salvador @ 2017-11-24 17:00 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Gary Bisson, Otavio Salvador, Fabio Estevam, devicetree,
	Sascha Hauer, linux-kernel, Rob Herring, Mark Rutland,
	Russell King, Shawn Guo

This adds the partitions definition for the SPI NOR to provide
backward compatibility with the documented[1] layout used with
Boundary Devices BSP.

1. https://boundarydevices.com/boot-flash-access-linux/

It exports to Linux:

 mtd0: bootloader
 mtd1: env
 mtd2: splash

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
---

Changes in v2:
 - rework labels (Fabio Estevam)
 - add read-only flags (Fabio Estevam)

 arch/arm/boot/dts/imx6qdl-nitrogen6x.dtsi | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/arch/arm/boot/dts/imx6qdl-nitrogen6x.dtsi b/arch/arm/boot/dts/imx6qdl-nitrogen6x.dtsi
index 4bdf29169d2a..28545412577a 100644
--- a/arch/arm/boot/dts/imx6qdl-nitrogen6x.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-nitrogen6x.dtsi
@@ -276,6 +276,26 @@
 		compatible = "sst,sst25vf016b", "jedec,spi-nor";
 		spi-max-frequency = <20000000>;
 		reg = <0>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "bootloader";
+			reg = <0x0 0xc0000>;
+			read-only;
+		};
+
+		partition@c0000 {
+			label = "env";
+			reg = <0xc0000 0x2000>;
+			read-only;
+		};
+
+		partition@c2000 {
+			label = "splash";
+			reg = <0xc2000 0x13e000>;
+			read-only;
+		};
 	};
 };
 
-- 
2.15.0

^ permalink raw reply related

* Re: [PATCH 03/10] PM / QoS: Fix device resume latency for non PM QoS devices
From: Rafael J. Wysocki @ 2017-11-24 16:50 UTC (permalink / raw)
  To: Manikanta Maddireddy
  Cc: Thierry Reding, Jon Hunter, Rob Herring, Frank Rowand,
	Bjorn Helgaas, Rafael J. Wysocki, Thomas Gleixner, vidyas, kthota,
	linux-tegra, devicetree@vger.kernel.org, Linux PCI, Linux PM
In-Reply-To: <1511538800-8275-4-git-send-email-mmaddireddy@nvidia.com>

On Fri, Nov 24, 2017 at 4:53 PM, Manikanta Maddireddy
<mmaddireddy@nvidia.com> wrote:
> In 'commit 0cc2b4e5a020 ("PM / QoS: Fix device resume latency PM QoS")'
> PM QoS resume latency modified 0 as "no latency at all". However
> dev_pm_qos_raw_read_value() returns 0 for devices which doesn't have
> PM QoS constraints. This is blocking runtime suspend for these devices
> in rpm_check_suspend_allowed(). Return PM_QOS_RESUME_LATENCY_NO_CONSTRAINT
> when PM QoS constraints are not available for a particular device.
>
> Fixes: 0cc2b4e5a020 ("PM / QoS: Fix device resume latency PM QoS")

That commit has been reverted, so this patch is not applicable and
therefore the whole series isn't.

What kernel is it based off?

Thanks,
Rafael

^ permalink raw reply

* [RESEND PATCH 4/4] ARM: dts: socfpga: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-11-24 16:27 UTC (permalink / raw)
  To: linux-kernel
  Cc: olof, arm, arnd, Javier Martinez Canillas, devicetree,
	Dinh Nguyen, Rob Herring, Mark Rutland, Russell King,
	linux-arm-kernel
In-Reply-To: <20171124162750.18756-1-javierm@redhat.com>

The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.

But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.

So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.

Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>

---

 arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts b/arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts
index 655fe87e272d..2459d133f1be 100644
--- a/arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts
+++ b/arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts
@@ -237,7 +237,7 @@
 	clock-frequency = <100000>;
 
 	at24@50 {
-		compatible = "at24,24c02";
+		compatible = "atmel,24c02";
 		pagesize = <8>;
 		reg = <0x50>;
 	};
-- 
2.14.3

^ permalink raw reply related

* [RESEND PATCH 3/4] ARM: dts: lpc18xx: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-11-24 16:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: olof-nZhT3qVonbNeoWH0uzbU5w, arm-DgEjT+Ai2ygdnm+yROfE0A,
	arnd-r2nGTMty4D4, Javier Martinez Canillas,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Joachim Eastwood, Rob Herring,
	Mark Rutland, Russell King,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20171124162750.18756-1-javierm-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.

But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.

So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.

Signed-off-by: Javier Martinez Canillas <javierm-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---

 arch/arm/boot/dts/lpc4337-ciaa.dts          | 6 +++---
 arch/arm/boot/dts/lpc4350-hitex-eval.dts    | 2 +-
 arch/arm/boot/dts/lpc4357-ea4357-devkit.dts | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/arch/arm/boot/dts/lpc4337-ciaa.dts b/arch/arm/boot/dts/lpc4337-ciaa.dts
index 7c16d639a1b4..beddaba85393 100644
--- a/arch/arm/boot/dts/lpc4337-ciaa.dts
+++ b/arch/arm/boot/dts/lpc4337-ciaa.dts
@@ -174,17 +174,17 @@
 	clock-frequency = <400000>;
 
 	eeprom@50 {
-		compatible = "microchip,24c512";
+		compatible = "microchip,24c512", "atmel,24c512";
 		reg = <0x50>;
 	};
 
 	eeprom@51 {
-		compatible = "microchip,24c02";
+		compatible = "microchip,24c02", "atmel,24c02";
 		reg = <0x51>;
 	};
 
 	eeprom@54 {
-		compatible = "microchip,24c512";
+		compatible = "microchip,24c512", "atmel,24c512";
 		reg = <0x54>;
 	};
 };
diff --git a/arch/arm/boot/dts/lpc4350-hitex-eval.dts b/arch/arm/boot/dts/lpc4350-hitex-eval.dts
index 874c75d44013..8b973f537d3a 100644
--- a/arch/arm/boot/dts/lpc4350-hitex-eval.dts
+++ b/arch/arm/boot/dts/lpc4350-hitex-eval.dts
@@ -429,7 +429,7 @@
 	};
 
 	eeprom@50 {
-		compatible = "nxp,24c02";
+		compatible = "nxp,24c02", "atmel,24c02";
 		reg = <0x50>;
 	};
 
diff --git a/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts b/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
index 9b5fad622522..02b23fa29d75 100644
--- a/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
+++ b/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
@@ -490,7 +490,7 @@
 	};
 
 	eeprom@57 {
-		compatible = "microchip,24c64";
+		compatible = "microchip,24c64", "atmel,24c64";
 		reg = <0x57>;
 	};
 };
-- 
2.14.3

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [RESEND PATCH 2/4] ARM: dts: keystone: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-11-24 16:27 UTC (permalink / raw)
  To: linux-kernel
  Cc: olof, arm, arnd, Javier Martinez Canillas, devicetree,
	Santosh Shilimkar, Rob Herring, Mark Rutland, Russell King,
	linux-arm-kernel
In-Reply-To: <20171124162750.18756-1-javierm@redhat.com>

The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.

But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.

So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.

Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
---

 arch/arm/boot/dts/keystone-k2e-evm.dts  | 2 +-
 arch/arm/boot/dts/keystone-k2hk-evm.dts | 2 +-
 arch/arm/boot/dts/keystone-k2l-evm.dts  | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/boot/dts/keystone-k2e-evm.dts b/arch/arm/boot/dts/keystone-k2e-evm.dts
index f1f32c54e72f..2c59f4cb3b44 100644
--- a/arch/arm/boot/dts/keystone-k2e-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2e-evm.dts
@@ -82,7 +82,7 @@
 
 &i2c0 {
 	dtt@50 {
-		compatible = "at,24c1024";
+		compatible = "atmel,24c1024";
 		reg = <0x50>;
 	};
 };
diff --git a/arch/arm/boot/dts/keystone-k2hk-evm.dts b/arch/arm/boot/dts/keystone-k2hk-evm.dts
index 6dd13b98aaba..13759db99d81 100644
--- a/arch/arm/boot/dts/keystone-k2hk-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2hk-evm.dts
@@ -158,7 +158,7 @@
 
 &i2c0 {
 	dtt@50 {
-		compatible = "at,24c1024";
+		compatible = "atmel,24c1024";
 		reg = <0x50>;
 	};
 };
diff --git a/arch/arm/boot/dts/keystone-k2l-evm.dts b/arch/arm/boot/dts/keystone-k2l-evm.dts
index 528667618db4..e6de1ae33c73 100644
--- a/arch/arm/boot/dts/keystone-k2l-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2l-evm.dts
@@ -55,7 +55,7 @@
 
 &i2c0 {
 	dtt@50 {
-		compatible = "at,24c1024";
+		compatible = "atmel,24c1024";
 		reg = <0x50>;
 	};
 };
-- 
2.14.3

^ permalink raw reply related

* [RESEND PATCH 1/4] ARM: dts: efm32: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-11-24 16:27 UTC (permalink / raw)
  To: linux-kernel
  Cc: olof, arm, arnd, Javier Martinez Canillas, devicetree,
	Rob Herring, Mark Rutland, Russell King, linux-arm-kernel

The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.

But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.

So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.

Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
---

 arch/arm/boot/dts/efm32gg-dk3750.dts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/efm32gg-dk3750.dts b/arch/arm/boot/dts/efm32gg-dk3750.dts
index c9f3ea29cf9e..adfa559a488b 100644
--- a/arch/arm/boot/dts/efm32gg-dk3750.dts
+++ b/arch/arm/boot/dts/efm32gg-dk3750.dts
@@ -37,7 +37,7 @@
 			};
 
 			eeprom@50 {
-				compatible = "microchip,24c02";
+				compatible = "microchip,24c02", "atmel,24c02";
 				reg = <0x50>;
 				pagesize = <16>;
 			};
-- 
2.14.3

^ permalink raw reply related

* Re: [PATCH 2/3] hwrng: exynos - add Samsung Exynos True RNG driver
From: PrasannaKumar Muralidharan @ 2017-11-24 15:54 UTC (permalink / raw)
  To: Łukasz Stelmach
  Cc: Rob Herring, Matt Mackall, Herbert Xu, Krzysztof Kozlowski,
	devicetree, open list:HARDWARE RANDOM NUMBER GENERATOR CORE,
	linux-samsung-soc, open list, Marek Szyprowski,
	Bartlomiej Zolnierkiewicz
In-Reply-To: <CANc+2y4=Eas-i0jRwgTxqFeAngpPMKoUjVwsXWeU7G24g4252w@mail.gmail.com>

On 24 November 2017 at 20:55, PrasannaKumar Muralidharan
<prasannatsmkumar@gmail.com> wrote:
> Hi Lukasz,
>
> Some minor comments below.
>
> On 23 November 2017 at 20:39, Łukasz Stelmach <l.stelmach@samsung.com> wrote:
>> Add support for True Random Number Generator found in Samsung Exynos
>> 5250+ SoCs.
>>
>> Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
>> ---
>>  MAINTAINERS                          |   7 +
>>  drivers/char/hw_random/Kconfig       |  12 ++
>>  drivers/char/hw_random/Makefile      |   1 +
>>  drivers/char/hw_random/exynos-trng.c | 256 +++++++++++++++++++++++++++++++++++
>>  4 files changed, 276 insertions(+)
>>  create mode 100644 drivers/char/hw_random/exynos-trng.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2811a211632c..992074cca612 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -11780,6 +11780,13 @@ S:     Maintained
>>  F:     drivers/crypto/exynos-rng.c
>>  F:     Documentation/devicetree/bindings/rng/samsung,exynos-rng4.txt
>>
>> +SAMSUNG EXYNOS TRUE RANDOM NUMBER GENERATOR (TRNG) DRIVER
>> +M:     Łukasz Stelmach <l.stelmach@samsung.com>
>> +L:     linux-samsung-soc@vger.kernel.org
>> +S:     Maintained
>> +F:     drivers/char/hw_random/exynos-trng.c
>> +F:     Documentation/devicetree/bindings/rng/samsung,exynos5250-trng.txt
>> +
>>  SAMSUNG FRAMEBUFFER DRIVER
>>  M:     Jingoo Han <jingoohan1@gmail.com>
>>  L:     linux-fbdev@vger.kernel.org
>> diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
>> index 95a031e9eced..a788ac07081b 100644
>> --- a/drivers/char/hw_random/Kconfig
>> +++ b/drivers/char/hw_random/Kconfig
>> @@ -449,6 +449,18 @@ config HW_RANDOM_S390
>>
>>           If unsure, say Y.
>>
>> +config HW_RANDOM_EXYNOS
>> +       tristate "Samsung Exynos True Random Number Generator support"
>> +       depends on ARCH_EXYNOS || COMPILE_TEST
>> +       default HW_RANDOM
>> +       ---help---
>> +         This driver provides support for the True Random Number
>> +         Generator available in Exynos SoCs.
>> +
>> +        To compile this driver as a module, choose M here: the module
>> +        will be called exynos-trng.
>> +
>> +        If unsure, say Y.
>>  endif # HW_RANDOM
>>
>>  config UML_RANDOM
>> diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
>> index f3728d008fff..5595df97da7a 100644
>> --- a/drivers/char/hw_random/Makefile
>> +++ b/drivers/char/hw_random/Makefile
>> @@ -14,6 +14,7 @@ obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o
>>  obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o
>>  n2-rng-y := n2-drv.o n2-asm.o
>>  obj-$(CONFIG_HW_RANDOM_VIA) += via-rng.o
>> +obj-$(CONFIG_HW_RANDOM_EXYNOS) += exynos-trng.o
>>  obj-$(CONFIG_HW_RANDOM_IXP4XX) += ixp4xx-rng.o
>>  obj-$(CONFIG_HW_RANDOM_OMAP) += omap-rng.o
>>  obj-$(CONFIG_HW_RANDOM_OMAP3_ROM) += omap3-rom-rng.o
>> diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c
>> new file mode 100644
>> index 000000000000..340e106cae24
>> --- /dev/null
>> +++ b/drivers/char/hw_random/exynos-trng.c
>> @@ -0,0 +1,256 @@
>> +/*
>> + * RNG driver for Exynos TRNGs
>> + *
>> + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
>> + *
>> + * Copyright 2017 (c) Samsung Electronics Software, Inc.
>> + *
>> + * Based on the Exynos PRNG driver drivers/crypto/exynos-rng by
>> + * Krzysztof Kozłowski <krzk@kernel.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation;
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/crypto.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/hw_random.h>
>> +#include <linux/io.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#define EXYNOS_TRNG_CLKDIV         (0x0)
>> +#define EXYNOS_TRNG_CTRL           (0x20)
>> +#define EXYNOS_TRNG_POST_CTRL      (0x30)
>> +#define EXYNOS_TRNG_ONLINE_CTRL    (0x40)
>> +#define EXYNOS_TRNG_ONLINE_STAT    (0x44)
>> +#define EXYNOS_TRNG_ONLINE_MAXCHI2 (0x48)
>> +#define EXYNOS_TRNG_FIFO_CTRL      (0x50)
>> +#define EXYNOS_TRNG_FIFO_0         (0x80)
>> +#define EXYNOS_TRNG_FIFO_1         (0x84)
>> +#define EXYNOS_TRNG_FIFO_2         (0x88)
>> +#define EXYNOS_TRNG_FIFO_3         (0x8c)
>> +#define EXYNOS_TRNG_FIFO_4         (0x90)
>> +#define EXYNOS_TRNG_FIFO_5         (0x94)
>> +#define EXYNOS_TRNG_FIFO_6         (0x98)
>> +#define EXYNOS_TRNG_FIFO_7         (0x9c)
>> +#define EXYNOS_TRNG_FIFO_LEN       (8)
>> +#define EXYNOS_TRNG_CLOCK_RATE     (500000)
>> +
>> +struct exynos_trng_dev {
>> +       struct device    *dev;
>> +       void __iomem     *mem;
>> +       struct clk       *clk;
>> +       struct hwrng rng;
>> +};
>> +
>> +struct exynos_trng_dev *exynos_trng_dev;
>> +
>> +static inline void exynos_trng_set_reg(struct exynos_trng_dev *trng, u16 reg,
>> +                                      u32 val)
>> +{
>> +       /* Check range of reg? */
>> +       __raw_writel(val, trng->mem + reg);
>
> Any specific reason to use __raw_writel? Why not just writel?
>
>> +}
>> +
>> +static inline u32 exynos_trng_get_reg(struct exynos_trng_dev *trng, u16 reg)
>> +{
>> +       /* Check range of reg? */
>> +       return __raw_readl(trng->mem + reg);
>
> Same as above.
>
>> +}
>> +
>> +static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max,
>> +                              bool wait)
>> +{
>> +       struct exynos_trng_dev *trng;
>> +       u32 val;
>> +
>> +       max = max > (EXYNOS_TRNG_FIFO_LEN * 4) ?
>> +               (EXYNOS_TRNG_FIFO_LEN * 4) : max;
>
> max is always > 32. This condition is not required.
>
>> +
>> +       trng = (struct exynos_trng_dev *)rng->priv;
>> +
>> +       exynos_trng_set_reg(trng, EXYNOS_TRNG_FIFO_CTRL, max * 8);
>> +       val = readl_poll_timeout(trng->mem + EXYNOS_TRNG_FIFO_CTRL, val,
>> +                                val == 0, 200, 1000000);
>> +       if (val < 0)
>> +               return val;
>> +
>> +       memcpy_fromio(data, trng->mem + EXYNOS_TRNG_FIFO_0, max);
>> +
>> +       return max;
>> +}
>> +
>> +static int exynos_trng_init(struct hwrng *rng)
>> +{
>> +       struct exynos_trng_dev *trng;
>> +       unsigned long sss_rate;
>> +       u32 val;
>> +
>> +       trng = (struct exynos_trng_dev *)rng->priv;
>> +       sss_rate = clk_get_rate(trng->clk);
>> +
>> +       /* For most TRNG circuits the clock frequency of under 500 kHz
>> +        * is safe.
>> +        */
>> +       val = sss_rate / (EXYNOS_TRNG_CLOCK_RATE * 2);
>> +       if (val > 0x7fff) {
>> +               dev_err(trng->dev, "clock divider too large: %d", val);
>> +               return -ERANGE;
>> +       }
>> +       val = val << 1;
>> +       exynos_trng_set_reg(trng, EXYNOS_TRNG_CLKDIV, val);
>> +
>> +       /* Enable the generator. */
>> +       val = 1 << 31;
>> +       exynos_trng_set_reg(trng, EXYNOS_TRNG_CTRL, val);
>> +
>> +       /* Disable post processing. /dev/hwrng is supposed to deliver
>> +        * unprocessed data.
>> +        */
>> +       exynos_trng_set_reg(trng, EXYNOS_TRNG_POST_CTRL, 0);
>> +
>> +       return 0;
>> +}
>> +
>> +static int exynos_trng_probe(struct platform_device *pdev)
>> +{
>> +       struct exynos_trng_dev *trng;
>> +       struct resource *res;
>> +       int ret = -ENOMEM;
>> +
>> +       if (exynos_trng_dev)
>> +               return -EEXIST;
>> +
>> +       trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
>> +       if (!trng)
>> +               goto err_ioremap;
>> +
>> +       trng->rng.name = devm_kstrdup(&pdev->dev, dev_name(&pdev->dev),
>> +                                     GFP_KERNEL);
>> +       if (!trng->rng.name)
>> +               goto err_ioremap;
>> +
>> +       trng->rng.init = exynos_trng_init;
>> +       trng->rng.read = exynos_trng_do_read;
>> +       trng->rng.priv = (unsigned long) trng;
>> +
>> +       platform_set_drvdata(pdev, trng);
>> +       trng->dev = &pdev->dev;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       trng->mem = devm_ioremap_resource(&pdev->dev, res);
>> +       if (IS_ERR(trng->mem)) {
>> +               dev_err(&pdev->dev, "Couldn't map IO resources.\n");
>> +               ret = PTR_ERR(trng->mem);
>> +               goto err_ioremap;
>> +       }
>> +
>> +       pm_runtime_enable(&pdev->dev);
>> +       ret = pm_runtime_get_sync(&pdev->dev);
>> +       if (ret < 0) {
>> +               dev_err(&pdev->dev, "pm_runtime_get_sync() failed: %d.\n", ret);
>> +               goto err_ioremap;
>> +       }
>> +
>> +       trng->clk = devm_clk_get(&pdev->dev, "secss");
>> +       if (IS_ERR(trng->clk)) {
>> +               /* XXX: EPROBE_DEFER ? */
>> +               dev_err(&pdev->dev, "Couldn't get clock.\n");
>> +               ret = PTR_ERR(trng->clk);
>> +               goto err_clock;
>> +       }
>> +
>> +       ret = clk_prepare_enable(trng->clk);
>> +       if (ret) {
>> +               dev_err(&pdev->dev, "Unable to enable the clk: %d.\n", ret);
>> +               goto err_clock;
>> +       }
>
> Why not move clk_prepare_enable to init call? While at it please add a
> cleanup callback as well.
>
>> +
>> +       ret = hwrng_register(&trng->rng);
>> +       if (ret) {
>> +               dev_err(&pdev->dev, "Couldn't register hwrng device.\n");
>> +               goto err_register;
>> +       }
>> +
>> +       dev_info(&pdev->dev, "Exynos True Random Number Generator.\n");
>> +
>> +       return 0;
>> +
>> +err_register:
>> +       clk_disable_unprepare(trng->clk);
>> +
>> +err_clock:
>> +       trng->mem = NULL;
>> +       pm_runtime_put_sync(&pdev->dev);
>> +       pm_runtime_disable(&pdev->dev);
>> +
>> +err_ioremap:
>> +       dev_err(&pdev->dev, "Initialisation failed.\n");
>> +       return ret;
>> +}
>> +
>> +static int exynos_trng_remove(struct platform_device *pdev)
>> +{
>> +       exynos_trng_dev = NULL;
>> +
>> +       return 0;
>> +}
>
> remove is not required as it does not do anything significant.

Should unregister from hwrng in remove callback.

>
>> +
>> +static int __maybe_unused exynos_trng_suspend(struct device *dev)
>> +{
>> +       pm_runtime_put_sync(dev);
>> +
>> +       return 0;
>> +}
>> +
>> +static int __maybe_unused exynos_trng_resume(struct device *dev)
>> +{
>> +       int ret;
>> +
>> +       ret = pm_runtime_get_sync(dev);
>> +       if (ret < 0) {
>> +               dev_err(dev, "pm_runtime_get_sync() failed: %d.\n", ret);
>> +               pm_runtime_put_noidle(dev);
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static SIMPLE_DEV_PM_OPS(exynos_trng_pm_ops, exynos_trng_suspend,
>> +                        exynos_trng_resume);
>> +
>> +static const struct of_device_id exynos_trng_dt_match[] = {
>> +       {
>> +               .compatible = "samsung,exynos5250-trng",
>> +       },
>> +       { },
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos_rng_dt_match);
>> +
>> +static struct platform_driver exynos_trng_driver = {
>> +       .driver = {
>> +               .name = "exynos-trng",
>> +               .pm = &exynos_trng_pm_ops,
>> +               .of_match_table = exynos_trng_dt_match,
>> +       },
>> +       .probe = exynos_trng_probe,
>> +       .remove = exynos_trng_remove,
>> +};
>> +
>> +module_platform_driver(exynos_trng_driver);
>> +MODULE_AUTHOR("Łukasz Stelmach");
>> +MODULE_DESCRIPTION("H/W TRNG driver for Exynos chips");
>> +MODULE_LICENSE("GPL");
>> --
>> 2.11.0
>>
>
> Regards,
> PrasannaKumar

^ permalink raw reply

* [PATCH 10/10] PCI: tegra: Add power management support
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
	jonathanh-DDmLM1+adcrQT0dZR+AlfA, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	frowand.list-Re5JQEeQqe8AvxtiuMwx3w,
	bhelgaas-hpIqsD4AKlfQT0dZR+AlfA, rjw-LthD3rsA81gm4RdzfppkhA,
	tglx-hfZtesqFncYOwBW4kG4KsQ
  Cc: vidyas-DDmLM1+adcrQT0dZR+AlfA, kthota-DDmLM1+adcrQT0dZR+AlfA,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-pci-u79uwXL29TY76Z2rM5mHXA, linux-pm-u79uwXL29TY76Z2rM5mHXA,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

Tegra186 powergate driver is implemented as power domain driver, power
partition ungate/gate are registered as power_on/power_off callback
functions. There are no direct functions to power gate/ungate host
controller in Tegra186. Host controller driver should add "power-domains"
property in device tree and implement runtime suspend and resume
callback functons. Power gate and ungate is taken care by power domain
driver when host controller driver calls pm_runtime_put_sync and
pm_runtime_get_sync respectively.

Register suspend_noirq & resume_noirq callback functions to allow PCIe to
come up after resume from RAM. Both runtime and noirq pm ops share same
callback functions.

Signed-off-by: Manikanta Maddireddy <mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
---
 drivers/pci/host/pci-tegra.c | 174 ++++++++++++++++++++++++++-----------------
 1 file changed, 106 insertions(+), 68 deletions(-)

diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
index af999c650941..e443b3ebc4b5 100644
--- a/drivers/pci/host/pci-tegra.c
+++ b/drivers/pci/host/pci-tegra.c
@@ -1404,31 +1404,25 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 		}
 	}
 
-	err = tegra_pcie_power_on(pcie);
-	if (err) {
-		dev_err(dev, "failed to power up: %d\n", err);
-		goto phys_put;
-	}
-
 	pads = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pads");
 	pcie->pads = devm_ioremap_resource(dev, pads);
 	if (IS_ERR(pcie->pads)) {
 		err = PTR_ERR(pcie->pads);
-		goto poweroff;
+		goto phys_put;
 	}
 
 	afi = platform_get_resource_byname(pdev, IORESOURCE_MEM, "afi");
 	pcie->afi = devm_ioremap_resource(dev, afi);
 	if (IS_ERR(pcie->afi)) {
 		err = PTR_ERR(pcie->afi);
-		goto poweroff;
+		goto phys_put;
 	}
 
 	/* request configuration space, but remap later, on demand */
 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cs");
 	if (!res) {
 		err = -EADDRNOTAVAIL;
-		goto poweroff;
+		goto phys_put;
 	}
 
 	axi_addr = pcie->soc->use_4k_conf_space ?
@@ -1436,21 +1430,21 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 	pcie->cs = devm_request_mem_region(dev, axi_addr, SZ_4K, res->name);
 	if (!pcie->cs) {
 		err = -EADDRNOTAVAIL;
-		goto poweroff;
+		goto phys_put;
 	}
 
 	pcie->cfg_va_base = devm_ioremap(dev, pcie->cs->start, SZ_4K);
 	if (!pcie->cfg_va_base) {
 		dev_err(pcie->dev, "failed to ioremap config space\n");
 		err = -EADDRNOTAVAIL;
-		goto poweroff;
+		goto phys_put;
 	}
 
 	/* request interrupt */
 	err = platform_get_irq_byname(pdev, "intr");
 	if (err < 0) {
 		dev_err(dev, "failed to get IRQ: %d\n", err);
-		goto poweroff;
+		goto phys_put;
 	}
 
 	pcie->irq = err;
@@ -1458,13 +1452,11 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 	err = request_irq(pcie->irq, tegra_pcie_isr, IRQF_SHARED, "PCIE", pcie);
 	if (err) {
 		dev_err(dev, "failed to register IRQ: %d\n", err);
-		goto poweroff;
+		goto phys_put;
 	}
 
 	return 0;
 
-poweroff:
-	tegra_pcie_power_off(pcie);
 phys_put:
 	if (soc->program_uphy)
 		tegra_pcie_phys_put(pcie);
@@ -1478,8 +1470,6 @@ static int tegra_pcie_put_resources(struct tegra_pcie *pcie)
 	if (pcie->irq > 0)
 		free_irq(pcie->irq, pcie);
 
-	tegra_pcie_power_off(pcie);
-
 	if (soc->program_uphy)
 		tegra_pcie_phys_put(pcie);
 
@@ -1702,37 +1692,41 @@ static int tegra_pcie_enable_msi(struct tegra_pcie *pcie)
 	int err;
 	u32 reg;
 
-	mutex_init(&msi->lock);
+	if (!msi->phys) {
+		mutex_init(&msi->lock);
 
-	msi->chip.dev = dev;
-	msi->chip.setup_irq = tegra_msi_setup_irq;
-	msi->chip.teardown_irq = tegra_msi_teardown_irq;
+		msi->chip.dev = dev;
+		msi->chip.setup_irq = tegra_msi_setup_irq;
+		msi->chip.teardown_irq = tegra_msi_teardown_irq;
 
-	msi->domain = irq_domain_add_linear(dev->of_node, INT_PCI_MSI_NR,
-					    &msi_domain_ops, &msi->chip);
-	if (!msi->domain) {
-		dev_err(dev, "failed to create IRQ domain\n");
-		return -ENOMEM;
-	}
+		msi->domain = irq_domain_add_linear(dev->of_node,
+						    INT_PCI_MSI_NR,
+						    &msi_domain_ops,
+						    &msi->chip);
+		if (!msi->domain) {
+			dev_err(dev, "failed to create IRQ domain\n");
+			return -ENOMEM;
+		}
 
-	err = platform_get_irq_byname(pdev, "msi");
-	if (err < 0) {
-		dev_err(dev, "failed to get IRQ: %d\n", err);
-		goto err;
-	}
+		err = platform_get_irq_byname(pdev, "msi");
+		if (err < 0) {
+			dev_err(dev, "failed to get IRQ: %d\n", err);
+			goto err;
+		}
 
-	msi->irq = err;
+		msi->irq = err;
 
-	err = request_irq(msi->irq, tegra_pcie_msi_irq, IRQF_NO_THREAD,
-			  tegra_msi_irq_chip.name, pcie);
-	if (err < 0) {
-		dev_err(dev, "failed to request IRQ: %d\n", err);
-		goto err;
-	}
+		err = request_irq(msi->irq, tegra_pcie_msi_irq, IRQF_NO_THREAD,
+				  tegra_msi_irq_chip.name, pcie);
+		if (err < 0) {
+			dev_err(dev, "failed to request IRQ: %d\n", err);
+			goto err;
+		}
 
-	/* setup AFI/FPCI range */
-	msi->pages = __get_free_pages(GFP_KERNEL, 0);
-	msi->phys = virt_to_phys((void *)msi->pages);
+		/* setup AFI/FPCI range */
+		msi->pages = __get_free_pages(GFP_KERNEL, 0);
+		msi->phys = virt_to_phys((void *)msi->pages);
+	}
 
 	afi_writel(pcie, msi->phys >> soc->msi_base_shift, AFI_MSI_FPCI_BAR_ST);
 	afi_writel(pcie, msi->phys, AFI_MSI_AXI_BAR_ST);
@@ -2811,26 +2805,16 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 		return err;
 	}
 
-	err = tegra_pcie_enable_controller(pcie);
-	if (err)
+	pm_runtime_enable(pcie->dev);
+	err = pm_runtime_get_sync(pcie->dev);
+	if (err) {
+		dev_err(dev, "fail to enable pcie controller: %d\n", err);
 		goto put_resources;
+	}
 
 	err = tegra_pcie_request_resources(pcie);
 	if (err)
-		goto disable_controller;
-
-	/* setup the AFI address translations */
-	tegra_pcie_setup_translations(pcie);
-
-	if (IS_ENABLED(CONFIG_PCI_MSI)) {
-		err = tegra_pcie_enable_msi(pcie);
-		if (err < 0) {
-			dev_err(dev, "failed to enable MSI support: %d\n", err);
-			goto free_resources;
-		}
-	}
-
-	tegra_pcie_enable_ports(pcie);
+		goto pm_runtime_put;
 
 	pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS);
 	host->busnr = pcie->busn.start;
@@ -2842,7 +2826,7 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 	err = pci_scan_root_bus_bridge(host);
 	if (err < 0) {
 		dev_err(dev, "failed to register host: %d\n", err);
-		goto disable_msi;
+		goto free_resources;
 	}
 
 	pci_bus_size_bridges(host->bus);
@@ -2864,14 +2848,13 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 
 	return 0;
 
-disable_msi:
-	if (IS_ENABLED(CONFIG_PCI_MSI))
-		tegra_pcie_disable_msi(pcie);
-	tegra_pcie_disable_ports(pcie);
 free_resources:
 	tegra_pcie_free_resources(pcie);
-disable_controller:
-	tegra_pcie_disable_controller(pcie);
+pm_runtime_put:
+	if (IS_ENABLED(CONFIG_PCI_MSI))
+		tegra_pcie_disable_msi(pcie);
+	pm_runtime_put_sync(pcie->dev);
+	pm_runtime_disable(pcie->dev);
 put_resources:
 	tegra_pcie_put_resources(pcie);
 	return err;
@@ -2881,7 +2864,6 @@ static int tegra_pcie_remove(struct platform_device *pdev)
 {
 	struct tegra_pcie *pcie = platform_get_drvdata(pdev);
 	struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
-	struct tegra_pcie_port *port, *tmp;
 
 	if (IS_ENABLED(CONFIG_DEBUG_FS))
 		tegra_pcie_debugfs_exit(pcie);
@@ -2889,21 +2871,77 @@ static int tegra_pcie_remove(struct platform_device *pdev)
 	pci_remove_root_bus(host->bus);
 	if (IS_ENABLED(CONFIG_PCI_MSI))
 		tegra_pcie_disable_msi(pcie);
+	tegra_pcie_free_resources(pcie);
+	pm_runtime_put_sync(pcie->dev);
+	pm_runtime_disable(pcie->dev);
+	tegra_pcie_put_resources(pcie);
+
+	return 0;
+}
+
+static int tegra_pcie_pm_suspend(struct device *dev)
+{
+	struct tegra_pcie *pcie = dev_get_drvdata(dev);
+	struct tegra_pcie_port *port, *tmp;
+
 	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
 		tegra_pcie_pme_turnoff(port);
 	tegra_pcie_disable_ports(pcie);
-	tegra_pcie_free_resources(pcie);
 	tegra_pcie_disable_controller(pcie);
-	tegra_pcie_put_resources(pcie);
+	tegra_pcie_power_off(pcie);
 
 	return 0;
 }
 
+static int tegra_pcie_pm_resume(struct device *dev)
+{
+	struct tegra_pcie *pcie = dev_get_drvdata(dev);
+	int err;
+
+	err = tegra_pcie_power_on(pcie);
+	if (err) {
+		dev_err(dev, "tegra pcie power on fail: %d\n", err);
+		return err;
+	}
+	err = tegra_pcie_enable_controller(pcie);
+	if (err) {
+		dev_err(dev, "tegra pcie controller enable fail: %d\n", err);
+		goto poweroff;
+	}
+	tegra_pcie_setup_translations(pcie);
+
+	if (IS_ENABLED(CONFIG_PCI_MSI)) {
+		err = tegra_pcie_enable_msi(pcie);
+		if (err < 0) {
+			dev_err(dev, "failed to enable MSI support: %d\n", err);
+			goto disable_controller;
+		}
+	}
+
+	tegra_pcie_enable_ports(pcie);
+
+	return 0;
+
+disable_controller:
+	tegra_pcie_disable_controller(pcie);
+poweroff:
+	tegra_pcie_power_off(pcie);
+
+	return err;
+}
+
+static const struct dev_pm_ops tegra_pcie_pm_ops = {
+	SET_RUNTIME_PM_OPS(tegra_pcie_pm_suspend, tegra_pcie_pm_resume, NULL)
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_pcie_pm_suspend,
+				      tegra_pcie_pm_resume)
+};
+
 static struct platform_driver tegra_pcie_driver = {
 	.driver = {
 		.name = "tegra-pcie",
 		.of_match_table = tegra_pcie_of_match,
 		.suppress_bind_attrs = true,
+		.pm = &tegra_pcie_pm_ops,
 	},
 	.probe = tegra_pcie_probe,
 	.remove = tegra_pcie_remove,
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 09/10] PCI: tegra: Broadcast PME_turn_Off message before link goes to L2
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding, jonathanh, robh+dt, frowand.list, bhelgaas, rjw,
	tglx
  Cc: vidyas, kthota, linux-tegra, devicetree, linux-pci, linux-pm,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy@nvidia.com>

Per PCIe r3.0, sec 5.3.3.2.1, PCIe root port shoould broadcast PME_turn_Off
message before PCIe link goes to L2. PME_turn_Off broadcast mechanism is
implemented in AFI module. Each Tegra PCIe root port has its own
PME_turn_Off and PME_TO_Ack bitmap in AFI_PME register, program this
register to broadcast PME_turn_Off message.

Signed-off-by: Manikanta Maddireddy <mmaddireddy@nvidia.com>
---
 drivers/pci/host/pci-tegra.c | 76 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
index 2ff1ac3aefc1..af999c650941 100644
--- a/drivers/pci/host/pci-tegra.c
+++ b/drivers/pci/host/pci-tegra.c
@@ -155,6 +155,8 @@
 #define  AFI_INTR_EN_FPCI_TIMEOUT	(1 << 7)
 #define  AFI_INTR_EN_PRSNT_SENSE	(1 << 8)
 
+#define AFI_PCIE_PME		0xf0
+
 #define AFI_PCIE_CONFIG					0x0f8
 #define  AFI_PCIE_CONFIG_PCIE_DISABLE(x)		(1 << ((x) + 1))
 #define  AFI_PCIE_CONFIG_PCIE_DISABLE_ALL		0xe
@@ -315,6 +317,7 @@
 #define PADS_REFCLK_CFG_DRVI_SHIFT		12 /* 15:12 */
 
 #define LINK_RETRAIN_TIMEOUT 100000
+#define PME_ACK_TIMEOUT 10000
 
 struct tegra_msi {
 	struct msi_controller chip;
@@ -1483,6 +1486,76 @@ static int tegra_pcie_put_resources(struct tegra_pcie *pcie)
 	return 0;
 }
 
+static inline u32 get_pme_turnoff_bitmap(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	struct device_node *np = dev->of_node;
+	int ret = 0;
+
+	switch (port->index) {
+	case 0:
+		ret = 0;
+	case 1:
+		ret = 8;
+	case 2:
+		if (of_device_is_compatible(np, "nvidia,tegra30-pcie"))
+			ret = 16;
+		else
+			ret = 12;
+	}
+	return ret;
+}
+
+static inline u32 get_pme_ack_bitmap(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	struct device_node *np = dev->of_node;
+	int ret = 0;
+
+	switch (port->index) {
+	case 0:
+		ret = 5;
+	case 1:
+		ret = 10;
+	case 2:
+		if (of_device_is_compatible(np, "nvidia,tegra30-pcie"))
+			ret = 18;
+		else
+			ret = 14;
+	}
+	return ret;
+}
+
+static void tegra_pcie_pme_turnoff(struct tegra_pcie_port *port)
+{
+	struct tegra_pcie *pcie = port->pcie;
+	ktime_t deadline;
+	unsigned int data;
+
+	data = afi_readl(pcie, AFI_PCIE_PME);
+	data |= (0x1 << get_pme_turnoff_bitmap(port));
+	afi_writel(pcie, data, AFI_PCIE_PME);
+
+	deadline = ktime_add_us(ktime_get(), PME_ACK_TIMEOUT);
+	do {
+		data = afi_readl(pcie, AFI_PCIE_PME);
+		data &= (0x1 << get_pme_ack_bitmap(port));
+		udelay(1);
+		if (ktime_after(ktime_get(), deadline))
+			break;
+	} while (!data);
+
+	if (data)
+		dev_err(pcie->dev, "PME Ack is not receieved on port: %d\n",
+			port->index);
+
+	usleep_range(10000, 11000);
+
+	data = afi_readl(pcie, AFI_PCIE_PME);
+	data &= ~(0x1 << get_pme_turnoff_bitmap(port));
+	afi_writel(pcie, data, AFI_PCIE_PME);
+}
+
 static int tegra_msi_alloc(struct tegra_msi *chip)
 {
 	int msi;
@@ -2808,6 +2881,7 @@ static int tegra_pcie_remove(struct platform_device *pdev)
 {
 	struct tegra_pcie *pcie = platform_get_drvdata(pdev);
 	struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
+	struct tegra_pcie_port *port, *tmp;
 
 	if (IS_ENABLED(CONFIG_DEBUG_FS))
 		tegra_pcie_debugfs_exit(pcie);
@@ -2815,6 +2889,8 @@ static int tegra_pcie_remove(struct platform_device *pdev)
 	pci_remove_root_bus(host->bus);
 	if (IS_ENABLED(CONFIG_PCI_MSI))
 		tegra_pcie_disable_msi(pcie);
+	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+		tegra_pcie_pme_turnoff(port);
 	tegra_pcie_disable_ports(pcie);
 	tegra_pcie_free_resources(pcie);
 	tegra_pcie_disable_controller(pcie);
-- 
2.1.4

^ permalink raw reply related

* [PATCH 08/10] PCI: tegra: Add loadable kernel module support
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
	jonathanh-DDmLM1+adcrQT0dZR+AlfA, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	frowand.list-Re5JQEeQqe8AvxtiuMwx3w,
	bhelgaas-hpIqsD4AKlfQT0dZR+AlfA, rjw-LthD3rsA81gm4RdzfppkhA,
	tglx-hfZtesqFncYOwBW4kG4KsQ
  Cc: vidyas-DDmLM1+adcrQT0dZR+AlfA, kthota-DDmLM1+adcrQT0dZR+AlfA,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-pci-u79uwXL29TY76Z2rM5mHXA, linux-pm-u79uwXL29TY76Z2rM5mHXA,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

Implement remove call back function for Tegra PCIe driver to add
loadable kernel module support. Change PCI_TEGRA config to tristate to
allow pci-tegra driver to build as a module.

Signed-off-by: Manikanta Maddireddy <mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
---
 drivers/pci/host/Kconfig     |  2 +-
 drivers/pci/host/pci-tegra.c | 31 ++++++++++++++++++++++++++++++-
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index b868803792d8..15eb265461b2 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -34,7 +34,7 @@ config PCI_FTPCI100
 	default ARCH_GEMINI
 
 config PCI_TEGRA
-	bool "NVIDIA Tegra PCIe controller"
+	tristate "NVIDIA Tegra PCIe controller"
 	depends on ARCH_TEGRA
 	help
 	  Say Y here if you want support for the PCIe host controller found
diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
index 09f52cd569c6..2ff1ac3aefc1 100644
--- a/drivers/pci/host/pci-tegra.c
+++ b/drivers/pci/host/pci-tegra.c
@@ -35,6 +35,7 @@
 #include <linux/irqdomain.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
+#include <linux/module.h>
 #include <linux/msi.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
@@ -2700,6 +2701,12 @@ static int tegra_pcie_debugfs_init(struct tegra_pcie *pcie)
 	return -ENOMEM;
 }
 
+static void tegra_pcie_debugfs_exit(struct tegra_pcie *pcie)
+{
+	debugfs_remove_recursive(pcie->debugfs);
+	pcie->debugfs = NULL;
+}
+
 static int tegra_pcie_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -2714,6 +2721,7 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 		return -ENOMEM;
 
 	pcie = pci_host_bridge_priv(host);
+	platform_set_drvdata(pdev, pcie);
 
 	pcie->soc = of_device_get_match_data(dev);
 	INIT_LIST_HEAD(&pcie->buses);
@@ -2796,6 +2804,25 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 	return err;
 }
 
+static int tegra_pcie_remove(struct platform_device *pdev)
+{
+	struct tegra_pcie *pcie = platform_get_drvdata(pdev);
+	struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
+
+	if (IS_ENABLED(CONFIG_DEBUG_FS))
+		tegra_pcie_debugfs_exit(pcie);
+	pci_stop_root_bus(host->bus);
+	pci_remove_root_bus(host->bus);
+	if (IS_ENABLED(CONFIG_PCI_MSI))
+		tegra_pcie_disable_msi(pcie);
+	tegra_pcie_disable_ports(pcie);
+	tegra_pcie_free_resources(pcie);
+	tegra_pcie_disable_controller(pcie);
+	tegra_pcie_put_resources(pcie);
+
+	return 0;
+}
+
 static struct platform_driver tegra_pcie_driver = {
 	.driver = {
 		.name = "tegra-pcie",
@@ -2803,5 +2830,7 @@ static struct platform_driver tegra_pcie_driver = {
 		.suppress_bind_attrs = true,
 	},
 	.probe = tegra_pcie_probe,
+	.remove = tegra_pcie_remove,
 };
-builtin_platform_driver(tegra_pcie_driver);
+module_platform_driver(tegra_pcie_driver);
+MODULE_LICENSE("GPL");
-- 
2.1.4

^ permalink raw reply related

* [PATCH 07/10] PCI: tegra: free resources on probe failure
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding, jonathanh, robh+dt, frowand.list, bhelgaas, rjw,
	tglx
  Cc: vidyas, kthota, linux-tegra, devicetree, linux-pci, linux-pm,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy@nvidia.com>

tegra_pcie_probe() can fail in multiple instances, this patch takes care
of freeing the resources which are allocated before probe fail.

Signed-off-by: Manikanta Maddireddy <mmaddireddy@nvidia.com>
---
 drivers/pci/host/pci-tegra.c | 102 ++++++++++++++++++++++++++++++++++++-------
 1 file changed, 86 insertions(+), 16 deletions(-)

diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
index b52bdd941249..09f52cd569c6 100644
--- a/drivers/pci/host/pci-tegra.c
+++ b/drivers/pci/host/pci-tegra.c
@@ -681,14 +681,25 @@ static int tegra_pcie_request_resources(struct tegra_pcie *pcie)
 	pci_add_resource(windows, &pcie->busn);
 
 	err = devm_request_pci_bus_resources(dev, windows);
-	if (err < 0)
+	if (err < 0) {
+		pci_free_resource_list(windows);
 		return err;
+	}
 
 	pci_remap_iospace(&pcie->pio, pcie->io.start);
 
 	return 0;
 }
 
+static void tegra_pcie_free_resources(struct tegra_pcie *pcie)
+{
+	struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
+	struct list_head *windows = &host->windows;
+
+	pci_unmap_iospace(&pcie->pio);
+	pci_free_resource_list(windows);
+}
+
 static int tegra_pcie_map_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
 {
 	struct pci_host_bridge *host = pci_find_host_bridge(pdev->bus);
@@ -1089,29 +1100,40 @@ static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
 	return 0;
 }
 
-static void tegra_pcie_power_off(struct tegra_pcie *pcie)
+static void tegra_pcie_disable_controller(struct tegra_pcie *pcie)
 {
 	struct device *dev = pcie->dev;
 	const struct tegra_pcie_soc *soc = pcie->soc;
 	int err;
 
-	/* TODO: disable and unprepare clocks? */
-
 	if (soc->program_uphy) {
 		err = tegra_pcie_phy_power_off(pcie);
 		if (err < 0)
 			dev_err(dev, "failed to power off PHY(s): %d\n", err);
 	}
+}
+
+static void tegra_pcie_power_off(struct tegra_pcie *pcie)
+{
+	struct device *dev = pcie->dev;
+	const struct tegra_pcie_soc *soc = pcie->soc;
+	int err;
 
 	reset_control_assert(pcie->afi_rst);
 	reset_control_assert(pcie->pex_rst);
 
-	if (!dev->pm_domain)
-		tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+	clk_disable_unprepare(pcie->pll_e);
+	if (soc->has_cml_clk)
+		clk_disable_unprepare(pcie->cml_clk);
+	clk_disable_unprepare(pcie->afi_clk);
+	clk_disable_unprepare(pcie->pex_clk);
 
 	err = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
 	if (err < 0)
 		dev_warn(dev, "failed to disable regulators: %d\n", err);
+
+	if (!dev->pm_domain)
+		tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
 }
 
 static int tegra_pcie_power_on(struct tegra_pcie *pcie)
@@ -1242,6 +1264,15 @@ static int tegra_pcie_phys_get_legacy(struct tegra_pcie *pcie)
 	return 0;
 }
 
+static void tegra_pcie_phys_put_legacy(struct tegra_pcie *pcie)
+{
+	int err;
+
+	err = phy_exit(pcie->phy);
+	if (err < 0)
+		dev_err(pcie->dev, "failed to teardown PHY: %d\n", err);
+}
+
 static struct phy *devm_of_phy_optional_get_index(struct device *dev,
 						  struct device_node *np,
 						  const char *consumer,
@@ -1295,6 +1326,19 @@ static int tegra_pcie_port_get_phys(struct tegra_pcie_port *port)
 	return 0;
 }
 
+static void tegra_pcie_port_put_phys(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < port->lanes; i++) {
+		err = phy_exit(port->phys[i]);
+		if (err < 0)
+			dev_err(dev, "failed to teardown PHY#%u: %d\n", i, err);
+	}
+}
+
 static int tegra_pcie_phys_get(struct tegra_pcie *pcie)
 {
 	const struct tegra_pcie_soc *soc = pcie->soc;
@@ -1314,6 +1358,19 @@ static int tegra_pcie_phys_get(struct tegra_pcie *pcie)
 	return 0;
 }
 
+static void tegra_pcie_phys_put(struct tegra_pcie *pcie)
+{
+	const struct tegra_pcie_soc *soc = pcie->soc;
+	struct device_node *np = pcie->dev->of_node;
+	struct tegra_pcie_port *port;
+
+	if (!soc->has_gen2 || of_find_property(np, "phys", NULL) != NULL)
+		tegra_pcie_phys_put_legacy(pcie);
+
+	list_for_each_entry(port, &pcie->ports, list)
+		tegra_pcie_port_put_phys(port);
+}
+
 static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 {
 	struct device *dev = pcie->dev;
@@ -1346,7 +1403,7 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 	err = tegra_pcie_power_on(pcie);
 	if (err) {
 		dev_err(dev, "failed to power up: %d\n", err);
-		return err;
+		goto phys_put;
 	}
 
 	pads = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pads");
@@ -1404,25 +1461,23 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 
 poweroff:
 	tegra_pcie_power_off(pcie);
+phys_put:
+	if (soc->program_uphy)
+		tegra_pcie_phys_put(pcie);
 	return err;
 }
 
 static int tegra_pcie_put_resources(struct tegra_pcie *pcie)
 {
-	struct device *dev = pcie->dev;
 	const struct tegra_pcie_soc *soc = pcie->soc;
-	int err;
 
 	if (pcie->irq > 0)
 		free_irq(pcie->irq, pcie);
 
 	tegra_pcie_power_off(pcie);
 
-	if (soc->program_uphy) {
-		err = phy_exit(pcie->phy);
-		if (err < 0)
-			dev_err(dev, "failed to teardown PHY: %d\n", err);
-	}
+	if (soc->program_uphy)
+		tegra_pcie_phys_put(pcie);
 
 	return 0;
 }
@@ -2351,6 +2406,16 @@ static void tegra_pcie_enable_ports(struct tegra_pcie *pcie)
 	}
 }
 
+static void tegra_pcie_disable_ports(struct tegra_pcie *pcie)
+{
+	struct tegra_pcie_port *port, *tmp;
+
+	reset_control_assert(pcie->pcie_xrst);
+
+	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+		tegra_pcie_port_disable(port);
+}
+
 static void tegra_pcie_change_link_speed(struct tegra_pcie *pcie,
 					 struct pci_dev *pci_dev)
 {
@@ -2671,7 +2736,7 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 
 	err = tegra_pcie_request_resources(pcie);
 	if (err)
-		goto put_resources;
+		goto disable_controller;
 
 	/* setup the AFI address translations */
 	tegra_pcie_setup_translations(pcie);
@@ -2680,7 +2745,7 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 		err = tegra_pcie_enable_msi(pcie);
 		if (err < 0) {
 			dev_err(dev, "failed to enable MSI support: %d\n", err);
-			goto put_resources;
+			goto free_resources;
 		}
 	}
 
@@ -2721,6 +2786,11 @@ static int tegra_pcie_probe(struct platform_device *pdev)
 disable_msi:
 	if (IS_ENABLED(CONFIG_PCI_MSI))
 		tegra_pcie_disable_msi(pcie);
+	tegra_pcie_disable_ports(pcie);
+free_resources:
+	tegra_pcie_free_resources(pcie);
+disable_controller:
+	tegra_pcie_disable_controller(pcie);
 put_resources:
 	tegra_pcie_put_resources(pcie);
 	return err;
-- 
2.1.4

^ permalink raw reply related

* [PATCH 06/10] PCI: Export pci_flags
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
	jonathanh-DDmLM1+adcrQT0dZR+AlfA, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	frowand.list-Re5JQEeQqe8AvxtiuMwx3w,
	bhelgaas-hpIqsD4AKlfQT0dZR+AlfA, rjw-LthD3rsA81gm4RdzfppkhA,
	tglx-hfZtesqFncYOwBW4kG4KsQ
  Cc: vidyas-DDmLM1+adcrQT0dZR+AlfA, kthota-DDmLM1+adcrQT0dZR+AlfA,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-pci-u79uwXL29TY76Z2rM5mHXA, linux-pm-u79uwXL29TY76Z2rM5mHXA,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

pci_flags is used in pci.h, export pci_flags for the driver which is
including pci.h

Signed-off-by: Manikanta Maddireddy <mmaddireddy-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
---
 drivers/pci/setup-bus.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c
index 958da7db9033..2d0e8588710a 100644
--- a/drivers/pci/setup-bus.c
+++ b/drivers/pci/setup-bus.c
@@ -29,6 +29,7 @@
 #include "pci.h"
 
 unsigned int pci_flags;
+EXPORT_SYMBOL(pci_flags);
 
 struct pci_dev_resource {
 	struct list_head list;
-- 
2.1.4

^ permalink raw reply related

* [PATCH 05/10] PCI: Export pci_find_host_bridge()
From: Manikanta Maddireddy @ 2017-11-24 15:53 UTC (permalink / raw)
  To: thierry.reding, jonathanh, robh+dt, frowand.list, bhelgaas, rjw,
	tglx
  Cc: vidyas, kthota, linux-tegra, devicetree, linux-pci, linux-pm,
	Manikanta Maddireddy
In-Reply-To: <1511538800-8275-1-git-send-email-mmaddireddy@nvidia.com>

Allow PCI host driver to use pci_find_host_bridge() to get
pci_host_bridge from pci_bus.

Signed-off-by: Manikanta Maddireddy <mmaddireddy@nvidia.com>
---
 drivers/pci/host-bridge.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/pci/host-bridge.c b/drivers/pci/host-bridge.c
index add66236215c..e0942fc086ad 100644
--- a/drivers/pci/host-bridge.c
+++ b/drivers/pci/host-bridge.c
@@ -22,6 +22,7 @@ struct pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus)
 
 	return to_pci_host_bridge(root_bus->bridge);
 }
+EXPORT_SYMBOL(pci_find_host_bridge);
 
 struct device *pci_get_host_bridge_device(struct pci_dev *dev)
 {
-- 
2.1.4

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox